24
24
25
25
*/
26
26
#include < Arduino.h>
27
-
28
27
#include " HTTPClient.h"
29
28
#include < WiFi.h>
29
+ #include < time.h>
30
30
#include " base64.h"
31
+ extern " C" char *strptime (const char *__restrict, const char *__restrict, struct tm *__restrict); // Not exposed by headers?
32
+
31
33
32
34
// per https://github.com/esp8266/Arduino/issues/8231
33
35
// make sure HTTPClient can be utilized as a movable class member
@@ -205,6 +207,7 @@ bool HTTPClient::begin(WiFiClient &client, const String& url) {
205
207
}
206
208
207
209
_port = (protocol == " https" ? 443 : 80 );
210
+ _secure = (protocol == " https" );
208
211
_clientIn = client.clone ();
209
212
_clientGiven = true ;
210
213
if (_clientMade) {
@@ -246,6 +249,7 @@ bool HTTPClient::begin(WiFiClient &client, const String& host, uint16_t port, co
246
249
_port = port;
247
250
_uri = uri;
248
251
_protocol = (https ? " https" : " http" );
252
+ _secure = https;
249
253
return true ;
250
254
}
251
255
@@ -590,6 +594,12 @@ int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t s
590
594
591
595
addHeader (F (" Content-Length" ), String (payload && size > 0 ? size : 0 ));
592
596
597
+ // add cookies to header, if present
598
+ String cookie_string;
599
+ if (generateCookieString (&cookie_string)) {
600
+ addHeader (" Cookie" , cookie_string);
601
+ }
602
+
593
603
// send Header
594
604
if (!sendHeader (type)) {
595
605
return returnError (HTTPC_ERROR_SEND_HEADER_FAILED);
@@ -691,6 +701,12 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size) {
691
701
addHeader (F (" Content-Length" ), String (size));
692
702
}
693
703
704
+ // add cookies to header, if present
705
+ String cookie_string;
706
+ if (generateCookieString (&cookie_string)) {
707
+ addHeader (" Cookie" , cookie_string);
708
+ }
709
+
694
710
// send Header
695
711
if (!sendHeader (type)) {
696
712
return returnError (HTTPC_ERROR_SEND_HEADER_FAILED);
@@ -1100,6 +1116,7 @@ int HTTPClient::handleHeaderResponse() {
1100
1116
1101
1117
_transferEncoding = HTTPC_TE_IDENTITY;
1102
1118
unsigned long lastDataTime = millis ();
1119
+ String date;
1103
1120
1104
1121
while (connected ()) {
1105
1122
size_t len = _client ()->available ();
@@ -1127,6 +1144,10 @@ int HTTPClient::handleHeaderResponse() {
1127
1144
_size = headerValue.toInt ();
1128
1145
}
1129
1146
1147
+ if (headerName.equalsIgnoreCase (" Date" )) {
1148
+ date = headerValue;
1149
+ }
1150
+
1130
1151
if (_canReuse && headerName.equalsIgnoreCase (F (" Connection" ))) {
1131
1152
if (headerValue.indexOf (F (" close" )) >= 0 &&
1132
1153
headerValue.indexOf (F (" keep-alive" )) < 0 ) {
@@ -1142,15 +1163,20 @@ int HTTPClient::handleHeaderResponse() {
1142
1163
_location = headerValue;
1143
1164
}
1144
1165
1166
+ if (headerName.equalsIgnoreCase (" Set-Cookie" )) {
1167
+ setCookie (date, headerValue);
1168
+ }
1169
+
1145
1170
for (size_t i = 0 ; i < _headerKeysCount; i++) {
1146
1171
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
+ // }
1154
1180
break ; // We found a match, stop looking
1155
1181
}
1156
1182
}
@@ -1210,3 +1236,176 @@ int HTTPClient::returnError(int error) {
1210
1236
}
1211
1237
return error;
1212
1238
}
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
+ }
0 commit comments