From 02438bfdf0e74936faa98baeaca5444b13cbc428 Mon Sep 17 00:00:00 2001 From: Christopher Liebman Date: Sun, 5 Aug 2018 07:00:26 -0700 Subject: [PATCH 1/9] optionally allow redirects on http OTA updates --- .../src/ESP8266HTTPClient.cpp | 23 +++++++++++++++++++ .../ESP8266HTTPClient/src/ESP8266HTTPClient.h | 2 +- .../src/ESP8266httpUpdate.cpp | 19 ++++++++++++--- .../ESP8266httpUpdate/src/ESP8266httpUpdate.h | 6 +++++ 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp index 28a3503484..0abc743b89 100644 --- a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp +++ b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp @@ -369,6 +369,29 @@ void HTTPClient::setTimeout(uint16_t timeout) } } +/** + * set the URL to a new value. Handy for following redirects. + * @param url + */ +bool HTTPClient::setURL(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); + if(protocol != "http" && protocol != "https") { + DEBUG_HTTPCLIENT("[HTTP-Client][begin] unknown protocol '%s'\n", protocol.c_str()); + return false; + } + + _port = (protocol == "https" ? 443 : 80); + return beginInternal(url, protocol.c_str()); +} + /** * use HTTP1.0 * @param timeout diff --git a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h index 72b42853f7..4c499aced5 100644 --- a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h +++ b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h @@ -154,7 +154,7 @@ class HTTPClient void setAuthorization(const char * user, const char * password); void setAuthorization(const char * auth); void setTimeout(uint16_t timeout); - + bool setURL(String url); // handy for handling redirects void useHTTP10(bool usehttp10 = true); /// request handling diff --git a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp index 5cc4b6a09e..a73352be46 100644 --- a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp +++ b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp @@ -30,12 +30,12 @@ extern "C" uint32_t _SPIFFS_start; extern "C" uint32_t _SPIFFS_end; ESP8266HTTPUpdate::ESP8266HTTPUpdate(void) - : _httpClientTimeout(8000) + : _httpClientTimeout(8000), _followRedirects(false) { } ESP8266HTTPUpdate::ESP8266HTTPUpdate(int httpClientTimeout) - : _httpClientTimeout(httpClientTimeout) + : _httpClientTimeout(httpClientTimeout), _followRedirects(false) { } @@ -219,7 +219,7 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String& http.addHeader(F("x-ESP8266-version"), currentVersion); } - const char * headerkeys[] = { "x-MD5" }; + const char * headerkeys[] = { "x-MD5" , "Location"}; size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*); // track these headers @@ -227,6 +227,19 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String& int code = http.GET(); + while(_followRedirects && (code == 302 || code == 301)) + { + String location = http.header("Location"); + DEBUG_HTTP_UPDATE("[httpUpdate] HTTP redirect(%d): %s\n", code, location.c_str()); + http.end(); + if (!http.setURL(location)) + { + DEBUG_HTTP_UPDATE("[httpUpdate] Bad redirect location: '%s'", location.c_str()); + _lastError = code; + return HTTP_UPDATE_FAILED; + } + code = http.GET(); + } int len = http.getSize(); if(code <= 0) { diff --git a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h index 1676d51dfc..978684aad1 100644 --- a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h +++ b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h @@ -72,6 +72,11 @@ class ESP8266HTTPUpdate _rebootOnUpdate = reboot; } + void followRedirects(bool follow) + { + _followRedirects = follow; + } + // This function is deprecated, use rebootOnUpdate and the next one instead t_httpUpdate_return update(const String& url, const String& currentVersion, const String& httpsFingerprint, bool reboot) __attribute__((deprecated)); @@ -111,6 +116,7 @@ class ESP8266HTTPUpdate bool _rebootOnUpdate = true; private: int _httpClientTimeout; + bool _followRedirects; }; #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_HTTPUPDATE) From 8721924c607263ae96df4b60d817b29da338cb7a Mon Sep 17 00:00:00 2001 From: Chris Liebman Date: Sat, 29 Sep 2018 16:39:41 -0700 Subject: [PATCH 2/9] Refactored HTTPClient::begin(url...) & setURL functions, now only beginInternal parses URL, sets ports Added HTTPRedirect example. --- .../examples/HTTPRedirect/HTTPRedirect.ino | 91 +++++++++++++++++++ .../src/ESP8266HTTPClient.cpp | 32 +++---- 2 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino diff --git a/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino b/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino new file mode 100644 index 0000000000..5109e13f64 --- /dev/null +++ b/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino @@ -0,0 +1,91 @@ +/** + HTTPRedirect.ino + + Created on: 29-Sep-2018 + +*/ + +#include + +#include +#include +#include + +#define WIFI_SSID "SSID" +#define WIFI_PASS "Password" + + +ESP8266WiFiMulti WiFiMulti; + + +void setup() { + + Serial.begin(115200); + // Serial.setDebugOutput(true); + Serial.println(); + Serial.println(); + Serial.println(); + + for (uint8_t t = 10; t > 0; t--) { + Serial.printf("[SETUP] WAIT %d...\n", t); + Serial.flush(); + delay(1000); + } + + WiFi.mode(WIFI_STA); + WiFiMulti.addAP(WIFI_SSID, WIFI_PASS); + +} + +void loop() { + // wait for WiFi connection + if ((WiFiMulti.run() == WL_CONNECTED)) { + + HTTPClient http; + + // we need the Location header to handle redirects + const char * headerkeys[] = { "Location" }; + size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*); + + // track these headers + http.collectHeaders(headerkeys, headerkeyssize); + + Serial.print("[HTTP] begin...\n"); + // configure url + http.begin("http://jigsaw.w3.org/HTTP/300/307.html"); //HTTP + + Serial.print("[HTTP] GET...\n"); + // start connection and send HTTP header + int httpCode = http.GET(); + + // As long as we get redirects, follow them + while(httpCode == 301 || httpCode == 302 || httpCode == 307) { + String location = http.header("Location"); + Serial.printf("[HTTP] redirect(%d): %s\n", httpCode, location.c_str()); + http.end(); + if (!http.setURL(location)) { + Serial.printf("[HTTP] Bad redirect location: '%s'", location.c_str()); + return; + } + 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... httpCode: %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/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp index 3e230d08c3..8e72a14b53 100644 --- a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp +++ b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp @@ -133,7 +133,6 @@ void HTTPClient::clear() bool HTTPClient::begin(String url, String httpsFingerprint) { _transportTraits.reset(nullptr); - _port = 443; if (httpsFingerprint.length() == 0) { return false; } @@ -149,7 +148,6 @@ bool HTTPClient::begin(String url, String httpsFingerprint) bool HTTPClient::begin(String url, const uint8_t httpsFingerprint[20]) { _transportTraits.reset(nullptr); - _port = 443; if (!beginInternal(url, "https")) { return false; } @@ -170,7 +168,6 @@ bool HTTPClient::begin(String url, const uint8_t httpsFingerprint[20]) bool HTTPClient::begin(String url) { _transportTraits.reset(nullptr); - _port = 80; if (!beginInternal(url, "http")) { return false; } @@ -193,6 +190,17 @@ bool HTTPClient::beginInternal(String url, const char* expectedProtocol) _protocol = url.substring(0, index); 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 @@ -217,7 +225,7 @@ bool HTTPClient::beginInternal(String url, const char* expectedProtocol) } _uri = url; - if (_protocol != expectedProtocol) { + if ( expectedProtocol != nullptr && _protocol != expectedProtocol) { DEBUG_HTTPCLIENT("[HTTP-Client][begin] unexpected protocol: %s, expected %s\n", _protocol.c_str(), expectedProtocol); return false; } @@ -386,21 +394,11 @@ void HTTPClient::setTimeout(uint16_t timeout) */ bool HTTPClient::setURL(String url) { - // check for : (http: or https:) - int index = url.indexOf(':'); - if(index < 0) { - DEBUG_HTTPCLIENT("[HTTP-Client][begin] failed to parse protocol\n"); + 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; } - - String protocol = url.substring(0, index); - if(protocol != "http" && protocol != "https") { - DEBUG_HTTPCLIENT("[HTTP-Client][begin] unknown protocol '%s'\n", protocol.c_str()); - return false; - } - - _port = (protocol == "https" ? 443 : 80); - return beginInternal(url, protocol.c_str()); + return beginInternal(url, nullptr); } /** From a552dbd616fec221e0d7523e4de3092e05c54619 Mon Sep 17 00:00:00 2001 From: Christopher Liebman Date: Sat, 29 Sep 2018 17:18:40 -0700 Subject: [PATCH 3/9] fix indentation for style check --- .../examples/HTTPRedirect/HTTPRedirect.ino | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino b/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino index 5109e13f64..dce7605a69 100644 --- a/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino +++ b/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino @@ -20,15 +20,15 @@ ESP8266WiFiMulti WiFiMulti; void setup() { - Serial.begin(115200); + Serial.begin(115200); // Serial.setDebugOutput(true); - Serial.println(); - Serial.println(); - Serial.println(); + Serial.println(); + Serial.println(); + Serial.println(); for (uint8_t t = 10; t > 0; t--) { - Serial.printf("[SETUP] WAIT %d...\n", t); - Serial.flush(); + Serial.printf("[SETUP] WAIT %d...\n", t); + Serial.flush(); delay(1000); } @@ -60,20 +60,20 @@ void loop() { // As long as we get redirects, follow them while(httpCode == 301 || httpCode == 302 || httpCode == 307) { - String location = http.header("Location"); - Serial.printf("[HTTP] redirect(%d): %s\n", httpCode, location.c_str()); - http.end(); - if (!http.setURL(location)) { - Serial.printf("[HTTP] Bad redirect location: '%s'", location.c_str()); - return; - } - httpCode = http.GET(); + String location = http.header("Location"); + Serial.printf("[HTTP] redirect(%d): %s\n", httpCode, location.c_str()); + http.end(); + if (!http.setURL(location)) { + Serial.printf("[HTTP] Bad redirect location: '%s'", location.c_str()); + return; + } + 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... httpCode: %d\n", httpCode); + Serial.printf("[HTTP] GET... httpCode: %d\n", httpCode); // file found at server if (httpCode == HTTP_CODE_OK) { @@ -81,7 +81,7 @@ void loop() { Serial.println(payload); } } else { - Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); } http.end(); From ac453c3d1e20e56404df56a0884256ed5ebb9b44 Mon Sep 17 00:00:00 2001 From: Christopher Liebman Date: Sat, 29 Sep 2018 18:05:57 -0700 Subject: [PATCH 4/9] add space after while for style check --- .../ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino b/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino index dce7605a69..86b5192e97 100644 --- a/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino +++ b/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino @@ -59,7 +59,7 @@ void loop() { int httpCode = http.GET(); // As long as we get redirects, follow them - while(httpCode == 301 || httpCode == 302 || httpCode == 307) { + while (httpCode == 301 || httpCode == 302 || httpCode == 307) { String location = http.header("Location"); Serial.printf("[HTTP] redirect(%d): %s\n", httpCode, location.c_str()); http.end(); From 686ae0709d835d113c319e1779096edc0597cb1b Mon Sep 17 00:00:00 2001 From: Christopher Liebman Date: Thu, 1 Nov 2018 10:06:23 -0700 Subject: [PATCH 5/9] don't use deprecated begin method in redirect example --- .../ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino b/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino index 86b5192e97..d733f265cc 100644 --- a/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino +++ b/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino @@ -41,6 +41,7 @@ void loop() { // wait for WiFi connection if ((WiFiMulti.run() == WL_CONNECTED)) { + WiFiClient client; HTTPClient http; // we need the Location header to handle redirects @@ -52,7 +53,7 @@ void loop() { Serial.print("[HTTP] begin...\n"); // configure url - http.begin("http://jigsaw.w3.org/HTTP/300/307.html"); //HTTP + http.begin(client, "http://jigsaw.w3.org/HTTP/300/307.html"); //HTTP Serial.print("[HTTP] GET...\n"); // start connection and send HTTP header From 4e62aec8b1f1b7fa9d4939565b8bda79de1f4b09 Mon Sep 17 00:00:00 2001 From: Christopher Liebman Date: Sun, 4 Nov 2018 06:09:02 -0800 Subject: [PATCH 6/9] moved redirect handling code to HTTPClient. only GET and HEAD requests are currently handled automatically Redirects that fail to be automatically handled return the redirect code as before --- .../examples/HTTPRedirect/HTTPRedirect.ino | 92 --------------- .../src/ESP8266HTTPClient.cpp | 105 ++++++++++++++---- .../ESP8266HTTPClient/src/ESP8266HTTPClient.h | 10 +- .../src/ESP8266httpUpdate.cpp | 16 +-- 4 files changed, 96 insertions(+), 127 deletions(-) delete mode 100644 libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino diff --git a/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino b/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino deleted file mode 100644 index d733f265cc..0000000000 --- a/libraries/ESP8266HTTPClient/examples/HTTPRedirect/HTTPRedirect.ino +++ /dev/null @@ -1,92 +0,0 @@ -/** - HTTPRedirect.ino - - Created on: 29-Sep-2018 - -*/ - -#include - -#include -#include -#include - -#define WIFI_SSID "SSID" -#define WIFI_PASS "Password" - - -ESP8266WiFiMulti WiFiMulti; - - -void setup() { - - Serial.begin(115200); - // Serial.setDebugOutput(true); - Serial.println(); - Serial.println(); - Serial.println(); - - for (uint8_t t = 10; t > 0; t--) { - Serial.printf("[SETUP] WAIT %d...\n", t); - Serial.flush(); - delay(1000); - } - - WiFi.mode(WIFI_STA); - WiFiMulti.addAP(WIFI_SSID, WIFI_PASS); - -} - -void loop() { - // wait for WiFi connection - if ((WiFiMulti.run() == WL_CONNECTED)) { - - WiFiClient client; - HTTPClient http; - - // we need the Location header to handle redirects - const char * headerkeys[] = { "Location" }; - size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*); - - // track these headers - http.collectHeaders(headerkeys, headerkeyssize); - - Serial.print("[HTTP] begin...\n"); - // configure url - http.begin(client, "http://jigsaw.w3.org/HTTP/300/307.html"); //HTTP - - Serial.print("[HTTP] GET...\n"); - // start connection and send HTTP header - int httpCode = http.GET(); - - // As long as we get redirects, follow them - while (httpCode == 301 || httpCode == 302 || httpCode == 307) { - String location = http.header("Location"); - Serial.printf("[HTTP] redirect(%d): %s\n", httpCode, location.c_str()); - http.end(); - if (!http.setURL(location)) { - Serial.printf("[HTTP] Bad redirect location: '%s'", location.c_str()); - return; - } - 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... httpCode: %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/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp index 8daa71b0d5..7d76f7e44f 100644 --- a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp +++ b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp @@ -136,6 +136,8 @@ void HTTPClient::clear() _size = -1; _headers = ""; _payload.reset(); + _location = ""; + _redirectCount = 0; } @@ -413,7 +415,7 @@ void HTTPClient::end(void) * disconnect * close the TCP socket */ -void HTTPClient::disconnect() +void HTTPClient::disconnect(bool preserveClient) { if(connected()) { if(_client->available() > 0) { @@ -429,7 +431,9 @@ void HTTPClient::disconnect() DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp stop\n"); if(_client) { _client->stop(); - _client = nullptr; + if (!preserveClient) { + _client = nullptr; + } } #ifdef HTTPCLIENT_1_1_COMPATIBLE if(_tcpDeprecated) { @@ -518,13 +522,39 @@ void HTTPClient::setTimeout(uint16_t timeout) */ bool HTTPClient::setURL(String url) { + // TBD: handle redirect with only the path component.... + // if the new location is only a path then only update the URI + // TBD: If reuse is not set then we need to close the connection + //if (_location.startsWith("/")) { + // _uri = _location; + // 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 + disconnect(true); + clear(); return beginInternal(url, nullptr); } +/** + * set true to follow redirects. + * @param follow + */ +void HTTPClient::setFollowRedirects(bool follow) +{ + _followRedirects = follow; +} + +void HTTPClient::setRedirectLimit(uint16_t limit) +{ + _redirectLimit = limit; +} + /** * use HTTP1.0 * @param timeout @@ -607,29 +637,62 @@ int HTTPClient::sendRequest(const char * type, String payload) */ int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size) { - // connect to server - if(!connect()) { - return returnError(HTTPC_ERROR_CONNECTION_REFUSED); - } + bool redirect = false; + int code = 0; + do { + redirect = false; + DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] type: '%s' redirCount: %d\n", type, _redirectCount); + + // connect to server + if(!connect()) { + return returnError(HTTPC_ERROR_CONNECTION_REFUSED); + } - if(payload && size > 0) { - addHeader(F("Content-Length"), String(size)); - } + if(payload && size > 0) { + addHeader(F("Content-Length"), String(size)); + } - // send Header - if(!sendHeader(type)) { - return returnError(HTTPC_ERROR_SEND_HEADER_FAILED); - } + // send Header + if(!sendHeader(type)) { + return returnError(HTTPC_ERROR_SEND_HEADER_FAILED); + } - // send Payload if needed - if(payload && size > 0) { - if(_client->write(&payload[0], size) != size) { - return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED); + // send Payload if needed + if(payload && size > 0) { + if(_client->write(&payload[0], size) != size) { + return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED); + } } - } + + // handle Server Response (Header) + code = handleHeaderResponse(); + + // + // We can follow redirects for 301/302/307 for GET and HEAD requests and + // and we have not exceeded the redirect limit. (prevent infinite loop. + // + // https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + // + if (_followRedirects && + (_redirectCount < _redirectLimit) && + (code == 301 || code == 302 || code == 307) && + (_location.length() > 0) && + (!strcmp(type, "GET") || !strcmp(type, "HEAD"))) { + _redirectCount += 1; // increment the count for redirect. + redirect = true; + DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] following redirect:: '%s' redirCount: %d\n", _location.c_str(), _redirectCount); + if (!setURL(_location)) { + // return the redirect instead of handling on failure of setURL() + redirect = false; + } + } + + } while (redirect); + + // TBD: handle 303 redirect for non GET/HEAD by changing to GET and requesting // handle Server Response (Header) - return returnError(handleHeaderResponse()); + return returnError(code); } /** @@ -1191,6 +1254,10 @@ int HTTPClient::handleHeaderResponse() transferEncoding = headerValue; } + if(headerName.equalsIgnoreCase("Location")) { + _location = headerValue; + } + for(size_t i = 0; i < _headerKeysCount; i++) { if(_currentHeaders[i].key.equalsIgnoreCase(headerName)) { if (_currentHeaders[i].value != "") { diff --git a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h index 2ddc273e65..f6d20d02ba 100644 --- a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h +++ b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h @@ -171,6 +171,8 @@ class HTTPClient void setAuthorization(const char * user, const char * password); void setAuthorization(const char * auth); void setTimeout(uint16_t timeout); + void setFollowRedirects(bool follow); + void setRedirectLimit(uint16_t limit); // max redirects to follow for a single request bool setURL(String url); // handy for handling redirects void useHTTP10(bool usehttp10 = true); @@ -198,12 +200,12 @@ class HTTPClient int getSize(void); + const String& getLocation(void); // Location header from redirect if 3XX WiFiClient& getStream(void); WiFiClient* getStreamPtr(void); int writeToStream(Stream* stream); const String& getString(void); - static String errorToString(int error); protected: @@ -213,7 +215,7 @@ class HTTPClient }; bool beginInternal(String url, const char* expectedProtocol); - void disconnect(); + void disconnect(bool preserveClient = false); void clear(); int returnError(int error); bool connect(void); @@ -248,6 +250,10 @@ class HTTPClient int _returnCode = 0; int _size = -1; bool _canReuse = false; + bool _followRedirects = false; + uint16_t _redirectCount = 0; + uint16_t _redirectLimit = 10; + String _location; transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY; std::unique_ptr _payload; }; diff --git a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp index a0326311dd..bc599f84bf 100644 --- a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp +++ b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp @@ -261,6 +261,7 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String& // use HTTP/1.0 for update since the update handler not support any transfer Encoding http.useHTTP10(true); http.setTimeout(_httpClientTimeout); + http.setFollowRedirects(_followRedirects); http.setUserAgent(F("ESP8266-http-Update")); http.addHeader(F("x-ESP8266-STA-MAC"), WiFi.macAddress()); http.addHeader(F("x-ESP8266-AP-MAC"), WiFi.softAPmacAddress()); @@ -280,7 +281,7 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String& http.addHeader(F("x-ESP8266-version"), currentVersion); } - const char * headerkeys[] = { "x-MD5" , "Location"}; + const char * headerkeys[] = { "x-MD5" }; size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*); // track these headers @@ -288,19 +289,6 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String& int code = http.GET(); - while(_followRedirects && (code == 302 || code == 301)) - { - String location = http.header("Location"); - DEBUG_HTTP_UPDATE("[httpUpdate] HTTP redirect(%d): %s\n", code, location.c_str()); - http.end(); - if (!http.setURL(location)) - { - DEBUG_HTTP_UPDATE("[httpUpdate] Bad redirect location: '%s'", location.c_str()); - _lastError = code; - return HTTP_UPDATE_FAILED; - } - code = http.GET(); - } int len = http.getSize(); if(code <= 0) { From ca8c891cf362f084c7eb59f4459c1092e1c7667d Mon Sep 17 00:00:00 2001 From: Chris Liebman Date: Wed, 7 Nov 2018 17:52:14 -0800 Subject: [PATCH 7/9] added support for POST/303 redirect added device redirect tests --- .../src/ESP8266HTTPClient.cpp | 28 ++++- .../test_http_client/test_http_client.ino | 103 +++++++++++++++++- .../test_http_client/test_http_client.py | 19 +++- 3 files changed, 143 insertions(+), 7 deletions(-) diff --git a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp index 6b6bdac06b..f7bab69063 100644 --- a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp +++ b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp @@ -640,6 +640,13 @@ int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size) bool redirect = false; int code = 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 = ""; + } + } + redirect = false; DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] type: '%s' redirCount: %d\n", type, _redirectCount); @@ -669,15 +676,17 @@ int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size) // // We can follow redirects for 301/302/307 for GET and HEAD requests and - // and we have not exceeded the redirect limit. (prevent infinite loop. + // and we have not exceeded the redirect limit preventing an infinite + // redirect loop. // // https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html // if (_followRedirects && (_redirectCount < _redirectLimit) && - (code == 301 || code == 302 || code == 307) && (_location.length() > 0) && - (!strcmp(type, "GET") || !strcmp(type, "HEAD"))) { + (code == 301 || code == 302 || code == 307) && + (!strcmp(type, "GET") || !strcmp(type, "HEAD")) + ) { _redirectCount += 1; // increment the count for redirect. redirect = true; DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] following redirect:: '%s' redirCount: %d\n", _location.c_str(), _redirectCount); @@ -689,7 +698,18 @@ int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size) } while (redirect); - // TBD: handle 303 redirect for non GET/HEAD by changing to GET and requesting + // handle 303 redirect for non GET/HEAD by changing to GET and requesting new url + if (_followRedirects && + (_redirectCount < _redirectLimit) && + (_location.length() > 0) && + (code == 303) && + strcmp(type, "GET") && strcmp(type, "HEAD") + ) { + _redirectCount += 1; + if (setURL(_location)) { + code = sendRequest("GET"); + } + } // handle Server Response (Header) return returnError(code); diff --git a/tests/device/test_http_client/test_http_client.ino b/tests/device/test_http_client/test_http_client.ino index 12aefd2b54..d666fb9556 100644 --- a/tests/device/test_http_client/test_http_client.ino +++ b/tests/device/test_http_client/test_http_client.ino @@ -64,9 +64,110 @@ TEST_CASE("HTTP GET & POST requests", "[HTTPClient]") REQUIRE(httpCode == HTTPC_ERROR_CONNECTION_REFUSED); http.end(); } + { + // 301 redirect with follow enabled + WiFiClient client; + HTTPClient http; + http.setFollowRedirects(true); + String uri = String("/redirect301?host=")+getenv("SERVER_IP"); + http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str()); + auto httpCode = http.GET(); + REQUIRE(httpCode == HTTP_CODE_OK); + String payload = http.getString(); + REQUIRE(payload == "redirect success"); + } + { + // 301 redirect with follow disabled + WiFiClient client; + HTTPClient http; + String uri = String("/redirect301?host=")+getenv("SERVER_IP"); + http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str()); + auto httpCode = http.GET(); + REQUIRE(httpCode == 301); + } + { + // 302 redirect with follow enabled + WiFiClient client; + HTTPClient http; + http.setFollowRedirects(true); + String uri = String("/redirect302?host=")+getenv("SERVER_IP"); + http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str()); + auto httpCode = http.GET(); + REQUIRE(httpCode == HTTP_CODE_OK); + String payload = http.getString(); + REQUIRE(payload == "redirect success"); + } + { + // 302 redirect with follow disabled + WiFiClient client; + HTTPClient http; + String uri = String("/redirect302?host=")+getenv("SERVER_IP"); + http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str()); + auto httpCode = http.GET(); + REQUIRE(httpCode == 302); + } + { + // 307 redirect with follow enabled + WiFiClient client; + HTTPClient http; + http.setFollowRedirects(true); + String uri = String("/redirect307?host=")+getenv("SERVER_IP"); + http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str()); + auto httpCode = http.GET(); + REQUIRE(httpCode == HTTP_CODE_OK); + String payload = http.getString(); + REQUIRE(payload == "redirect success"); + } + { + // 307 redirect with follow disabled + WiFiClient client; + HTTPClient http; + String uri = String("/redirect307?host=")+getenv("SERVER_IP"); + http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str()); + auto httpCode = http.GET(); + REQUIRE(httpCode == 307); + } + { + // 301 exceeding redirect limit + WiFiClient client; + HTTPClient http; + http.setFollowRedirects(true); + http.setRedirectLimit(0); + String uri = String("/redirect301?host=")+getenv("SERVER_IP"); + http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str()); + auto httpCode = http.GET(); + REQUIRE(httpCode == 301); + } + { + // POST 303 redirect with follow enabled + WiFiClient client; + HTTPClient http; + http.setFollowRedirects(true); + http.begin(client, getenv("SERVER_IP"), 8088, "/redirect303"); + auto httpCode = http.POST(getenv("SERVER_IP")); + REQUIRE(httpCode == HTTP_CODE_OK); + String payload = http.getString(); + REQUIRE(payload == "redirect success"); + } + { + // POST 303 redirect with follow disabled + WiFiClient client; + HTTPClient http; + http.begin(client, getenv("SERVER_IP"), 8088, "/redirect303"); + auto httpCode = http.POST(getenv("SERVER_IP")); + REQUIRE(httpCode == 303); + } + { + // 302 redirect with follow disabled + WiFiClient client; + HTTPClient http; + String uri = String("/redirect302?host=")+getenv("SERVER_IP"); + http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str()); + auto httpCode = http.GET(); + REQUIRE(httpCode == 302); + } } - TEST_CASE("HTTPS GET request", "[HTTPClient]") { // diff --git a/tests/device/test_http_client/test_http_client.py b/tests/device/test_http_client/test_http_client.py index a78d4108df..d991ca985a 100644 --- a/tests/device/test_http_client/test_http_client.py +++ b/tests/device/test_http_client/test_http_client.py @@ -1,5 +1,5 @@ from mock_decorators import setup, teardown -from flask import Flask, request +from flask import Flask, request, redirect from threading import Thread import urllib2 import os @@ -26,6 +26,21 @@ def root(): def get_data(): size = int(request.args['size']) return 'a'*size + @app.route("/target") + def target(): + return "redirect success" + @app.route("/redirect301") + def redirect301(): + return redirect("http://{}:8088/target".format(request.args['host']), code=301) + @app.route("/redirect302") + def redirect302(): + return redirect("http://{}:8088/target".format(request.args['host']), code=302) + @app.route("/redirect303", methods = ['POST']) + def redirect303(): + return redirect("http://{}:8088/target".format(request.data), code=303) + @app.route("/redirect307") + def redirect307(): + return redirect("http://{}:8088/target".format(request.args['host']), code=307) def flaskThread(): app.run(host='0.0.0.0', port=8088) th = Thread(target=flaskThread) @@ -35,7 +50,7 @@ def flaskThread(): def teardown_http_get(e): response = urllib2.urlopen('http://localhost:8088/shutdown') html = response.read() - time.sleep(30) + time.sleep(1) # avoid address in use error on macOS @setup('HTTPS GET request') From 12f7ad70fc22e6c84dfd589736a9ba9c458120fd Mon Sep 17 00:00:00 2001 From: Chris Liebman Date: Wed, 7 Nov 2018 18:40:36 -0800 Subject: [PATCH 8/9] add missing getLocation() implementation --- libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp index f7bab69063..1549a681ea 100644 --- a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp +++ b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp @@ -863,6 +863,14 @@ 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 From e9a2c234508f5196391eb4912a17f08bfd990656 Mon Sep 17 00:00:00 2001 From: Chris Liebman Date: Sun, 3 Feb 2019 10:22:15 -0800 Subject: [PATCH 9/9] if the new location is only a path then only update the URI --- .../ESP8266HTTPClient/src/ESP8266HTTPClient.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp index d1d206c9db..2c9542cb03 100644 --- a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp +++ b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp @@ -140,7 +140,6 @@ void HTTPClient::clear() _headers = ""; _payload.reset(); _location = ""; - _redirectCount = 0; } @@ -412,6 +411,7 @@ void HTTPClient::end(void) { disconnect(); clear(); + _redirectCount = 0; } /** @@ -525,14 +525,12 @@ void HTTPClient::setTimeout(uint16_t timeout) */ bool HTTPClient::setURL(String url) { - // TBD: handle redirect with only the path component.... // if the new location is only a path then only update the URI - // TBD: If reuse is not set then we need to close the connection - //if (_location.startsWith("/")) { - // _uri = _location; - // clear(); - // return true; - //} + if (_location.startsWith("/")) { + _uri = _location; + 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());