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

source code of qtbase/src/network/access/qnetworkcookie.cpp