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+ }
0 commit comments