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