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 
56 QT_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 */
QNetworkCookie(const QByteArray & name,const QByteArray & value)109 QNetworkCookie::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 */
QNetworkCookie(const QNetworkCookie & other)123 QNetworkCookie::QNetworkCookie(const QNetworkCookie &other)
124     : d(other.d)
125 {
126 }
127 
128 /*!
129     Destroys this QNetworkCookie object.
130 */
~QNetworkCookie()131 QNetworkCookie::~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 */
operator =(const QNetworkCookie & other)141 QNetworkCookie &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 */
operator ==(const QNetworkCookie & other) const173 bool 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 */
hasSameIdentifier(const QNetworkCookie & other) const193 bool 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 */
isSecure() const207 bool 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 */
setSecure(bool enable)220 void 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 */
isHttpOnly() const236 bool 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 */
setHttpOnly(bool enable)246 void 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 */
isSessionCookie() const259 bool 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 */
expirationDate() const275 QDateTime 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 */
setExpirationDate(const QDateTime & date)287 void 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 */
domain() const302 QString 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 */
setDomain(const QString & domain)312 void 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 */
path() const323 QString 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 */
setPath(const QString & path)333 void 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 */
name() const344 QByteArray 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 */
setName(const QByteArray & cookieName)356 void 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 */
value() const370 QByteArray 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 */
setValue(const QByteArray & value)380 void QNetworkCookie::setValue(const QByteArray &value)
381 {
382     d->value = value;
383 }
384 
385 // ### move this to qnetworkcookie_p.h and share with qnetworkaccesshttpbackend
nextField(const QByteArray & text,int & position,bool isNameValue)386 static 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, position);
394 
395     int semiColonPosition = text.indexOf(';', position);
396     if (semiColonPosition < 0)
397         semiColonPosition = length; //no ';' means take everything to end of string
398 
399     int equalsPosition = text.indexOf('=', position);
400     if (equalsPosition < 0 || equalsPosition > semiColonPosition) {
401         if (isNameValue)
402             return qMakePair(QByteArray(), 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(position, equalsPosition - position).trimmed();
407     QByteArray second;
408     int secondLength = semiColonPosition - equalsPosition - 1;
409     if (secondLength > 0)
410         second = text.mid(equalsPosition + 1, secondLength).trimmed();
411 
412     position = semiColonPosition;
413     return qMakePair(first, 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 */
toRawForm(RawForm form) const447 QByteArray 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(d->expirationDate.toUTC(),
470                                             QLatin1String("ddd, dd-MMM-yyyy hh:mm:ss 'GMT")).toLatin1();
471         }
472         if (!d->domain.isEmpty()) {
473             result += "; domain=";
474             if (d->domain.startsWith(QLatin1Char('.'))) {
475                 result += '.';
476                 result += QUrl::toAce(d->domain.mid(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 
496 static 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";
514 static const int zoneOffsets[] = {-8, -8, -7, -7, -6, -6, -5, -5, -4, -3, 0, 0, 0, 1, 2, 9 };
515 
516 static 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 
isNumber(char s)531 static inline bool isNumber(char s)
532 { return s >= '0' && s <= '9'; }
533 
isTerminator(char c)534 static inline bool isTerminator(char c)
535 { return c == '\n' || c == '\r'; }
536 
isValueSeparator(char c)537 static inline bool isValueSeparator(char c)
538 { return isTerminator(c) || c == ';'; }
539 
isWhitespace(char c)540 static inline bool isWhitespace(char c)
541 { return c == ' '  || c == '\t'; }
542 
checkStaticArray(int & val,const QByteArray & dateString,int at,const char * array,int size)543 static 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(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 */
parseDateString(const QByteArray & dateString)590 static 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(dateString[at]);
609 
610         // Month
611         if (!isNum
612             && checkStaticArray(month, dateString, at, months, 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(zoneOffset, dateString, at, zones, 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(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(dateString.mid(at + 3, 2).constData());
653                 Q_FALLTHROUGH();
654             case 2:
655                 hours = atoi(dateString.mid(at + 1, 2).constData());
656                 break;
657             case 1:
658                 hours = atoi(dateString.mid(at + 1, 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(QLatin1String(dateString), at);
682             if (pos != -1) {
683                 QStringList list = timeRx.capturedTexts();
684                 int h = atoi(list.at(1).toLatin1().constData());
685                 int m = atoi(list.at(2).toLatin1().constData());
686                 int s = atoi(list.at(4).toLatin1().constData());
687                 int ms = atoi(list.at(6).toLatin1().constData());
688                 if (h < 12 && !list.at(9).isEmpty())
689                     if (list.at(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(dateString[at + 1])
705                 && isNumber(dateString[at + 2])
706                 && isNumber(dateString[at + 3])) {
707                 year = atoi(dateString.mid(at, 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(dateString[at + 1]))
722                 ++length;
723             int x = atoi(dateString.mid(at, 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(m, 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(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 */
parseCookies(const QByteArray & cookieString)893 QList<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('\n');
899     for (int a = 0; a < list.size(); a++)
900         cookies += QNetworkCookiePrivate::parseSetCookieHeaderLine(list.at(a));
901     return cookies;
902 }
903 
parseSetCookieHeaderLine(const QByteArray & cookieString)904 QList<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(cookieString, position, 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(cookieString, position);
932         while (position < length) {
933             switch (cookieString.at(position++)) {
934             case ';':
935                 // new field in the cookie
936                 field = nextField(cookieString, position, 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(cookieString.at(end)))
944                             break;
945 
946                     QByteArray dateString = cookieString.mid(position, end - position).trimmed();
947                     position = end;
948                     QDateTime dt = parseDateString(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('.')) {
958                             maybeLeadingDot = QLatin1Char('.');
959                             rawDomain = rawDomain.mid(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(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);
976                     if (ok) {
977                         if (secs <= 0) {
978                             //earliest representable time (RFC6265 section 5.2.2)
979                             cookie.setExpirationDate(QDateTime::fromSecsSinceEpoch(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('/')) {
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(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(cookieString, 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 */
normalize(const QUrl & url)1021 void 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(pathAndFileName.lastIndexOf(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(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(QLatin1Char('.'));
1044         }
1045     }
1046 }
1047 
1048 #ifndef QT_NO_DEBUG_STREAM
operator <<(QDebug s,const QNetworkCookie & cookie)1049 QDebug operator<<(QDebug s, const QNetworkCookie &cookie)
1050 {
1051     QDebugStateSaver saver(s);
1052     s.resetFormat().nospace();
1053     s << "QNetworkCookie(" << cookie.toRawForm(QNetworkCookie::Full) << ')';
1054     return s;
1055 }
1056 #endif
1057 
1058 QT_END_NAMESPACE
1059