Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better follow redirection for HTTPClient #7157

Merged
merged 7 commits into from
Mar 25, 2020
160 changes: 91 additions & 69 deletions libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -558,8 +558,17 @@ bool HTTPClient::setURL(const String& url)
/**
* set true to follow redirects.
* @param follow
* @deprecated
*/
void HTTPClient::setFollowRedirects(bool follow)
{
_followRedirects = follow ? HTTPC_FOLLOW_REDIRECTS : HTTPC_DONT_FOLLOW_REDIRECTS;
}
/**
* set redirect follow mode. See `followRedirects_t` enum for avaliable modes.
* @param follow
*/
void HTTPClient::setFollowRedirects(followRedirects_t follow)
{
_followRedirects = follow;
}
Expand Down Expand Up @@ -652,87 +661,100 @@ int HTTPClient::sendRequest(const char * type, const String& payload)
*/
int HTTPClient::sendRequest(const char * type, const 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.clear();
}
// 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.clear();
}
}

redirect = false;
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] type: '%s' redirCount: %d\n", type, _redirectCount);
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] type: '%s' redirCount: %d\n", type, _redirectCount);

// connect to server
if(!connect()) {
return returnError(HTTPC_ERROR_CONNECTION_REFUSED);
}
// connect to server
if(!connect()) {
return returnError(HTTPC_ERROR_CONNECTION_REFUSED);
}

addHeader(F("Content-Length"), String(payload && size > 0 ? size : 0));
addHeader(F("Content-Length"), String(payload && size > 0 ? size : 0));

// 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) {
size_t bytesWritten = 0;
const uint8_t *p = payload;
size_t originalSize = size;
while (bytesWritten < originalSize) {
int written;
int towrite = std::min((int)size, (int)HTTP_TCP_BUFFER_SIZE);
written = _client->write(p + bytesWritten, towrite);
if (written < 0) {
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
} else if (written == 0) {
return returnError(HTTPC_ERROR_CONNECTION_LOST);
}
bytesWritten += written;
size -= written;
// send Payload if needed
if (payload && size > 0) {
size_t bytesWritten = 0;
const uint8_t *p = payload;
size_t originalSize = size;
while (bytesWritten < originalSize) {
int written;
int towrite = std::min((int)size, (int)HTTP_TCP_BUFFER_SIZE);
written = _client->write(p + bytesWritten, towrite);
if (written < 0) {
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
} else if (written == 0) {
return returnError(HTTPC_ERROR_CONNECTION_LOST);
}
bytesWritten += written;
size -= written;
}
}

// 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 preventing an infinite
// redirect loop.
//
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
//
if (_followRedirects &&
(_redirectCount < _redirectLimit) &&
(_location.length() > 0) &&
(code == 301 || code == 302 || code == 307) &&
(!strcmp(type, "GET") || !strcmp(type, "HEAD"))
// handle Server Response (Header)
int 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.
//
if (
_followRedirects != HTTPC_DONT_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; // 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;
_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;
}
code = sendRequest(type, payload, size);
AgainPsychoX marked this conversation as resolved.
Show resolved Hide resolved
}
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;
}
code = sendRequest("GET");
}
}

} while (redirect);

// 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");
default:
break;
}
}

Expand Down
14 changes: 12 additions & 2 deletions libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ typedef enum {
HTTPC_TE_CHUNKED
} transferEncoding_t;

typedef enum {
AgainPsychoX marked this conversation as resolved.
Show resolved Hide resolved
HTTPC_FOLLOW_REDIRECTS,
HTTPC_DONT_FOLLOW_REDIRECTS,
HTTPC_FORCE_FOLLOW_REDIRECTS
} followRedirects_t;

#if HTTPCLIENT_1_1_COMPATIBLE
class TransportTraits;
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
Expand Down Expand Up @@ -173,8 +179,12 @@ class HTTPClient
void setAuthorization(const char * user, const char * password);
void setAuthorization(const char * auth);
void setTimeout(uint16_t timeout);
void setFollowRedirects(bool follow);

// Redirections
void setFollowRedirects(bool follow) __attribute__ ((deprecated));
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);

Expand Down Expand Up @@ -252,7 +262,7 @@ class HTTPClient
int _returnCode = 0;
int _size = -1;
bool _canReuse = false;
bool _followRedirects = false;
followRedirects_t _followRedirects = HTTPC_DONT_FOLLOW_REDIRECTS;
uint16_t _redirectCount = 0;
uint16_t _redirectLimit = 10;
String _location;
Expand Down