3939
4040#include " HTTPClient.h"
4141
42+ // / Cookie jar support
43+ #include < time.h>
44+
4245#ifdef HTTPCLIENT_1_1_COMPATIBLE
4346class TransportTraits
4447{
@@ -157,6 +160,7 @@ bool HTTPClient::begin(WiFiClient &client, String url) {
157160 }
158161
159162 _port = (protocol == " https" ? 443 : 80 );
163+ _secure = (protocol == " https" );
160164 return beginInternal (url, protocol.c_str ());
161165}
162166
@@ -187,6 +191,7 @@ bool HTTPClient::begin(WiFiClient &client, String host, uint16_t port, String ur
187191 _port = port;
188192 _uri = uri;
189193 _protocol = (https ? " https" : " http" );
194+ _secure = https;
190195 return true ;
191196}
192197
@@ -603,6 +608,12 @@ int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size)
603608 addHeader (F (" Content-Length" ), String (size));
604609 }
605610
611+ // add cookies to header, if present
612+ String cookie_string;
613+ if (generateCookieString (&cookie_string)) {
614+ addHeader (" Cookie" , cookie_string);
615+ }
616+
606617 // send Header
607618 if (!sendHeader (type)) {
608619 return returnError (HTTPC_ERROR_SEND_HEADER_FAILED);
@@ -706,6 +717,12 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size)
706717 addHeader (" Content-Length" , String (size));
707718 }
708719
720+ // add cookies to header, if present
721+ String cookie_string;
722+ if (generateCookieString (&cookie_string)) {
723+ addHeader (" Cookie" , cookie_string);
724+ }
725+
709726 // send Header
710727 if (!sendHeader (type)) {
711728 return returnError (HTTPC_ERROR_SEND_HEADER_FAILED);
@@ -1222,6 +1239,7 @@ int HTTPClient::handleHeaderResponse()
12221239 _transferEncoding = HTTPC_TE_IDENTITY;
12231240 unsigned long lastDataTime = millis ();
12241241 bool firstLine = true ;
1242+ String date;
12251243
12261244 while (connected ()) {
12271245 size_t len = _client->available ();
@@ -1234,7 +1252,7 @@ int HTTPClient::handleHeaderResponse()
12341252 log_v (" RX: '%s'" , headerLine.c_str ());
12351253
12361254 if (firstLine) {
1237- firstLine = false ;
1255+ firstLine = false ;
12381256 if (_canReuse && headerLine.startsWith (" HTTP/1." )) {
12391257 _canReuse = (headerLine[sizeof " HTTP/1." - 1 ] != ' 0' );
12401258 }
@@ -1245,6 +1263,10 @@ int HTTPClient::handleHeaderResponse()
12451263 String headerValue = headerLine.substring (headerLine.indexOf (' :' ) + 1 );
12461264 headerValue.trim ();
12471265
1266+ if (headerName.equalsIgnoreCase (" Date" )) {
1267+ date = headerValue;
1268+ }
1269+
12481270 if (headerName.equalsIgnoreCase (" Content-Length" )) {
12491271 _size = headerValue.toInt ();
12501272 }
@@ -1263,12 +1285,24 @@ int HTTPClient::handleHeaderResponse()
12631285 _location = headerValue;
12641286 }
12651287
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 {
12681300 _currentHeaders[i].value = headerValue;
1269- break ;
1301+ // }
1302+ break ; // We found a match, stop looking
12701303 }
12711304 }
1305+
12721306 }
12731307
12741308 if (headerLine == " " ) {
@@ -1491,3 +1525,164 @@ const String &HTTPClient::getLocation(void)
14911525{
14921526 return _location;
14931527}
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