39
39
40
40
#include " HTTPClient.h"
41
41
42
+ // / Cookie jar support
43
+ #include < time.h>
44
+
42
45
#ifdef HTTPCLIENT_1_1_COMPATIBLE
43
46
class TransportTraits
44
47
{
@@ -157,6 +160,7 @@ bool HTTPClient::begin(WiFiClient &client, String url) {
157
160
}
158
161
159
162
_port = (protocol == " https" ? 443 : 80 );
163
+ _secure = (protocol == " https" );
160
164
return beginInternal (url, protocol.c_str ());
161
165
}
162
166
@@ -187,6 +191,7 @@ bool HTTPClient::begin(WiFiClient &client, String host, uint16_t port, String ur
187
191
_port = port;
188
192
_uri = uri;
189
193
_protocol = (https ? " https" : " http" );
194
+ _secure = https;
190
195
return true ;
191
196
}
192
197
@@ -603,6 +608,12 @@ int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size)
603
608
addHeader (F (" Content-Length" ), String (size));
604
609
}
605
610
611
+ // add cookies to header, if present
612
+ String cookie_string;
613
+ if (generateCookieString (&cookie_string)) {
614
+ addHeader (" Cookie" , cookie_string);
615
+ }
616
+
606
617
// send Header
607
618
if (!sendHeader (type)) {
608
619
return returnError (HTTPC_ERROR_SEND_HEADER_FAILED);
@@ -706,6 +717,12 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size)
706
717
addHeader (" Content-Length" , String (size));
707
718
}
708
719
720
+ // add cookies to header, if present
721
+ String cookie_string;
722
+ if (generateCookieString (&cookie_string)) {
723
+ addHeader (" Cookie" , cookie_string);
724
+ }
725
+
709
726
// send Header
710
727
if (!sendHeader (type)) {
711
728
return returnError (HTTPC_ERROR_SEND_HEADER_FAILED);
@@ -1222,6 +1239,7 @@ int HTTPClient::handleHeaderResponse()
1222
1239
_transferEncoding = HTTPC_TE_IDENTITY;
1223
1240
unsigned long lastDataTime = millis ();
1224
1241
bool firstLine = true ;
1242
+ String date;
1225
1243
1226
1244
while (connected ()) {
1227
1245
size_t len = _client->available ();
@@ -1234,7 +1252,7 @@ int HTTPClient::handleHeaderResponse()
1234
1252
log_v (" RX: '%s'" , headerLine.c_str ());
1235
1253
1236
1254
if (firstLine) {
1237
- firstLine = false ;
1255
+ firstLine = false ;
1238
1256
if (_canReuse && headerLine.startsWith (" HTTP/1." )) {
1239
1257
_canReuse = (headerLine[sizeof " HTTP/1." - 1 ] != ' 0' );
1240
1258
}
@@ -1245,6 +1263,10 @@ int HTTPClient::handleHeaderResponse()
1245
1263
String headerValue = headerLine.substring (headerLine.indexOf (' :' ) + 1 );
1246
1264
headerValue.trim ();
1247
1265
1266
+ if (headerName.equalsIgnoreCase (" Date" )) {
1267
+ date = headerValue;
1268
+ }
1269
+
1248
1270
if (headerName.equalsIgnoreCase (" Content-Length" )) {
1249
1271
_size = headerValue.toInt ();
1250
1272
}
@@ -1263,12 +1285,24 @@ int HTTPClient::handleHeaderResponse()
1263
1285
_location = headerValue;
1264
1286
}
1265
1287
1266
- for (size_t i = 0 ; i < _headerKeysCount; i++) {
1267
- if (_currentHeaders[i].key .equalsIgnoreCase (headerName)) {
1288
+ if (headerName.equalsIgnoreCase (" Set-Cookie" )) {
1289
+ setCookie (date, headerValue);
1290
+ }
1291
+
1292
+ for (size_t i = 0 ; i < _headerKeysCount; i++) {
1293
+ if (_currentHeaders[i].key .equalsIgnoreCase (headerName)) {
1294
+ // Uncomment the following lines if you need to add support for multiple headers with the same key:
1295
+ // if (!_currentHeaders[i].value.isEmpty()) {
1296
+ // // Existing value, append this one with a comma
1297
+ // _currentHeaders[i].value += ',';
1298
+ // _currentHeaders[i].value += headerValue;
1299
+ // } else {
1268
1300
_currentHeaders[i].value = headerValue;
1269
- break ;
1301
+ // }
1302
+ break ; // We found a match, stop looking
1270
1303
}
1271
1304
}
1305
+
1272
1306
}
1273
1307
1274
1308
if (headerLine == " " ) {
@@ -1491,3 +1525,164 @@ const String &HTTPClient::getLocation(void)
1491
1525
{
1492
1526
return _location;
1493
1527
}
1528
+
1529
+ void HTTPClient::setCookieJar (CookieJar* cookieJar)
1530
+ {
1531
+ _cookieJar = cookieJar;
1532
+ }
1533
+
1534
+ void HTTPClient::resetCookieJar ()
1535
+ {
1536
+ _cookieJar = nullptr ;
1537
+ }
1538
+
1539
+ void HTTPClient::clearAllCookies ()
1540
+ {
1541
+ if (_cookieJar) _cookieJar->clear ();
1542
+ }
1543
+
1544
+ void HTTPClient::setCookie (String date, String headerValue)
1545
+ {
1546
+ #define HTTP_TIME_PATTERN " %a, %d %b %Y %H:%M:%S"
1547
+
1548
+ Cookie cookie;
1549
+ String value;
1550
+ int pos1, pos2;
1551
+
1552
+ headerValue.toLowerCase ();
1553
+
1554
+ struct tm tm ;
1555
+ strptime (date.c_str (), HTTP_TIME_PATTERN, &tm );
1556
+ cookie.date = mktime (&tm );
1557
+
1558
+ pos1 = headerValue.indexOf (' =' );
1559
+ pos2 = headerValue.indexOf (' ;' );
1560
+
1561
+ if (pos1 >= 0 && pos2 > pos1){
1562
+ cookie.name = headerValue.substring (0 , pos1);
1563
+ cookie.value = headerValue.substring (pos1 + 1 , pos2);
1564
+ } else {
1565
+ return ; // invalid cookie header
1566
+ }
1567
+
1568
+ // expires
1569
+ if (headerValue.indexOf (" expires=" ) >= 0 ){
1570
+ pos1 = headerValue.indexOf (" expires=" ) + strlen (" expires=" );
1571
+ pos2 = headerValue.indexOf (' ;' , pos1);
1572
+
1573
+ if (pos2 > pos1)
1574
+ value = headerValue.substring (pos1, pos2);
1575
+ else
1576
+ value = headerValue.substring (pos1);
1577
+
1578
+ strptime (value.c_str (), HTTP_TIME_PATTERN, &tm );
1579
+ cookie.expires .date = mktime (&tm );
1580
+ cookie.expires .valid = true ;
1581
+ }
1582
+
1583
+ // max-age
1584
+ if (headerValue.indexOf (" max-age=" ) >= 0 ){
1585
+ pos1 = headerValue.indexOf (" max-age=" ) + strlen (" max-age=" );
1586
+ pos2 = headerValue.indexOf (' ;' , pos1);
1587
+
1588
+ if (pos2 > pos1)
1589
+ value = headerValue.substring (pos1, pos2);
1590
+ else
1591
+ value = headerValue.substring (pos1);
1592
+
1593
+ cookie.max_age .duration = value.toInt ();
1594
+ cookie.max_age .valid = true ;
1595
+ }
1596
+
1597
+ // domain
1598
+ if (headerValue.indexOf (" domain=" ) >= 0 ){
1599
+ pos1 = headerValue.indexOf (" domain=" ) + strlen (" domain=" );
1600
+ pos2 = headerValue.indexOf (' ;' , pos1);
1601
+
1602
+ if (pos2 > pos1)
1603
+ value = headerValue.substring (pos1, pos2);
1604
+ else
1605
+ value = headerValue.substring (pos1);
1606
+
1607
+ if (value.startsWith (" ." )) value.remove (0 , 1 );
1608
+
1609
+ if (_host.indexOf (value) >= 0 ) {
1610
+ cookie.domain = value;
1611
+ } else {
1612
+ return ; // server tries to set a cookie on a different domain; ignore it
1613
+ }
1614
+ } else {
1615
+ pos1 = _host.lastIndexOf (' .' , _host.lastIndexOf (' .' ) - 1 );
1616
+ if (pos1 >= 0 )
1617
+ cookie.domain = _host.substring (pos1 + 1 );
1618
+ else
1619
+ cookie.domain = _host;
1620
+ }
1621
+
1622
+ // path
1623
+ if (headerValue.indexOf (" path=" ) >= 0 ){
1624
+ pos1 = headerValue.indexOf (" path=" ) + strlen (" path=" );
1625
+ pos2 = headerValue.indexOf (' ;' , pos1);
1626
+
1627
+ if (pos2 > pos1)
1628
+ cookie.path = headerValue.substring (pos1, pos2);
1629
+ else
1630
+ cookie.path = headerValue.substring (pos1);
1631
+ }
1632
+
1633
+ // HttpOnly
1634
+ cookie.http_only = (headerValue.indexOf (" httponly" ) >= 0 );
1635
+
1636
+ // secure
1637
+ cookie.secure = (headerValue.indexOf (" secure" ) >= 0 );
1638
+
1639
+ // overwrite or delete cookie in/from cookie jar
1640
+ time_t now_local = time (NULL );
1641
+ time_t now_gmt = mktime (gmtime (&now_local));
1642
+
1643
+ bool found = false ;
1644
+
1645
+ for (auto c = _cookieJar->begin (); c != _cookieJar->end (); ++c) {
1646
+ if (c->domain == cookie.domain && c->name == cookie.name ) {
1647
+ // when evaluating, max-age takes precedence over expires if both are defined
1648
+ if (cookie.max_age .valid && ((cookie.date + cookie.max_age .duration ) < now_gmt || cookie.max_age .duration <= 0 )
1649
+ || (!cookie.max_age .valid && cookie.expires .valid && cookie.expires .date < now_gmt)) {
1650
+ _cookieJar->erase (c);
1651
+ c--;
1652
+ } else {
1653
+ *c = cookie;
1654
+ }
1655
+ found = true ;
1656
+ }
1657
+ }
1658
+
1659
+ // add cookie to jar
1660
+ if (!found && !(cookie.max_age .valid && cookie.max_age .duration <= 0 ))
1661
+ _cookieJar->push_back (cookie);
1662
+
1663
+ }
1664
+
1665
+ bool HTTPClient::generateCookieString (String *cookieString)
1666
+ {
1667
+ time_t now_local = time (NULL );
1668
+ time_t now_gmt = mktime (gmtime (&now_local));
1669
+
1670
+ *cookieString = " " ;
1671
+ bool found = false ;
1672
+
1673
+ for (auto c = _cookieJar->begin (); c != _cookieJar->end (); ++c) {
1674
+ 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)) {
1675
+ _cookieJar->erase (c);
1676
+ c--;
1677
+ } else if (_host.indexOf (c->domain ) >= 0 && (!c->secure || _secure) ) {
1678
+ if (*cookieString == " " )
1679
+ *cookieString = c->name + " =" + c->value ;
1680
+ else
1681
+ *cookieString += " ;" + c->name + " =" + c->value ;
1682
+ found = true ;
1683
+ }
1684
+ }
1685
+ return found;
1686
+ }
1687
+
1688
+
0 commit comments