From 2214640cb60c8949ca8b640f32f5691490ac16c7 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sun, 21 Aug 2022 02:09:06 -0700 Subject: [PATCH 1/6] Add HTTPClient, ported from the ESP8266 Remove the need to have a separate WiFiClient that's destroyed after the HTTPClient. Let the object handle its own client, and pass through any SSL requests. --- .../examples/Authorization/Authorization.ino | 87 ++ .../BasicHttpClient/BasicHttpClient.ino | 75 ++ .../BasicHttpsClient/BasicHttpsClient.ino | 125 ++ .../DigestAuthorization.ino | 140 ++ .../PostHttpClient/PostHttpClient.ino | 80 ++ .../ReuseConnectionV2/ReuseConnectionV2.ino | 81 ++ .../StreamHttpClient/StreamHttpClient.ino | 103 ++ .../StreamHttpsClient/StreamHttpsClient.ino | 105 ++ libraries/HTTPClient/keywords.txt | 149 +++ libraries/HTTPClient/library.properties | 10 + libraries/HTTPClient/src/HTTPClient.cpp | 1127 +++++++++++++++++ libraries/HTTPClient/src/HTTPClient.h | 353 ++++++ libraries/HTTPClient/src/base64.cpp | 69 + libraries/HTTPClient/src/base64.h | 48 + libraries/HTTPClient/src/libb64/AUTHORS | 7 + libraries/HTTPClient/src/libb64/LICENSE | 29 + libraries/HTTPClient/src/libb64/cdecode.cpp | 111 ++ libraries/HTTPClient/src/libb64/cdecode.h | 38 + libraries/HTTPClient/src/libb64/cencode.cpp | 126 ++ libraries/HTTPClient/src/libb64/cencode.h | 48 + libraries/WiFi/keywords.txt | 20 + libraries/WiFi/src/WiFiMulti.cpp | 3 + libraries/WiFi/src/WiFiMulti.h | 2 +- tests/restyle.sh | 2 +- tools/build.py | 4 +- 25 files changed, 2937 insertions(+), 5 deletions(-) create mode 100644 libraries/HTTPClient/examples/Authorization/Authorization.ino create mode 100644 libraries/HTTPClient/examples/BasicHttpClient/BasicHttpClient.ino create mode 100644 libraries/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino create mode 100644 libraries/HTTPClient/examples/DigestAuthorization/DigestAuthorization.ino create mode 100644 libraries/HTTPClient/examples/PostHttpClient/PostHttpClient.ino create mode 100644 libraries/HTTPClient/examples/ReuseConnectionV2/ReuseConnectionV2.ino create mode 100644 libraries/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino create mode 100644 libraries/HTTPClient/examples/StreamHttpsClient/StreamHttpsClient.ino create mode 100644 libraries/HTTPClient/keywords.txt create mode 100644 libraries/HTTPClient/library.properties create mode 100644 libraries/HTTPClient/src/HTTPClient.cpp create mode 100644 libraries/HTTPClient/src/HTTPClient.h create mode 100644 libraries/HTTPClient/src/base64.cpp create mode 100644 libraries/HTTPClient/src/base64.h create mode 100755 libraries/HTTPClient/src/libb64/AUTHORS create mode 100755 libraries/HTTPClient/src/libb64/LICENSE create mode 100755 libraries/HTTPClient/src/libb64/cdecode.cpp create mode 100755 libraries/HTTPClient/src/libb64/cdecode.h create mode 100755 libraries/HTTPClient/src/libb64/cencode.cpp create mode 100755 libraries/HTTPClient/src/libb64/cencode.h diff --git a/libraries/HTTPClient/examples/Authorization/Authorization.ino b/libraries/HTTPClient/examples/Authorization/Authorization.ino new file mode 100644 index 00000000..c1355b2c --- /dev/null +++ b/libraries/HTTPClient/examples/Authorization/Authorization.ino @@ -0,0 +1,87 @@ +/** + Authorization.ino + + Created on: 09.12.2015 + +*/ + +#include +#include +#include + +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +const char *ssid = STASSID; +const char *pass = STAPSK; + +WiFiMulti WiFiMulti; + +void setup() { + + Serial.begin(115200); + // Serial.setDebugOutput(true); + + Serial.println(); + Serial.println(); + Serial.println(); + + for (uint8_t t = 4; t > 0; t--) { + Serial.printf("[SETUP] WAIT %d...\n", t); + Serial.flush(); + delay(1000); + } + + WiFi.mode(WIFI_STA); + WiFiMulti.addAP(ssid, pass); +} + +void loop() { + // wait for WiFi connection + if ((WiFiMulti.run() == WL_CONNECTED)) { + + HTTPClient http; + http.setInsecure(); + + Serial.print("[HTTP] begin...\n"); + // configure traged server and url + + + http.begin("https://guest:guest@jigsaw.w3.org/HTTP/Basic/"); + + /* + // or + http.begin(client, "http://jigsaw.w3.org/HTTP/Basic/"); + http.setAuthorization("guest", "guest"); + + // or + http.begin(client, "http://jigsaw.w3.org/HTTP/Basic/"); + http.setAuthorization("Z3Vlc3Q6Z3Vlc3Q="); + */ + + + Serial.print("[HTTP] GET...\n"); + // start connection and send HTTP header + int httpCode = http.GET(); + + // httpCode will be negative on error + if (httpCode > 0) { + // HTTP header has been send and Server response header has been handled + Serial.printf("[HTTP] GET... code: %d\n", httpCode); + + // file found at server + if (httpCode == HTTP_CODE_OK) { + String payload = http.getString(); + Serial.println(payload); + } + } else { + Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + } + + delay(10000); +} diff --git a/libraries/HTTPClient/examples/BasicHttpClient/BasicHttpClient.ino b/libraries/HTTPClient/examples/BasicHttpClient/BasicHttpClient.ino new file mode 100644 index 00000000..c0efbcb9 --- /dev/null +++ b/libraries/HTTPClient/examples/BasicHttpClient/BasicHttpClient.ino @@ -0,0 +1,75 @@ +/** + BasicHTTPClient.ino + + Created on: 24.05.2015 + +*/ + +#include +#include +#include + +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +const char *ssid = STASSID; +const char *pass = STAPSK; + +WiFiMulti WiFiMulti; + +void setup() { + + Serial.begin(115200); + // Serial.setDebugOutput(true); + + Serial.println(); + Serial.println(); + Serial.println(); + + for (uint8_t t = 4; t > 0; t--) { + Serial.printf("[SETUP] WAIT %d...\n", t); + Serial.flush(); + delay(1000); + } + + WiFiMulti.addAP(ssid, pass); +} + +void loop() { + // wait for WiFi connection + if ((WiFiMulti.run() == WL_CONNECTED)) { + + HTTPClient http; + + Serial.print("[HTTP] begin...\n"); + if (http.begin("http://httpbin.org")) { // HTTP + + + Serial.print("[HTTP] GET...\n"); + // start connection and send HTTP header + int httpCode = http.GET(); + + // httpCode will be negative on error + if (httpCode > 0) { + // HTTP header has been send and Server response header has been handled + Serial.printf("[HTTP] GET... code: %d\n", httpCode); + + // file found at server + if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { + String payload = http.getString(); + Serial.println(payload); + } + } else { + Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + } else { + Serial.printf("[HTTP} Unable to connect\n"); + } + } + + delay(10000); +} diff --git a/libraries/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino b/libraries/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino new file mode 100644 index 00000000..646951a9 --- /dev/null +++ b/libraries/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino @@ -0,0 +1,125 @@ +/** + BasicHTTPSClient.ino + + Created on: 20.08.2018 + +*/ + +#include +#include +#include + +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +const char *ssid = STASSID; +const char *pass = STAPSK; + +WiFiMulti WiFiMulti; + +void setup() { + + Serial.begin(115200); + // Serial.setDebugOutput(true); + + Serial.println(); + Serial.println(); + Serial.println(); + + for (uint8_t t = 4; t > 0; t--) { + Serial.printf("[SETUP] WAIT %d...\n", t); + Serial.flush(); + delay(1000); + } + + WiFi.mode(WIFI_STA); + WiFiMulti.addAP(ssid, pass); +} + +const char *jigsaw_cert = R"EOF( +-----BEGIN CERTIFICATE----- +MIIFKTCCBM+gAwIBAgIQAbTKhAICxb7iDJbE6qU/NzAKBggqhkjOPQQDAjBKMQsw +CQYDVQQGEwJVUzEZMBcGA1UEChMQQ2xvdWRmbGFyZSwgSW5jLjEgMB4GA1UEAxMX +Q2xvdWRmbGFyZSBJbmMgRUNDIENBLTMwHhcNMjIwMzE3MDAwMDAwWhcNMjMwMzE2 +MjM1OTU5WjB1MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG +A1UEBxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQQ2xvdWRmbGFyZSwgSW5jLjEe +MBwGA1UEAxMVc25pLmNsb3VkZmxhcmVzc2wuY29tMFkwEwYHKoZIzj0CAQYIKoZI +zj0DAQcDQgAEYnkGDyrIltjRnxoVdy/xgndo+WGMOASzs2hHeCjbJ1KplKJc/ciK +XCWq/4+pTzSiVgTFhRmCdLcU1Fa05YFNQaOCA2owggNmMB8GA1UdIwQYMBaAFKXO +N+rrsHUOlGeItEX62SQQh5YfMB0GA1UdDgQWBBRIzOWGCDBB/PMrMucSrjIKqlgE +uDAvBgNVHREEKDAmghVzbmkuY2xvdWRmbGFyZXNzbC5jb22CDWppZ3Nhdy53My5v +cmcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD +AjB7BgNVHR8EdDByMDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vQ2xv +dWRmbGFyZUluY0VDQ0NBLTMuY3JsMDegNaAzhjFodHRwOi8vY3JsNC5kaWdpY2Vy +dC5jb20vQ2xvdWRmbGFyZUluY0VDQ0NBLTMuY3JsMD4GA1UdIAQ3MDUwMwYGZ4EM +AQICMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzB2 +BggrBgEFBQcBAQRqMGgwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0 +LmNvbTBABggrBgEFBQcwAoY0aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Ns +b3VkZmxhcmVJbmNFQ0NDQS0zLmNydDAMBgNVHRMBAf8EAjAAMIIBfwYKKwYBBAHW +eQIEAgSCAW8EggFrAWkAdQDoPtDaPvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9 +bgAAAX+aFPh6AAAEAwBGMEQCICivjuh2ywUYvVpTKHo65JEheR8dFq8QvBgEiXfw +m6q6AiAkxAgz77oboGQGetNmab45+peY+nAGOfyW9vi9S1gMaAB3ADXPGRu/sWxX +vw+tTG1Cy7u2JyAmUeo/4SrvqAPDO9ZMAAABf5oU+GEAAAQDAEgwRgIhANKeTNMy +GqUsCo7ph7YMWzrhMuDeyP8xPSiCtFzKcn/eAiEAyv5lgCUQ6K14V13zYfL99wZD +LFcIP/KZ1y7nuPAksTAAdwCzc3cH4YRQ+GOG1gWp3BEJSnktsWcMC4fc8AMOeTal +mgAAAX+aFPiWAAAEAwBIMEYCIQD6535jWw776D4vjyupP2fBw26CBMpVT5++k4rR +xqeOXwIhAIbEaEKkEq6JtpWWfVpTyDkMpMfTuiqYVe6REy2XsmEhMAoGCCqGSM49 +BAMCA0gAMEUCIH3r/puXZcX1bfUoBq2njuHe0bxWtvzDaz5k6WLYrazTAiEA+ePL +N6K5xrmaof185pVCxACPLc/BoKyUwMeC8iXCm00= +-----END CERTIFICATE----- +)EOF"; + +static int cnt = 0; + +void loop() { + // wait for WiFi connection + if ((WiFiMulti.run() == WL_CONNECTED)) { + HTTPClient https; + switch (cnt) { + case 0: + Serial.println("[HTTPS] using insecure SSL, not validating certificate"); + https.setInsecure(); // Note this is unsafe against MITM attacks + cnt++; + break; + case 1: + Serial.println("[HTTPS] using secure SSL, validating certificate"); + https.setCACert(jigsaw_cert); + cnt++; + break; + default: + Serial.println("[HTTPS] not setting any SSL verification settings, will fail"); + cnt = 0; + } + + Serial.print("[HTTPS] begin...\n"); + if (https.begin("https://jigsaw.w3.org/HTTP/connection.html")) { // HTTPS + + Serial.print("[HTTPS] GET...\n"); + // start connection and send HTTP header + int httpCode = https.GET(); + + // httpCode will be negative on error + if (httpCode > 0) { + // HTTP header has been send and Server response header has been handled + Serial.printf("[HTTPS] GET... code: %d\n", httpCode); + + // file found at server + if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { + String payload = https.getString(); + Serial.println(payload); + } + } else { + Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str()); + } + + https.end(); + } else { + Serial.printf("[HTTPS] Unable to connect\n"); + } + } + + Serial.println("Wait 10s before next round..."); + delay(10000); +} diff --git a/libraries/HTTPClient/examples/DigestAuthorization/DigestAuthorization.ino b/libraries/HTTPClient/examples/DigestAuthorization/DigestAuthorization.ino new file mode 100644 index 00000000..def7eb2a --- /dev/null +++ b/libraries/HTTPClient/examples/DigestAuthorization/DigestAuthorization.ino @@ -0,0 +1,140 @@ +/* + This sketch shows how to handle HTTP Digest Authorization. + + Written by Parham Alvani and Sajjad Rahnama, 2018-01-07. + + This example is released into public domain, + or, at your option, CC0 licensed. +*/ + +#include + +#include + +#ifndef STASSID +#define STASSID "NOBABIES" +#define STAPSK "ElephantsAreGreat" +#endif + +const char* ssid = STASSID; +const char* ssidPassword = STAPSK; + +const char* username = "admin"; +const char* password = "admin"; + +const char* server = "http://httpbin.org"; +const char* uri = "/digest-auth/auth/admin/admin/MD5"; + +String exractParam(String& authReq, const String& param, const char delimit) { + int _begin = authReq.indexOf(param); + if (_begin == -1) { + return ""; + } + return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length())); +} + +String getCNonce(const int len) { + static const char alphanum[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + String s = ""; + + for (int i = 0; i < len; ++i) { + s += alphanum[rand() % (sizeof(alphanum) - 1)]; + } + + return s; +} + +String getDigestAuth(String& authReq, const String& username, const String& password, const String& method, const String& uri, unsigned int counter) { + // extracting required parameters for RFC 2069 simpler Digest + String realm = exractParam(authReq, "realm=\"", '"'); + String nonce = exractParam(authReq, "nonce=\"", '"'); + String cNonce = getCNonce(8); + + char nc[9]; + snprintf(nc, sizeof(nc), "%08x", counter); + + // parameters for the RFC 2617 newer Digest + MD5Builder md5; + md5.begin(); + md5.add(username + ":" + realm + ":" + password); // md5 of the user:realm:user + md5.calculate(); + String h1 = md5.toString(); + + md5.begin(); + md5.add(method + ":" + uri); + md5.calculate(); + String h2 = md5.toString(); + + md5.begin(); + md5.add(h1 + ":" + nonce + ":" + String(nc) + ":" + cNonce + ":" + "auth" + ":" + h2); + md5.calculate(); + String response = md5.toString(); + + String authorization = "Digest username=\"" + username + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", uri=\"" + uri + "\", algorithm=\"MD5\", qop=auth, nc=" + String(nc) + ", cnonce=\"" + cNonce + "\", response=\"" + response + "\""; + Serial.println(authorization); + + return authorization; +} + +void setup() { + Serial.begin(115200); + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, ssidPassword); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + srand(rp2040.getCycleCount()); +} + +void loop() { + HTTPClient http; + + Serial.print("[HTTP] begin...\n"); + + // configure target server and url + http.begin(String(server) + String(uri)); + + + const char* keys[] = { "WWW-Authenticate" }; + http.collectHeaders(keys, 1); + + Serial.print("[HTTP] GET...\n"); + // start connection and send HTTP header + int httpCode = http.GET(); + + if (httpCode > 0) { + String authReq = http.header("WWW-Authenticate"); + Serial.println(authReq); + + String authorization = getDigestAuth(authReq, String(username), String(password), "GET", String(uri), 1); + + http.end(); + http.begin(String(server) + String(uri)); + + http.addHeader("Authorization", authorization); + + int httpCode = http.GET(); + if (httpCode > 0) { + String payload = http.getString(); + Serial.println(payload); + } else { + Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + } else { + Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + delay(10000); +} diff --git a/libraries/HTTPClient/examples/PostHttpClient/PostHttpClient.ino b/libraries/HTTPClient/examples/PostHttpClient/PostHttpClient.ino new file mode 100644 index 00000000..01f232aa --- /dev/null +++ b/libraries/HTTPClient/examples/PostHttpClient/PostHttpClient.ino @@ -0,0 +1,80 @@ +/** + PostHTTPClient.ino + + Created on: 21.11.2016 + +*/ + +#include +#include + +/* this can be run with an emulated server on host: + cd esp8266-core-root-dir + cd tests/host + make ../../libraries/ESP8266WebServer/examples/PostServer/PostServer + bin/PostServer/PostServer + then put your PC's IP address in SERVER_IP below, port 9080 (instead of default 80): +*/ +//#define SERVER_IP "10.0.1.7:9080" // PC address with emulation on host +#define SERVER_IP "192.168.1.42" + +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +void setup() { + + Serial.begin(115200); + + Serial.println(); + Serial.println(); + Serial.println(); + + WiFi.begin(STASSID, STAPSK); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.print("Connected! IP address: "); + Serial.println(WiFi.localIP()); +} + +void loop() { + // wait for WiFi connection + if ((WiFi.status() == WL_CONNECTED)) { + + HTTPClient http; + + Serial.print("[HTTP] begin...\n"); + // configure traged server and url + http.begin("http://" SERVER_IP "/postplain/"); // HTTP + http.addHeader("Content-Type", "application/json"); + + Serial.print("[HTTP] POST...\n"); + // start connection and send HTTP header and body + int httpCode = http.POST("{\"hello\":\"world\"}"); + + // httpCode will be negative on error + if (httpCode > 0) { + // HTTP header has been send and Server response header has been handled + Serial.printf("[HTTP] POST... code: %d\n", httpCode); + + // file found at server + if (httpCode == HTTP_CODE_OK) { + const String& payload = http.getString(); + Serial.println("received payload:\n<<"); + Serial.println(payload); + Serial.println(">>"); + } + } else { + Serial.printf("[HTTP] POST... failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + } + + delay(10000); +} diff --git a/libraries/HTTPClient/examples/ReuseConnectionV2/ReuseConnectionV2.ino b/libraries/HTTPClient/examples/ReuseConnectionV2/ReuseConnectionV2.ino new file mode 100644 index 00000000..73f4266f --- /dev/null +++ b/libraries/HTTPClient/examples/ReuseConnectionV2/ReuseConnectionV2.ino @@ -0,0 +1,81 @@ +/** + reuseConnectionV2.ino + + Created on: 22.11.2015 + + This example reuses the http connection and also restores the connection if the connection is lost +*/ + + +#include +#include +#include + +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +WiFiMulti WiFiMulti; + +HTTPClient http; + +void setup() { + + Serial.begin(115200); + // Serial.setDebugOutput(true); + + Serial.println(); + Serial.println(); + Serial.println("Connecting to WiFi..."); + + WiFi.mode(WIFI_STA); + WiFiMulti.addAP(STASSID, STAPSK); + + // wait for WiFi connection + while ((WiFiMulti.run() != WL_CONNECTED)) { + Serial.write('.'); + delay(500); + } + Serial.println(" connected to WiFi"); + + // allow reuse (if server supports it) + http.setReuse(true); + http.setInsecure(); + + http.begin("https://jigsaw.w3.org/HTTP/connection.html"); + // http.begin(client, "jigsaw.w3.org", 80, "/HTTP/connection.html"); +} + +int pass = 0; + +void loop() { + // First 10 loop()s, retrieve the URL + if (pass < 10) { + pass++; + Serial.printf("Reuse connection example, GET url for the %d time\n", pass); + int httpCode = http.GET(); + if (httpCode > 0) { + Serial.printf("[HTTP] GET... code: %d\n", httpCode); + + // file found at server + if (httpCode == HTTP_CODE_OK) { + http.writeToStream(&Serial); + } + } else { + Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + // Something went wrong with the connection, try to reconnect + http.end(); + http.begin("https://jigsaw.w3.org/HTTP/connection.html"); + // http.begin(client, "jigsaw.w3.org", 80, "/HTTP/connection.html"); + } + + if (pass == 10) { + http.end(); + Serial.println("Done testing"); + } else { + Serial.println("\n\n\nWait 5 second...\n"); + delay(5000); + } + } +} diff --git a/libraries/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino b/libraries/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino new file mode 100644 index 00000000..298f6129 --- /dev/null +++ b/libraries/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino @@ -0,0 +1,103 @@ +/** + StreamHTTPClient.ino + + Created on: 24.05.2015 + +*/ + +#include + +#include +#include + +#include + +WiFiMulti WiFiMulti; + +void setup() { + + Serial.begin(115200); + // Serial.setDebugOutput(true); + + Serial.println(); + Serial.println(); + Serial.println(); + + for (uint8_t t = 4; t > 0; t--) { + Serial.printf("[SETUP] WAIT %d...\n", t); + Serial.flush(); + delay(1000); + } + + WiFi.mode(WIFI_STA); + WiFiMulti.addAP("SSID", "PASSWORD"); +} + +void loop() { + // wait for WiFi connection + if ((WiFiMulti.run() == WL_CONNECTED)) { + + HTTPClient http; + http.setInsecure(); + + Serial.print("[HTTP] begin...\n"); + + // configure server and url + http.begin("https://jigsaw.w3.org/HTTP/connection.html"); + // http.begin("jigsaw.w3.org", 443, "/HTTP/connection.html", true); + + Serial.print("[HTTP] GET...\n"); + // start connection and send HTTP header + int httpCode = http.GET(); + if (httpCode > 0) { + // HTTP header has been send and Server response header has been handled + Serial.printf("[HTTP] GET... code: %d\n", httpCode); + + // file found at server + if (httpCode == HTTP_CODE_OK) { + + // get length of document (is -1 when Server sends no Content-Length header) + int len = http.getSize(); + + // create buffer for read + uint8_t buff[128] = { 0 }; + +#if 0 + // with API + Serial.println(http.getString()); +#else + // or "by hand" + + // get tcp stream + WiFiClient* stream = &client; + + // read all data from server + while (http.connected() && (len > 0 || len == -1)) { + // read up to 128 byte + int c = stream->readBytes(buff, std::min((size_t)len, sizeof(buff))); + Serial.printf("readBytes: %d\n", c); + if (!c) { + Serial.println("read timeout"); + } + + // write it to Serial + Serial.write(buff, c); + + if (len > 0) { + len -= c; + } + } +#endif + + Serial.println(); + Serial.print("[HTTP] connection closed or file end.\n"); + } + } else { + Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + } + + delay(60000); +} diff --git a/libraries/HTTPClient/examples/StreamHttpsClient/StreamHttpsClient.ino b/libraries/HTTPClient/examples/StreamHttpsClient/StreamHttpsClient.ino new file mode 100644 index 00000000..5584f693 --- /dev/null +++ b/libraries/HTTPClient/examples/StreamHttpsClient/StreamHttpsClient.ino @@ -0,0 +1,105 @@ +/** + StreamHTTPClient.ino + + Created on: 24.05.2015 + +*/ + +#include +#include +#include + +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +const char *ssid = STASSID; +const char *pass = STAPSK; + +WiFiMulti WiFiMulti; + +void setup() { + + Serial.begin(115200); + // Serial.setDebugOutput(true); + + Serial.println(); + Serial.println(); + Serial.println(); + + for (uint8_t t = 4; t > 0; t--) { + Serial.printf("[SETUP] WAIT %d...\n", t); + Serial.flush(); + delay(1000); + } + + WiFi.mode(WIFI_STA); + WiFiMulti.addAP(ssid, pass); +} + +void loop() { + // wait for WiFi connection + if ((WiFiMulti.run() == WL_CONNECTED)) { + + Serial.print("[HTTPS] begin...\n"); + + // configure server and url + const char *fp = "41:FA:FD:B6:96:5F:33:09:F4:ED:09:28:BF:66:4D:5B:A2:88:03:65"; + + HTTPClient https; + https.setFingerprint(fp); + + if (https.begin("https://www.trustedfirmware.org/projects/mbed-tls")) { + + Serial.print("[HTTPS] GET...\n"); + // start connection and send HTTP header + int httpCode = https.GET(); + if (httpCode > 0) { + // HTTP header has been send and Server response header has been handled + Serial.printf("[HTTPS] GET... code: %d\n", httpCode); + + // file found at server + if (httpCode == HTTP_CODE_OK) { + + // get length of document (is -1 when Server sends no Content-Length header) + int len = https.getSize(); + + // create buffer for read + static uint8_t buff[128] = { 0 }; + + // read all data from server + while (https.connected() && (len > 0 || len == -1)) { + // get available data size + size_t size = https.getStreamPtr()->available(); + + if (size) { + // read up to 128 byte + int c = https.getStreamPtr()->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); + + // write it to Serial + Serial.write(buff, c); + + if (len > 0) { + len -= c; + } + } + delay(1); + } + + Serial.println(); + Serial.print("[HTTPS] connection closed or file end.\n"); + } + } else { + Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str()); + } + + https.end(); + } else { + Serial.printf("Unable to connect\n"); + } + } + + Serial.println("Wait 10s before the next round..."); + delay(10000); +} diff --git a/libraries/HTTPClient/keywords.txt b/libraries/HTTPClient/keywords.txt new file mode 100644 index 00000000..fe79fdcd --- /dev/null +++ b/libraries/HTTPClient/keywords.txt @@ -0,0 +1,149 @@ +####################################### +# Syntax Coloring Map For HTTPClient +####################################### + +####################################### +# Library (KEYWORD3) +####################################### + +HTTPClient KEYWORD3 RESERVED_WORD + +####################################### +# Datatypes (KEYWORD1) +####################################### + +t_http_codes KEYWORD1 DATA_TYPE +transferEncoding_t KEYWORD1 DATA_TYPE +TransportTraits KEYWORD1 DATA_TYPE +TransportTraitsPtr KEYWORD1 DATA_TYPE +StreamString KEYWORD1 DATA_TYPE +HTTPClient KEYWORD1 DATA_TYPE + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +end KEYWORD2 +connected KEYWORD2 +setReuse KEYWORD2 +setUserAgent KEYWORD2 +setAuthorization KEYWORD2 +setTimeout KEYWORD2 +useHTTP10 KEYWORD2 +GET KEYWORD2 +POST KEYWORD2 +PUT KEYWORD2 +PATCH KEYWORD2 +sendRequest KEYWORD2 +addHeader KEYWORD2 +collectHeaders KEYWORD2 +header KEYWORD2 +headerName KEYWORD2 +headers KEYWORD2 +hasHeader KEYWORD2 +getSize KEYWORD2 +getStream KEYWORD2 +getStreamPtr KEYWORD2 +writeToStream KEYWORD2 +getString KEYWORD2 +errorToString KEYWORD2 + +setSession KEYWORD2 +setInsecure KEYWORD2 +setKnownKey KEYWORD2 +setFingerprint KEYWORD2 +allowSelfSignedCerts KEYWORD2 +setTrustAnchors KEYWORD2 +setX509Time KEYWORD2 +setClientRSACert KEYWORD2 +setClientECCert KEYWORD2 +setBufferSizes KEYWORD2 +setCertStore KEYWORD2 +setCiphers KEYWORD2 +setCiphersLessSecure KEYWORD2 +setSSLVersion KEYWORD2 +setCACert KEYWORD2 +setCertificate KEYWORD2 +setPrivateKey KEYWORD2 +loadCACert KEYWORD2 +loadCertificate KEYWORD2 +loadPrivateKey KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +HTTPCLIENT_DEFAULT_TCP_TIMEOUT LITERAL1 RESERVED_WORD_2 +HTTPC_ERROR_CONNECTION_REFUSED LITERAL1 RESERVED_WORD_2 +HTTPC_ERROR_SEND_HEADER_FAILED LITERAL1 RESERVED_WORD_2 +HTTPC_ERROR_SEND_PAYLOAD_FAILED LITERAL1 RESERVED_WORD_2 +HTTPC_ERROR_NOT_CONNECTED LITERAL1 RESERVED_WORD_2 +HTTPC_ERROR_CONNECTION_LOST LITERAL1 RESERVED_WORD_2 +HTTPC_ERROR_NO_STREAM LITERAL1 RESERVED_WORD_2 +HTTPC_ERROR_NO_HTTP_SERVER LITERAL1 RESERVED_WORD_2 +HTTPC_ERROR_TOO_LESS_RAM LITERAL1 RESERVED_WORD_2 +HTTPC_ERROR_ENCODING LITERAL1 RESERVED_WORD_2 +HTTPC_ERROR_STREAM_WRITE LITERAL1 RESERVED_WORD_2 +HTTPC_ERROR_READ_TIMEOUT LITERAL1 RESERVED_WORD_2 +HTTP_TCP_BUFFER_SIZE LITERAL1 RESERVED_WORD_2 +HTTP_CODE_CONTINUE LITERAL1 RESERVED_WORD_2 +HTTP_CODE_SWITCHING_PROTOCOLS LITERAL1 RESERVED_WORD_2 +HTTP_CODE_PROCESSING LITERAL1 RESERVED_WORD_2 +HTTP_CODE_OK LITERAL1 RESERVED_WORD_2 +HTTP_CODE_CREATED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_ACCEPTED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_NON_AUTHORITATIVE_INFORMATION LITERAL1 RESERVED_WORD_2 +HTTP_CODE_NO_CONTENT LITERAL1 RESERVED_WORD_2 +HTTP_CODE_RESET_CONTENT LITERAL1 RESERVED_WORD_2 +HTTP_CODE_PARTIAL_CONTENT LITERAL1 RESERVED_WORD_2 +HTTP_CODE_MULTI_STATUS LITERAL1 RESERVED_WORD_2 +HTTP_CODE_ALREADY_REPORTED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_IM_USED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_MULTIPLE_CHOICES LITERAL1 RESERVED_WORD_2 +HTTP_CODE_MOVED_PERMANENTLY LITERAL1 RESERVED_WORD_2 +HTTP_CODE_FOUND LITERAL1 RESERVED_WORD_2 +HTTP_CODE_SEE_OTHER LITERAL1 RESERVED_WORD_2 +HTTP_CODE_NOT_MODIFIED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_USE_PROXY LITERAL1 RESERVED_WORD_2 +HTTP_CODE_TEMPORARY_REDIRECT LITERAL1 RESERVED_WORD_2 +HTTP_CODE_PERMANENT_REDIRECT LITERAL1 RESERVED_WORD_2 +HTTP_CODE_BAD_REQUEST LITERAL1 RESERVED_WORD_2 +HTTP_CODE_UNAUTHORIZED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_PAYMENT_REQUIRED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_FORBIDDEN LITERAL1 RESERVED_WORD_2 +HTTP_CODE_NOT_FOUND LITERAL1 RESERVED_WORD_2 +HTTP_CODE_METHOD_NOT_ALLOWED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_NOT_ACCEPTABLE LITERAL1 RESERVED_WORD_2 +HTTP_CODE_PROXY_AUTHENTICATION_REQUIRED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_REQUEST_TIMEOUT LITERAL1 RESERVED_WORD_2 +HTTP_CODE_CONFLICT LITERAL1 RESERVED_WORD_2 +HTTP_CODE_GONE LITERAL1 RESERVED_WORD_2 +HTTP_CODE_LENGTH_REQUIRED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_PRECONDITION_FAILED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_PAYLOAD_TOO_LARGE LITERAL1 RESERVED_WORD_2 +HTTP_CODE_URI_TOO_LONG LITERAL1 RESERVED_WORD_2 +HTTP_CODE_UNSUPPORTED_MEDIA_TYPE LITERAL1 RESERVED_WORD_2 +HTTP_CODE_RANGE_NOT_SATISFIABLE LITERAL1 RESERVED_WORD_2 +HTTP_CODE_EXPECTATION_FAILED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_MISDIRECTED_REQUEST LITERAL1 RESERVED_WORD_2 +HTTP_CODE_UNPROCESSABLE_ENTITY LITERAL1 RESERVED_WORD_2 +HTTP_CODE_LOCKED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_FAILED_DEPENDENCY LITERAL1 RESERVED_WORD_2 +HTTP_CODE_UPGRADE_REQUIRED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_PRECONDITION_REQUIRED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_TOO_MANY_REQUESTS LITERAL1 RESERVED_WORD_2 +HTTP_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE LITERAL1 RESERVED_WORD_2 +HTTP_CODE_INTERNAL_SERVER_ERROR LITERAL1 RESERVED_WORD_2 +HTTP_CODE_NOT_IMPLEMENTED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_BAD_GATEWAY LITERAL1 RESERVED_WORD_2 +HTTP_CODE_SERVICE_UNAVAILABLE LITERAL1 RESERVED_WORD_2 +HTTP_CODE_GATEWAY_TIMEOUT LITERAL1 RESERVED_WORD_2 +HTTP_CODE_HTTP_VERSION_NOT_SUPPORTED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_VARIANT_ALSO_NEGOTIATES LITERAL1 RESERVED_WORD_2 +HTTP_CODE_INSUFFICIENT_STORAGE LITERAL1 RESERVED_WORD_2 +HTTP_CODE_LOOP_DETECTED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_NOT_EXTENDED LITERAL1 RESERVED_WORD_2 +HTTP_CODE_NETWORK_AUTHENTICATION_REQUIRED LITERAL1 RESERVED_WORD_2 +HTTPC_TE_IDENTITY LITERAL1 RESERVED_WORD_2 +HTTPC_TE_CHUNKED LITERAL1 RESERVED_WORD_2 diff --git a/libraries/HTTPClient/library.properties b/libraries/HTTPClient/library.properties new file mode 100644 index 00000000..cb6eef0c --- /dev/null +++ b/libraries/HTTPClient/library.properties @@ -0,0 +1,10 @@ +name=HTTPClient +version=1.2 +author=Markus Sattler +maintainer=Earle F. Philhower, III +sentence=http Client for ESP8266, portes to the Pico +paragraph= +category=Communication +url=https://github.com/earlephilhower/arduino-pico/blob/master/libraries/HTTPClient +architectures=rp2040 +dot_a_linkage=true diff --git a/libraries/HTTPClient/src/HTTPClient.cpp b/libraries/HTTPClient/src/HTTPClient.cpp new file mode 100644 index 00000000..a38c98c4 --- /dev/null +++ b/libraries/HTTPClient/src/HTTPClient.cpp @@ -0,0 +1,1127 @@ +/** + HTTPClient.cpp + + Created on: 02.11.2015 + + Copyright (c) 2015 Markus Sattler. All rights reserved. + This file is part of the ESP8266HTTPClient for Arduino. + + Modified 2022 by Earle F. Philhower, III for the Pico RP2040 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ +#include + +#include "HTTPClient.h" +#include +#include "base64.h" + +// per https://github.com/esp8266/Arduino/issues/8231 +// make sure HTTPClient can be utilized as a movable class member +static_assert(std::is_default_constructible_v, ""); +static_assert(!std::is_copy_constructible_v, ""); +static_assert(std::is_move_constructible_v, ""); +static_assert(std::is_move_assignable_v, ""); + +static const char defaultUserAgentPstr[] PROGMEM = "Pico"; +const String HTTPClient::defaultUserAgent = defaultUserAgentPstr; + +//static int StreamReportToHttpClientReport (Stream::Report streamSendError) +//{ +// switch (streamSendError) +// { +// case Stream::Report::TimedOut: return HTTPC_ERROR_READ_TIMEOUT; +// case Stream::Report::ReadError: return HTTPC_ERROR_NO_STREAM; +// case Stream::Report::WriteError: return HTTPC_ERROR_STREAM_WRITE; +// case Stream::Report::ShortOperation: return HTTPC_ERROR_STREAM_WRITE; +// case Stream::Report::Success: return 0; +// } +// return 0; // never reached, keep gcc quiet +//} + +void HTTPClient::clear() { + _returnCode = 0; + _size = -1; + _headers = ""; + _location = ""; + _payload.reset(); +} + + + +/** + parsing the url for all needed parameters + @param client Client& + @param url String + @param https bool + @return success bool +*/ +bool HTTPClient::begin(String url) { + // check for : (http: or https:) + int index = url.indexOf(':'); + if (index < 0) { + DEBUG_HTTPCLIENT("[HTTP-Client][begin] failed to parse protocol\n"); + return false; + } + + String protocol = url.substring(0, index); + protocol.toLowerCase(); + if (protocol != "http" && protocol != "https") { + DEBUG_HTTPCLIENT("[HTTP-Client][begin] unknown protocol '%s'\n", protocol.c_str()); + return false; + } + + _port = (protocol == "https" ? 443 : 80); + if (!_client) { + if (protocol == "https") { + _tls(); + //_tls()->setInsecure(); + } else { + _client = new WiFiClient(); + } + } + + return beginInternal(url, protocol.c_str()); +} + + +/** + directly supply all needed parameters + @param client Client& + @param host String + @param port uint16_t + @param uri String + @param https bool + @return success bool +*/ +bool HTTPClient::begin(String host, uint16_t port, String uri, bool https) { + // Disconnect when reusing HTTPClient to talk to a different host + if (_host != host) { + _canReuse = false; + disconnect(true); + } + + clear(); + + _host = host; + _port = port; + _uri = uri; + _protocol = (https ? "https" : "http"); + if (!_client) { + if (https) { + _tls(); + //_tls()->setInsecure(); + } else { + _client = new WiFiClient(); + } + } + return true; +} + + +bool HTTPClient::beginInternal(const String& __url, const char* expectedProtocol) { + String url(__url); + + DEBUG_HTTPCLIENT("[HTTP-Client][begin] url: %s\n", url.c_str()); + clear(); + + // check for : (http: or https: + int index = url.indexOf(':'); + if (index < 0) { + DEBUG_HTTPCLIENT("[HTTP-Client][begin] failed to parse protocol\n"); + return false; + } + + _protocol = url.substring(0, index); + _protocol.toLowerCase(); + url.remove(0, (index + 3)); // remove http:// or https:// + + if (_protocol == "http") { + // set default port for 'http' + _port = 80; + } else if (_protocol == "https") { + // set default port for 'https' + _port = 443; + } else { + DEBUG_HTTPCLIENT("[HTTP-Client][begin] unsupported protocol: %s\n", _protocol.c_str()); + return false; + } + + index = url.indexOf('/'); + String host = url.substring(0, index); + url.remove(0, index); // remove host part + + // get Authorization + index = host.indexOf('@'); + if (index >= 0) { + // auth info + String auth = host.substring(0, index); + host.remove(0, index + 1); // remove auth part including @ + _base64Authorization = base64::encode(auth, false /* doNewLines */); + } + + const String oldHost = _host; + + // get port + index = host.indexOf(':'); + if (index >= 0) { + _host = host.substring(0, index); // hostname + host.remove(0, (index + 1)); // remove hostname + : + _port = host.toInt(); // get port + } else { + _host = host; + } + + // Disconnect when reusing HTTPClient to talk to a different host + if (oldHost != "" && _host != oldHost) { + _canReuse = false; + disconnect(true); + } + + _uri = url; + + if (expectedProtocol != nullptr && _protocol != expectedProtocol) { + DEBUG_HTTPCLIENT("[HTTP-Client][begin] unexpected protocol: %s, expected %s\n", _protocol.c_str(), expectedProtocol); + return false; + } + DEBUG_HTTPCLIENT("[HTTP-Client][begin] host: %s port: %d url: %s\n", _host.c_str(), _port, _uri.c_str()); + return true; +} + + +/** + end + called after the payload is handled +*/ +void HTTPClient::end(void) { + disconnect(false); + clear(); + if (_client) { + delete _client; + _client = nullptr; + _clientTLS = false; + } +} + +/** + disconnect + close the TCP socket +*/ +void HTTPClient::disconnect(bool preserveClient) { + if (connected()) { + if (_client->available() > 0) { + DEBUG_HTTPCLIENT("[HTTP-Client][end] still data in buffer (%d), clean up.\n", _client->available()); + while (_client->available() > 0) { + _client->read(); + } + } + + if (_reuse && _canReuse) { + DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp keep open for reuse\n"); + } else { + DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp stop\n"); + if (_client) { + _client->stop(); + if (!preserveClient) { + _client = nullptr; + } + } + } + } else { + if (!preserveClient && _client) { // Also destroy _client if not connected() + _client = nullptr; + } + + DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp is closed\n"); + } +} + +/** + connected + @return connected status +*/ +bool HTTPClient::connected() { + if (_client) { + return (_client->connected() || (_client->available() > 0)); + } + return false; +} + +/** + try to reuse the connection to the server + keep-alive + @param reuse bool +*/ +void HTTPClient::setReuse(bool reuse) { + _reuse = reuse; +} + +/** + set User Agent + @param userAgent const char +*/ +void HTTPClient::setUserAgent(const String& userAgent) { + _userAgent = userAgent; +} + +/** + set the Authorizatio for the http request + @param user const char + @param password const char +*/ +void HTTPClient::setAuthorization(const char * user, const char * password) { + if (user && password) { + String auth = user; + auth += ':'; + auth += password; + _base64Authorization = base64::encode(auth, false /* doNewLines */); + } +} + +/** + set the Authorization for the http request + @param auth const char * base64 +*/ +void HTTPClient::setAuthorization(const char * auth) { + if (auth) { + setAuthorization(String(auth)); + } +} + +/** + set the Authorization for the http request + @param auth String base64 +*/ +void HTTPClient::setAuthorization(String auth) { + _base64Authorization = std::move(auth); + _base64Authorization.replace(String('\n'), ""); +} + +/** + set the timeout for the TCP connection + @param timeout unsigned int +*/ +void HTTPClient::setTimeout(uint16_t timeout) { + _tcpTimeout = timeout; + if (connected()) { + _client->setTimeout(timeout); + } +} + +/** + set the URL to a new value. Handy for following redirects. + @param url +*/ +bool HTTPClient::setURL(const String& url) { + // if the new location is only a path then only update the URI + if (url && url[0] == '/') { + _uri = url; + clear(); + return true; + } + + if (!url.startsWith(_protocol + ':')) { + DEBUG_HTTPCLIENT("[HTTP-Client][setURL] new URL not the same protocol, expected '%s', URL: '%s'\n", _protocol.c_str(), url.c_str()); + return false; + } + // disconnect but preserve _client (clear _canReuse so disconnect will close the connection) + _canReuse = false; + disconnect(true); + return beginInternal(url, nullptr); +} + +/** + set redirect follow mode. See `followRedirects_t` enum for available modes. + @param follow +*/ +void HTTPClient::setFollowRedirects(followRedirects_t follow) { + _followRedirects = follow; +} + +void HTTPClient::setRedirectLimit(uint16_t limit) { + _redirectLimit = limit; +} + +/** + use HTTP1.0 + @param useHTTP10 bool +*/ +void HTTPClient::useHTTP10(bool useHTTP10) { + _useHTTP10 = useHTTP10; + _reuse = !useHTTP10; +} + +/** + send a GET request + @return http code +*/ +int HTTPClient::GET() { + return sendRequest("GET"); +} +/** + send a DELETE request + @return http code +*/ +int HTTPClient::DELETE() { + return sendRequest("DELETE"); +} + +/** + sends a post request to the server + @param payload const uint8_t + @param size size_t + @return http code +*/ +int HTTPClient::POST(const uint8_t* payload, size_t size) { + return sendRequest("POST", payload, size); +} + +int HTTPClient::POST(const String& payload) { + return POST((uint8_t *) payload.c_str(), payload.length()); +} + +/** + sends a put request to the server + @param payload uint8_t + @param size size_t + @return http code +*/ +int HTTPClient::PUT(const uint8_t* payload, size_t size) { + return sendRequest("PUT", payload, size); +} + +int HTTPClient::PUT(const String& payload) { + return PUT((const uint8_t *) payload.c_str(), payload.length()); +} + +/** + sends a patch request to the server + @param payload const uint8_t + @param size size_t + @return http code +*/ +int HTTPClient::PATCH(const uint8_t * payload, size_t size) { + return sendRequest("PATCH", payload, size); +} + +int HTTPClient::PATCH(const String& payload) { + return PATCH((const uint8_t *) payload.c_str(), payload.length()); +} + +/** + sendRequest + @param type const char * "GET", "POST", .... + @param payload String data for the message body + @return +*/ +int HTTPClient::sendRequest(const char * type, const String& payload) { + return sendRequest(type, (const uint8_t *) payload.c_str(), payload.length()); +} + +class StreamConstPtr { +public: + StreamConstPtr(const uint8_t *payload, size_t size) { + _payload = payload; + _size = size; + } + StreamConstPtr(const String& string) { + _payload = (const uint8_t *)string.c_str(); + _size = string.length(); + } + size_t sendAll(Client *dst) { + uint32_t start = millis(); + size_t sent = 0; + while ((sent < _size) && (millis() - start < 5000)) { + size_t towrite = std::min((size_t)128, _size - sent); + auto wrote = dst->write(_payload, towrite); + if (wrote <= 0) { + break; + } + sent += wrote; + _payload += wrote; + } + return sent; + } + const uint8_t *_payload; + size_t _size; +}; + +/** + sendRequest + @param type const char * "GET", "POST", .... + @param payload const uint8_t * data for the message body if null not send + @param size size_t size for the message body if 0 not send + @return -1 if no info or > 0 when Content-Length is set by server +*/ +int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t size) { + int code; + bool redirect = false; + uint16_t redirectCount = 0; + do { + // wipe out any existing headers from previous request + for (size_t i = 0; i < _headerKeysCount; i++) { + if (_currentHeaders[i].value.length() > 0) { + _currentHeaders[i].value = ""; + } + } + + DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] type: '%s' redirCount: %d\n", type, redirectCount); + + // connect to server + if (!connect()) { + return returnError(HTTPC_ERROR_CONNECTION_FAILED); + } + + addHeader(F("Content-Length"), String(payload && size > 0 ? size : 0)); + + // send Header + if (!sendHeader(type)) { + return returnError(HTTPC_ERROR_SEND_HEADER_FAILED); + } + + // transfer all of it, with send-timeout + if (size && StreamConstPtr(payload, size).sendAll(_client) != size) { + return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED); + } + + // handle Server Response (Header) + code = handleHeaderResponse(); + + // + // Handle redirections as stated in RFC document: + // https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + // + // Implementing HTTP_CODE_FOUND as redirection with GET method, + // to follow most of existing user agent implementations. + // + redirect = false; + if ( + _followRedirects != HTTPC_DISABLE_FOLLOW_REDIRECTS && + redirectCount < _redirectLimit && + _location.length() > 0 + ) { + switch (code) { + // redirecting using the same method + case HTTP_CODE_MOVED_PERMANENTLY: + case HTTP_CODE_TEMPORARY_REDIRECT: { + if ( + // allow to force redirections on other methods + // (the RFC require user to accept the redirection) + _followRedirects == HTTPC_FORCE_FOLLOW_REDIRECTS || + // allow GET and HEAD methods without force + !strcmp(type, "GET") || + !strcmp(type, "HEAD") + ) { + redirectCount += 1; + DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] following redirect (the same method): '%s' redirCount: %d\n", _location.c_str(), redirectCount); + if (!setURL(_location)) { + DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] failed setting URL for redirection\n"); + // no redirection + break; + } + // redirect using the same request method and payload, different URL + redirect = true; + } + break; + } + // redirecting with method dropped to GET or HEAD + // note: it does not need `HTTPC_FORCE_FOLLOW_REDIRECTS` for any method + case HTTP_CODE_FOUND: + case HTTP_CODE_SEE_OTHER: { + redirectCount += 1; + DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] following redirect (dropped to GET/HEAD): '%s' redirCount: %d\n", _location.c_str(), redirectCount); + if (!setURL(_location)) { + DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] failed setting URL for redirection\n"); + // no redirection + break; + } + // redirect after changing method to GET/HEAD and dropping payload + type = "GET"; + payload = nullptr; + size = 0; + redirect = true; + break; + } + + default: + break; + } + } + } while (redirect); + + // handle Server Response (Header) + return returnError(code); +} + +size_t StreamSendSize(Stream *s, Print *c, int size) { + int sent = 0; + if (size < 0) { + size = 999999; // Transfer until read fails + } + uint32_t start = millis(); + while ((sent < size) && (millis() - start < 5000)) { + int x = s->read(); + if (x < 0) { + break; + } + if (c->write(x)) { + sent++; + } else { + break; + } + } + return sent; +} +/** + sendRequest + @param type const char * "GET", "POST", .... + @param stream Stream * data stream for the message body + @param size size_t size for the message body if 0 not Content-Length is send + @return -1 if no info or > 0 when Content-Length is set by server +*/ +int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size) { + + if (!stream) { + return returnError(HTTPC_ERROR_NO_STREAM); + } + + // connect to server + if (!connect()) { + return returnError(HTTPC_ERROR_CONNECTION_FAILED); + } + + if (size > 0) { + addHeader(F("Content-Length"), String(size)); + } + + // send Header + if (!sendHeader(type)) { + return returnError(HTTPC_ERROR_SEND_HEADER_FAILED); + } + + // transfer all of it, with timeout + size_t transferred = StreamSendSize(stream, _client, size); + if (transferred != size) { + DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %zu but got %zu failed.\n", size, transferred); + return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED); + } + + // handle Server Response (Header) + return returnError(handleHeaderResponse()); +} + +/** + size of message body / payload + @return -1 if no info or > 0 when Content-Length is set by server +*/ +int HTTPClient::getSize(void) { + return _size; +} + +/** + Location if redirect +*/ +const String& HTTPClient::getLocation(void) { + return _location; +} + +/** + returns the stream of the tcp connection + @return WiFiClient +*/ +WiFiClient& HTTPClient::getStream(void) { + if (connected()) { + return *_client; + } + + DEBUG_HTTPCLIENT("[HTTP-Client] getStream: not connected\n"); + static WiFiClient empty; + return empty; +} + +/** + returns the stream of the tcp connection + @return WiFiClient +*/ +WiFiClient* HTTPClient::getStreamPtr(void) { + if (connected()) { + return _client; + } + + DEBUG_HTTPCLIENT("[HTTP-Client] getStreamPtr: not connected\n"); + return nullptr; +} + +/** + write all message body / payload to Stream + @param stream Stream + @return bytes written ( negative values are error codes ) +*/ +int HTTPClient::writeToStream(Stream * stream) { + return writeToPrint(stream); +} + +/** + write all message body / payload to Print + @param print Print + @return bytes written ( negative values are error codes ) +*/ +int HTTPClient::writeToPrint(Print * print) { + + if (!print) { + return returnError(HTTPC_ERROR_NO_STREAM); + } + + // Only return error if not connected and no data available, because otherwise ::getString() will return an error instead of an empty + // string when the server returned a http code 204 (no content) + if (!connected() && _transferEncoding != HTTPC_TE_IDENTITY && _size > 0) { + return returnError(HTTPC_ERROR_NOT_CONNECTED); + } + + // get length of document (is -1 when Server sends no Content-Length header) + int len = _size; + int ret = 0; + + if (_transferEncoding == HTTPC_TE_IDENTITY) { + // len < 0: transfer all of it, with timeout + // len >= 0: max:len, with timeout + ret = StreamSendSize(_client, print, len); + + if (len > 0 && ret != len) { + return HTTPC_ERROR_NO_STREAM; + } + // do we have an error? + // if(_client->getLastSendReport() != Stream::Report::Success) { + // return returnError(StreamReportToHttpClientReport(_client->getLastSendReport())); + // } + } else if (_transferEncoding == HTTPC_TE_CHUNKED) { + int size = 0; + while (1) { + if (!connected()) { + return returnError(HTTPC_ERROR_CONNECTION_LOST); + } + String chunkHeader = _client->readStringUntil('\n'); + + if (chunkHeader.length() <= 0) { + return returnError(HTTPC_ERROR_READ_TIMEOUT); + } + + chunkHeader.trim(); // remove \r + + // read size of chunk + len = (uint32_t) strtol((const char *) chunkHeader.c_str(), NULL, 16); + size += len; + DEBUG_HTTPCLIENT("[HTTP-Client] read chunk len: %d\n", len); + + // data left? + if (len > 0) { + // read len bytes with timeout + int r = StreamSendSize(_client, print, len); + if (r != len) { + return HTTPC_ERROR_NO_STREAM; + } + + // if (_client->getLastSendReport() != Stream::Report::Success) + // // not all data transferred + // return returnError(StreamReportToHttpClientReport(_client->getLastSendReport())); + ret += r; + } else { + + // if no length Header use global chunk size + if (_size <= 0) { + _size = size; + } + + // check if we have write all data out + if (ret != _size) { + return returnError(HTTPC_ERROR_STREAM_WRITE); + } + break; + } + + // read trailing \r\n at the end of the chunk + char buf[2]; + auto trailing_seq_len = _client->readBytes((uint8_t*)buf, 2); + if (trailing_seq_len != 2 || buf[0] != '\r' || buf[1] != '\n') { + return returnError(HTTPC_ERROR_READ_TIMEOUT); + } + } + } else { + return returnError(HTTPC_ERROR_ENCODING); + } + + disconnect(true); + return ret; +} + +/** + return all payload as String (may need lot of ram or trigger out of memory!) + @return String +*/ +const String& HTTPClient::getString(void) { + if (_payload) { + return *_payload; + } + + _payload.reset(new StreamString()); + + if (_size > 0) { + // try to reserve needed memory + if (!_payload->reserve((_size + 1))) { + DEBUG_HTTPCLIENT("[HTTP-Client][getString] not enough memory to reserve a string! need: %d\n", (_size + 1)); + return *_payload; + } + } + + writeToStream(_payload.get()); + return *_payload; +} + +/** + converts error code to String + @param error int + @return String +*/ +String HTTPClient::errorToString(int error) { + switch (error) { + case HTTPC_ERROR_CONNECTION_FAILED: + return F("connection failed"); + case HTTPC_ERROR_SEND_HEADER_FAILED: + return F("send header failed"); + case HTTPC_ERROR_SEND_PAYLOAD_FAILED: + return F("send payload failed"); + case HTTPC_ERROR_NOT_CONNECTED: + return F("not connected"); + case HTTPC_ERROR_CONNECTION_LOST: + return F("connection lost"); + case HTTPC_ERROR_NO_STREAM: + return F("no stream"); + case HTTPC_ERROR_NO_HTTP_SERVER: + return F("no HTTP server"); + case HTTPC_ERROR_TOO_LESS_RAM: + return F("not enough ram"); + case HTTPC_ERROR_ENCODING: + return F("Transfer-Encoding not supported"); + case HTTPC_ERROR_STREAM_WRITE: + return F("Stream write error"); + case HTTPC_ERROR_READ_TIMEOUT: + return F("read Timeout"); + default: + return String(); + } +} + +/** + adds Header to the request + @param name + @param value + @param first +*/ +void HTTPClient::addHeader(const String& name, const String& value, bool first, bool replace) { + // not allow set of Header handled by code + if (!name.equalsIgnoreCase(F("Connection")) && + !name.equalsIgnoreCase(F("User-Agent")) && + !name.equalsIgnoreCase(F("Host")) && + !(name.equalsIgnoreCase(F("Authorization")) && _base64Authorization.length())) { + + String headerLine; + headerLine.reserve(name.length() + value.length() + 4); + headerLine += name; + headerLine += ": "; + + if (replace) { + int headerStart = _headers.indexOf(headerLine); + if (headerStart != -1) { + int headerEnd = _headers.indexOf('\n', headerStart); + _headers = _headers.substring(0, headerStart) + _headers.substring(headerEnd + 1); + } + } + + headerLine += value; + headerLine += "\r\n"; + if (first) { + _headers = headerLine + _headers; + } else { + _headers += headerLine; + } + } +} + +void HTTPClient::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) { + _headerKeysCount = headerKeysCount; + _currentHeaders = std::make_unique(_headerKeysCount); + for (size_t i = 0; i < _headerKeysCount; i++) { + _currentHeaders[i].key = headerKeys[i]; + } +} + +String HTTPClient::header(const char* name) { + for (size_t i = 0; i < _headerKeysCount; ++i) { + if (_currentHeaders[i].key == name) { + return _currentHeaders[i].value; + } + } + return String(); +} + +String HTTPClient::header(size_t i) { + if (i < _headerKeysCount) { + return _currentHeaders[i].value; + } + return String(); +} + +String HTTPClient::headerName(size_t i) { + if (i < _headerKeysCount) { + return _currentHeaders[i].key; + } + return String(); +} + +int HTTPClient::headers() { + return _headerKeysCount; +} + +bool HTTPClient::hasHeader(const char* name) { + for (size_t i = 0; i < _headerKeysCount; ++i) { + if ((_currentHeaders[i].key == name) && (_currentHeaders[i].value.length() > 0)) { + return true; + } + } + return false; +} + +/** + init TCP connection and handle ssl verify if needed + @return true if connection is ok +*/ +bool HTTPClient::connect(void) { + if (_reuse && _canReuse && connected()) { + DEBUG_HTTPCLIENT("[HTTP-Client] connect: already connected, reusing connection\n"); + // clear _client's output (all of it, no timeout) + while (_client->available()) { + _client->read(); + } + return true; + } + + if (!_client) { + DEBUG_HTTPCLIENT("[HTTP-Client] connect: HTTPClient::begin was not called or returned error\n"); + return false; + } + + _client->setTimeout(_tcpTimeout); + + if (!_client->connect(_host.c_str(), _port)) { + DEBUG_HTTPCLIENT("[HTTP-Client] failed connect to %s:%u\n", _host.c_str(), _port); + return false; + } + + DEBUG_HTTPCLIENT("[HTTP-Client] connected to %s:%u\n", _host.c_str(), _port); + _client->setNoDelay(true); + return connected(); +} + +/** + sends HTTP request header + @param type (GET, POST, ...) + @return status +*/ +bool HTTPClient::sendHeader(const char * type) { + if (!connected()) { + return false; + } + + String header; + // 128: Arbitrarily chosen to have enough buffer space for avoiding internal reallocations + header.reserve(_headers.length() + _uri.length() + + _base64Authorization.length() + _host.length() + _userAgent.length() + 128); + header += type; + header += ' '; + if (_uri.length()) { + header += _uri; + } else { + header += '/'; + } + header += F(" HTTP/1."); + + if (_useHTTP10) { + header += '0'; + } else { + header += '1'; + } + + header += F("\r\nHost: "); + header += _host; + if (_port != 80 && _port != 443) { + header += ':'; + header += String(_port); + } + if (_userAgent.length()) { + header += F("\r\nUser-Agent: "); + header += _userAgent; + } + + if (!_useHTTP10) { + header += F("\r\nAccept-Encoding: identity;q=1,chunked;q=0.1,*;q=0"); + } + + if (_base64Authorization.length()) { + header += F("\r\nAuthorization: Basic "); + header += _base64Authorization; + } + + header += F("\r\nConnection: "); + header += _reuse ? F("keep-alive") : F("close"); + header += "\r\n"; + + header += _headers; + header += "\r\n"; + + DEBUG_HTTPCLIENT("[HTTP-Client] sending request header\n-----\n%s-----\n", header.c_str()); + + // transfer all of it, with timeout + return StreamConstPtr(header).sendAll(_client) == header.length(); +} + +/** + reads the response from the server + @return int http code +*/ +int HTTPClient::handleHeaderResponse() { + + if (!connected()) { + return HTTPC_ERROR_NOT_CONNECTED; + } + + clear(); + + _canReuse = _reuse; + + String transferEncoding; + + _transferEncoding = HTTPC_TE_IDENTITY; + unsigned long lastDataTime = millis(); + + while (connected()) { + size_t len = _client->available(); + if (len > 0) { + int headerSeparator = -1; + String headerLine = _client->readStringUntil('\n'); + + lastDataTime = millis(); + + DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] RX: '%s'\n", headerLine.c_str()); + + if (headerLine.startsWith(F("HTTP/1."))) { + + constexpr auto httpVersionIdx = sizeof "HTTP/1." - 1; + _canReuse = _canReuse && (headerLine[httpVersionIdx] != '0'); + _returnCode = headerLine.substring(httpVersionIdx + 2, headerLine.indexOf(' ', httpVersionIdx + 2)).toInt(); + _canReuse = _canReuse && (_returnCode > 0) && (_returnCode < 500); + + } else if ((headerSeparator = headerLine.indexOf(':')) > 0) { + String headerName = headerLine.substring(0, headerSeparator); + String headerValue = headerLine.substring(headerSeparator + 1); + headerValue.trim(); + + if (headerName.equalsIgnoreCase(F("Content-Length"))) { + _size = headerValue.toInt(); + } + + if (_canReuse && headerName.equalsIgnoreCase(F("Connection"))) { + if (headerValue.indexOf(F("close")) >= 0 && + headerValue.indexOf(F("keep-alive")) < 0) { + _canReuse = false; + } + } + + if (headerName.equalsIgnoreCase(F("Transfer-Encoding"))) { + transferEncoding = headerValue; + } + + if (headerName.equalsIgnoreCase(F("Location"))) { + _location = headerValue; + } + + for (size_t i = 0; i < _headerKeysCount; i++) { + if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) { + if (_currentHeaders[i].value != "") { + // Existing value, append this one with a comma + _currentHeaders[i].value += ','; + _currentHeaders[i].value += headerValue; + } else { + _currentHeaders[i].value = headerValue; + } + break; // We found a match, stop looking + } + } + continue; + } + + headerLine.trim(); // remove \r + + if (headerLine == "") { + DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] code: %d\n", _returnCode); + + if (_size > 0) { + DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] size: %d\n", _size); + } + + if (transferEncoding.length() > 0) { + DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] Transfer-Encoding: %s\n", transferEncoding.c_str()); + if (transferEncoding.equalsIgnoreCase(F("chunked"))) { + _transferEncoding = HTTPC_TE_CHUNKED; + } else { + _returnCode = HTTPC_ERROR_ENCODING; + return _returnCode; + } + } else { + _transferEncoding = HTTPC_TE_IDENTITY; + } + + if (_returnCode <= 0) { + DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] Remote host is not an HTTP Server!"); + _returnCode = HTTPC_ERROR_NO_HTTP_SERVER; + } + return _returnCode; + } + + } else { + if ((millis() - lastDataTime) > _tcpTimeout) { + return HTTPC_ERROR_READ_TIMEOUT; + } + } + } + + return HTTPC_ERROR_CONNECTION_LOST; +} + +/** + called to handle error return, may disconnect the connection if still exists + @param error + @return error +*/ +int HTTPClient::returnError(int error) { + if (error < 0) { + DEBUG_HTTPCLIENT("[HTTP-Client][returnError] error(%d): %s\n", error, errorToString(error).c_str()); + if (connected()) { + DEBUG_HTTPCLIENT("[HTTP-Client][returnError] tcp stop\n"); + _client->stop(); + } + } + return error; +} diff --git a/libraries/HTTPClient/src/HTTPClient.h b/libraries/HTTPClient/src/HTTPClient.h new file mode 100644 index 00000000..841819f4 --- /dev/null +++ b/libraries/HTTPClient/src/HTTPClient.h @@ -0,0 +1,353 @@ +/** + HTTPClient.h + + Modified 2022 by Earle F. Philhower, III + + Created on: 02.11.2015 + + Copyright (c) 2015 Markus Sattler. All rights reserved. + This file is part of the ESP8266HTTPClient for Arduino. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Modified by Jeroen Döll, June 2018 +*/ + +#pragma once + +#include +#include +#include +#include + +#include + +#ifdef DEBUG_ESP_HTTP_CLIENT +#ifdef DEBUG_ESP_PORT +#define DEBUG_HTTPCLIENT(fmt, ...) DEBUG_ESP_PORT.printf_P( (PGM_P)PSTR(fmt), ## __VA_ARGS__ ) +#endif +#endif + +#ifndef DEBUG_HTTPCLIENT +#define DEBUG_HTTPCLIENT(...) do { (void)0; } while (0) +#endif + +#define HTTPCLIENT_DEFAULT_TCP_TIMEOUT (5000) + +/// HTTP client errors +#define HTTPC_ERROR_CONNECTION_FAILED (-1) +#define HTTPC_ERROR_SEND_HEADER_FAILED (-2) +#define HTTPC_ERROR_SEND_PAYLOAD_FAILED (-3) +#define HTTPC_ERROR_NOT_CONNECTED (-4) +#define HTTPC_ERROR_CONNECTION_LOST (-5) +#define HTTPC_ERROR_NO_STREAM (-6) +#define HTTPC_ERROR_NO_HTTP_SERVER (-7) +#define HTTPC_ERROR_TOO_LESS_RAM (-8) +#define HTTPC_ERROR_ENCODING (-9) +#define HTTPC_ERROR_STREAM_WRITE (-10) +#define HTTPC_ERROR_READ_TIMEOUT (-11) + +constexpr int HTTPC_ERROR_CONNECTION_REFUSED __attribute__((deprecated)) = HTTPC_ERROR_CONNECTION_FAILED; + +/// size for the stream handling +#define HTTP_TCP_BUFFER_SIZE (1460) + +/// HTTP codes see RFC7231 +typedef enum { + HTTP_CODE_CONTINUE = 100, + HTTP_CODE_SWITCHING_PROTOCOLS = 101, + HTTP_CODE_PROCESSING = 102, + HTTP_CODE_OK = 200, + HTTP_CODE_CREATED = 201, + HTTP_CODE_ACCEPTED = 202, + HTTP_CODE_NON_AUTHORITATIVE_INFORMATION = 203, + HTTP_CODE_NO_CONTENT = 204, + HTTP_CODE_RESET_CONTENT = 205, + HTTP_CODE_PARTIAL_CONTENT = 206, + HTTP_CODE_MULTI_STATUS = 207, + HTTP_CODE_ALREADY_REPORTED = 208, + HTTP_CODE_IM_USED = 226, + HTTP_CODE_MULTIPLE_CHOICES = 300, + HTTP_CODE_MOVED_PERMANENTLY = 301, + HTTP_CODE_FOUND = 302, + HTTP_CODE_SEE_OTHER = 303, + HTTP_CODE_NOT_MODIFIED = 304, + HTTP_CODE_USE_PROXY = 305, + HTTP_CODE_TEMPORARY_REDIRECT = 307, + HTTP_CODE_PERMANENT_REDIRECT = 308, + HTTP_CODE_BAD_REQUEST = 400, + HTTP_CODE_UNAUTHORIZED = 401, + HTTP_CODE_PAYMENT_REQUIRED = 402, + HTTP_CODE_FORBIDDEN = 403, + HTTP_CODE_NOT_FOUND = 404, + HTTP_CODE_METHOD_NOT_ALLOWED = 405, + HTTP_CODE_NOT_ACCEPTABLE = 406, + HTTP_CODE_PROXY_AUTHENTICATION_REQUIRED = 407, + HTTP_CODE_REQUEST_TIMEOUT = 408, + HTTP_CODE_CONFLICT = 409, + HTTP_CODE_GONE = 410, + HTTP_CODE_LENGTH_REQUIRED = 411, + HTTP_CODE_PRECONDITION_FAILED = 412, + HTTP_CODE_PAYLOAD_TOO_LARGE = 413, + HTTP_CODE_URI_TOO_LONG = 414, + HTTP_CODE_UNSUPPORTED_MEDIA_TYPE = 415, + HTTP_CODE_RANGE_NOT_SATISFIABLE = 416, + HTTP_CODE_EXPECTATION_FAILED = 417, + HTTP_CODE_MISDIRECTED_REQUEST = 421, + HTTP_CODE_UNPROCESSABLE_ENTITY = 422, + HTTP_CODE_LOCKED = 423, + HTTP_CODE_FAILED_DEPENDENCY = 424, + HTTP_CODE_UPGRADE_REQUIRED = 426, + HTTP_CODE_PRECONDITION_REQUIRED = 428, + HTTP_CODE_TOO_MANY_REQUESTS = 429, + HTTP_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, + HTTP_CODE_INTERNAL_SERVER_ERROR = 500, + HTTP_CODE_NOT_IMPLEMENTED = 501, + HTTP_CODE_BAD_GATEWAY = 502, + HTTP_CODE_SERVICE_UNAVAILABLE = 503, + HTTP_CODE_GATEWAY_TIMEOUT = 504, + HTTP_CODE_HTTP_VERSION_NOT_SUPPORTED = 505, + HTTP_CODE_VARIANT_ALSO_NEGOTIATES = 506, + HTTP_CODE_INSUFFICIENT_STORAGE = 507, + HTTP_CODE_LOOP_DETECTED = 508, + HTTP_CODE_NOT_EXTENDED = 510, + HTTP_CODE_NETWORK_AUTHENTICATION_REQUIRED = 511 +} t_http_codes; + +typedef enum { + HTTPC_TE_IDENTITY, + HTTPC_TE_CHUNKED +} transferEncoding_t; + +/** + redirection follow mode. + + `HTTPC_DISABLE_FOLLOW_REDIRECTS` - no redirection will be followed. + + `HTTPC_STRICT_FOLLOW_REDIRECTS` - strict RFC2616, only requests using + GET or HEAD methods will be redirected (using the same method), + since the RFC requires end-user confirmation in other cases. + + `HTTPC_FORCE_FOLLOW_REDIRECTS` - all redirections will be followed, + regardless of a used method. New request will use the same method, + and they will include the same body data and the same headers. + In the sense of the RFC, it's just like every redirection is confirmed. +*/ +typedef enum { + HTTPC_DISABLE_FOLLOW_REDIRECTS, + HTTPC_STRICT_FOLLOW_REDIRECTS, + HTTPC_FORCE_FOLLOW_REDIRECTS +} followRedirects_t; + +class TransportTraits; +typedef std::unique_ptr TransportTraitsPtr; + +class HTTPClient { +public: + HTTPClient() = default; + ~HTTPClient() = default; + HTTPClient(HTTPClient&&) = default; + HTTPClient& operator=(HTTPClient&&) = default; + + bool begin(String url); + bool begin(String host, uint16_t port, String uri = "/", bool https = false); + bool begin(String url, const uint8_t httpsFingerprint[20]) { + setFingerprint(httpsFingerprint); + return begin(url); + } + bool begin(String host, uint16_t port, String uri, const uint8_t httpsFingerprint[20]) { + setFingerprint(httpsFingerprint); + return begin(host, port, uri); + } + + void end(void); + + bool connected(void); + + void setReuse(bool reuse); /// keep-alive + void setUserAgent(const String& userAgent); + void setAuthorization(const char * user, const char * password); + void setAuthorization(const char * auth); + void setAuthorization(String auth); + void setTimeout(uint16_t timeout); + + // Redirections + void setFollowRedirects(followRedirects_t follow); + void setRedirectLimit(uint16_t limit); // max redirects to follow for a single request + + bool setURL(const String& url); // handy for handling redirects + void useHTTP10(bool usehttp10 = true); + + /// request handling + int GET(); + int DELETE(); + int POST(const uint8_t* payload, size_t size); + int POST(const String& payload); + int PUT(const uint8_t* payload, size_t size); + int PUT(const String& payload); + int PATCH(const uint8_t* payload, size_t size); + int PATCH(const String& payload); + int sendRequest(const char* type, const String& payload); + int sendRequest(const char* type, const uint8_t* payload = NULL, size_t size = 0); + int sendRequest(const char* type, Stream * stream, size_t size = 0); + + void addHeader(const String& name, const String& value, bool first = false, bool replace = true); + + /// Response handling + void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); + String header(const char* name); // get request header value by name + String header(size_t i); // get request header value by number + String headerName(size_t i); // get request header name by number + int headers(); // get header count + bool hasHeader(const char* name); // check if header exists + + + int getSize(void); + const String& getLocation(void); // Location header from redirect if 3XX + + WiFiClient& getStream(void); + WiFiClient* getStreamPtr(void); + int writeToPrint(Print* print); + int writeToStream(Stream* stream); + const String& getString(void); + static String errorToString(int error); + + // ---------------------------------------------------------------------------------------------- + // HTTPS support, mirrors the WiFiClientSecure interface + // Could possibly use a virtual interface class between the two, but for now it is more + // straightforward to simply feed calls through manually here. + void setSession(Session *session) { + _tls()->setSession(session); + } + void setInsecure() { + _tls()->setInsecure(); + } + void setKnownKey(const PublicKey *pk, unsigned usages = BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN) { + _tls()->setKnownKey(pk, usages); + } + bool setFingerprint(const uint8_t fingerprint[20]) { + return _tls()->setFingerprint(fingerprint); + } + bool setFingerprint(const char *fpStr) { + return _tls()->setFingerprint(fpStr); + } + void allowSelfSignedCerts() { + _tls()->allowSelfSignedCerts(); + } + void setTrustAnchors(const X509List *ta) { + _tls()->setTrustAnchors(ta); + } + void setX509Time(time_t now) { + _tls()->setX509Time(now); + } + void setClientRSACert(const X509List *cert, const PrivateKey *sk) { + _tls()->setClientRSACert(cert, sk); + } + void setClientECCert(const X509List *cert, const PrivateKey *sk, unsigned allowed_usages, unsigned cert_issuer_key_type) { + _tls()->setClientECCert(cert, sk, allowed_usages, cert_issuer_key_type); + } + void setBufferSizes(int recv, int xmit) { + _tls()->setBufferSizes(recv, xmit); + } + void setCertStore(CertStoreBase *certStore) { + _tls()->setCertStore(certStore); + } + bool setCiphers(const uint16_t *cipherAry, int cipherCount) { + return _tls()->setCiphers(cipherAry, cipherCount); + } + bool setCiphers(const std::vector& list) { + return _tls()->setCiphers(list); + } + bool setCiphersLessSecure() { + return _tls()->setCiphersLessSecure(); + } + bool setSSLVersion(uint32_t min = BR_TLS10, uint32_t max = BR_TLS12) { + return _tls()->setSSLVersion(min, max); + } + void setCACert(const char *rootCA) { + _tls()->setCACert(rootCA); + } + void setCertificate(const char *client_ca) { + _tls()->setCertificate(client_ca); + } + void setPrivateKey(const char *private_key) { + _tls()->setPrivateKey(private_key); + } + bool loadCACert(Stream& stream, size_t size) { + return _tls()->loadCACert(stream, size); + } + bool loadCertificate(Stream& stream, size_t size) { + return _tls()->loadCertificate(stream, size); + } + bool loadPrivateKey(Stream& stream, size_t size) { + return _tls()->loadPrivateKey(stream, size); + } + + + +protected: + // HTTPS helpers + WiFiClientSecure *_tls() { + if (!_client) { + _client = new WiFiClientSecure(); + } + _clientTLS = true; + return (WiFiClientSecure*)_client; + } + + struct RequestArgument { + String key; + String value; + }; + + bool beginInternal(const String& url, const char* expectedProtocol); + void disconnect(bool preserveClient = false); + void clear(); + int returnError(int error); + bool connect(void); + bool sendHeader(const char * type); + int handleHeaderResponse(); + int writeToStreamDataBlock(Stream * stream, int len); + + WiFiClient *_client = nullptr; + bool _clientTLS = false; + + /// request handling + String _host; + uint16_t _port = 0; + bool _reuse = true; + uint16_t _tcpTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT; + bool _useHTTP10 = false; + + String _uri; + String _protocol; + String _headers; + String _base64Authorization; + + static const String defaultUserAgent; + String _userAgent = defaultUserAgent; + + /// Response handling + std::unique_ptr _currentHeaders; + size_t _headerKeysCount = 0; + + int _returnCode = 0; + int _size = -1; + bool _canReuse = false; + followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS; + uint16_t _redirectLimit = 10; + String _location; + transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY; + std::unique_ptr _payload; + + +}; diff --git a/libraries/HTTPClient/src/base64.cpp b/libraries/HTTPClient/src/base64.cpp new file mode 100644 index 00000000..64fd1b49 --- /dev/null +++ b/libraries/HTTPClient/src/base64.cpp @@ -0,0 +1,69 @@ +/** + base64.cpp + + Created on: 09.12.2015 + + Copyright (c) 2015 Markus Sattler. All rights reserved. + This file is part of the ESP8266 core for Arduino. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "Arduino.h" +extern "C" { +#include "libb64/cencode.h" +} +#include "base64.h" + +/** + convert input data to base64 + @param data const uint8_t + @param length size_t + @return String +*/ +String base64::encode(const uint8_t * data, size_t length, bool doNewLines) { + String base64; + + // base64 needs more size then the source data, use cencode.h macros + size_t size = ((doNewLines ? base64_encode_expected_len(length) + : base64_encode_expected_len_nonewlines(length)) + 1); + + if (base64.reserve(size)) { + + base64_encodestate _state; + if (doNewLines) { + base64_init_encodestate(&_state); + } else { + base64_init_encodestate_nonewlines(&_state); + } + + constexpr size_t BUFSIZE = 48; + char buf[BUFSIZE + 1 /* newline */ + 1 /* NUL */]; + for (size_t len = 0; len < length; len += BUFSIZE * 3 / 4) { + size_t blocklen = base64_encode_block((const char*) data + len, + std::min(BUFSIZE * 3 / 4, length - len), buf, &_state); + buf[blocklen] = '\0'; + base64 += buf; + } + if (base64_encode_blockend(buf, &_state)) { + base64 += buf; + } + } else { + base64 = F("-FAIL-"); + } + + return base64; +} diff --git a/libraries/HTTPClient/src/base64.h b/libraries/HTTPClient/src/base64.h new file mode 100644 index 00000000..32ae7776 --- /dev/null +++ b/libraries/HTTPClient/src/base64.h @@ -0,0 +1,48 @@ +/** + base64.h + + Created on: 09.12.2015 + + Copyright (c) 2015 Markus Sattler. All rights reserved. + This file is part of the ESP8266 core for Arduino. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#pragma once + +#include + +class base64 { +public: + // NOTE: The default behaviour of backend (lib64) + // is to add a newline every 72 (encoded) characters output. + // This may 'break' longer uris and json variables + static String encode(const uint8_t * data, size_t length, bool doNewLines); + static inline String encode(const String& text, bool doNewLines) { + return encode((const uint8_t *) text.c_str(), text.length(), doNewLines); + } + + // esp32 compat: + + static inline String encode(const uint8_t * data, size_t length) { + return encode(data, length, false); + } + + static inline String encode(const String& text) { + return encode(text, false); + } +}; diff --git a/libraries/HTTPClient/src/libb64/AUTHORS b/libraries/HTTPClient/src/libb64/AUTHORS new file mode 100755 index 00000000..af687375 --- /dev/null +++ b/libraries/HTTPClient/src/libb64/AUTHORS @@ -0,0 +1,7 @@ +libb64: Base64 Encoding/Decoding Routines +====================================== + +Authors: +------- + +Chris Venter chris.venter@gmail.com http://rocketpod.blogspot.com diff --git a/libraries/HTTPClient/src/libb64/LICENSE b/libraries/HTTPClient/src/libb64/LICENSE new file mode 100755 index 00000000..a6b56069 --- /dev/null +++ b/libraries/HTTPClient/src/libb64/LICENSE @@ -0,0 +1,29 @@ +Copyright-Only Dedication (based on United States law) +or Public Domain Certification + +The person or persons who have associated work with this document (the +"Dedicator" or "Certifier") hereby either (a) certifies that, to the best of +his knowledge, the work of authorship identified is in the public domain of the +country from which the work is published, or (b) hereby dedicates whatever +copyright the dedicators holds in the work of authorship identified below (the +"Work") to the public domain. A certifier, moreover, dedicates any copyright +interest he may have in the associated work, and for these purposes, is +described as a "dedicator" below. + +A certifier has taken reasonable steps to verify the copyright status of this +work. Certifier recognizes that his good faith efforts may not shield him from +liability if in fact the work certified is not in the public domain. + +Dedicator makes this dedication for the benefit of the public at large and to +the detriment of the Dedicator's heirs and successors. Dedicator intends this +dedication to be an overt act of relinquishment in perpetuity of all present +and future rights under copyright law, whether vested or contingent, in the +Work. Dedicator understands that such relinquishment of all rights includes +the relinquishment of all rights to enforce (by lawsuit or otherwise) those +copyrights in the Work. + +Dedicator recognizes that, once placed in the public domain, the Work may be +freely reproduced, distributed, transmitted, used, modified, built upon, or +otherwise exploited by anyone for any purpose, commercial or non-commercial, +and in any way, including by methods that have not yet been invented or +conceived. \ No newline at end of file diff --git a/libraries/HTTPClient/src/libb64/cdecode.cpp b/libraries/HTTPClient/src/libb64/cdecode.cpp new file mode 100755 index 00000000..670a9714 --- /dev/null +++ b/libraries/HTTPClient/src/libb64/cdecode.cpp @@ -0,0 +1,111 @@ +/* + cdecoder.c - c source to a base64 decoding algorithm implementation + + This is part of the libb64 project, and has been placed in the public domain. + For details, see http://sourceforge.net/projects/libb64 +*/ + +#include +#include +#include "cdecode.h" + +extern "C" { + + static int base64_decode_value_signed(int8_t value_in) { + static const int8_t decoding[] PROGMEM = {62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51}; + static const int8_t decoding_size = sizeof(decoding); + value_in -= 43; + if (value_in < 0 || value_in > decoding_size) { + return -1; + } + return pgm_read_byte(&decoding[(int)value_in]); + } + + void base64_init_decodestate(base64_decodestate* state_in) { + state_in->step = step_a; + state_in->plainchar = 0; + } + + static int base64_decode_block_signed(const int8_t* code_in, const int length_in, int8_t* plaintext_out, base64_decodestate* state_in) { + const int8_t* codechar = code_in; + int8_t* plainchar = plaintext_out; + int8_t fragment; + + *plainchar = state_in->plainchar; + + switch (state_in->step) { + while (1) { + case step_a: + do { + if (codechar == code_in + length_in) { + state_in->step = step_a; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (int8_t)base64_decode_value_signed(*codechar++); + } while (fragment < 0); + *plainchar = (fragment & 0x03f) << 2; + // falls through + case step_b: + do { + if (codechar == code_in + length_in) { + state_in->step = step_b; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (int8_t)base64_decode_value_signed(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x030) >> 4; + *plainchar = (fragment & 0x00f) << 4; + // falls through + case step_c: + do { + if (codechar == code_in + length_in) { + state_in->step = step_c; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (int8_t)base64_decode_value_signed(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03c) >> 2; + *plainchar = (fragment & 0x003) << 6; + // falls through + case step_d: + do { + if (codechar == code_in + length_in) { + state_in->step = step_d; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (int8_t)base64_decode_value_signed(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03f); + } + } + /* control should not reach here */ + return plainchar - plaintext_out; + } + + static int base64_decode_chars_signed(const int8_t* code_in, const int length_in, int8_t* plaintext_out) { + base64_decodestate _state; + base64_init_decodestate(&_state); + int len = base64_decode_block_signed(code_in, length_in, plaintext_out, &_state); + if (len > 0) { + plaintext_out[len] = 0; + } + return len; + } + + int base64_decode_value(char value_in) { + return base64_decode_value_signed(*((int8_t *) &value_in)); + } + + int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) { + return base64_decode_block_signed((int8_t *) code_in, length_in, (int8_t *) plaintext_out, state_in); + } + + int base64_decode_chars(const char* code_in, const int length_in, char* plaintext_out) { + return base64_decode_chars_signed((int8_t *) code_in, length_in, (int8_t *) plaintext_out); + } + +}; diff --git a/libraries/HTTPClient/src/libb64/cdecode.h b/libraries/HTTPClient/src/libb64/cdecode.h new file mode 100755 index 00000000..6305cc55 --- /dev/null +++ b/libraries/HTTPClient/src/libb64/cdecode.h @@ -0,0 +1,38 @@ +/* + cdecode.h - c header for a base64 decoding algorithm + + This is part of the libb64 project, and has been placed in the public domain. + For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CDECODE_H +#define BASE64_CDECODE_H + +#define base64_decode_expected_len(n) ((n * 3) / 4) + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + step_a, step_b, step_c, step_d +} base64_decodestep; + +typedef struct { + base64_decodestep step; + char plainchar; +} base64_decodestate; + +void base64_init_decodestate(base64_decodestate* state_in); + +int base64_decode_value(char value_in); + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); + +int base64_decode_chars(const char* code_in, const int length_in, char* plaintext_out); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* BASE64_CDECODE_H */ diff --git a/libraries/HTTPClient/src/libb64/cencode.cpp b/libraries/HTTPClient/src/libb64/cencode.cpp new file mode 100755 index 00000000..c2af544f --- /dev/null +++ b/libraries/HTTPClient/src/libb64/cencode.cpp @@ -0,0 +1,126 @@ +/* + cencoder.c - c source to a base64 encoding algorithm implementation + + This is part of the libb64 project, and has been placed in the public domain. + For details, see http://sourceforge.net/projects/libb64 +*/ + +#include "cencode.h" + +extern "C" { + + void base64_init_encodestate(base64_encodestate* state_in) { + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; + state_in->stepsnewline = BASE64_CHARS_PER_LINE; + } + + + void base64_init_encodestate_nonewlines(base64_encodestate* state_in) { + base64_init_encodestate(state_in); + state_in->stepsnewline = -1; + } + + char base64_encode_value(const char n) { + char r; + + if (n < 26) { + r = n + 'A'; + } else if (n < 26 + 26) { + r = n - 26 + 'a'; + } else if (n < 26 + 26 + 10) { + r = n - 26 - 26 + '0'; + } else if (n == 62) { + r = '+'; + } else { + r = '/'; + } + return r; + } + + int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) { + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) { + while (1) { + case step_A: + if (plainchar == plaintextend) { + state_in->result = result; + state_in->step = step_A; + return codechar - code_out; + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + // falls through + case step_B: + if (plainchar == plaintextend) { + state_in->result = result; + state_in->step = step_B; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + // falls through + case step_C: + if (plainchar == plaintextend) { + state_in->result = result; + state_in->step = step_C; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + if ((state_in->stepcount == BASE64_CHARS_PER_LINE / 4) && (state_in->stepsnewline > 0)) { + *codechar++ = '\n'; + state_in->stepcount = 0; + } + } + } + /* control should not reach here */ + return codechar - code_out; + } + + int base64_encode_blockend(char* code_out, base64_encodestate* state_in) { + char* codechar = code_out; + + switch (state_in->step) { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + *codechar = 0x00; + + return codechar - code_out; + } + + int base64_encode_chars(const char* plaintext_in, int length_in, char* code_out) { + base64_encodestate _state; + base64_init_encodestate(&_state); + int len = base64_encode_block(plaintext_in, length_in, code_out, &_state); + return len + base64_encode_blockend((code_out + len), &_state); + } + +}; diff --git a/libraries/HTTPClient/src/libb64/cencode.h b/libraries/HTTPClient/src/libb64/cencode.h new file mode 100755 index 00000000..13b24fbd --- /dev/null +++ b/libraries/HTTPClient/src/libb64/cencode.h @@ -0,0 +1,48 @@ +/* + cencode.h - c header for a base64 encoding algorithm + + This is part of the libb64 project, and has been placed in the public domain. + For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +#define BASE64_CENCODE_H + +#define BASE64_CHARS_PER_LINE 72 + +#define base64_encode_expected_len_nonewlines(n) ((((4 * (n)) / 3) + 3) & ~3) +#define base64_encode_expected_len(n) \ + (base64_encode_expected_len_nonewlines(n) + ((n / ((BASE64_CHARS_PER_LINE * 3) / 4)) + 1)) + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + step_A, step_B, step_C +} base64_encodestep; + +typedef struct { + base64_encodestep step; + char result; + int stepcount; + int stepsnewline; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); +void base64_init_encodestate_nonewlines(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +int base64_encode_chars(const char* plaintext_in, int length_in, char* code_out); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* BASE64_CENCODE_H */ diff --git a/libraries/WiFi/keywords.txt b/libraries/WiFi/keywords.txt index 53db6664..88c4e3e6 100644 --- a/libraries/WiFi/keywords.txt +++ b/libraries/WiFi/keywords.txt @@ -63,6 +63,26 @@ beginMulticast KEYWORD2 setTimeout KEYWORD2 waitSet KEYWORD2 +setSession KEYWORD2 +setInsecure KEYWORD2 +setKnownKey KEYWORD2 +setFingerprint KEYWORD2 +allowSelfSignedCerts KEYWORD2 +setTrustAnchors KEYWORD2 +setX509Time KEYWORD2 +setClientRSACert KEYWORD2 +setClientECCert KEYWORD2 +setBufferSizes KEYWORD2 +setCertStore KEYWORD2 +setCiphers KEYWORD2 +setCiphersLessSecure KEYWORD2 +setSSLVersion KEYWORD2 +setCACert KEYWORD2 +setCertificate KEYWORD2 +setPrivateKey KEYWORD2 +loadCACert KEYWORD2 +loadCertificate KEYWORD2 +loadPrivateKey KEYWORD2 ####################################### # Constants (LITERAL1) diff --git a/libraries/WiFi/src/WiFiMulti.cpp b/libraries/WiFi/src/WiFiMulti.cpp index 65f2791b..7a9d0226 100644 --- a/libraries/WiFi/src/WiFiMulti.cpp +++ b/libraries/WiFi/src/WiFiMulti.cpp @@ -53,6 +53,7 @@ bool WiFiMulti::addAP(const char *ssid, const char *pass) { } else { ap.pass = nullptr; } + DEBUGV("[WIFIMULTI] Adding: '%s' %s' to list\n", ap.ssid, ap.pass); _list.push_front(ap); return true; } @@ -71,6 +72,7 @@ uint8_t WiFiMulti::run(uint32_t to) { for (int i = 0; i < cnt; i++) { if (WiFi.RSSI(i) > maxRSSID) { for (auto j = _list.begin(); j != _list.end(); j++) { + DEBUGV("[WIFIMULTI] Checking for '%s' at %d\n", WiFi.SSID(i), WiFi.RSSI(i)); if (!strcmp(j->ssid, WiFi.SSID(i))) { hit = j; maxRSSID = WiFi.RSSI(i); @@ -84,6 +86,7 @@ uint8_t WiFiMulti::run(uint32_t to) { } // Connect! + DEBUGV("[WIFIMULTI] Connecting to '%s' and '%s'\n", hit->ssid, hit->pass); uint32_t start = millis(); if (hit->pass) { WiFi.begin(hit->ssid, hit->pass); diff --git a/libraries/WiFi/src/WiFiMulti.h b/libraries/WiFi/src/WiFiMulti.h index c6437e38..839a8814 100644 --- a/libraries/WiFi/src/WiFiMulti.h +++ b/libraries/WiFi/src/WiFiMulti.h @@ -32,7 +32,7 @@ public: bool addAP(const char *ssid, const char *pass = NULL); - uint8_t run(uint32_t to = 5000); + uint8_t run(uint32_t to = 10000); private: struct _AP { diff --git a/tests/restyle.sh b/tests/restyle.sh index aae714d5..b5b7a4db 100755 --- a/tests/restyle.sh +++ b/tests/restyle.sh @@ -7,7 +7,7 @@ for dir in ./cores/rp2040 ./libraries/EEPROM ./libraries/I2S \ ./libraries/WiFi ./libraries/lwIP_Ethernet ./libraries/lwIP_CYW43 \ ./libraries/FreeRTOS/src ./libraries/LEAmDNS ./libraries/MD5Builder \ ./libraries/PicoOTA ./libraries/SDFS ./libraries/ArduinoOTA \ - ./libraries/Updater; do + ./libraries/Updater ./libraries/HTTPClient; do find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" \) -a \! -path '*api*' -exec astyle --suffix=none --options=./tests/astyle_core.conf \{\} \; find $dir -type f -name "*.ino" -exec astyle --suffix=none --options=./tests/astyle_examples.conf \{\} \; done diff --git a/tools/build.py b/tools/build.py index 86cc160b..ed6a60a6 100755 --- a/tools/build.py +++ b/tools/build.py @@ -65,9 +65,7 @@ def compile(tmp_dir, sketch, cache, tools_dir, hardware_dir, ide_path, f, args): 'dbgport={dbgport},' \ 'dbglvl={dbglvl},' \ 'usbstack={usbstack}'.format(**vars(args)) - if "/WiFi" in sketch: - fqbn = fqbn.replace("rpipico", "rpipicow") - if "/ArduinoOTA" in sketch: + if ("/WiFi" in sketch) or ("/ArduinoOTA" in sketch) or ("/HTTPClient" in sketch): fqbn = fqbn.replace("rpipico", "rpipicow") cmd += [fqbn] cmd += ['-built-in-libraries', ide_path + '/libraries'] -- 2.54.0 From de45de2cd4e1de398c8e130c9a226635bbb8a9c0 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sun, 21 Aug 2022 10:38:36 -0700 Subject: [PATCH 2/6] Remove broken old example --- .../StreamHttpClient/StreamHttpClient.ino | 103 ------------------ 1 file changed, 103 deletions(-) delete mode 100644 libraries/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino diff --git a/libraries/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino b/libraries/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino deleted file mode 100644 index 298f6129..00000000 --- a/libraries/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino +++ /dev/null @@ -1,103 +0,0 @@ -/** - StreamHTTPClient.ino - - Created on: 24.05.2015 - -*/ - -#include - -#include -#include - -#include - -WiFiMulti WiFiMulti; - -void setup() { - - Serial.begin(115200); - // Serial.setDebugOutput(true); - - Serial.println(); - Serial.println(); - Serial.println(); - - for (uint8_t t = 4; t > 0; t--) { - Serial.printf("[SETUP] WAIT %d...\n", t); - Serial.flush(); - delay(1000); - } - - WiFi.mode(WIFI_STA); - WiFiMulti.addAP("SSID", "PASSWORD"); -} - -void loop() { - // wait for WiFi connection - if ((WiFiMulti.run() == WL_CONNECTED)) { - - HTTPClient http; - http.setInsecure(); - - Serial.print("[HTTP] begin...\n"); - - // configure server and url - http.begin("https://jigsaw.w3.org/HTTP/connection.html"); - // http.begin("jigsaw.w3.org", 443, "/HTTP/connection.html", true); - - Serial.print("[HTTP] GET...\n"); - // start connection and send HTTP header - int httpCode = http.GET(); - if (httpCode > 0) { - // HTTP header has been send and Server response header has been handled - Serial.printf("[HTTP] GET... code: %d\n", httpCode); - - // file found at server - if (httpCode == HTTP_CODE_OK) { - - // get length of document (is -1 when Server sends no Content-Length header) - int len = http.getSize(); - - // create buffer for read - uint8_t buff[128] = { 0 }; - -#if 0 - // with API - Serial.println(http.getString()); -#else - // or "by hand" - - // get tcp stream - WiFiClient* stream = &client; - - // read all data from server - while (http.connected() && (len > 0 || len == -1)) { - // read up to 128 byte - int c = stream->readBytes(buff, std::min((size_t)len, sizeof(buff))); - Serial.printf("readBytes: %d\n", c); - if (!c) { - Serial.println("read timeout"); - } - - // write it to Serial - Serial.write(buff, c); - - if (len > 0) { - len -= c; - } - } -#endif - - Serial.println(); - Serial.print("[HTTP] connection closed or file end.\n"); - } - } else { - Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); - } - - http.end(); - } - - delay(60000); -} -- 2.54.0 From 7a4b4cb2ff9db1d5a3a8ba750e1a2eeea9a30305 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sun, 21 Aug 2022 10:46:23 -0700 Subject: [PATCH 3/6] Add POST example --- .../examples/PostHttpClient/PostHttpClient.ino | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/libraries/HTTPClient/examples/PostHttpClient/PostHttpClient.ino b/libraries/HTTPClient/examples/PostHttpClient/PostHttpClient.ino index 01f232aa..34d5fadb 100644 --- a/libraries/HTTPClient/examples/PostHttpClient/PostHttpClient.ino +++ b/libraries/HTTPClient/examples/PostHttpClient/PostHttpClient.ino @@ -8,16 +8,6 @@ #include #include -/* this can be run with an emulated server on host: - cd esp8266-core-root-dir - cd tests/host - make ../../libraries/ESP8266WebServer/examples/PostServer/PostServer - bin/PostServer/PostServer - then put your PC's IP address in SERVER_IP below, port 9080 (instead of default 80): -*/ -//#define SERVER_IP "10.0.1.7:9080" // PC address with emulation on host -#define SERVER_IP "192.168.1.42" - #ifndef STASSID #define STASSID "your-ssid" #define STAPSK "your-password" @@ -47,10 +37,11 @@ void loop() { if ((WiFi.status() == WL_CONNECTED)) { HTTPClient http; + http.setInsecure(); Serial.print("[HTTP] begin...\n"); - // configure traged server and url - http.begin("http://" SERVER_IP "/postplain/"); // HTTP + // configure target server and url + http.begin("https://httpbin.org/post"); http.addHeader("Content-Type", "application/json"); Serial.print("[HTTP] POST...\n"); -- 2.54.0 From 286892096c235f86cac60ade2f84f59aa587e8a4 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sun, 21 Aug 2022 11:46:35 -0700 Subject: [PATCH 4/6] Add ChunkedClient test --- .../examples/ChunkedClient/ChunkedClient.ino | 75 +++++++++++++++++++ libraries/HTTPClient/src/HTTPClient.cpp | 4 +- 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 libraries/HTTPClient/examples/ChunkedClient/ChunkedClient.ino diff --git a/libraries/HTTPClient/examples/ChunkedClient/ChunkedClient.ino b/libraries/HTTPClient/examples/ChunkedClient/ChunkedClient.ino new file mode 100644 index 00000000..54d8cc45 --- /dev/null +++ b/libraries/HTTPClient/examples/ChunkedClient/ChunkedClient.ino @@ -0,0 +1,75 @@ +/** + ChunkedClient.ino + +*/ + +#include +#include +#include + +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +const char *ssid = STASSID; +const char *pass = STAPSK; + +WiFiMulti WiFiMulti; + +void setup() { + + Serial.begin(115200); + // Serial.setDebugOutput(true); + + Serial.println(); + Serial.println(); + Serial.println(); + + for (uint8_t t = 4; t > 0; t--) { + Serial.printf("[SETUP] WAIT %d...\n", t); + Serial.flush(); + delay(1000); + } + + WiFi.mode(WIFI_STA); + WiFiMulti.addAP(ssid, pass); +} + +void loop() { + // wait for WiFi connection + if ((WiFiMulti.run() == WL_CONNECTED)) { + HTTPClient http; + + Serial.print("[HTTP] begin...\n"); + if (http.begin("http://anglesharp.azurewebsites.net/Chunked")) { + + Serial.print("[HTTP] GET...\n"); + // start connection and send HTTP header + int httpCode = http.GET(); + + // httpCode will be negative on error + if (httpCode > 0) { + // HTTP header has been send and Server response header has been handled + Serial.printf("[HTTP] GET... code: %d\n", httpCode); + + // file found at server + if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { + String payload = http.getString(); + Serial.println(payload); + } + } else { + Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + } else { + Serial.printf("[HTTP] Unable to connect\n"); + } + } + + Serial.println("Wait forever..."); + while (1) { + continue; + } +} diff --git a/libraries/HTTPClient/src/HTTPClient.cpp b/libraries/HTTPClient/src/HTTPClient.cpp index a38c98c4..94d8b9ff 100644 --- a/libraries/HTTPClient/src/HTTPClient.cpp +++ b/libraries/HTTPClient/src/HTTPClient.cpp @@ -576,8 +576,7 @@ size_t StreamSendSize(Stream *s, Print *c, int size) { int x = s->read(); if (x < 0) { break; - } - if (c->write(x)) { + } else if (c->write(x)) { sent++; } else { break; @@ -720,6 +719,7 @@ int HTTPClient::writeToPrint(Print * print) { } chunkHeader.trim(); // remove \r + DEBUG_HTTPCLIENT("[HTTP-Client] chunk header: '%s'\n", chunkHeader.c_str()); // read size of chunk len = (uint32_t) strtol((const char *) chunkHeader.c_str(), NULL, 16); -- 2.54.0 From b82979c7842fd4bc5a30340ace32e6e273248851 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sun, 21 Aug 2022 12:01:20 -0700 Subject: [PATCH 5/6] Add HTTPClient basic docs --- docs/httpclient.rst | 40 ++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 2 ++ 2 files changed, 42 insertions(+) create mode 100644 docs/httpclient.rst diff --git a/docs/httpclient.rst b/docs/httpclient.rst new file mode 100644 index 00000000..d5055008 --- /dev/null +++ b/docs/httpclient.rst @@ -0,0 +1,40 @@ +HTTPClient Library +================== + +A simple HTTP requestor that can handle both HTTP and HTTP requests is +included as the ``HTTPClient`` library. + +Check the examples for use under HTTP and HTTPS configurations. In general, +for HTTP connections (unsecured and very uncommon on the internet today) simply +passing in a URL and performiung a GET is sufficient to transfer data. + +.. code:: cpp + + // Error checking is left as an exercise for the reader... + HTTPClient http; + if (http.begin("http://my.server/url")) { + if (http.GET() > 0) { + String data = http.getString(); + } + http.end(); + } + +For HTTPS connections, simply add the appropriate WiFiClientSecure calls +as needed (i.e. ``setInsecure()``, ``setTrustAnchor``, etc.). See the +WiFiClientSecure documentation for more details. + +.. code:: cpp + + // Error checking is left as an exercise for the reader... + HTTPClient https; + https.setInsecure(); // Use certs, but do not check their authenticity + if (https.begin("https://my.secure.server/url")) { + if (http.GET() > 0) { + String data = http.getString(); + } + http.end(); + } + +Unlike the ESP8266 and ESP32 ``HTTPClient`` implementations it is not necessary +to create a ``WiFiClient`` or ``WiFiClientSecure`` to pass in to the ``HTTPClient`` +object. diff --git a/docs/index.rst b/docs/index.rst index cf191af2..5b42cf0e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -50,6 +50,8 @@ For the latest version, always check https://github.com/earlephilhower/arduino-p WiFiClientSecure (TLS/SSL/HTTPS) WiFiServerSecure (TLS/SSL/HTTPS) + HTTP/HTTPS Client + Over-the-Air (OTA) Updates Ported/Optimized Libraries -- 2.54.0 From d934fba884b571b9a00fd61a89eb2d2b65662c95 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sun, 21 Aug 2022 12:39:11 -0700 Subject: [PATCH 6/6] Add back in manual pass-in-client :begin() calls --- keywords.txt | 2 + .../BasicHttpsClient-Hard.ino | 80 +++++++++ libraries/HTTPClient/src/HTTPClient.cpp | 160 +++++++++++++----- libraries/HTTPClient/src/HTTPClient.h | 28 ++- 4 files changed, 227 insertions(+), 43 deletions(-) create mode 100644 libraries/HTTPClient/examples/BasicHttpsClient-Hard/BasicHttpsClient-Hard.ino diff --git a/keywords.txt b/keywords.txt index d6174707..a98cae45 100644 --- a/keywords.txt +++ b/keywords.txt @@ -2,6 +2,8 @@ # Syntax Coloring Map ####################################### +Arduino KEYWORD3 RESERVED_WORD + ####################################### # Datatypes (KEYWORD1) ####################################### diff --git a/libraries/HTTPClient/examples/BasicHttpsClient-Hard/BasicHttpsClient-Hard.ino b/libraries/HTTPClient/examples/BasicHttpsClient-Hard/BasicHttpsClient-Hard.ino new file mode 100644 index 00000000..f6276237 --- /dev/null +++ b/libraries/HTTPClient/examples/BasicHttpsClient-Hard/BasicHttpsClient-Hard.ino @@ -0,0 +1,80 @@ +/** + BasicHTTPSClient-Hard.ino + + Demonstrates the manual way of making a WiFiClient and passing it in to the HTTPClient + + Created on: 20.08.2018 + +*/ + +#include +#include +#include + +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +const char *ssid = STASSID; +const char *pass = STAPSK; + +WiFiMulti WiFiMulti; + +void setup() { + + Serial.begin(115200); + + Serial.println(); + Serial.println(); + Serial.println(); + + for (uint8_t t = 4; t > 0; t--) { + Serial.printf("[SETUP] WAIT %d...\n", t); + Serial.flush(); + delay(1000); + } + + WiFi.mode(WIFI_STA); + WiFiMulti.addAP(ssid, pass); +} + +void loop() { + // wait for WiFi connection + if ((WiFiMulti.run() == WL_CONNECTED)) { + + WiFiClientSecure client; + client.setInsecure(); // Not safe against MITM attacks + + HTTPClient https; + + Serial.print("[HTTPS] begin...\n"); + if (https.begin(client, "https://jigsaw.w3.org/HTTP/connection.html")) { // HTTPS + + Serial.print("[HTTPS] GET...\n"); + // start connection and send HTTP header + int httpCode = https.GET(); + + // httpCode will be negative on error + if (httpCode > 0) { + // HTTP header has been send and Server response header has been handled + Serial.printf("[HTTPS] GET... code: %d\n", httpCode); + + // file found at server + if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { + String payload = https.getString(); + Serial.println(payload); + } + } else { + Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str()); + } + + https.end(); + } else { + Serial.printf("[HTTPS] Unable to connect\n"); + } + } + + Serial.println("Wait 10s before next round..."); + delay(10000); +} diff --git a/libraries/HTTPClient/src/HTTPClient.cpp b/libraries/HTTPClient/src/HTTPClient.cpp index 94d8b9ff..431a5401 100644 --- a/libraries/HTTPClient/src/HTTPClient.cpp +++ b/libraries/HTTPClient/src/HTTPClient.cpp @@ -85,12 +85,12 @@ bool HTTPClient::begin(String url) { } _port = (protocol == "https" ? 443 : 80); - if (!_client) { + if (!_client()) { if (protocol == "https") { _tls(); - //_tls()->setInsecure(); } else { - _client = new WiFiClient(); + _clientMade = new WiFiClient(); + _clientGiven = false; } } @@ -120,18 +120,90 @@ bool HTTPClient::begin(String host, uint16_t port, String uri, bool https) { _port = port; _uri = uri; _protocol = (https ? "https" : "http"); - if (!_client) { + if (!_client()) { if (https) { _tls(); - //_tls()->setInsecure(); } else { - _client = new WiFiClient(); + _clientMade = new WiFiClient(); + _clientGiven = false; } } return true; } + +/** + parsing the url for all needed parameters + @param client Client& + @param url String + @param https bool + @return success bool +*/ +bool HTTPClient::begin(WiFiClient &client, const String& url) { + // check for : (http: or https:) + int index = url.indexOf(':'); + if (index < 0) { + DEBUG_HTTPCLIENT("[HTTP-Client][begin] failed to parse protocol\n"); + return false; + } + + String protocol = url.substring(0, index); + protocol.toLowerCase(); + if (protocol != "http" && protocol != "https") { + DEBUG_HTTPCLIENT("[HTTP-Client][begin] unknown protocol '%s'\n", protocol.c_str()); + return false; + } + + _port = (protocol == "https" ? 443 : 80); + _clientIn = client.clone(); + _clientGiven = true; + if (_clientMade) { + delete _clientMade; + _clientMade = nullptr; + } + + + return beginInternal(url, protocol.c_str()); +} + + +/** + directly supply all needed parameters + @param client Client& + @param host String + @param port uint16_t + @param uri String + @param https bool + @return success bool +*/ +bool HTTPClient::begin(WiFiClient &client, const String& host, uint16_t port, const String& uri, bool https) { + // Disconnect when reusing HTTPClient to talk to a different host + if ((_host != "") && (_host != host)) { + _canReuse = false; + disconnect(true); + } + + _clientIn = client.clone(); + _clientGiven = true; + if (_clientMade) { + delete _clientMade; + _clientMade = nullptr; + } + + clear(); + + _host = host; + _port = port; + _uri = uri; + _protocol = (https ? "https" : "http"); + return true; +} + + + + + bool HTTPClient::beginInternal(const String& __url, const char* expectedProtocol) { String url(__url); @@ -209,9 +281,9 @@ bool HTTPClient::beginInternal(const String& __url, const char* expectedProtocol void HTTPClient::end(void) { disconnect(false); clear(); - if (_client) { - delete _client; - _client = nullptr; + if (_clientMade) { + delete _clientMade; + _clientMade = nullptr; _clientTLS = false; } } @@ -222,10 +294,10 @@ void HTTPClient::end(void) { */ void HTTPClient::disconnect(bool preserveClient) { if (connected()) { - if (_client->available() > 0) { - DEBUG_HTTPCLIENT("[HTTP-Client][end] still data in buffer (%d), clean up.\n", _client->available()); - while (_client->available() > 0) { - _client->read(); + if (_client()->available() > 0) { + DEBUG_HTTPCLIENT("[HTTP-Client][end] still data in buffer (%d), clean up.\n", _client()->available()); + while (_client()->available() > 0) { + _client()->read(); } } @@ -233,16 +305,26 @@ void HTTPClient::disconnect(bool preserveClient) { DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp keep open for reuse\n"); } else { DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp stop\n"); - if (_client) { - _client->stop(); + if (_client()) { + _client()->stop(); if (!preserveClient) { - _client = nullptr; + _clientIn = nullptr; + if (_clientMade) { + delete _clientMade; + _clientMade = nullptr; + } + _clientGiven = false; } } } } else { - if (!preserveClient && _client) { // Also destroy _client if not connected() - _client = nullptr; + if (!preserveClient && _client()) { // Also destroy _client if not connected() + _clientIn = nullptr; + if (_clientMade) { + delete _clientMade; + _clientMade = nullptr; + } + _clientGiven = false; } DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp is closed\n"); @@ -254,8 +336,8 @@ void HTTPClient::disconnect(bool preserveClient) { @return connected status */ bool HTTPClient::connected() { - if (_client) { - return (_client->connected() || (_client->available() > 0)); + if (_client()) { + return (_client()->connected() || (_client()->available() > 0)); } return false; } @@ -317,7 +399,7 @@ void HTTPClient::setAuthorization(String auth) { void HTTPClient::setTimeout(uint16_t timeout) { _tcpTimeout = timeout; if (connected()) { - _client->setTimeout(timeout); + _client()->setTimeout(timeout); } } @@ -493,7 +575,7 @@ int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t s } // transfer all of it, with send-timeout - if (size && StreamConstPtr(payload, size).sendAll(_client) != size) { + if (size && StreamConstPtr(payload, size).sendAll(_client()) != size) { return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED); } @@ -612,7 +694,7 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size) { } // transfer all of it, with timeout - size_t transferred = StreamSendSize(stream, _client, size); + size_t transferred = StreamSendSize(stream, _client(), size); if (transferred != size) { DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %zu but got %zu failed.\n", size, transferred); return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED); @@ -643,7 +725,7 @@ const String& HTTPClient::getLocation(void) { */ WiFiClient& HTTPClient::getStream(void) { if (connected()) { - return *_client; + return *_client(); } DEBUG_HTTPCLIENT("[HTTP-Client] getStream: not connected\n"); @@ -657,7 +739,7 @@ WiFiClient& HTTPClient::getStream(void) { */ WiFiClient* HTTPClient::getStreamPtr(void) { if (connected()) { - return _client; + return _client(); } DEBUG_HTTPCLIENT("[HTTP-Client] getStreamPtr: not connected\n"); @@ -697,7 +779,7 @@ int HTTPClient::writeToPrint(Print * print) { if (_transferEncoding == HTTPC_TE_IDENTITY) { // len < 0: transfer all of it, with timeout // len >= 0: max:len, with timeout - ret = StreamSendSize(_client, print, len); + ret = StreamSendSize(_client(), print, len); if (len > 0 && ret != len) { return HTTPC_ERROR_NO_STREAM; @@ -712,7 +794,7 @@ int HTTPClient::writeToPrint(Print * print) { if (!connected()) { return returnError(HTTPC_ERROR_CONNECTION_LOST); } - String chunkHeader = _client->readStringUntil('\n'); + String chunkHeader = _client()->readStringUntil('\n'); if (chunkHeader.length() <= 0) { return returnError(HTTPC_ERROR_READ_TIMEOUT); @@ -729,7 +811,7 @@ int HTTPClient::writeToPrint(Print * print) { // data left? if (len > 0) { // read len bytes with timeout - int r = StreamSendSize(_client, print, len); + int r = StreamSendSize(_client(), print, len); if (r != len) { return HTTPC_ERROR_NO_STREAM; } @@ -754,7 +836,7 @@ int HTTPClient::writeToPrint(Print * print) { // read trailing \r\n at the end of the chunk char buf[2]; - auto trailing_seq_len = _client->readBytes((uint8_t*)buf, 2); + auto trailing_seq_len = _client()->readBytes((uint8_t*)buf, 2); if (trailing_seq_len != 2 || buf[0] != '\r' || buf[1] != '\n') { return returnError(HTTPC_ERROR_READ_TIMEOUT); } @@ -912,26 +994,26 @@ bool HTTPClient::connect(void) { if (_reuse && _canReuse && connected()) { DEBUG_HTTPCLIENT("[HTTP-Client] connect: already connected, reusing connection\n"); // clear _client's output (all of it, no timeout) - while (_client->available()) { - _client->read(); + while (_client()->available()) { + _client()->read(); } return true; } - if (!_client) { + if (!_client()) { DEBUG_HTTPCLIENT("[HTTP-Client] connect: HTTPClient::begin was not called or returned error\n"); return false; } - _client->setTimeout(_tcpTimeout); + _client()->setTimeout(_tcpTimeout); - if (!_client->connect(_host.c_str(), _port)) { + if (!_client()->connect(_host.c_str(), _port)) { DEBUG_HTTPCLIENT("[HTTP-Client] failed connect to %s:%u\n", _host.c_str(), _port); return false; } DEBUG_HTTPCLIENT("[HTTP-Client] connected to %s:%u\n", _host.c_str(), _port); - _client->setNoDelay(true); + _client()->setNoDelay(true); return connected(); } @@ -994,7 +1076,7 @@ bool HTTPClient::sendHeader(const char * type) { DEBUG_HTTPCLIENT("[HTTP-Client] sending request header\n-----\n%s-----\n", header.c_str()); // transfer all of it, with timeout - return StreamConstPtr(header).sendAll(_client) == header.length(); + return StreamConstPtr(header).sendAll(_client()) == header.length(); } /** @@ -1017,10 +1099,10 @@ int HTTPClient::handleHeaderResponse() { unsigned long lastDataTime = millis(); while (connected()) { - size_t len = _client->available(); + size_t len = _client()->available(); if (len > 0) { int headerSeparator = -1; - String headerLine = _client->readStringUntil('\n'); + String headerLine = _client()->readStringUntil('\n'); lastDataTime = millis(); @@ -1120,7 +1202,7 @@ int HTTPClient::returnError(int error) { DEBUG_HTTPCLIENT("[HTTP-Client][returnError] error(%d): %s\n", error, errorToString(error).c_str()); if (connected()) { DEBUG_HTTPCLIENT("[HTTP-Client][returnError] tcp stop\n"); - _client->stop(); + _client()->stop(); } } return error; diff --git a/libraries/HTTPClient/src/HTTPClient.h b/libraries/HTTPClient/src/HTTPClient.h index 841819f4..05953a7f 100644 --- a/libraries/HTTPClient/src/HTTPClient.h +++ b/libraries/HTTPClient/src/HTTPClient.h @@ -40,10 +40,12 @@ #endif #endif +//#define DEBUG_HTTPCLIENT(fmt, ...) Serial.printf(fmt, ## __VA_ARGS__ ) #ifndef DEBUG_HTTPCLIENT #define DEBUG_HTTPCLIENT(...) do { (void)0; } while (0) #endif + #define HTTPCLIENT_DEFAULT_TCP_TIMEOUT (5000) /// HTTP client errors @@ -158,6 +160,7 @@ public: HTTPClient(HTTPClient&&) = default; HTTPClient& operator=(HTTPClient&&) = default; + // The easier way bool begin(String url); bool begin(String host, uint16_t port, String uri = "/", bool https = false); bool begin(String url, const uint8_t httpsFingerprint[20]) { @@ -169,6 +172,11 @@ public: return begin(host, port, uri); } + // Let's do it the hard way, too + bool begin(WiFiClient &client, const String& url); + bool begin(WiFiClient &client, const String& host, uint16_t port, const String& uri = "/", bool https = false); + + void end(void); bool connected(void); @@ -297,11 +305,12 @@ public: protected: // HTTPS helpers WiFiClientSecure *_tls() { - if (!_client) { - _client = new WiFiClientSecure(); + if (!_clientMade) { + _clientMade = new WiFiClientSecure(); + _clientGiven = false; } _clientTLS = true; - return (WiFiClientSecure*)_client; + return (WiFiClientSecure*)_clientMade; } struct RequestArgument { @@ -318,9 +327,20 @@ protected: int handleHeaderResponse(); int writeToStreamDataBlock(Stream * stream, int len); - WiFiClient *_client = nullptr; + WiFiClient *_clientMade = nullptr; bool _clientTLS = false; + std::unique_ptr _clientIn; + bool _clientGiven = false; + + WiFiClient *_client() { + if (_clientGiven) { + return _clientIn.get(); + } else { + return _clientMade; + } + } + /// request handling String _host; uint16_t _port = 0; -- 2.54.0