diff options
Diffstat (limited to 'src/network/access/qnetworkcookie.cpp')
-rw-r--r-- | src/network/access/qnetworkcookie.cpp | 188 |
1 files changed, 82 insertions, 106 deletions
diff --git a/src/network/access/qnetworkcookie.cpp b/src/network/access/qnetworkcookie.cpp index 13fc147c15..8ea5fdbe57 100644 --- a/src/network/access/qnetworkcookie.cpp +++ b/src/network/access/qnetworkcookie.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtNetwork module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qnetworkcookie.h" #include "qnetworkcookie_p.h" @@ -43,18 +7,24 @@ #include "qnetworkrequest.h" #include "qnetworkreply.h" #include "QtCore/qbytearray.h" +#include "QtCore/qdatetime.h" #include "QtCore/qdebug.h" #include "QtCore/qlist.h" #include "QtCore/qlocale.h" #include <QtCore/qregularexpression.h> #include "QtCore/qstring.h" #include "QtCore/qstringlist.h" +#include "QtCore/qtimezone.h" #include "QtCore/qurl.h" #include "QtNetwork/qhostaddress.h" #include "private/qobject_p.h" QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + +QT_IMPL_METATYPE_EXTERN(QNetworkCookie) + /*! \class QNetworkCookie \since 4.4 @@ -406,13 +376,13 @@ void QNetworkCookie::setValue(const QByteArray &value) } // ### move this to qnetworkcookie_p.h and share with qnetworkaccesshttpbackend -static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &position, bool isNameValue) +static QPair<QByteArray, QByteArray> nextField(QByteArrayView text, int &position, bool isNameValue) { // format is one of: // (1) token // (2) token = token // (3) token = quoted-string - const int length = text.length(); + const int length = text.size(); position = nextNonWhitespace(text, position); int semiColonPosition = text.indexOf(';', position); @@ -426,11 +396,11 @@ static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &posi equalsPosition = semiColonPosition; //no '=' means there is an attribute-name but no attribute-value } - QByteArray first = text.mid(position, equalsPosition - position).trimmed(); + QByteArray first = text.mid(position, equalsPosition - position).trimmed().toByteArray(); QByteArray second; int secondLength = semiColonPosition - equalsPosition - 1; if (secondLength > 0) - second = text.mid(equalsPosition + 1, secondLength).trimmed(); + second = text.mid(equalsPosition + 1, secondLength).trimmed().toByteArray(); position = semiColonPosition; return qMakePair(first, second); @@ -473,29 +443,33 @@ static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &posi */ namespace { -QByteArray sameSiteToRawString(QNetworkCookie::SameSite samesite) + +constexpr QByteArrayView sameSiteNone() noexcept { return "None"; } +constexpr QByteArrayView sameSiteLax() noexcept { return "Lax"; } +constexpr QByteArrayView sameSiteStrict() noexcept { return "Strict"; } + +QByteArrayView sameSiteToRawString(QNetworkCookie::SameSite samesite) noexcept { switch (samesite) { case QNetworkCookie::SameSite::None: - return QByteArrayLiteral("None"); + return sameSiteNone(); case QNetworkCookie::SameSite::Lax: - return QByteArrayLiteral("Lax"); + return sameSiteLax(); case QNetworkCookie::SameSite::Strict: - return QByteArrayLiteral("Strict"); + return sameSiteStrict(); case QNetworkCookie::SameSite::Default: break; } - return QByteArray(); + return QByteArrayView(); } -QNetworkCookie::SameSite sameSiteFromRawString(QByteArray str) +QNetworkCookie::SameSite sameSiteFromRawString(QByteArrayView str) noexcept { - str = str.toLower(); - if (str == QByteArrayLiteral("none")) + if (str.compare(sameSiteNone(), Qt::CaseInsensitive) == 0) return QNetworkCookie::SameSite::None; - if (str == QByteArrayLiteral("lax")) + if (str.compare(sameSiteLax(), Qt::CaseInsensitive) == 0) return QNetworkCookie::SameSite::Lax; - if (str == QByteArrayLiteral("strict")) + if (str.compare(sameSiteStrict(), Qt::CaseInsensitive) == 0) return QNetworkCookie::SameSite::Strict; return QNetworkCookie::SameSite::Default; } @@ -533,11 +507,11 @@ QByteArray QNetworkCookie::toRawForm(RawForm form) const if (!isSessionCookie()) { result += "; expires="; result += QLocale::c().toString(d->expirationDate.toUTC(), - QLatin1String("ddd, dd-MMM-yyyy hh:mm:ss 'GMT")).toLatin1(); + "ddd, dd-MMM-yyyy hh:mm:ss 'GMT"_L1).toLatin1(); } if (!d->domain.isEmpty()) { result += "; domain="; - if (d->domain.startsWith(QLatin1Char('.'))) { + if (d->domain.startsWith(u'.')) { result += '.'; result += QUrl::toAce(d->domain.mid(1)); } else { @@ -606,11 +580,11 @@ static inline bool isValueSeparator(char c) static inline bool isWhitespace(char c) { return c == ' ' || c == '\t'; } -static bool checkStaticArray(int &val, const QByteArray &dateString, int at, const char *array, int size) +static bool checkStaticArray(int &val, QByteArrayView dateString, int at, const char *array, int size) { if (dateString[at] < 'a' || dateString[at] > 'z') return false; - if (val == -1 && dateString.length() >= at + 3) { + if (val == -1 && dateString.size() >= at + 3) { int j = 0; int i = 0; while (i <= size) { @@ -653,7 +627,7 @@ static bool checkStaticArray(int &val, const QByteArray &dateString, int at, con Or in their own words: "} // else what the hell is this." */ -static QDateTime parseDateString(const QByteArray &dateString) +static QDateTime parseDateString(QByteArrayView dateString) { QTime time; // placeholders for values when we are not sure it is a year, month or day @@ -664,10 +638,11 @@ static QDateTime parseDateString(const QByteArray &dateString) int zoneOffset = -1; // hour:minute:second.ms pm - QRegularExpression timeRx(QLatin1String("(\\d{1,2}):(\\d{1,2})(:(\\d{1,2})|)(\\.(\\d{1,3})|)((\\s{0,}(am|pm))|)")); + static const QRegularExpression timeRx( + u"(\\d\\d?):(\\d\\d?)(?::(\\d\\d?)(?:\\.(\\d{1,3}))?)?(?:\\s*(am|pm))?"_s); int at = 0; - while (at < dateString.length()) { + while (at < dateString.size()) { #ifdef PARSEDATESTRINGDEBUG qDebug() << dateString.mid(at); #endif @@ -708,20 +683,20 @@ static QDateTime parseDateString(const QByteArray &dateString) && (dateString[at - 1] == 't')))) { int end = 1; - while (end < 5 && dateString.length() > at+end + while (end < 5 && dateString.size() > at+end && dateString[at + end] >= '0' && dateString[at + end] <= '9') ++end; int minutes = 0; int hours = 0; switch (end - 1) { case 4: - minutes = atoi(dateString.mid(at + 3, 2).constData()); + minutes = dateString.mid(at + 3, 2).toInt(); Q_FALLTHROUGH(); case 2: - hours = atoi(dateString.mid(at + 1, 2).constData()); + hours = dateString.mid(at + 1, 2).toInt(); break; case 1: - hours = atoi(dateString.mid(at + 1, 1).constData()); + hours = dateString.mid(at + 1, 1).toInt(); break; default: at += end; @@ -740,25 +715,25 @@ static QDateTime parseDateString(const QByteArray &dateString) // Time if (isNum && time.isNull() - && dateString.length() >= at + 3 + && dateString.size() >= at + 3 && (dateString[at + 2] == ':' || dateString[at + 1] == ':')) { // While the date can be found all over the string the format // for the time is set and a nice regexp can be used. - QRegularExpressionMatch match; - int pos = QString::fromLatin1(dateString).indexOf(timeRx, at, &match); - if (pos != -1) { - QStringList list = match.capturedTexts(); - int h = match.captured(1).toInt(); - int m = match.captured(2).toInt(); - int s = match.captured(4).toInt(); - int ms = match.captured(6).toInt(); - QString ampm = match.captured(9); + // This string needs to stay for as long as the QRegularExpressionMatch is used, + // or else we get use-after-free issues: + QString dateToString = QString::fromLatin1(dateString); + if (auto match = timeRx.match(dateToString, at); match.hasMatch()) { + int h = match.capturedView(1).toInt(); + int m = match.capturedView(2).toInt(); + int s = match.capturedView(3).toInt(); + int ms = match.capturedView(4).toInt(); + QStringView ampm = match.capturedView(5); if (h < 12 && !ampm.isEmpty()) - if (ampm == QLatin1String("pm")) + if (ampm == "pm"_L1) h += 12; time = QTime(h, m, s, ms); #ifdef PARSEDATESTRINGDEBUG - qDebug() << "Time:" << list << timeRx.matchedLength(); + qDebug() << "Time:" << match.capturedTexts() << match.capturedLength(); #endif at += match.capturedLength(); continue; @@ -768,11 +743,11 @@ static QDateTime parseDateString(const QByteArray &dateString) // 4 digit Year if (isNum && year == -1 - && dateString.length() > at + 3) { + && dateString.size() > at + 3) { if (isNumber(dateString[at + 1]) && isNumber(dateString[at + 2]) && isNumber(dateString[at + 3])) { - year = atoi(dateString.mid(at, 4).constData()); + year = dateString.mid(at, 4).toInt(); at += 4; #ifdef PARSEDATESTRINGDEBUG qDebug() << "Year:" << year; @@ -785,10 +760,10 @@ static QDateTime parseDateString(const QByteArray &dateString) // Could be month, day or year if (isNum) { int length = 1; - if (dateString.length() > at + 1 + if (dateString.size() > at + 1 && isNumber(dateString[at + 1])) ++length; - int x = atoi(dateString.mid(at, length).constData()); + int x = dateString.mid(at, length).toInt(); if (year == -1 && (x > 31 || x == 0)) { year = x; } else { @@ -937,11 +912,11 @@ static QDateTime parseDateString(const QByteArray &dateString) if (!date.isValid()) date = QDate(day + y2k, month, year); - QDateTime dateTime(date, time, Qt::UTC); + QDateTime dateTime(date, time, QTimeZone::UTC); - if (zoneOffset != -1) { + if (zoneOffset != -1) dateTime = dateTime.addSecs(zoneOffset); - } + if (!dateTime.isValid()) return QDateTime(); return dateTime; @@ -957,19 +932,19 @@ static QDateTime parseDateString(const QByteArray &dateString) cookie that is parsed. \sa toRawForm() + \note In Qt versions prior to 6.7, this function took QByteArray only. */ -QList<QNetworkCookie> QNetworkCookie::parseCookies(const QByteArray &cookieString) +QList<QNetworkCookie> QNetworkCookie::parseCookies(QByteArrayView cookieString) { // cookieString can be a number of set-cookie header strings joined together // by \n, parse each line separately. QList<QNetworkCookie> cookies; - QList<QByteArray> list = cookieString.split('\n'); - for (int a = 0; a < list.size(); a++) - cookies += QNetworkCookiePrivate::parseSetCookieHeaderLine(list.at(a)); + for (auto s : QLatin1StringView(cookieString).tokenize('\n'_L1)) + cookies += QNetworkCookiePrivate::parseSetCookieHeaderLine(s); return cookies; } -QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByteArray &cookieString) +QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(QByteArrayView cookieString) { // According to http://wp.netscape.com/newsref/std/cookie_spec.html,< // the Set-Cookie response header is of the format: @@ -984,7 +959,7 @@ QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByt const QDateTime now = QDateTime::currentDateTimeUtc(); int position = 0; - const int length = cookieString.length(); + const int length = cookieString.size(); while (position < length) { QNetworkCookie cookie; @@ -1002,28 +977,27 @@ QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByt case ';': // new field in the cookie field = nextField(cookieString, position, false); - field.first = field.first.toLower(); // everything but the NAME=VALUE is case-insensitive - if (field.first == "expires") { - position -= field.second.length(); + if (field.first.compare("expires", Qt::CaseInsensitive) == 0) { + position -= field.second.size(); int end; for (end = position; end < length; ++end) if (isValueSeparator(cookieString.at(end))) break; - QByteArray dateString = cookieString.mid(position, end - position).trimmed(); + QByteArray dateString = cookieString.mid(position, end - position).trimmed().toByteArray().toLower(); position = end; - QDateTime dt = parseDateString(dateString.toLower()); + QDateTime dt = parseDateString(dateString); if (dt.isValid()) cookie.setExpirationDate(dt); //if unparsed, ignore the attribute but not the whole cookie (RFC6265 section 5.2.1) - } else if (field.first == "domain") { - QByteArray rawDomain = field.second; + } else if (field.first.compare("domain", Qt::CaseInsensitive) == 0) { + QByteArrayView rawDomain = field.second; //empty domain should be ignored (RFC6265 section 5.2.3) if (!rawDomain.isEmpty()) { - QString maybeLeadingDot; + QLatin1StringView maybeLeadingDot; if (rawDomain.startsWith('.')) { - maybeLeadingDot = QLatin1Char('.'); + maybeLeadingDot = "."_L1; rawDomain = rawDomain.mid(1); } @@ -1038,7 +1012,7 @@ QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByt return result; } } - } else if (field.first == "max-age") { + } else if (field.first.compare("max-age", Qt::CaseInsensitive) == 0) { bool ok = false; int secs = field.second.toInt(&ok); if (ok) { @@ -1050,7 +1024,7 @@ QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByt } } //if unparsed, ignore the attribute but not the whole cookie (RFC6265 section 5.2.2) - } else if (field.first == "path") { + } else if (field.first.compare("path", Qt::CaseInsensitive) == 0) { if (field.second.startsWith('/')) { // ### we should treat cookie paths as an octet sequence internally // However RFC6265 says we should assume UTF-8 for presentation as a string @@ -1060,11 +1034,11 @@ QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByt // and also IETF test case path0030 which has valid and empty path in the same cookie cookie.setPath(QString()); } - } else if (field.first == "secure") { + } else if (field.first.compare("secure", Qt::CaseInsensitive) == 0) { cookie.setSecure(true); - } else if (field.first == "httponly") { + } else if (field.first.compare("httponly", Qt::CaseInsensitive) == 0) { cookie.setHttpOnly(true); - } else if (field.first == "samesite") { + } else if (field.first.compare("samesite", Qt::CaseInsensitive) == 0) { cookie.setSameSitePolicy(sameSiteFromRawString(field.second)); } else { // ignore unknown fields in the cookie (RFC6265 section 5.2, rule 6) @@ -1091,9 +1065,9 @@ void QNetworkCookie::normalize(const QUrl &url) // don't do path checking. See QTBUG-5815 if (d->path.isEmpty()) { QString pathAndFileName = url.path(); - QString defaultPath = pathAndFileName.left(pathAndFileName.lastIndexOf(QLatin1Char('/'))+1); + QString defaultPath = pathAndFileName.left(pathAndFileName.lastIndexOf(u'/') + 1); if (defaultPath.isEmpty()) - defaultPath = QLatin1Char('/'); + defaultPath = u'/'; d->path = defaultPath; } @@ -1103,12 +1077,12 @@ void QNetworkCookie::normalize(const QUrl &url) QHostAddress hostAddress(d->domain); if (hostAddress.protocol() != QAbstractSocket::IPv4Protocol && hostAddress.protocol() != QAbstractSocket::IPv6Protocol - && !d->domain.startsWith(QLatin1Char('.'))) { + && !d->domain.startsWith(u'.')) { // Ensure the domain starts with a dot if its field was not empty // in the HTTP header. There are some servers that forget the // leading dot and this is actually forbidden according to RFC 2109, // but all browsers accept it anyway so we do that as well. - d->domain.prepend(QLatin1Char('.')); + d->domain.prepend(u'.'); } } } @@ -1124,3 +1098,5 @@ QDebug operator<<(QDebug s, const QNetworkCookie &cookie) #endif QT_END_NAMESPACE + +#include "moc_qnetworkcookie.cpp" |