Skip to content

Commit 88b26ff

Browse files
1 parent 1f9350d commit 88b26ff

File tree

2 files changed

+242
-10
lines changed

2 files changed

+242
-10
lines changed

libraries/HTTPClient/src/HTTPClient.cpp

+207-8
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424
2525
*/
2626
#include <Arduino.h>
27-
2827
#include "HTTPClient.h"
2928
#include <WiFi.h>
29+
#include <time.h>
3030
#include "base64.h"
31+
extern "C" char *strptime (const char *__restrict, const char *__restrict, struct tm *__restrict); // Not exposed by headers?
32+
3133

3234
// per https://github.com/esp8266/Arduino/issues/8231
3335
// make sure HTTPClient can be utilized as a movable class member
@@ -205,6 +207,7 @@ bool HTTPClient::begin(WiFiClient &client, const String& url) {
205207
}
206208

207209
_port = (protocol == "https" ? 443 : 80);
210+
_secure = (protocol == "https");
208211
_clientIn = client.clone();
209212
_clientGiven = true;
210213
if (_clientMade) {
@@ -246,6 +249,7 @@ bool HTTPClient::begin(WiFiClient &client, const String& host, uint16_t port, co
246249
_port = port;
247250
_uri = uri;
248251
_protocol = (https ? "https" : "http");
252+
_secure = https;
249253
return true;
250254
}
251255

@@ -590,6 +594,12 @@ int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t s
590594

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

597+
// add cookies to header, if present
598+
String cookie_string;
599+
if (generateCookieString(&cookie_string)) {
600+
addHeader("Cookie", cookie_string);
601+
}
602+
593603
// send Header
594604
if (!sendHeader(type)) {
595605
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
@@ -691,6 +701,12 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size) {
691701
addHeader(F("Content-Length"), String(size));
692702
}
693703

704+
// add cookies to header, if present
705+
String cookie_string;
706+
if (generateCookieString(&cookie_string)) {
707+
addHeader("Cookie", cookie_string);
708+
}
709+
694710
// send Header
695711
if (!sendHeader(type)) {
696712
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
@@ -1100,6 +1116,7 @@ int HTTPClient::handleHeaderResponse() {
11001116

11011117
_transferEncoding = HTTPC_TE_IDENTITY;
11021118
unsigned long lastDataTime = millis();
1119+
String date;
11031120

11041121
while (connected()) {
11051122
size_t len = _client()->available();
@@ -1127,6 +1144,10 @@ int HTTPClient::handleHeaderResponse() {
11271144
_size = headerValue.toInt();
11281145
}
11291146

1147+
if (headerName.equalsIgnoreCase("Date")) {
1148+
date = headerValue;
1149+
}
1150+
11301151
if (_canReuse && headerName.equalsIgnoreCase(F("Connection"))) {
11311152
if (headerValue.indexOf(F("close")) >= 0 &&
11321153
headerValue.indexOf(F("keep-alive")) < 0) {
@@ -1142,15 +1163,20 @@ int HTTPClient::handleHeaderResponse() {
11421163
_location = headerValue;
11431164
}
11441165

1166+
if (headerName.equalsIgnoreCase("Set-Cookie")) {
1167+
setCookie(date, headerValue);
1168+
}
1169+
11451170
for (size_t i = 0; i < _headerKeysCount; i++) {
11461171
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
1147-
if (_currentHeaders[i].value != "") {
1148-
// Existing value, append this one with a comma
1149-
_currentHeaders[i].value += ',';
1150-
_currentHeaders[i].value += headerValue;
1151-
} else {
1152-
_currentHeaders[i].value = headerValue;
1153-
}
1172+
// Uncomment the following lines if you need to add support for multiple headers with the same key:
1173+
// if (!_currentHeaders[i].value.isEmpty()) {
1174+
// // Existing value, append this one with a comma
1175+
// _currentHeaders[i].value += ',';
1176+
// _currentHeaders[i].value += headerValue;
1177+
// } else {
1178+
_currentHeaders[i].value = headerValue;
1179+
// }
11541180
break; // We found a match, stop looking
11551181
}
11561182
}
@@ -1210,3 +1236,176 @@ int HTTPClient::returnError(int error) {
12101236
}
12111237
return error;
12121238
}
1239+
1240+
void HTTPClient::setCookieJar(CookieJar* cookieJar) {
1241+
_cookieJar = cookieJar;
1242+
}
1243+
1244+
void HTTPClient::resetCookieJar() {
1245+
_cookieJar = nullptr;
1246+
}
1247+
1248+
void HTTPClient::clearAllCookies() {
1249+
if (_cookieJar) {
1250+
_cookieJar->clear();
1251+
}
1252+
}
1253+
1254+
void HTTPClient::setCookie(String date, String headerValue) {
1255+
if (!_cookieJar) {
1256+
return;
1257+
}
1258+
1259+
#define HTTP_TIME_PATTERN "%a, %d %b %Y %H:%M:%S"
1260+
1261+
Cookie cookie;
1262+
String value;
1263+
int pos1, pos2;
1264+
1265+
struct tm tm;
1266+
strptime(date.c_str(), HTTP_TIME_PATTERN, &tm);
1267+
cookie.date = mktime(&tm);
1268+
1269+
pos1 = headerValue.indexOf('=');
1270+
pos2 = headerValue.indexOf(';');
1271+
1272+
if (pos1 >= 0 && pos2 > pos1) {
1273+
cookie.name = headerValue.substring(0, pos1);
1274+
cookie.value = headerValue.substring(pos1 + 1, pos2);
1275+
} else {
1276+
return; // invalid cookie header
1277+
}
1278+
1279+
// only Cookie Attributes are case insensitive from this point on
1280+
headerValue.toLowerCase();
1281+
1282+
// expires
1283+
if (headerValue.indexOf("expires=") >= 0) {
1284+
pos1 = headerValue.indexOf("expires=") + strlen("expires=");
1285+
pos2 = headerValue.indexOf(';', pos1);
1286+
1287+
if (pos2 > pos1) {
1288+
value = headerValue.substring(pos1, pos2);
1289+
} else {
1290+
value = headerValue.substring(pos1);
1291+
}
1292+
1293+
strptime(value.c_str(), HTTP_TIME_PATTERN, &tm);
1294+
cookie.expires.date = mktime(&tm);
1295+
cookie.expires.valid = true;
1296+
}
1297+
1298+
// max-age
1299+
if (headerValue.indexOf("max-age=") >= 0) {
1300+
pos1 = headerValue.indexOf("max-age=") + strlen("max-age=");
1301+
pos2 = headerValue.indexOf(';', pos1);
1302+
1303+
if (pos2 > pos1) {
1304+
value = headerValue.substring(pos1, pos2);
1305+
} else {
1306+
value = headerValue.substring(pos1);
1307+
}
1308+
1309+
cookie.max_age.duration = value.toInt();
1310+
cookie.max_age.valid = true;
1311+
}
1312+
1313+
// domain
1314+
if (headerValue.indexOf("domain=") >= 0) {
1315+
pos1 = headerValue.indexOf("domain=") + strlen("domain=");
1316+
pos2 = headerValue.indexOf(';', pos1);
1317+
1318+
if (pos2 > pos1) {
1319+
value = headerValue.substring(pos1, pos2);
1320+
} else {
1321+
value = headerValue.substring(pos1);
1322+
}
1323+
1324+
if (value.startsWith(".")) {
1325+
value.remove(0, 1);
1326+
}
1327+
1328+
if (_host.indexOf(value) >= 0) {
1329+
cookie.domain = value;
1330+
} else {
1331+
return; // server tries to set a cookie on a different domain; ignore it
1332+
}
1333+
} else {
1334+
pos1 = _host.lastIndexOf('.', _host.lastIndexOf('.') - 1);
1335+
if (pos1 >= 0) {
1336+
cookie.domain = _host.substring(pos1 + 1);
1337+
} else {
1338+
cookie.domain = _host;
1339+
}
1340+
}
1341+
1342+
// path
1343+
if (headerValue.indexOf("path=") >= 0) {
1344+
pos1 = headerValue.indexOf("path=") + strlen("path=");
1345+
pos2 = headerValue.indexOf(';', pos1);
1346+
1347+
if (pos2 > pos1) {
1348+
cookie.path = headerValue.substring(pos1, pos2);
1349+
} else {
1350+
cookie.path = headerValue.substring(pos1);
1351+
}
1352+
}
1353+
1354+
// HttpOnly
1355+
cookie.http_only = (headerValue.indexOf("httponly") >= 0);
1356+
1357+
// secure
1358+
cookie.secure = (headerValue.indexOf("secure") >= 0);
1359+
1360+
// overwrite or delete cookie in/from cookie jar
1361+
time_t now_local = time(NULL);
1362+
time_t now_gmt = mktime(gmtime(&now_local));
1363+
1364+
bool found = false;
1365+
1366+
for (auto c = _cookieJar->begin(); c != _cookieJar->end(); ++c) {
1367+
if (c->domain == cookie.domain && c->name == cookie.name) {
1368+
// when evaluating, max-age takes precedence over expires if both are defined
1369+
if ((cookie.max_age.valid && ((cookie.date + cookie.max_age.duration) < now_gmt || (cookie.max_age.duration <= 0)))
1370+
|| (!cookie.max_age.valid && cookie.expires.valid && (cookie.expires.date < now_gmt))) {
1371+
_cookieJar->erase(c);
1372+
c--;
1373+
} else {
1374+
*c = cookie;
1375+
}
1376+
found = true;
1377+
}
1378+
}
1379+
1380+
// add cookie to jar
1381+
if (!found && !(cookie.max_age.valid && cookie.max_age.duration <= 0)) {
1382+
_cookieJar->push_back(cookie);
1383+
}
1384+
1385+
}
1386+
1387+
bool HTTPClient::generateCookieString(String *cookieString) {
1388+
if (!_cookieJar) {
1389+
return false;
1390+
}
1391+
time_t now_local = time(NULL);
1392+
time_t now_gmt = mktime(gmtime(&now_local));
1393+
1394+
*cookieString = "";
1395+
bool found = false;
1396+
1397+
for (auto c = _cookieJar->begin(); c != _cookieJar->end(); ++c) {
1398+
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))) {
1399+
_cookieJar->erase(c);
1400+
c--;
1401+
} else if (_host.indexOf(c->domain) >= 0 && (!c->secure || _secure)) {
1402+
if (*cookieString == "") {
1403+
*cookieString = c->name + "=" + c->value;
1404+
} else {
1405+
*cookieString += " ;" + c->name + "=" + c->value;
1406+
}
1407+
found = true;
1408+
}
1409+
}
1410+
return found;
1411+
}

libraries/HTTPClient/src/HTTPClient.h

+35-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <WiFiClientSecure.h>
3434

3535
#include <memory>
36+
#include <vector>
3637

3738
#ifdef DEBUG_ESP_HTTP_CLIENT
3839
#ifdef DEBUG_ESP_PORT
@@ -153,6 +154,28 @@ typedef enum {
153154
class TransportTraits;
154155
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
155156

157+
158+
// cookie jar support
159+
typedef struct {
160+
String host; // host which tries to set the cookie
161+
time_t date; // timestamp of the response that set the cookie
162+
String name;
163+
String value;
164+
String domain;
165+
String path = "";
166+
struct {
167+
time_t date = 0;
168+
bool valid = false;
169+
} expires;
170+
struct {
171+
time_t duration = 0;
172+
bool valid = false;
173+
} max_age;
174+
bool http_only = false;
175+
bool secure = false;
176+
} Cookie;
177+
typedef std::vector<Cookie> CookieJar;
178+
156179
class HTTPClient {
157180
public:
158181
HTTPClient() = default;
@@ -229,6 +252,11 @@ class HTTPClient {
229252
const String& getString(void);
230253
static String errorToString(int error);
231254

255+
// Cookie jar support
256+
void setCookieJar(CookieJar* cookieJar);
257+
void resetCookieJar();
258+
void clearAllCookies();
259+
232260
// ----------------------------------------------------------------------------------------------
233261
// HTTPS support, mirrors the WiFiClientSecure interface
234262
// Could possibly use a virtual interface class between the two, but for now it is more
@@ -327,6 +355,10 @@ class HTTPClient {
327355
int handleHeaderResponse();
328356
int writeToStreamDataBlock(Stream * stream, int len);
329357

358+
// Cookie jar support
359+
void setCookie(String date, String headerValue);
360+
bool generateCookieString(String *cookieString);
361+
330362
WiFiClient *_clientMade = nullptr;
331363
bool _clientTLS = false;
332364

@@ -350,6 +382,7 @@ class HTTPClient {
350382

351383
String _uri;
352384
String _protocol;
385+
bool _secure = false;
353386
String _headers;
354387
String _base64Authorization;
355388

@@ -368,6 +401,6 @@ class HTTPClient {
368401
String _location;
369402
transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
370403
std::unique_ptr<StreamString> _payload;
371-
372-
404+
// Cookie jar support
405+
CookieJar *_cookieJar = nullptr;
373406
};

0 commit comments

Comments
 (0)