From 4ec5d35060b3d4eae1a31dfb27fb2f4bd0fd94df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnter=20Obiltschnig?= Date: Sat, 4 Jan 2020 10:15:41 +0100 Subject: [PATCH] added support for SameSite cookie attribute --- Net/include/Poco/Net/HTTPCookie.h | 83 +++++++++++++++++----------- Net/src/HTTPCookie.cpp | 77 +++++++++++++++++++++----- Net/testsuite/src/HTTPCookieTest.cpp | 16 +++++- 3 files changed, 128 insertions(+), 48 deletions(-) diff --git a/Net/include/Poco/Net/HTTPCookie.h b/Net/include/Poco/Net/HTTPCookie.h index c7c47b2e0..94411a6c3 100644 --- a/Net/include/Poco/Net/HTTPCookie.h +++ b/Net/include/Poco/Net/HTTPCookie.h @@ -31,30 +31,38 @@ class NameValueCollection; class Net_API HTTPCookie /// This class represents a HTTP Cookie. /// - /// A cookie is a small amount of information sent by a Web - /// server to a Web browser, saved by the browser, and later sent back - /// to the server. A cookie's value can uniquely identify a client, so + /// A cookie is a small amount of information sent by a Web + /// server to a Web browser, saved by the browser, and later sent back + /// to the server. A cookie's value can uniquely identify a client, so /// cookies are commonly used for session management. /// - /// A cookie has a name, a single value, and optional attributes such - /// as a comment, path and domain qualifiers, a maximum age, and a + /// A cookie has a name, a single value, and optional attributes such + /// as a comment, path and domain qualifiers, a maximum age, and a /// version number. /// - /// This class supports both the Version 0 (by Netscape) and Version 1 - /// (by RFC 2109) cookie specifications. By default, cookies are created + /// This class supports both the Version 0 (by Netscape) and Version 1 + /// (by RFC 2109) cookie specifications. By default, cookies are created /// using Version 0 to ensure the best interoperability. { public: + enum SameSite + { + SAME_SITE_NOT_SPECIFIED, + SAME_SITE_NONE, + SAME_SITE_LAX, + SAME_SITE_STRICT + }; + HTTPCookie(); /// Creates an empty HTTPCookie. - + explicit HTTPCookie(const std::string& name); - /// Creates a cookie with the given name. + /// Creates a cookie with the given name. /// The cookie never expires. - + explicit HTTPCookie(const NameValueCollection& nvc); /// Creates a cookie from the given NameValueCollection. - + HTTPCookie(const std::string& name, const std::string& value); /// Creates a cookie with the given name and value. /// The cookie never expires. @@ -62,32 +70,32 @@ public: /// Note: If value contains whitespace or non-alphanumeric /// characters, the value should be escaped by calling escape() /// before passing it to the constructor. - + HTTPCookie(const HTTPCookie& cookie); /// Creates the HTTPCookie by copying another one. ~HTTPCookie(); /// Destroys the HTTPCookie. - + HTTPCookie& operator = (const HTTPCookie& cookie); /// Assigns a cookie. - + void setVersion(int version); /// Sets the version of the cookie. /// /// Version must be either 0 (denoting a Netscape cookie) /// or 1 (denoting a RFC 2109 cookie). - + int getVersion() const; /// Returns the version of the cookie, which is - /// either 0 or 1. - + /// either 0 or 1. + void setName(const std::string& name); /// Sets the name of the cookie. - + const std::string& getName() const; /// Returns the name of the cookie. - + void setValue(const std::string& value); /// Sets the value of the cookie. /// @@ -97,10 +105,10 @@ public: /// Note: If value contains whitespace or non-alphanumeric /// characters, the value should be escaped by calling escape() /// prior to passing it to setName(). - + const std::string& getValue() const; /// Returns the value of the cookie. - + void setComment(const std::string& comment); /// Sets the comment for the cookie. /// @@ -111,7 +119,7 @@ public: void setDomain(const std::string& domain); /// Sets the domain for the cookie. - + const std::string& getDomain() const; /// Returns the domain for the cookie. @@ -120,7 +128,7 @@ public: void setPriority(const std::string& priority); /// Sets the priority for the cookie. - + const std::string& getPath() const; /// Returns the path for the cookie. @@ -130,7 +138,7 @@ public: void setSecure(bool secure); /// Sets the value of the secure flag for /// the cookie. - + bool getSecure() const; /// Returns the value of the secure flag /// for the cookie. @@ -139,7 +147,7 @@ public: /// Sets the maximum age in seconds for /// the cookie. /// - /// A value of -1 (default) causes the cookie + /// A value of -1 (default) causes the cookie /// to become a session cookie, which will /// be deleted when the browser window /// is closed. @@ -150,19 +158,25 @@ public: int getMaxAge() const; /// Returns the maximum age in seconds for /// the cookie. - + void setHttpOnly(bool flag = true); /// Sets the HttpOnly flag for the cookie. - + bool getHttpOnly() const; /// Returns true iff the cookie's HttpOnly flag is set. - + + void setSameSite(SameSite value); + /// Sets the cookie's SameSite attribute. + + SameSite getSameSite() const; + /// Returns the cookie's SameSite attribute. + std::string toString() const; /// Returns a string representation of the cookie, /// suitable for use in a Set-Cookie header. - + static std::string escape(const std::string& str); - /// Escapes the given string by replacing all + /// Escapes the given string by replacing all /// non-alphanumeric characters with escape /// sequences in the form %xx, where xx is the /// hexadecimal character code. @@ -183,7 +197,7 @@ public: /// - grave accent ` /// - comma and semicolon , and ; /// - whitespace and control characters - + static std::string unescape(const std::string& str); /// Unescapes the given string by replacing all /// escape sequences in the form %xx with the @@ -200,6 +214,7 @@ private: bool _secure; int _maxAge; bool _httpOnly; + SameSite _sameSite; }; @@ -266,6 +281,12 @@ inline bool HTTPCookie::getHttpOnly() const } +inline HTTPCookie::SameSite HTTPCookie::getSameSite() const +{ + return _sameSite; +} + + } } // namespace Poco::Net diff --git a/Net/src/HTTPCookie.cpp b/Net/src/HTTPCookie.cpp index 9cbae4ad8..b389e5dc3 100644 --- a/Net/src/HTTPCookie.cpp +++ b/Net/src/HTTPCookie.cpp @@ -43,17 +43,19 @@ HTTPCookie::HTTPCookie(): _version(0), _secure(false), _maxAge(-1), - _httpOnly(false) + _httpOnly(false), + _sameSite(SAME_SITE_NOT_SPECIFIED) { } - + HTTPCookie::HTTPCookie(const std::string& name): _version(0), _name(name), _secure(false), _maxAge(-1), - _httpOnly(false) + _httpOnly(false), + _sameSite(SAME_SITE_NOT_SPECIFIED) { } @@ -62,7 +64,8 @@ HTTPCookie::HTTPCookie(const NameValueCollection& nvc): _version(0), _secure(false), _maxAge(-1), - _httpOnly(false) + _httpOnly(false), + _sameSite(SAME_SITE_NOT_SPECIFIED) { for (NameValueCollection::ConstIterator it = nvc.begin(); it != nvc.end(); ++it) { @@ -99,6 +102,15 @@ HTTPCookie::HTTPCookie(const NameValueCollection& nvc): Timestamp now; setMaxAge((int) ((exp.timestamp() - now) / Timestamp::resolution())); } + else if (icompare(name, "SameSite") == 0) + { + if (icompare(value, "None") == 0) + _sameSite = SAME_SITE_NONE; + else if (icompare(value, "Lax") == 0) + _sameSite = SAME_SITE_LAX; + else if (icompare(value, "Strict") == 0) + _sameSite = SAME_SITE_STRICT; + } else if (icompare(name, "version") == 0) { setVersion(NumberParser::parse(value)); @@ -115,18 +127,19 @@ HTTPCookie::HTTPCookie(const NameValueCollection& nvc): } } - + HTTPCookie::HTTPCookie(const std::string& name, const std::string& value): _version(0), _name(name), _value(value), _secure(false), _maxAge(-1), - _httpOnly(false) + _httpOnly(false), + _sameSite(SAME_SITE_NOT_SPECIFIED) { } - + HTTPCookie::HTTPCookie(const HTTPCookie& cookie): _version(cookie._version), _name(cookie._name), @@ -137,7 +150,8 @@ HTTPCookie::HTTPCookie(const HTTPCookie& cookie): _priority(cookie._priority), _secure(cookie._secure), _maxAge(cookie._maxAge), - _httpOnly(cookie._httpOnly) + _httpOnly(cookie._httpOnly), + _sameSite(cookie._sameSite) { } @@ -146,7 +160,7 @@ HTTPCookie::~HTTPCookie() { } - + HTTPCookie& HTTPCookie::operator = (const HTTPCookie& cookie) { if (&cookie != this) @@ -161,29 +175,30 @@ HTTPCookie& HTTPCookie::operator = (const HTTPCookie& cookie) _secure = cookie._secure; _maxAge = cookie._maxAge; _httpOnly = cookie._httpOnly; + _sameSite = cookie._sameSite; } return *this; } - + void HTTPCookie::setVersion(int version) { _version = version; } - + void HTTPCookie::setName(const std::string& name) { _name = name; } - + void HTTPCookie::setValue(const std::string& value) { _value = value; } - + void HTTPCookie::setComment(const std::string& comment) { _comment = comment; @@ -214,7 +229,7 @@ void HTTPCookie::setSecure(bool secure) } -void HTTPCookie::setMaxAge(int maxAge) +void HTTPCookie::setMaxAge(int maxAge) { _maxAge = maxAge; } @@ -226,6 +241,12 @@ void HTTPCookie::setHttpOnly(bool flag) } +void HTTPCookie::setSameSite(SameSite value) +{ + _sameSite = value; +} + + std::string HTTPCookie::toString() const { std::string result; @@ -258,6 +279,20 @@ std::string HTTPCookie::toString() const result.append("; expires="); DateTimeFormatter::append(result, ts, DateTimeFormat::HTTP_FORMAT); } + switch (_sameSite) + { + case SAME_SITE_NONE: + result.append("; SameSite=None"); + break; + case SAME_SITE_LAX: + result.append("; SameSite=Lax"); + break; + case SAME_SITE_STRICT: + result.append("; SameSite=Strict"); + break; + case SAME_SITE_NOT_SPECIFIED: + break; + } if (_secure) { result.append("; secure"); @@ -304,6 +339,20 @@ std::string HTTPCookie::toString() const NumberFormatter::append(result, _maxAge); result.append("\""); } + switch (_sameSite) + { + case SAME_SITE_NONE: + result.append("; SameSite=None"); + break; + case SAME_SITE_LAX: + result.append("; SameSite=Lax"); + break; + case SAME_SITE_STRICT: + result.append("; SameSite=Strict"); + break; + case SAME_SITE_NOT_SPECIFIED: + break; + } if (_secure) { result.append("; secure"); diff --git a/Net/testsuite/src/HTTPCookieTest.cpp b/Net/testsuite/src/HTTPCookieTest.cpp index 301749a4b..e75fc2c49 100644 --- a/Net/testsuite/src/HTTPCookieTest.cpp +++ b/Net/testsuite/src/HTTPCookieTest.cpp @@ -70,11 +70,11 @@ void HTTPCookieTest::testCookie() cookie.setVersion(1); assertTrue (cookie.toString() == "name=\"value\"; Comment=\"comment\"; Domain=\"appinf.com\"; Path=\"/\"; secure; Version=\"1\""); - + cookie.setSecure(false); cookie.setMaxAge(100); assertTrue (cookie.toString() == "name=\"value\"; Comment=\"comment\"; Domain=\"appinf.com\"; Path=\"/\"; Max-Age=\"100\"; Version=\"1\""); - + cookie.setHttpOnly(true); assertTrue (cookie.toString() == "name=\"value\"; Comment=\"comment\"; Domain=\"appinf.com\"; Path=\"/\"; Max-Age=\"100\"; HttpOnly; Version=\"1\""); @@ -83,7 +83,17 @@ void HTTPCookieTest::testCookie() cookie.setPriority("Medium"); assertTrue (cookie.toString() == "name=\"value\"; Comment=\"comment\"; Domain=\"appinf.com\"; Path=\"/\"; Priority=\"Medium\"; Max-Age=\"100\"; HttpOnly; Version=\"1\""); cookie.setPriority("High"); - assertTrue (cookie.toString() == "name=\"value\"; Comment=\"comment\"; Domain=\"appinf.com\"; Path=\"/\"; Priority=\"High\"; Max-Age=\"100\"; HttpOnly; Version=\"1\""); + assertTrue (cookie.toString() == "name=\"value\"; Comment=\"comment\"; Domain=\"appinf.com\"; Path=\"/\"; Priority=\"High\"; Max-Age=\"100\"; HttpOnly; Version=\"1\""); + + cookie.setPriority(""); + cookie.setSameSite(HTTPCookie::SAME_SITE_NONE); + assertTrue (cookie.toString() == "name=\"value\"; Comment=\"comment\"; Domain=\"appinf.com\"; Path=\"/\"; Max-Age=\"100\"; SameSite=None; HttpOnly; Version=\"1\""); + cookie.setSameSite(HTTPCookie::SAME_SITE_LAX); + assertTrue (cookie.toString() == "name=\"value\"; Comment=\"comment\"; Domain=\"appinf.com\"; Path=\"/\"; Max-Age=\"100\"; SameSite=Lax; HttpOnly; Version=\"1\""); + cookie.setSameSite(HTTPCookie::SAME_SITE_STRICT); + assertTrue (cookie.toString() == "name=\"value\"; Comment=\"comment\"; Domain=\"appinf.com\"; Path=\"/\"; Max-Age=\"100\"; SameSite=Strict; HttpOnly; Version=\"1\""); + cookie.setSameSite(HTTPCookie::SAME_SITE_NOT_SPECIFIED); + assertTrue (cookie.toString() == "name=\"value\"; Comment=\"comment\"; Domain=\"appinf.com\"; Path=\"/\"; Max-Age=\"100\"; HttpOnly; Version=\"1\""); }