Skip to content

Commit e46ccae

Browse files
liebmand-a-v
authored andcommitted
optionally allow redirects on HTTPClient & OTA updates (#5009)
* optionally allow redirects on http OTA updates * Refactored HTTPClient::begin(url...) & setURL functions, now only beginInternal parses URL, sets ports Added HTTPRedirect example. * fix indentation for style check * add space after while for style check * don't use deprecated begin method in redirect example * 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 * added support for POST/303 redirect added device redirect tests * add missing getLocation() implementation * if the new location is only a path then only update the URI
1 parent 071eeb8 commit e46ccae

File tree

6 files changed

+273
-30
lines changed

6 files changed

+273
-30
lines changed

libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp

+136-22
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ void HTTPClient::clear()
139139
_size = -1;
140140
_headers = "";
141141
_payload.reset();
142+
_location = "";
142143
}
143144

144145

@@ -217,7 +218,6 @@ bool HTTPClient::begin(String url, String httpsFingerprint)
217218
end();
218219
}
219220

220-
_port = 443;
221221
if (httpsFingerprint.length() == 0) {
222222
return false;
223223
}
@@ -238,7 +238,6 @@ bool HTTPClient::begin(String url, const uint8_t httpsFingerprint[20])
238238
end();
239239
}
240240

241-
_port = 443;
242241
if (!beginInternal(url, "https")) {
243242
return false;
244243
}
@@ -264,7 +263,6 @@ bool HTTPClient::begin(String url)
264263
end();
265264
}
266265

267-
_port = 80;
268266
if (!beginInternal(url, "http")) {
269267
return false;
270268
}
@@ -288,6 +286,17 @@ bool HTTPClient::beginInternal(String url, const char* expectedProtocol)
288286
_protocol = url.substring(0, index);
289287
url.remove(0, (index + 3)); // remove http:// or https://
290288

289+
if (_protocol == "http") {
290+
// set default port for 'http'
291+
_port = 80;
292+
} else if (_protocol == "https") {
293+
// set default port for 'https'
294+
_port = 443;
295+
} else {
296+
DEBUG_HTTPCLIENT("[HTTP-Client][begin] unsupported protocol: %s\n", _protocol.c_str());
297+
return false;
298+
}
299+
291300
index = url.indexOf('/');
292301
String host = url.substring(0, index);
293302
url.remove(0, index); // remove host part
@@ -312,7 +321,7 @@ bool HTTPClient::beginInternal(String url, const char* expectedProtocol)
312321
}
313322
_uri = url;
314323

315-
if (_protocol != expectedProtocol) {
324+
if ( expectedProtocol != nullptr && _protocol != expectedProtocol) {
316325
DEBUG_HTTPCLIENT("[HTTP-Client][begin] unexpected protocol: %s, expected %s\n", _protocol.c_str(), expectedProtocol);
317326
return false;
318327
}
@@ -402,13 +411,14 @@ void HTTPClient::end(void)
402411
{
403412
disconnect();
404413
clear();
414+
_redirectCount = 0;
405415
}
406416

407417
/**
408418
* disconnect
409419
* close the TCP socket
410420
*/
411-
void HTTPClient::disconnect()
421+
void HTTPClient::disconnect(bool preserveClient)
412422
{
413423
if(connected()) {
414424
if(_client->available() > 0) {
@@ -424,7 +434,9 @@ void HTTPClient::disconnect()
424434
DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp stop\n");
425435
if(_client) {
426436
_client->stop();
427-
_client = nullptr;
437+
if (!preserveClient) {
438+
_client = nullptr;
439+
}
428440
}
429441
#if HTTPCLIENT_1_1_COMPATIBLE
430442
if(_tcpDeprecated) {
@@ -507,6 +519,43 @@ void HTTPClient::setTimeout(uint16_t timeout)
507519
}
508520
}
509521

522+
/**
523+
* set the URL to a new value. Handy for following redirects.
524+
* @param url
525+
*/
526+
bool HTTPClient::setURL(String url)
527+
{
528+
// if the new location is only a path then only update the URI
529+
if (_location.startsWith("/")) {
530+
_uri = _location;
531+
clear();
532+
return true;
533+
}
534+
535+
if (!url.startsWith(_protocol + ":")) {
536+
DEBUG_HTTPCLIENT("[HTTP-Client][setURL] new URL not the same protocol, expected '%s', URL: '%s'\n", _protocol.c_str(), url.c_str());
537+
return false;
538+
}
539+
// disconnect but preserve _client
540+
disconnect(true);
541+
clear();
542+
return beginInternal(url, nullptr);
543+
}
544+
545+
/**
546+
* set true to follow redirects.
547+
* @param follow
548+
*/
549+
void HTTPClient::setFollowRedirects(bool follow)
550+
{
551+
_followRedirects = follow;
552+
}
553+
554+
void HTTPClient::setRedirectLimit(uint16_t limit)
555+
{
556+
_redirectLimit = limit;
557+
}
558+
510559
/**
511560
* use HTTP1.0
512561
* @param timeout
@@ -589,29 +638,82 @@ int HTTPClient::sendRequest(const char * type, String payload)
589638
*/
590639
int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size)
591640
{
592-
// connect to server
593-
if(!connect()) {
594-
return returnError(HTTPC_ERROR_CONNECTION_REFUSED);
595-
}
641+
bool redirect = false;
642+
int code = 0;
643+
do {
644+
// wipe out any existing headers from previous request
645+
for(size_t i = 0; i < _headerKeysCount; i++) {
646+
if (_currentHeaders[i].value.length() > 0) {
647+
_currentHeaders[i].value = "";
648+
}
649+
}
596650

597-
if(payload && size > 0) {
598-
addHeader(F("Content-Length"), String(size));
599-
}
651+
redirect = false;
652+
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] type: '%s' redirCount: %d\n", type, _redirectCount);
600653

601-
// send Header
602-
if(!sendHeader(type)) {
603-
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
604-
}
654+
// connect to server
655+
if(!connect()) {
656+
return returnError(HTTPC_ERROR_CONNECTION_REFUSED);
657+
}
605658

606-
// send Payload if needed
607-
if(payload && size > 0) {
608-
if(_client->write(&payload[0], size) != size) {
609-
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
659+
if(payload && size > 0) {
660+
addHeader(F("Content-Length"), String(size));
661+
}
662+
663+
// send Header
664+
if(!sendHeader(type)) {
665+
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
666+
}
667+
668+
// send Payload if needed
669+
if(payload && size > 0) {
670+
if(_client->write(&payload[0], size) != size) {
671+
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
672+
}
673+
}
674+
675+
// handle Server Response (Header)
676+
code = handleHeaderResponse();
677+
678+
//
679+
// We can follow redirects for 301/302/307 for GET and HEAD requests and
680+
// and we have not exceeded the redirect limit preventing an infinite
681+
// redirect loop.
682+
//
683+
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
684+
//
685+
if (_followRedirects &&
686+
(_redirectCount < _redirectLimit) &&
687+
(_location.length() > 0) &&
688+
(code == 301 || code == 302 || code == 307) &&
689+
(!strcmp(type, "GET") || !strcmp(type, "HEAD"))
690+
) {
691+
_redirectCount += 1; // increment the count for redirect.
692+
redirect = true;
693+
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] following redirect:: '%s' redirCount: %d\n", _location.c_str(), _redirectCount);
694+
if (!setURL(_location)) {
695+
// return the redirect instead of handling on failure of setURL()
696+
redirect = false;
697+
}
698+
}
699+
700+
} while (redirect);
701+
702+
// handle 303 redirect for non GET/HEAD by changing to GET and requesting new url
703+
if (_followRedirects &&
704+
(_redirectCount < _redirectLimit) &&
705+
(_location.length() > 0) &&
706+
(code == 303) &&
707+
strcmp(type, "GET") && strcmp(type, "HEAD")
708+
) {
709+
_redirectCount += 1;
710+
if (setURL(_location)) {
711+
code = sendRequest("GET");
610712
}
611713
}
612714

613715
// handle Server Response (Header)
614-
return returnError(handleHeaderResponse());
716+
return returnError(code);
615717
}
616718

617719
/**
@@ -762,6 +864,14 @@ int HTTPClient::getSize(void)
762864
return _size;
763865
}
764866

867+
/**
868+
* Location if redirect
869+
*/
870+
const String& HTTPClient::getLocation(void)
871+
{
872+
return _location;
873+
}
874+
765875
/**
766876
* returns the stream of the tcp connection
767877
* @return WiFiClient
@@ -1173,6 +1283,10 @@ int HTTPClient::handleHeaderResponse()
11731283
transferEncoding = headerValue;
11741284
}
11751285

1286+
if(headerName.equalsIgnoreCase("Location")) {
1287+
_location = headerValue;
1288+
}
1289+
11761290
for(size_t i = 0; i < _headerKeysCount; i++) {
11771291
if(_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
11781292
if (_currentHeaders[i].value != "") {

libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h

+9-3
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,9 @@ class HTTPClient
173173
void setAuthorization(const char * user, const char * password);
174174
void setAuthorization(const char * auth);
175175
void setTimeout(uint16_t timeout);
176-
176+
void setFollowRedirects(bool follow);
177+
void setRedirectLimit(uint16_t limit); // max redirects to follow for a single request
178+
bool setURL(String url); // handy for handling redirects
177179
void useHTTP10(bool usehttp10 = true);
178180

179181
/// request handling
@@ -200,12 +202,12 @@ class HTTPClient
200202

201203

202204
int getSize(void);
205+
const String& getLocation(void); // Location header from redirect if 3XX
203206

204207
WiFiClient& getStream(void);
205208
WiFiClient* getStreamPtr(void);
206209
int writeToStream(Stream* stream);
207210
const String& getString(void);
208-
209211
static String errorToString(int error);
210212

211213
protected:
@@ -215,7 +217,7 @@ class HTTPClient
215217
};
216218

217219
bool beginInternal(String url, const char* expectedProtocol);
218-
void disconnect();
220+
void disconnect(bool preserveClient = false);
219221
void clear();
220222
int returnError(int error);
221223
bool connect(void);
@@ -250,6 +252,10 @@ class HTTPClient
250252
int _returnCode = 0;
251253
int _size = -1;
252254
bool _canReuse = false;
255+
bool _followRedirects = false;
256+
uint16_t _redirectCount = 0;
257+
uint16_t _redirectLimit = 10;
258+
String _location;
253259
transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
254260
std::unique_ptr<StreamString> _payload;
255261
};

libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ extern "C" uint32_t _SPIFFS_start;
3030
extern "C" uint32_t _SPIFFS_end;
3131

3232
ESP8266HTTPUpdate::ESP8266HTTPUpdate(void)
33-
: _httpClientTimeout(8000), _ledPin(-1)
33+
: _httpClientTimeout(8000), _followRedirects(false), _ledPin(-1)
3434
{
3535
}
3636

3737
ESP8266HTTPUpdate::ESP8266HTTPUpdate(int httpClientTimeout)
38-
: _httpClientTimeout(httpClientTimeout), _ledPin(-1)
38+
: _httpClientTimeout(httpClientTimeout), _followRedirects(false), _ledPin(-1)
3939
{
4040
}
4141

@@ -261,6 +261,7 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String&
261261
// use HTTP/1.0 for update since the update handler not support any transfer Encoding
262262
http.useHTTP10(true);
263263
http.setTimeout(_httpClientTimeout);
264+
http.setFollowRedirects(_followRedirects);
264265
http.setUserAgent(F("ESP8266-http-Update"));
265266
http.addHeader(F("x-ESP8266-STA-MAC"), WiFi.macAddress());
266267
http.addHeader(F("x-ESP8266-AP-MAC"), WiFi.softAPmacAddress());

libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.h

+6
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ class ESP8266HTTPUpdate
7474
_rebootOnUpdate = reboot;
7575
}
7676

77+
void followRedirects(bool follow)
78+
{
79+
_followRedirects = follow;
80+
}
81+
7782
void setLedPin(int ledPin = -1, uint8_t ledOn = HIGH)
7883
{
7984
_ledPin = ledPin;
@@ -129,6 +134,7 @@ class ESP8266HTTPUpdate
129134
bool _rebootOnUpdate = true;
130135
private:
131136
int _httpClientTimeout;
137+
bool _followRedirects;
132138

133139
int _ledPin;
134140
uint8_t _ledOn;

0 commit comments

Comments
 (0)