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;
}
/*!
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
@ -435,6 +458,49 @@ static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &posi
\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
returned by this function is suitable for an HTTP header, either
@ -460,9 +526,9 @@ QByteArray QNetworkCookie::toRawForm(RawForm form) const
result += "; secure";
if (isHttpOnly())
result += "; HttpOnly";
if (!d->sameSite.isEmpty()) {
if (d->sameSite != SameSite::Default) {
result += "; SameSite=";
result += d->sameSite;
result += sameSiteToRawString(d->sameSite);
}
if (!isSessionCookie()) {
result += "; expires=";
@ -999,7 +1065,7 @@ QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByt
} else if (field.first == "httponly") {
cookie.setHttpOnly(true);
} else if (field.first == "samesite") {
cookie.d->sameSite = field.second;
cookie.setSameSite(sameSiteFromRawString(field.second));
} else {
// ignore unknown fields in the cookie (RFC6265 section 5.2, rule 6)
}

View File

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

View File

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

View File

@ -44,6 +44,8 @@ private slots:
void parseMultipleCookies_data();
void parseMultipleCookies();
void sameSite();
};
void tst_QNetworkCookie::getterSetter()
@ -683,5 +685,16 @@ void tst_QNetworkCookie::parseMultipleCookies()
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)
#include "tst_qnetworkcookie.moc"