#include #include #include #include #include #include #include #include #ifdef ESP_PLATFORM namespace tt::network { constexpr auto* TAG = "HttpdReq"; bool getHeaderOrSendError(httpd_req_t* request, const std::string& name, std::string& value) { size_t header_size = httpd_req_get_hdr_value_len(request, name.c_str()); if (header_size == 0) { httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "header missing"); return false; } auto header_buffer = std::make_unique(header_size + 1); if (header_buffer == nullptr) { LOG_E(TAG, LOG_MESSAGE_ALLOC_FAILED); httpd_resp_send_500(request); return false; } if (httpd_req_get_hdr_value_str(request, name.c_str(), header_buffer.get(), header_size + 1) != ESP_OK) { httpd_resp_send_500(request); return false; } value = header_buffer.get(); return true; } bool getMultiPartBoundaryOrSendError(httpd_req_t* request, std::string& boundary) { std::string content_type_header; if (!getHeaderOrSendError(request, "Content-Type", content_type_header)) { return false; } auto boundary_index = content_type_header.find("boundary="); if (boundary_index == std::string::npos) { httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "boundary not found in Content-Type"); return false; } boundary = content_type_header.substr(boundary_index + 9); return true; } bool getQueryOrSendError(httpd_req_t* request, std::string& query) { size_t buffer_length = httpd_req_get_url_query_len(request); if (buffer_length == 0) { httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "id not specified"); return false; } auto buffer = std::make_unique(buffer_length + 1); if (buffer.get() == nullptr || httpd_req_get_url_query_str(request, buffer.get(), buffer_length + 1) != ESP_OK) { httpd_resp_send_500(request); return false; } query = buffer.get(); return true; } std::unique_ptr receiveByteArray(httpd_req_t* request, size_t length, size_t& bytesRead) { assert(length > 0); bytesRead = 0; // We have to use malloc() because make_unique() throws an exception // and we don't have exceptions enabled in the compiler settings auto* buffer = static_cast(malloc(length)); if (buffer == nullptr) { LOG_E(TAG, "Out of memory (failed to allocated %u bytes)", (unsigned)length); return nullptr; } constexpr int MAX_TIMEOUT_RETRIES = 5; int timeout_retries = 0; while (bytesRead < length) { size_t read_size = length - bytesRead; int bytes_received = httpd_req_recv(request, buffer + bytesRead, read_size); if (bytes_received == HTTPD_SOCK_ERR_TIMEOUT) { // Timeout - retry with backoff timeout_retries++; if (timeout_retries >= MAX_TIMEOUT_RETRIES) { LOG_W(TAG, "Recv timeout after %d retries, read %u/%u bytes", timeout_retries, (unsigned)bytesRead, (unsigned)length); free(buffer); return nullptr; } LOG_W(TAG, "Recv timeout, retry %d/%d", timeout_retries, MAX_TIMEOUT_RETRIES); vTaskDelay(pdMS_TO_TICKS(100 * timeout_retries)); // Exponential backoff continue; } if (bytes_received <= 0) { LOG_W(TAG, "Received error %d after reading %u/%u bytes", bytes_received, (unsigned)bytesRead, (unsigned)length); free(buffer); return nullptr; } // Successful read - reset timeout counter timeout_retries = 0; bytesRead += bytes_received; } return std::unique_ptr(buffer); } std::string receiveTextUntil(httpd_req_t* request, const std::string& terminator) { size_t read_index = 0; std::stringstream result; while (!result.str().ends_with(terminator)) { char buffer; size_t bytes_read = httpd_req_recv(request, &buffer, 1); if (bytes_read <= 0) { return ""; } else { read_index += bytes_read; } result << buffer; } return result.str(); } std::map parseContentDisposition(const std::vector& input) { std::map result; static std::string prefix = "Content-Disposition: "; // Find header auto content_disposition_header = std::ranges::find_if(input, [](const std::string& header) { return header.starts_with(prefix); }); // Header not found if (content_disposition_header == input.end()) { return result; } auto parseable = content_disposition_header->substr(prefix.size()); auto parts = string::split(parseable, "; "); for (const auto& part : parts) { auto key_value = string::split(part, "="); if (key_value.size() == 2) { // Trim trailing newlines auto value = string::trim(key_value[1], "\r\n"); if (value.size() > 2) { result[key_value[0]] = value.substr(1, value.size() - 2); } else { result[key_value[0]] = ""; } } } return result; } bool readAndDiscardOrSendError(httpd_req_t* request, const std::string& toRead) { size_t bytes_read; auto buffer = receiveByteArray(request, toRead.length(), bytes_read); if (buffer == nullptr || bytes_read != toRead.length()) { httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "failed to read discardable data"); return false; } if (memcmp(buffer.get(), toRead.c_str(), bytes_read) != 0) { httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "discardable data mismatch"); return false; } return true; } size_t receiveFile(httpd_req_t* request, size_t length, const std::string& filePath) { constexpr auto BUFFER_SIZE = 512; char buffer[BUFFER_SIZE]; size_t bytes_received = 0; auto lock = file::getLock(filePath)->asScopedLock(); lock.lock(); auto* file = fopen(filePath.c_str(), "wb"); if (file == nullptr) { LOG_E(TAG, "Failed to open file for writing: %s", filePath.c_str()); return 0; } while (bytes_received < length) { auto expected_chunk_size = std::min(BUFFER_SIZE, length - bytes_received); size_t receive_chunk_size = httpd_req_recv(request, buffer, expected_chunk_size); if (receive_chunk_size <= 0) { LOG_E(TAG, "Receive failed"); break; } if (fwrite(buffer, 1, receive_chunk_size, file) != (size_t)receive_chunk_size) { LOG_E(TAG, "Failed to write all bytes"); break; } bytes_received += receive_chunk_size; } // Write file fclose(file); return bytes_received; } } #endif // ESP_PLATFORM