Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qnetworkcookie.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
7
10#include "QtCore/qbytearray.h"
11#include "QtCore/qdatetime.h"
12#include "QtCore/qdebug.h"
13#include "QtCore/qlist.h"
14#include "QtCore/qlocale.h"
15#include <QtCore/qregularexpression.h>
16#include "QtCore/qstring.h"
17#include "QtCore/qstringlist.h"
18#include "QtCore/qtimezone.h"
19#include "QtCore/qurl.h"
20#include "QtNetwork/qhostaddress.h"
21#include "private/qobject_p.h"
22
23#include <utility>
24
25QT_BEGIN_NAMESPACE
26
27using namespace Qt::StringLiterals;
28
30
31/*!
32 \class QNetworkCookie
33 \since 4.4
34 \ingroup shared
35 \inmodule QtNetwork
36
37 \brief The QNetworkCookie class holds one network cookie.
38
39 Cookies are small bits of information that stateless protocols
40 like HTTP use to maintain some persistent information across
41 requests.
42
43 A cookie is set by a remote server when it replies to a request
44 and it expects the same cookie to be sent back when further
45 requests are sent.
46
47 QNetworkCookie holds one such cookie as received from the
48 network. A cookie has a name and a value, but those are opaque to
49 the application (that is, the information stored in them has no
50 meaning to the application). A cookie has an associated path name
51 and domain, which indicate when the cookie should be sent again to
52 the server.
53
54 A cookie can also have an expiration date, indicating its
55 validity. If the expiration date is not present, the cookie is
56 considered a "session cookie" and should be discarded when the
57 application exits (or when its concept of session is over).
58
59 QNetworkCookie provides a way of parsing a cookie from the HTTP
60 header format using the QNetworkCookie::parseCookies()
61 function. However, when received in a QNetworkReply, the cookie is
62 already parsed.
63
64 This class implements cookies as described by the
65 \l{Netscape Cookie Specification}{initial cookie specification by
66 Netscape}, which is somewhat similar to the \l{http://www.rfc-editor.org/rfc/rfc2109.txt}{RFC 2109} specification,
67 plus the \l{Mitigating Cross-site Scripting With HTTP-only Cookies}
68 {"HttpOnly" extension}. The more recent \l{http://www.rfc-editor.org/rfc/rfc2965.txt}{RFC 2965} specification
69 (which uses the Set-Cookie2 header) is not supported.
70
71 \sa QNetworkCookieJar, QNetworkRequest, QNetworkReply
72*/
73
74/*!
75 Create a new QNetworkCookie object, initializing the cookie name
76 to \a name and its value to \a value.
77
78 A cookie is only valid if it has a name. However, the value is
79 opaque to the application and being empty may have significance to
80 the remote server.
81*/
82QNetworkCookie::QNetworkCookie(const QByteArray &name, const QByteArray &value)
83 : d(new QNetworkCookiePrivate)
84{
85 qRegisterMetaType<QNetworkCookie>();
86 qRegisterMetaType<QList<QNetworkCookie> >();
87
88 d->name = name;
89 d->value = value;
90}
91
92/*!
93 Creates a new QNetworkCookie object by copying the contents of \a
94 other.
95*/
96QNetworkCookie::QNetworkCookie(const QNetworkCookie &other)
97 : d(other.d)
98{
99}
100
101/*!
102 Destroys this QNetworkCookie object.
103*/
104QNetworkCookie::~QNetworkCookie()
105{
106 // QSharedDataPointer auto deletes
107 d = nullptr;
108}
109
110/*!
111 Copies the contents of the QNetworkCookie object \a other to this
112 object.
113*/
114QNetworkCookie &QNetworkCookie::operator=(const QNetworkCookie &other)
115{
116 d = other.d;
117 return *this;
118}
119
120/*!
121 \fn void QNetworkCookie::swap(QNetworkCookie &other)
122 \since 5.0
123 \memberswap{cookie}
124*/
125
126/*!
127 \fn bool QNetworkCookie::operator!=(const QNetworkCookie &other) const
128
129 Returns \c true if this cookie is not equal to \a other.
130
131 \sa operator==()
132*/
133
134/*!
135 \since 5.0
136 Returns \c true if this cookie is equal to \a other. This function
137 only returns \c true if all fields of the cookie are the same.
138
139 However, in some contexts, two cookies of the same name could be
140 considered equal.
141
142 \sa operator!=(), hasSameIdentifier()
143*/
144bool QNetworkCookie::operator==(const QNetworkCookie &other) const
145{
146 if (d == other.d)
147 return true;
148 return d->name == other.d->name &&
149 d->value == other.d->value &&
150 d->expirationDate.toUTC() == other.d->expirationDate.toUTC() &&
151 d->domain == other.d->domain &&
152 d->path == other.d->path &&
153 d->secure == other.d->secure &&
154 d->comment == other.d->comment &&
155 d->sameSite == other.d->sameSite;
156}
157
158/*!
159 Returns \c true if this cookie has the same identifier tuple as \a other.
160 The identifier tuple is composed of the name, domain and path.
161
162 \sa operator==()
163*/
164bool QNetworkCookie::hasSameIdentifier(const QNetworkCookie &other) const
165{
166 return d->name == other.d->name && d->domain == other.d->domain && d->path == other.d->path;
167}
168
169/*!
170 Returns \c true if the "secure" option was specified in the cookie
171 string, false otherwise.
172
173 Secure cookies may contain private information and should not be
174 resent over unencrypted connections.
175
176 \sa setSecure()
177*/
178bool QNetworkCookie::isSecure() const
179{
180 return d->secure;
181}
182
183/*!
184 Sets the secure flag of this cookie to \a enable.
185
186 Secure cookies may contain private information and should not be
187 resent over unencrypted connections.
188
189 \sa isSecure()
190*/
191void QNetworkCookie::setSecure(bool enable)
192{
193 d->secure = enable;
194}
195
196/*!
197 Returns the "SameSite" option if specified in the cookie
198 string, \c SameSite::Default if not present.
199
200 \since 6.1
201 \sa setSameSitePolicy()
202*/
203QNetworkCookie::SameSite QNetworkCookie::sameSitePolicy() const
204{
205 return d->sameSite;
206}
207
208/*!
209 Sets the "SameSite" option of this cookie to \a sameSite.
210
211 \since 6.1
212 \sa sameSitePolicy()
213*/
214void QNetworkCookie::setSameSitePolicy(QNetworkCookie::SameSite sameSite)
215{
216 d->sameSite = sameSite;
217}
218
219/*!
220 \since 4.5
221
222 Returns \c true if the "HttpOnly" flag is enabled for this cookie.
223
224 A cookie that is "HttpOnly" is only set and retrieved by the
225 network requests and replies; i.e., the HTTP protocol. It is not
226 accessible from scripts running on browsers.
227
228 \sa isSecure()
229*/
230bool QNetworkCookie::isHttpOnly() const
231{
232 return d->httpOnly;
233}
234
235/*!
236 \since 4.5
237
238 Sets this cookie's "HttpOnly" flag to \a enable.
239*/
240void QNetworkCookie::setHttpOnly(bool enable)
241{
242 d->httpOnly = enable;
243}
244
245/*!
246 Returns \c true if this cookie is a session cookie. A session cookie
247 is a cookie which has no expiration date, which means it should be
248 discarded when the application's concept of session is over
249 (usually, when the application exits).
250
251 \sa expirationDate(), setExpirationDate()
252*/
253bool QNetworkCookie::isSessionCookie() const
254{
255 return !d->expirationDate.isValid();
256}
257
258/*!
259 Returns the expiration date for this cookie. If this cookie is a
260 session cookie, the QDateTime returned will not be valid. If the
261 date is in the past, this cookie has already expired and should
262 not be sent again back to a remote server.
263
264 The expiration date corresponds to the parameters of the "expires"
265 entry in the cookie string.
266
267 \sa isSessionCookie(), setExpirationDate()
268*/
269QDateTime QNetworkCookie::expirationDate() const
270{
271 return d->expirationDate;
272}
273
274/*!
275 Sets the expiration date of this cookie to \a date. Setting an
276 invalid expiration date to this cookie will mean it's a session
277 cookie.
278
279 \sa isSessionCookie(), expirationDate()
280*/
281void QNetworkCookie::setExpirationDate(const QDateTime &date)
282{
283 d->expirationDate = date;
284}
285
286/*!
287 Returns the domain this cookie is associated with. This
288 corresponds to the "domain" field of the cookie string.
289
290 Note that the domain here may start with a dot, which is not a
291 valid hostname. However, it means this cookie matches all
292 hostnames ending with that domain name.
293
294 \sa setDomain()
295*/
296QString QNetworkCookie::domain() const
297{
298 return d->domain;
299}
300
301/*!
302 Sets the domain associated with this cookie to be \a domain.
303
304 \sa domain()
305*/
306void QNetworkCookie::setDomain(const QString &domain)
307{
308 d->domain = domain;
309}
310
311/*!
312 Returns the path associated with this cookie. This corresponds to
313 the "path" field of the cookie string.
314
315 \sa setPath()
316*/
317QString QNetworkCookie::path() const
318{
319 return d->path;
320}
321
322/*!
323 Sets the path associated with this cookie to be \a path.
324
325 \sa path()
326*/
327void QNetworkCookie::setPath(const QString &path)
328{
329 d->path = path;
330}
331
332/*!
333 Returns the name of this cookie. The only mandatory field of a
334 cookie is its name, without which it is not considered valid.
335
336 \sa setName(), value()
337*/
338QByteArray QNetworkCookie::name() const
339{
340 return d->name;
341}
342
343/*!
344 Sets the name of this cookie to be \a cookieName. Note that
345 setting a cookie name to an empty QByteArray will make this cookie
346 invalid.
347
348 \sa name(), value()
349*/
350void QNetworkCookie::setName(const QByteArray &cookieName)
351{
352 d->name = cookieName;
353}
354
355/*!
356 Returns this cookies value, as specified in the cookie
357 string. Note that a cookie is still valid if its value is empty.
358
359 Cookie name-value pairs are considered opaque to the application:
360 that is, their values don't mean anything.
361
362 \sa setValue(), name()
363*/
364QByteArray QNetworkCookie::value() const
365{
366 return d->value;
367}
368
369/*!
370 Sets the value of this cookie to be \a value.
371
372 \sa value(), name()
373*/
374void QNetworkCookie::setValue(const QByteArray &value)
375{
376 d->value = value;
377}
378
379// ### move this to qnetworkcookie_p.h and share with qnetworkaccesshttpbackend
380static std::pair<QByteArray, QByteArray> nextField(QByteArrayView text, int &position, bool isNameValue)
381{
382 // format is one of:
383 // (1) token
384 // (2) token = token
385 // (3) token = quoted-string
386 const int length = text.size();
387 position = nextNonWhitespace(text, position);
388
389 int semiColonPosition = text.indexOf(';', position);
390 if (semiColonPosition < 0)
391 semiColonPosition = length; //no ';' means take everything to end of string
392
393 int equalsPosition = text.indexOf('=', position);
394 if (equalsPosition < 0 || equalsPosition > semiColonPosition) {
395 if (isNameValue)
396 return std::pair(QByteArray(), QByteArray()); //'=' is required for name-value-pair (RFC6265 section 5.2, rule 2)
397 equalsPosition = semiColonPosition; //no '=' means there is an attribute-name but no attribute-value
398 }
399
400 QByteArray first = text.mid(position, equalsPosition - position).trimmed().toByteArray();
401 QByteArray second;
402 int secondLength = semiColonPosition - equalsPosition - 1;
403 if (secondLength > 0)
404 second = text.mid(equalsPosition + 1, secondLength).trimmed().toByteArray();
405
406 position = semiColonPosition;
407 return std::pair(first, second);
408}
409
410/*!
411 \enum QNetworkCookie::RawForm
412
413 This enum is used with the toRawForm() function to declare which
414 form of a cookie shall be returned.
415
416 \value NameAndValueOnly makes toRawForm() return only the
417 "NAME=VALUE" part of the cookie, as suitable for sending back
418 to a server in a client request's "Cookie:" header. Multiple
419 cookies are separated by a semi-colon in the "Cookie:" header
420 field.
421
422 \value Full makes toRawForm() return the full
423 cookie contents, as suitable for sending to a client in a
424 server's "Set-Cookie:" header.
425
426 Note that only the Full form of the cookie can be parsed back into
427 its original contents.
428
429 \sa toRawForm(), parseCookies()
430*/
431
432/*!
433 \enum QNetworkCookie::SameSite
434 \since 6.1
435
436 \value Default SameSite is not set. Can be interpreted as None or Lax by the browser.
437 \value None Cookies can be sent in all contexts. This used to be default, but
438 recent browsers made Lax default, and will now require the cookie to be both secure and to set SameSite=None.
439 \value Lax Cookies are sent on first party requests and GET requests initiated by third party website.
440 This is the default in modern browsers (since mid 2020).
441 \value Strict Cookies will only be sent in a first-party context.
442
443 \sa setSameSitePolicy(), sameSitePolicy()
444*/
445
446namespace {
447
448constexpr QByteArrayView sameSiteNone() noexcept { return "None"; }
449constexpr QByteArrayView sameSiteLax() noexcept { return "Lax"; }
450constexpr QByteArrayView sameSiteStrict() noexcept { return "Strict"; }
451
452QByteArrayView sameSiteToRawString(QNetworkCookie::SameSite samesite) noexcept
453{
454 switch (samesite) {
455 case QNetworkCookie::SameSite::None:
456 return sameSiteNone();
457 case QNetworkCookie::SameSite::Lax:
458 return sameSiteLax();
459 case QNetworkCookie::SameSite::Strict:
460 return sameSiteStrict();
461 case QNetworkCookie::SameSite::Default:
462 break;
463 }
464 return QByteArrayView();
465}
466
467QNetworkCookie::SameSite sameSiteFromRawString(QByteArrayView str) noexcept
468{
469 if (str.compare(sameSiteNone(), Qt::CaseInsensitive) == 0)
470 return QNetworkCookie::SameSite::None;
471 if (str.compare(sameSiteLax(), Qt::CaseInsensitive) == 0)
472 return QNetworkCookie::SameSite::Lax;
473 if (str.compare(sameSiteStrict(), Qt::CaseInsensitive) == 0)
474 return QNetworkCookie::SameSite::Strict;
475 return QNetworkCookie::SameSite::Default;
476}
477} // namespace
478
479/*!
480 Returns the raw form of this QNetworkCookie. The QByteArray
481 returned by this function is suitable for an HTTP header, either
482 in a server response (the Set-Cookie header) or the client request
483 (the Cookie header). You can choose from one of two formats, using
484 \a form.
485
486 \sa parseCookies()
487*/
488QByteArray QNetworkCookie::toRawForm(RawForm form) const
489{
490 QByteArray result;
491 if (d->name.isEmpty())
492 return result; // not a valid cookie
493
494 result = d->name;
495 result += '=';
496 result += d->value;
497
498 if (form == Full) {
499 // same as above, but encoding everything back
500 if (isSecure())
501 result += "; secure";
502 if (isHttpOnly())
503 result += "; HttpOnly";
504 if (d->sameSite != SameSite::Default) {
505 result += "; SameSite=";
506 result += sameSiteToRawString(d->sameSite);
507 }
508 if (!isSessionCookie()) {
509 result += "; expires=";
510 result += QLocale::c().toString(d->expirationDate.toUTC(),
511 "ddd, dd-MMM-yyyy hh:mm:ss 'GMT"_L1).toLatin1();
512 }
513 if (!d->domain.isEmpty()) {
514 result += "; domain=";
515 if (d->domain.startsWith(u'.')) {
516 result += '.';
517 result += QUrl::toAce(d->domain.mid(1));
518 } else {
519 QHostAddress hostAddr(d->domain);
520 if (hostAddr.protocol() == QAbstractSocket::IPv6Protocol) {
521 result += '[';
522 result += d->domain.toUtf8();
523 result += ']';
524 } else {
525 result += QUrl::toAce(d->domain);
526 }
527 }
528 }
529 if (!d->path.isEmpty()) {
530 result += "; path=";
531 result += d->path.toUtf8();
532 }
533 }
534 return result;
535}
536
537static const char zones[] =
538 "pst\0" // -8
539 "pdt\0"
540 "mst\0" // -7
541 "mdt\0"
542 "cst\0" // -6
543 "cdt\0"
544 "est\0" // -5
545 "edt\0"
546 "ast\0" // -4
547 "nst\0" // -3
548 "gmt\0" // 0
549 "utc\0"
550 "bst\0"
551 "met\0" // 1
552 "eet\0" // 2
553 "jst\0" // 9
554 "\0";
555static const int zoneOffsets[] = {-8, -8, -7, -7, -6, -6, -5, -5, -4, -3, 0, 0, 0, 1, 2, 9 };
556
557static const char months[] =
558 "jan\0"
559 "feb\0"
560 "mar\0"
561 "apr\0"
562 "may\0"
563 "jun\0"
564 "jul\0"
565 "aug\0"
566 "sep\0"
567 "oct\0"
568 "nov\0"
569 "dec\0"
570 "\0";
571
572static inline bool isNumber(char s)
573{ return s >= '0' && s <= '9'; }
574
575static inline bool isTerminator(char c)
576{ return c == '\n' || c == '\r'; }
577
578static inline bool isValueSeparator(char c)
579{ return isTerminator(c) || c == ';'; }
580
581static inline bool isWhitespace(char c)
582{ return c == ' ' || c == '\t'; }
583
584static bool checkStaticArray(int &val, QByteArrayView dateString, int at, const char *array, int size)
585{
586 if (dateString[at] < 'a' || dateString[at] > 'z')
587 return false;
588 if (val == -1 && dateString.size() >= at + 3) {
589 int j = 0;
590 int i = 0;
591 while (i <= size) {
592 const char *str = array + i;
593 if (str[0] == dateString[at]
594 && str[1] == dateString[at + 1]
595 && str[2] == dateString[at + 2]) {
596 val = j;
597 return true;
598 }
599 i += int(strlen(str)) + 1;
600 ++j;
601 }
602 }
603 return false;
604}
605
606//#define PARSEDATESTRINGDEBUG
607
608#define ADAY 1
609#define AMONTH 2
610#define AYEAR 4
611
612/*
613 Parse all the date formats that Firefox can.
614
615 The official format is:
616 expires=ddd(d)?, dd-MMM-yyyy hh:mm:ss GMT
617
618 But browsers have been supporting a very wide range of date
619 strings. To work on many sites we need to support more then
620 just the official date format.
621
622 For reference see Firefox's PR_ParseTimeStringToExplodedTime in
623 prtime.c. The Firefox date parser is coded in a very complex way
624 and is slightly over ~700 lines long. While this implementation
625 will be slightly slower for the non standard dates it is smaller,
626 more readable, and maintainable.
627
628 Or in their own words:
629 "} // else what the hell is this."
630*/
631static QDateTime parseDateString(QByteArrayView dateString)
632{
633 QTime time;
634 // placeholders for values when we are not sure it is a year, month or day
635 int unknown[3] = {-1, -1, -1};
636 int month = -1;
637 int day = -1;
638 int year = -1;
639 int zoneOffset = -1;
640
641 // hour:minute:second.ms pm
642 static const QRegularExpression timeRx(
643 u"(\\d\\d?):(\\d\\d?)(?::(\\d\\d?)(?:\\.(\\d{1,3}))?)?(?:\\s*(am|pm))?"_s);
644
645 int at = 0;
646 while (at < dateString.size()) {
647#ifdef PARSEDATESTRINGDEBUG
648 qDebug() << dateString.mid(at);
649#endif
650 bool isNum = isNumber(dateString[at]);
651
652 // Month
653 if (!isNum
654 && checkStaticArray(month, dateString, at, months, sizeof(months)- 1)) {
655 ++month;
656#ifdef PARSEDATESTRINGDEBUG
657 qDebug() << "Month:" << month;
658#endif
659 at += 3;
660 continue;
661 }
662 // Zone
663 if (!isNum
664 && zoneOffset == -1
665 && checkStaticArray(zoneOffset, dateString, at, zones, sizeof(zones)- 1)) {
666 int sign = (at >= 0 && dateString[at - 1] == '-') ? -1 : 1;
667 zoneOffset = sign * zoneOffsets[zoneOffset] * 60 * 60;
668#ifdef PARSEDATESTRINGDEBUG
669 qDebug() << "Zone:" << month;
670#endif
671 at += 3;
672 continue;
673 }
674 // Zone offset
675 if (!isNum
676 && (zoneOffset == -1 || zoneOffset == 0) // Can only go after gmt
677 && (dateString[at] == '+' || dateString[at] == '-')
678 && (at == 0
679 || isWhitespace(dateString[at - 1])
680 || dateString[at - 1] == ','
681 || (at >= 3
682 && (dateString[at - 3] == 'g')
683 && (dateString[at - 2] == 'm')
684 && (dateString[at - 1] == 't')))) {
685
686 int end = 1;
687 while (end < 5 && dateString.size() > at+end
688 && dateString[at + end] >= '0' && dateString[at + end] <= '9')
689 ++end;
690 int minutes = 0;
691 int hours = 0;
692 switch (end - 1) {
693 case 4:
694 minutes = dateString.mid(at + 3, 2).toInt();
695 Q_FALLTHROUGH();
696 case 2:
697 hours = dateString.mid(at + 1, 2).toInt();
698 break;
699 case 1:
700 hours = dateString.mid(at + 1, 1).toInt();
701 break;
702 default:
703 at += end;
704 continue;
705 }
706 if (end != 1) {
707 int sign = dateString[at] == '-' ? -1 : 1;
708 zoneOffset = sign * ((minutes * 60) + (hours * 60 * 60));
709#ifdef PARSEDATESTRINGDEBUG
710 qDebug() << "Zone offset:" << zoneOffset << hours << minutes;
711#endif
712 at += end;
713 continue;
714 }
715 }
716
717 // Time
718 if (isNum && time.isNull()
719 && dateString.size() >= at + 3
720 && (dateString[at + 2] == ':' || dateString[at + 1] == ':')) {
721 // While the date can be found all over the string the format
722 // for the time is set and a nice regexp can be used.
723 // This string needs to stay for as long as the QRegularExpressionMatch is used,
724 // or else we get use-after-free issues:
725 QString dateToString = QString::fromLatin1(dateString);
726 if (auto match = timeRx.match(dateToString, at); match.hasMatch()) {
727 int h = match.capturedView(1).toInt();
728 int m = match.capturedView(2).toInt();
729 int s = match.capturedView(3).toInt();
730 int ms = match.capturedView(4).toInt();
731 QStringView ampm = match.capturedView(5);
732 if (h < 12 && !ampm.isEmpty())
733 if (ampm == "pm"_L1)
734 h += 12;
735 time = QTime(h, m, s, ms);
736#ifdef PARSEDATESTRINGDEBUG
737 qDebug() << "Time:" << match.capturedTexts() << match.capturedLength();
738#endif
739 at += match.capturedLength();
740 continue;
741 }
742 }
743
744 // 4 digit Year
745 if (isNum
746 && year == -1
747 && dateString.size() > at + 3) {
748 if (isNumber(dateString[at + 1])
749 && isNumber(dateString[at + 2])
750 && isNumber(dateString[at + 3])) {
751 year = dateString.mid(at, 4).toInt();
752 at += 4;
753#ifdef PARSEDATESTRINGDEBUG
754 qDebug() << "Year:" << year;
755#endif
756 continue;
757 }
758 }
759
760 // a one or two digit number
761 // Could be month, day or year
762 if (isNum) {
763 int length = 1;
764 if (dateString.size() > at + 1
765 && isNumber(dateString[at + 1]))
766 ++length;
767 int x = dateString.mid(at, length).toInt();
768 if (year == -1 && (x > 31 || x == 0)) {
769 year = x;
770 } else {
771 if (unknown[0] == -1) unknown[0] = x;
772 else if (unknown[1] == -1) unknown[1] = x;
773 else if (unknown[2] == -1) unknown[2] = x;
774 }
775 at += length;
776#ifdef PARSEDATESTRINGDEBUG
777 qDebug() << "Saving" << x;
778#endif
779 continue;
780 }
781
782 // Unknown character, typically a weekday such as 'Mon'
783 ++at;
784 }
785
786 // Once we are done parsing the string take the digits in unknown
787 // and determine which is the unknown year/month/day
788
789 int couldBe[3] = { 0, 0, 0 };
790 int unknownCount = 3;
791 for (int i = 0; i < unknownCount; ++i) {
792 if (unknown[i] == -1) {
793 couldBe[i] = ADAY | AYEAR | AMONTH;
794 unknownCount = i;
795 continue;
796 }
797
798 if (unknown[i] >= 1)
799 couldBe[i] = ADAY;
800
801 if (month == -1 && unknown[i] >= 1 && unknown[i] <= 12)
802 couldBe[i] |= AMONTH;
803
804 if (year == -1)
805 couldBe[i] |= AYEAR;
806 }
807
808 // For any possible day make sure one of the values that could be a month
809 // can contain that day.
810 // For any possible month make sure one of the values that can be a
811 // day that month can have.
812 // Example: 31 11 06
813 // 31 can't be a day because 11 and 6 don't have 31 days
814 for (int i = 0; i < unknownCount; ++i) {
815 int currentValue = unknown[i];
816 bool findMatchingMonth = couldBe[i] & ADAY && currentValue >= 29;
817 bool findMatchingDay = couldBe[i] & AMONTH;
818 if (!findMatchingMonth || !findMatchingDay)
819 continue;
820 for (int j = 0; j < 3; ++j) {
821 if (j == i)
822 continue;
823 for (int k = 0; k < 2; ++k) {
824 if (k == 0 && !(findMatchingMonth && (couldBe[j] & AMONTH)))
825 continue;
826 else if (k == 1 && !(findMatchingDay && (couldBe[j] & ADAY)))
827 continue;
828 int m = currentValue;
829 int d = unknown[j];
830 if (k == 0)
831 qSwap(m, d);
832 if (m == -1) m = month;
833 bool found = true;
834 switch(m) {
835 case 2:
836 // When we get 29 and the year ends up having only 28
837 // See date.isValid below
838 // Example: 29 23 Feb
839 if (d <= 29)
840 found = false;
841 break;
842 case 4: case 6: case 9: case 11:
843 if (d <= 30)
844 found = false;
845 break;
846 default:
847 if (d > 0 && d <= 31)
848 found = false;
849 }
850 if (k == 0) findMatchingMonth = found;
851 else if (k == 1) findMatchingDay = found;
852 }
853 }
854 if (findMatchingMonth)
855 couldBe[i] &= ~ADAY;
856 if (findMatchingDay)
857 couldBe[i] &= ~AMONTH;
858 }
859
860 // First set the year/month/day that have been deduced
861 // and reduce the set as we go along to deduce more
862 for (int i = 0; i < unknownCount; ++i) {
863 int unset = 0;
864 for (int j = 0; j < 3; ++j) {
865 if (couldBe[j] == ADAY && day == -1) {
866 day = unknown[j];
867 unset |= ADAY;
868 } else if (couldBe[j] == AMONTH && month == -1) {
869 month = unknown[j];
870 unset |= AMONTH;
871 } else if (couldBe[j] == AYEAR && year == -1) {
872 year = unknown[j];
873 unset |= AYEAR;
874 } else {
875 // common case
876 break;
877 }
878 couldBe[j] &= ~unset;
879 }
880 }
881
882 // Now fallback to a standardized order to fill in the rest with
883 for (int i = 0; i < unknownCount; ++i) {
884 if (couldBe[i] & AMONTH && month == -1) month = unknown[i];
885 else if (couldBe[i] & ADAY && day == -1) day = unknown[i];
886 else if (couldBe[i] & AYEAR && year == -1) year = unknown[i];
887 }
888#ifdef PARSEDATESTRINGDEBUG
889 qDebug() << "Final set" << year << month << day;
890#endif
891
892 if (year == -1 || month == -1 || day == -1) {
893#ifdef PARSEDATESTRINGDEBUG
894 qDebug() << "Parser failure" << year << month << day;
895#endif
896 return QDateTime();
897 }
898
899 // Y2k behavior
900 int y2k = 0;
901 if (year < 70)
902 y2k = 2000;
903 else if (year < 100)
904 y2k = 1900;
905
906 QDate date(year + y2k, month, day);
907
908 // When we were given a bad cookie that when parsed
909 // set the day to 29 and the year to one that doesn't
910 // have the 29th of Feb rather then adding the extra
911 // complicated checking earlier just swap here.
912 // Example: 29 23 Feb
913 if (!date.isValid())
914 date = QDate(day + y2k, month, year);
915
916 QDateTime dateTime(date, time, QTimeZone::UTC);
917
918 if (zoneOffset != -1)
919 dateTime = dateTime.addSecs(zoneOffset);
920
921 if (!dateTime.isValid())
922 return QDateTime();
923 return dateTime;
924}
925
926/*!
927 Parses the cookie string \a cookieString as received from a server
928 response in the "Set-Cookie:" header. If there's a parsing error,
929 this function returns an empty list.
930
931 Since the HTTP header can set more than one cookie at the same
932 time, this function returns a QList<QNetworkCookie>, one for each
933 cookie that is parsed.
934
935 \sa toRawForm()
936 \note In Qt versions prior to 6.7, this function took QByteArray only.
937*/
938QList<QNetworkCookie> QNetworkCookie::parseCookies(QByteArrayView cookieString)
939{
940 // cookieString can be a number of set-cookie header strings joined together
941 // by \n, parse each line separately.
942 QList<QNetworkCookie> cookies;
943 for (auto s : QLatin1StringView(cookieString).tokenize('\n'_L1))
944 cookies += QNetworkCookiePrivate::parseSetCookieHeaderLine(s);
945 return cookies;
946}
947
948QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(QByteArrayView cookieString)
949{
950 // According to http://wp.netscape.com/newsref/std/cookie_spec.html,<
951 // the Set-Cookie response header is of the format:
952 //
953 // Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure
954 //
955 // where only the NAME=VALUE part is mandatory
956 //
957 // We do not support RFC 2965 Set-Cookie2-style cookies
958
959 QList<QNetworkCookie> result;
960 const QDateTime now = QDateTime::currentDateTimeUtc();
961
962 int position = 0;
963 const int length = cookieString.size();
964 while (position < length) {
965 QNetworkCookie cookie;
966
967 // The first part is always the "NAME=VALUE" part
968 std::pair<QByteArray,QByteArray> field = nextField(cookieString, position, true);
969 if (field.first.isEmpty())
970 // parsing error
971 break;
972 cookie.setName(field.first);
973 cookie.setValue(field.second);
974
975 position = nextNonWhitespace(cookieString, position);
976 while (position < length) {
977 switch (cookieString.at(position++)) {
978 case ';':
979 // new field in the cookie
980 field = nextField(cookieString, position, false);
981
982 if (field.first.compare("expires", Qt::CaseInsensitive) == 0) {
983 position -= field.second.size();
984 int end;
985 for (end = position; end < length; ++end)
986 if (isValueSeparator(cookieString.at(end)))
987 break;
988
989 QByteArray dateString = cookieString.mid(position, end - position).trimmed().toByteArray().toLower();
990 position = end;
991 QDateTime dt = parseDateString(dateString);
992 if (dt.isValid())
993 cookie.setExpirationDate(dt);
994 //if unparsed, ignore the attribute but not the whole cookie (RFC6265 section 5.2.1)
995 } else if (field.first.compare("domain", Qt::CaseInsensitive) == 0) {
996 QByteArrayView rawDomain = field.second;
997 //empty domain should be ignored (RFC6265 section 5.2.3)
998 if (!rawDomain.isEmpty()) {
999 QLatin1StringView maybeLeadingDot;
1000 if (rawDomain.startsWith('.')) {
1001 maybeLeadingDot = "."_L1;
1002 rawDomain = rawDomain.mid(1);
1003 }
1004
1005 //IDN domains are required by RFC6265, accepting utf8 as well doesn't break any test cases.
1006 QString normalizedDomain = QUrl::fromAce(QUrl::toAce(QString::fromUtf8(rawDomain)));
1007 if (!normalizedDomain.isEmpty()) {
1008 cookie.setDomain(maybeLeadingDot + normalizedDomain);
1009 } else {
1010 //Normalization fails for malformed domains, e.g. "..example.org", reject the cookie now
1011 //rather than accepting it but never sending it due to domain match failure, as the
1012 //strict reading of RFC6265 would indicate.
1013 return result;
1014 }
1015 }
1016 } else if (field.first.compare("max-age", Qt::CaseInsensitive) == 0) {
1017 bool ok = false;
1018 int secs = field.second.toInt(&ok);
1019 if (ok) {
1020 if (secs <= 0) {
1021 //earliest representable time (RFC6265 section 5.2.2)
1022 cookie.setExpirationDate(QDateTime::fromSecsSinceEpoch(0));
1023 } else {
1024 cookie.setExpirationDate(now.addSecs(secs));
1025 }
1026 }
1027 //if unparsed, ignore the attribute but not the whole cookie (RFC6265 section 5.2.2)
1028 } else if (field.first.compare("path", Qt::CaseInsensitive) == 0) {
1029 if (field.second.startsWith('/')) {
1030 // ### we should treat cookie paths as an octet sequence internally
1031 // However RFC6265 says we should assume UTF-8 for presentation as a string
1032 cookie.setPath(QString::fromUtf8(field.second));
1033 } else {
1034 // if the path doesn't start with '/' then set the default path (RFC6265 section 5.2.4)
1035 // and also IETF test case path0030 which has valid and empty path in the same cookie
1036 cookie.setPath(QString());
1037 }
1038 } else if (field.first.compare("secure", Qt::CaseInsensitive) == 0) {
1039 cookie.setSecure(true);
1040 } else if (field.first.compare("httponly", Qt::CaseInsensitive) == 0) {
1041 cookie.setHttpOnly(true);
1042 } else if (field.first.compare("samesite", Qt::CaseInsensitive) == 0) {
1043 cookie.setSameSitePolicy(sameSiteFromRawString(field.second));
1044 } else {
1045 // ignore unknown fields in the cookie (RFC6265 section 5.2, rule 6)
1046 }
1047
1048 position = nextNonWhitespace(cookieString, position);
1049 }
1050 }
1051
1052 if (!cookie.name().isEmpty())
1053 result += cookie;
1054 }
1055
1056 return result;
1057}
1058
1059/*!
1060 \since 5.0
1061 This functions normalizes the path and domain of the cookie if they were previously empty.
1062 The \a url parameter is used to determine the correct domain and path.
1063*/
1064void QNetworkCookie::normalize(const QUrl &url)
1065{
1066 // don't do path checking. See QTBUG-5815
1067 if (d->path.isEmpty()) {
1068 QString pathAndFileName = url.path();
1069 QString defaultPath = pathAndFileName.left(pathAndFileName.lastIndexOf(u'/') + 1);
1070 if (defaultPath.isEmpty())
1071 defaultPath = u'/';
1072 d->path = defaultPath;
1073 }
1074
1075 if (d->domain.isEmpty()) {
1076 d->domain = url.host();
1077 } else {
1078 QHostAddress hostAddress(d->domain);
1079 if (hostAddress.protocol() != QAbstractSocket::IPv4Protocol
1080 && hostAddress.protocol() != QAbstractSocket::IPv6Protocol
1081 && !d->domain.startsWith(u'.')) {
1082 // Ensure the domain starts with a dot if its field was not empty
1083 // in the HTTP header. There are some servers that forget the
1084 // leading dot and this is actually forbidden according to RFC 2109,
1085 // but all browsers accept it anyway so we do that as well.
1086 d->domain.prepend(u'.');
1087 }
1088 }
1089}
1090
1091#ifndef QT_NO_DEBUG_STREAM
1092QDebug operator<<(QDebug s, const QNetworkCookie &cookie)
1093{
1094 QDebugStateSaver saver(s);
1095 s.resetFormat().nospace();
1096 s << "QNetworkCookie(" << cookie.toRawForm(QNetworkCookie::Full) << ')';
1097 return s;
1098}
1099#endif
1100
1101QT_END_NAMESPACE
1102
1103#include "moc_qnetworkcookie.cpp"
The QNetworkCookie class holds one network cookie.
#define ADAY
static bool checkStaticArray(int &val, QByteArrayView dateString, int at, const char *array, int size)
#define AMONTH
static bool isValueSeparator(char c)
static bool isWhitespace(char c)
static QDateTime parseDateString(QByteArrayView dateString)
#define AYEAR
static const char months[]
static std::pair< QByteArray, QByteArray > nextField(QByteArrayView text, int &position, bool isNameValue)
static const char zones[]
static bool isNumber(char s)
static const int zoneOffsets[]
static bool isTerminator(char c)