Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ab6e010

Browse filesBrowse files
authored
HttpClient: Add cookie support (cookie jar) (espressif#6216)
* Support concatenation of headers (as in esp8266/Arduino@1de0c34#diff-977435a9cc4619fa0b8b995085f6ae683485cf563722756bab57108b362da316 for ESP8266, fixes espressif#4069) * Add support for receiving, storing and sending cookies (cookie jar) * Cookie support: Respect `secure` attribute when sending a request * Fix missing `_secure` flag * Comment out support concatenation of headers (not needed anymore when using cookie jar)
1 parent 7eec41d commit ab6e010
Copy full SHA for ab6e010

File tree

2 files changed

+236
-4
lines changed
Filter options

2 files changed

+236
-4
lines changed

‎libraries/HTTPClient/src/HTTPClient.cpp

Copy file name to clipboardExpand all lines: libraries/HTTPClient/src/HTTPClient.cpp
+199-4Lines changed: 199 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939

4040
#include "HTTPClient.h"
4141

42+
/// Cookie jar support
43+
#include <time.h>
44+
4245
#ifdef HTTPCLIENT_1_1_COMPATIBLE
4346
class 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+

‎libraries/HTTPClient/src/HTTPClient.h

Copy file name to clipboardExpand all lines: libraries/HTTPClient/src/HTTPClient.h
+37Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
#include <WiFiClient.h>
3737
#include <WiFiClientSecure.h>
3838

39+
/// Cookie jar support
40+
#include <vector>
41+
3942
#define HTTPCLIENT_DEFAULT_TCP_TIMEOUT (5000)
4043

4144
/// HTTP client errors
@@ -144,6 +147,28 @@ class TransportTraits;
144147
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
145148
#endif
146149

150+
// cookie jar support
151+
typedef struct {
152+
String host; // host which tries to set the cookie
153+
time_t date; // timestamp of the response that set the cookie
154+
String name;
155+
String value;
156+
String domain;
157+
String path = "";
158+
struct {
159+
time_t date = 0;
160+
bool valid = false;
161+
} expires;
162+
struct {
163+
time_t duration = 0;
164+
bool valid = false;
165+
} max_age;
166+
bool http_only = false;
167+
bool secure = false;
168+
} Cookie;
169+
typedef std::vector<Cookie> CookieJar;
170+
171+
147172
class HTTPClient
148173
{
149174
public:
@@ -217,6 +242,11 @@ class HTTPClient
217242

218243
static String errorToString(int error);
219244

245+
/// Cookie jar support
246+
void setCookieJar(CookieJar* cookieJar);
247+
void resetCookieJar();
248+
void clearAllCookies();
249+
220250
protected:
221251
struct RequestArgument {
222252
String key;
@@ -232,6 +262,9 @@ class HTTPClient
232262
int handleHeaderResponse();
233263
int writeToStreamDataBlock(Stream * stream, int len);
234264

265+
/// Cookie jar support
266+
void setCookie(String date, String headerValue);
267+
bool generateCookieString(String *cookieString);
235268

236269
#ifdef HTTPCLIENT_1_1_COMPATIBLE
237270
TransportTraitsPtr _transportTraits;
@@ -267,6 +300,10 @@ class HTTPClient
267300
uint16_t _redirectLimit = 10;
268301
String _location;
269302
transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
303+
304+
/// Cookie jar support
305+
CookieJar* _cookieJar = nullptr;
306+
270307
};
271308

272309

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.