#include "HTTPSHandler.h" HTTPSHandler::HTTPSHandler(const char *serverCert) { _client.setCACert(serverCert); } bool HTTPSHandler::connect(const char *host, int port, const char *serverCert) { _host = host; _port = port; if (serverCert != nullptr) { _client.setCACert(serverCert); } _currentState = HttpsState::CONNECTING; // reset response _response = ""; return _connect(); } bool HTTPSHandler::_connect() { return _client.connect(_host, _port); } int HTTPSHandler::_getContentLength() { unsigned long startTime = millis(); String line; int contentLength = -1; if (!_client.connected()) { Serial.print("Not connected when getting content length"); _currentState = HttpsState::ERROR; return -1; } while (contentLength == -1) { while (_client.available()) { line = _client.readStringUntil('\n'); line.trim(); // Remove any trailing whitespace if (line.startsWith("Content-Length: ")) { contentLength = line.substring(15).toInt(); } // Empty line / end of headers if (line.length() == 0) { break; } } // Check for timeout if (millis() - startTime > _responseTimeout * 1000) { Serial.println("Timeout while trying to get Content-Length!"); _currentState = HttpsState::ERROR; return -1; } } return contentLength; } void HTTPSHandler::getRequest(const char *path) { // Check if the client is connected before sending if (!_client.connected()) { Serial.println("Error: Client is not connected!"); _currentState = HttpsState::ERROR; return; } String request = "GET " + String(path) + " HTTP/1.1\r\n"; request += "Host: " + String(_host) + "\r\n"; if (_customHeader.length() > 0) { request += _customHeader + "\r\n"; } request += "Connection: keep-alive\r\n\r\n"; size_t bytesSent = _client.print(request); // Check if all bytes of the request were sent if (bytesSent != request.length()) { Serial.println("Error: Not all bytes sent!"); _currentState = HttpsState::ERROR; return; } _currentState = HttpsState::WAITING; _requestType = RequestType::GET; } void HTTPSHandler::_processDownload(File &dest) { const uint32_t BUFFER_SIZE = 2048; int contentLength = _getContentLength(); static int bytesRead = 0; // Number of bytes downloaded so far byte buffer[BUFFER_SIZE]; unsigned long startMillis = millis(); if (contentLength <= 0) { _currentState = HttpsState::ERROR; } while (_currentState == HttpsState::WAITING) { // Then proceed to download content while (_client.available()) { // If no data is received for the duration of _timeout, it times out if (millis() - startMillis > _downloadTimeout * 1000) { Serial.println("Data inactivity timeout!"); _currentState = HttpsState::ERROR; dest.close(); close(); return; } uint32_t bytesAvailable = _client.available(); int bytesToRead = min(bytesAvailable, BUFFER_SIZE); int bytesReadFromClient = _client.read(buffer, bytesToRead); dest.write(buffer, bytesReadFromClient); bytesRead += bytesReadFromClient; } if (_progressCallback) { _progressCallback(bytesRead, contentLength); } if (bytesRead >= contentLength) { dest.close(); _currentState = HttpsState::COMPLETE; close(); return; } } } String HTTPSHandler::_extractFileName(const String &fullPath) { int lastSlashIndex = fullPath.lastIndexOf('/'); if (lastSlashIndex == -1) { // No slashes found, return the full path itself. return fullPath; } // Return the substring from the last slash onwards. return fullPath.substring(lastSlashIndex + 1); } String HTTPSHandler::_createBoundary(const String &fileNameToUpload) { // Build the multipart form data request String boundary = "--" + _BOUNDARY + "\r\n"; boundary += "Content-Disposition: form-data; name=\"fileToUpload\"; filename=\"" + fileNameToUpload + "\"\r\n"; boundary += "Content-Type: application/json\r\n"; boundary += "\r\n"; return boundary; } bool HTTPSHandler::uploadFile(const char *destPath, File &source, const String &fileName) { String fileNameToUpload = fileName == "" ? _extractFileName(source.name()) : fileName; Serial.println("Uploading file: " + fileNameToUpload); // Needed for header String boundary = _createBoundary(fileNameToUpload); String endBoundary = "\r\n--" + _BOUNDARY + "--\r\n"; // Calculate content length int contentLength = boundary.length() + endBoundary.length() + source.size(); // Start sending the request _client.println("POST " + String(destPath) + " HTTP/1.1"); _client.println("Host: appdig.com"); if (_customHeader.length() > 0) { _client.println(_customHeader); } _client.print("Content-Length: " + String(contentLength) + "\r\n"); _client.println("Content-Type: multipart/form-data; boundary=" + _BOUNDARY); _client.println(); _client.print(boundary); const int CHUNK_SIZE = 1024; const long startTime = millis(); // Start sending body in chunks while (source.available()) { // Check for timeout if (millis() - startTime > _responseTimeout * 1000) { Serial.println("Timeout while trying to get Content-Length!"); _currentState = HttpsState::ERROR; return 0; } byte buffer[CHUNK_SIZE]; int bytesRead = source.read(buffer, CHUNK_SIZE); _client.write(buffer, bytesRead); if (_progressCallback) { _progressCallback(bytesRead, contentLength); } } _client.print(endBoundary); _currentState = HttpsState::COMPLETE; // give the server some time to process the request // update in the future to handle response from server delay(10); close(); return 1; } bool HTTPSHandler::postRequest(const char *destPath, const String &postData) { _client.println("POST " + String(destPath) + " HTTP/1.1"); _client.println("Host: " + String(_host)); _client.println("Content-Length: " + String(postData.length())); _client.println("Connection: close"); _client.println(); _client.println(postData); _currentState = HttpsState::WAITING; _requestType = RequestType::POST; return 1; } void HTTPSHandler::_processResponse() { unsigned long startMillis = millis(); // Read available data from the client and append to _response while (_client.available()) { // If no data is received for the duration of _timeout, it times out if (millis() - startMillis > _responseTimeout * 1000) { Serial.println("Data inactivity timeout"); _currentState = HttpsState::ERROR; close(); return; } char c = _client.read(); Serial.print(c); _response += c; if (_response.endsWith("\r\n0\r\n")) { break; } } if (!_client.connected() || _response.endsWith("\r\n0\r\n")) { _currentState = HttpsState::COMPLETE; } close(); } void HTTPSHandler::setTarget(const char *sourcePath, File &destFile) { _path = sourcePath; _destinationFile = destFile; _currentState = HttpsState::WAITING; getRequest(_path); _requestType = RequestType::DOWNLOAD; } void HTTPSHandler::advanceStateMachine() { static int lastState = -1; if (lastState != static_cast(_currentState)) { Serial.println("HTTPSHandler state changed to " + static_cast(_currentState)); lastState = static_cast(_currentState); } switch (_currentState) { case HttpsState::CONNECTING: if (!_client.connected()) { Serial.println("Could not connect to server"); _currentState = HttpsState::ERROR; } _currentState = HttpsState::WAITING; break; case HttpsState::WAITING: switch (_requestType) { case RequestType::DOWNLOAD: _processDownload(_destinationFile); break; default: _processResponse(); break; } break; case HttpsState::ERROR: _currentState = HttpsState::IDLE; break; case HttpsState::COMPLETE: _currentState = HttpsState::IDLE; break; case HttpsState::IDLE: break; default: break; } } // void HTTPSHandler::get(const String &url, Callback callback) // { // if (!_client.connect(_host, _port)) // { // Serial.println("Could not connect to server"); // return; // } // _client.println("GET / HTTP/1.1"); // _client.println("HOST appdig.com"); // _client.println("Connection: close"); // _requestStarted = 1; // _callback = callback; // } void HTTPSHandler::close() { if (_client.connected()) { _client.stop(); } } HTTPSHandler::~HTTPSHandler() { close(); }