Add Cookies to HTTPClient (#2186)
From: https://github.com/espressif/arduino-esp32/pull/6216 https://github.com/espressif/arduino-esp32/pull/6280 https://github.com/espressif/arduino-esp32/pull/7112
This commit was merged in pull request #2186.
This commit is contained in:
@@ -24,10 +24,12 @@
|
||||
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "HTTPClient.h"
|
||||
#include <WiFi.h>
|
||||
#include <time.h>
|
||||
#include "base64.h"
|
||||
extern "C" char *strptime(const char *__restrict, const char *__restrict, struct tm *__restrict); // Not exposed by headers?
|
||||
|
||||
|
||||
// per https://github.com/esp8266/Arduino/issues/8231
|
||||
// make sure HTTPClient can be utilized as a movable class member
|
||||
@@ -205,6 +207,7 @@ bool HTTPClient::begin(WiFiClient &client, const String& url) {
|
||||
}
|
||||
|
||||
_port = (protocol == "https" ? 443 : 80);
|
||||
_secure = (protocol == "https");
|
||||
_clientIn = client.clone();
|
||||
_clientGiven = true;
|
||||
if (_clientMade) {
|
||||
@@ -246,6 +249,7 @@ bool HTTPClient::begin(WiFiClient &client, const String& host, uint16_t port, co
|
||||
_port = port;
|
||||
_uri = uri;
|
||||
_protocol = (https ? "https" : "http");
|
||||
_secure = https;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -590,6 +594,12 @@ int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t s
|
||||
|
||||
addHeader(F("Content-Length"), String(payload && size > 0 ? size : 0));
|
||||
|
||||
// add cookies to header, if present
|
||||
String cookie_string;
|
||||
if (generateCookieString(&cookie_string)) {
|
||||
addHeader("Cookie", cookie_string);
|
||||
}
|
||||
|
||||
// send Header
|
||||
if (!sendHeader(type)) {
|
||||
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
|
||||
@@ -691,6 +701,12 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size) {
|
||||
addHeader(F("Content-Length"), String(size));
|
||||
}
|
||||
|
||||
// add cookies to header, if present
|
||||
String cookie_string;
|
||||
if (generateCookieString(&cookie_string)) {
|
||||
addHeader("Cookie", cookie_string);
|
||||
}
|
||||
|
||||
// send Header
|
||||
if (!sendHeader(type)) {
|
||||
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
|
||||
@@ -1100,6 +1116,7 @@ int HTTPClient::handleHeaderResponse() {
|
||||
|
||||
_transferEncoding = HTTPC_TE_IDENTITY;
|
||||
unsigned long lastDataTime = millis();
|
||||
String date;
|
||||
|
||||
while (connected()) {
|
||||
size_t len = _client()->available();
|
||||
@@ -1127,6 +1144,10 @@ int HTTPClient::handleHeaderResponse() {
|
||||
_size = headerValue.toInt();
|
||||
}
|
||||
|
||||
if (headerName.equalsIgnoreCase("Date")) {
|
||||
date = headerValue;
|
||||
}
|
||||
|
||||
if (_canReuse && headerName.equalsIgnoreCase(F("Connection"))) {
|
||||
if (headerValue.indexOf(F("close")) >= 0 &&
|
||||
headerValue.indexOf(F("keep-alive")) < 0) {
|
||||
@@ -1142,15 +1163,20 @@ int HTTPClient::handleHeaderResponse() {
|
||||
_location = headerValue;
|
||||
}
|
||||
|
||||
if (headerName.equalsIgnoreCase("Set-Cookie")) {
|
||||
setCookie(date, 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;
|
||||
}
|
||||
// Uncomment the following lines if you need to add support for multiple headers with the same key:
|
||||
// if (!_currentHeaders[i].value.isEmpty()) {
|
||||
// // 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
|
||||
}
|
||||
}
|
||||
@@ -1210,3 +1236,176 @@ int HTTPClient::returnError(int error) {
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void HTTPClient::setCookieJar(CookieJar* cookieJar) {
|
||||
_cookieJar = cookieJar;
|
||||
}
|
||||
|
||||
void HTTPClient::resetCookieJar() {
|
||||
_cookieJar = nullptr;
|
||||
}
|
||||
|
||||
void HTTPClient::clearAllCookies() {
|
||||
if (_cookieJar) {
|
||||
_cookieJar->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPClient::setCookie(String date, String headerValue) {
|
||||
if (!_cookieJar) {
|
||||
return;
|
||||
}
|
||||
|
||||
#define HTTP_TIME_PATTERN "%a, %d %b %Y %H:%M:%S"
|
||||
|
||||
Cookie cookie;
|
||||
String value;
|
||||
int pos1, pos2;
|
||||
|
||||
struct tm tm;
|
||||
strptime(date.c_str(), HTTP_TIME_PATTERN, &tm);
|
||||
cookie.date = mktime(&tm);
|
||||
|
||||
pos1 = headerValue.indexOf('=');
|
||||
pos2 = headerValue.indexOf(';');
|
||||
|
||||
if (pos1 >= 0 && pos2 > pos1) {
|
||||
cookie.name = headerValue.substring(0, pos1);
|
||||
cookie.value = headerValue.substring(pos1 + 1, pos2);
|
||||
} else {
|
||||
return; // invalid cookie header
|
||||
}
|
||||
|
||||
// only Cookie Attributes are case insensitive from this point on
|
||||
headerValue.toLowerCase();
|
||||
|
||||
// expires
|
||||
if (headerValue.indexOf("expires=") >= 0) {
|
||||
pos1 = headerValue.indexOf("expires=") + strlen("expires=");
|
||||
pos2 = headerValue.indexOf(';', pos1);
|
||||
|
||||
if (pos2 > pos1) {
|
||||
value = headerValue.substring(pos1, pos2);
|
||||
} else {
|
||||
value = headerValue.substring(pos1);
|
||||
}
|
||||
|
||||
strptime(value.c_str(), HTTP_TIME_PATTERN, &tm);
|
||||
cookie.expires.date = mktime(&tm);
|
||||
cookie.expires.valid = true;
|
||||
}
|
||||
|
||||
// max-age
|
||||
if (headerValue.indexOf("max-age=") >= 0) {
|
||||
pos1 = headerValue.indexOf("max-age=") + strlen("max-age=");
|
||||
pos2 = headerValue.indexOf(';', pos1);
|
||||
|
||||
if (pos2 > pos1) {
|
||||
value = headerValue.substring(pos1, pos2);
|
||||
} else {
|
||||
value = headerValue.substring(pos1);
|
||||
}
|
||||
|
||||
cookie.max_age.duration = value.toInt();
|
||||
cookie.max_age.valid = true;
|
||||
}
|
||||
|
||||
// domain
|
||||
if (headerValue.indexOf("domain=") >= 0) {
|
||||
pos1 = headerValue.indexOf("domain=") + strlen("domain=");
|
||||
pos2 = headerValue.indexOf(';', pos1);
|
||||
|
||||
if (pos2 > pos1) {
|
||||
value = headerValue.substring(pos1, pos2);
|
||||
} else {
|
||||
value = headerValue.substring(pos1);
|
||||
}
|
||||
|
||||
if (value.startsWith(".")) {
|
||||
value.remove(0, 1);
|
||||
}
|
||||
|
||||
if (_host.indexOf(value) >= 0) {
|
||||
cookie.domain = value;
|
||||
} else {
|
||||
return; // server tries to set a cookie on a different domain; ignore it
|
||||
}
|
||||
} else {
|
||||
pos1 = _host.lastIndexOf('.', _host.lastIndexOf('.') - 1);
|
||||
if (pos1 >= 0) {
|
||||
cookie.domain = _host.substring(pos1 + 1);
|
||||
} else {
|
||||
cookie.domain = _host;
|
||||
}
|
||||
}
|
||||
|
||||
// path
|
||||
if (headerValue.indexOf("path=") >= 0) {
|
||||
pos1 = headerValue.indexOf("path=") + strlen("path=");
|
||||
pos2 = headerValue.indexOf(';', pos1);
|
||||
|
||||
if (pos2 > pos1) {
|
||||
cookie.path = headerValue.substring(pos1, pos2);
|
||||
} else {
|
||||
cookie.path = headerValue.substring(pos1);
|
||||
}
|
||||
}
|
||||
|
||||
// HttpOnly
|
||||
cookie.http_only = (headerValue.indexOf("httponly") >= 0);
|
||||
|
||||
// secure
|
||||
cookie.secure = (headerValue.indexOf("secure") >= 0);
|
||||
|
||||
// overwrite or delete cookie in/from cookie jar
|
||||
time_t now_local = time(NULL);
|
||||
time_t now_gmt = mktime(gmtime(&now_local));
|
||||
|
||||
bool found = false;
|
||||
|
||||
for (auto c = _cookieJar->begin(); c != _cookieJar->end(); ++c) {
|
||||
if (c->domain == cookie.domain && c->name == cookie.name) {
|
||||
// when evaluating, max-age takes precedence over expires if both are defined
|
||||
if ((cookie.max_age.valid && ((cookie.date + cookie.max_age.duration) < now_gmt || (cookie.max_age.duration <= 0)))
|
||||
|| (!cookie.max_age.valid && cookie.expires.valid && (cookie.expires.date < now_gmt))) {
|
||||
_cookieJar->erase(c);
|
||||
c--;
|
||||
} else {
|
||||
*c = cookie;
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// add cookie to jar
|
||||
if (!found && !(cookie.max_age.valid && cookie.max_age.duration <= 0)) {
|
||||
_cookieJar->push_back(cookie);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool HTTPClient::generateCookieString(String *cookieString) {
|
||||
if (!_cookieJar) {
|
||||
return false;
|
||||
}
|
||||
time_t now_local = time(NULL);
|
||||
time_t now_gmt = mktime(gmtime(&now_local));
|
||||
|
||||
*cookieString = "";
|
||||
bool found = false;
|
||||
|
||||
for (auto c = _cookieJar->begin(); c != _cookieJar->end(); ++c) {
|
||||
if ((c->max_age.valid && ((c->date + c->max_age.duration) < now_gmt)) || (!c->max_age.valid && c->expires.valid && (c->expires.date < now_gmt))) {
|
||||
_cookieJar->erase(c);
|
||||
c--;
|
||||
} else if (_host.indexOf(c->domain) >= 0 && (!c->secure || _secure)) {
|
||||
if (*cookieString == "") {
|
||||
*cookieString = c->name + "=" + c->value;
|
||||
} else {
|
||||
*cookieString += " ;" + c->name + "=" + c->value;
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user