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

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