Add SameSite API to QNetworkCookie

Change-Id: I3f8b25418154f74bb55fa978b03465f75771d015
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Allan Sandfeld Jensen 2020-10-14 13:18:32 +02:00
parent c0a17ecfaf
commit 37bd7b5733
4 changed files with 97 additions and 7 deletions

View File

@ -222,6 +222,29 @@ void QNetworkCookie::setSecure(bool enable)
d->secure = enable; d->secure = enable;
} }
/*!
Returns the "SameSite" option if specified in the cookie
string, \c SameSite::Default if not present.
\since 6.1
\sa setSameSite()
*/
QNetworkCookie::SameSite QNetworkCookie::sameSite() const
{
return d->sameSite;
}
/*!
Sets the "SameSite" option of this cookie to \a sameSite.
\since 6.1
\sa sameSite()
*/
void QNetworkCookie::setSameSite(QNetworkCookie::SameSite sameSite)
{
d->sameSite = sameSite;
}
/*! /*!
\since 4.5 \since 4.5
@ -435,6 +458,49 @@ static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &posi
\sa toRawForm(), parseCookies() \sa toRawForm(), parseCookies()
*/ */
/*!
\enum QNetworkCookie::SameSite
\since 6.1
\value Default SameSite is not set. Can be interpreted as None or Lax by the browser.
\value None Cookies can be sent in all contexts. This used to be default, but
recent browsers made Lax default, and will now require the cookie to be both secure and to set SameSite=None.
\value Lax Cookies are sent on first party requests and GET requests initiated by third party website.
This is the default in modern browsers (since mid 2020).
\value Strict Cookies will only be sent in a first-party context.
\sa setSameSite(), sameSite()
*/
namespace {
QByteArray sameSiteToRawString(QNetworkCookie::SameSite samesite)
{
switch (samesite) {
case QNetworkCookie::SameSite::None:
return QByteArrayLiteral("None");
case QNetworkCookie::SameSite::Lax:
return QByteArrayLiteral("Lax");
case QNetworkCookie::SameSite::Strict:
return QByteArrayLiteral("Strict");
case QNetworkCookie::SameSite::Default:
break;
}
return QByteArray();
}
QNetworkCookie::SameSite sameSiteFromRawString(QByteArray str)
{
str = str.toLower();
if (str == QByteArrayLiteral("none"))
return QNetworkCookie::SameSite::None;
if (str == QByteArrayLiteral("lax"))
return QNetworkCookie::SameSite::Lax;
if (str == QByteArrayLiteral("strict"))
return QNetworkCookie::SameSite::Strict;
return QNetworkCookie::SameSite::Default;
}
} // namespace
/*! /*!
Returns the raw form of this QNetworkCookie. The QByteArray Returns the raw form of this QNetworkCookie. The QByteArray
returned by this function is suitable for an HTTP header, either returned by this function is suitable for an HTTP header, either
@ -460,9 +526,9 @@ QByteArray QNetworkCookie::toRawForm(RawForm form) const
result += "; secure"; result += "; secure";
if (isHttpOnly()) if (isHttpOnly())
result += "; HttpOnly"; result += "; HttpOnly";
if (!d->sameSite.isEmpty()) { if (d->sameSite != SameSite::Default) {
result += "; SameSite="; result += "; SameSite=";
result += d->sameSite; result += sameSiteToRawString(d->sameSite);
} }
if (!isSessionCookie()) { if (!isSessionCookie()) {
result += "; expires="; result += "; expires=";
@ -999,7 +1065,7 @@ QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByt
} else if (field.first == "httponly") { } else if (field.first == "httponly") {
cookie.setHttpOnly(true); cookie.setHttpOnly(true);
} else if (field.first == "samesite") { } else if (field.first == "samesite") {
cookie.d->sameSite = field.second; cookie.setSameSite(sameSiteFromRawString(field.second));
} else { } else {
// ignore unknown fields in the cookie (RFC6265 section 5.2, rule 6) // ignore unknown fields in the cookie (RFC6265 section 5.2, rule 6)
} }

View File

@ -57,11 +57,19 @@ class QUrl;
class QNetworkCookiePrivate; class QNetworkCookiePrivate;
class Q_NETWORK_EXPORT QNetworkCookie class Q_NETWORK_EXPORT QNetworkCookie
{ {
Q_GADGET
public: public:
enum RawForm { enum RawForm {
NameAndValueOnly, NameAndValueOnly,
Full Full
}; };
enum class SameSite {
Default,
None,
Lax,
Strict
};
Q_ENUM(SameSite)
explicit QNetworkCookie(const QByteArray &name = QByteArray(), const QByteArray &value = QByteArray()); explicit QNetworkCookie(const QByteArray &name = QByteArray(), const QByteArray &value = QByteArray());
QNetworkCookie(const QNetworkCookie &other); QNetworkCookie(const QNetworkCookie &other);
@ -79,6 +87,8 @@ public:
void setSecure(bool enable); void setSecure(bool enable);
bool isHttpOnly() const; bool isHttpOnly() const;
void setHttpOnly(bool enable); void setHttpOnly(bool enable);
SameSite sameSite() const;
void setSameSite(SameSite sameSite);
bool isSessionCookie() const; bool isSessionCookie() const;
QDateTime expirationDate() const; QDateTime expirationDate() const;

View File

@ -53,24 +53,25 @@
#include <QtNetwork/private/qtnetworkglobal_p.h> #include <QtNetwork/private/qtnetworkglobal_p.h>
#include "QtCore/qdatetime.h" #include "QtCore/qdatetime.h"
#include "QtNetwork/qnetworkcookie.h"
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QNetworkCookiePrivate: public QSharedData class QNetworkCookiePrivate: public QSharedData
{ {
public: public:
inline QNetworkCookiePrivate() : secure(false), httpOnly(false) { } QNetworkCookiePrivate() = default;
static QList<QNetworkCookie> parseSetCookieHeaderLine(const QByteArray &cookieString); static QList<QNetworkCookie> parseSetCookieHeaderLine(const QByteArray &cookieString);
QDateTime expirationDate; QDateTime expirationDate;
QString domain; QString domain;
QString path; QString path;
QString comment; QString comment;
QByteArray sameSite;
QByteArray name; QByteArray name;
QByteArray value; QByteArray value;
bool secure; QNetworkCookie::SameSite sameSite = QNetworkCookie::SameSite::Default;
bool httpOnly; bool secure = false;
bool httpOnly = false;
}; };
static inline bool isLWS(char c) static inline bool isLWS(char c)

View File

@ -44,6 +44,8 @@ private slots:
void parseMultipleCookies_data(); void parseMultipleCookies_data();
void parseMultipleCookies(); void parseMultipleCookies();
void sameSite();
}; };
void tst_QNetworkCookie::getterSetter() void tst_QNetworkCookie::getterSetter()
@ -683,5 +685,16 @@ void tst_QNetworkCookie::parseMultipleCookies()
QCOMPARE(result, expectedCookies); QCOMPARE(result, expectedCookies);
} }
void tst_QNetworkCookie::sameSite()
{
QList<QNetworkCookie> result = QNetworkCookie::parseCookies(QByteArrayLiteral("a=b;domain=qt-project.org"));
QCOMPARE(result.first().sameSite(), QNetworkCookie::SameSite::Default);
result = QNetworkCookie::parseCookies(QByteArrayLiteral("a=b;domain=qt-project.org;samesite=strict"));
QCOMPARE(result.first().sameSite(), QNetworkCookie::SameSite::Strict);
result = QNetworkCookie::parseCookies(QByteArrayLiteral("a=b;domain=qt-project.org;samesite=none;secure"));
QCOMPARE(result.first().sameSite(), QNetworkCookie::SameSite::None);
QCOMPARE(result.first().toRawForm(), QByteArrayLiteral("a=b; secure; SameSite=None; domain=qt-project.org"));
}
QTEST_MAIN(tst_QNetworkCookie) QTEST_MAIN(tst_QNetworkCookie)
#include "tst_qnetworkcookie.moc" #include "tst_qnetworkcookie.moc"