1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtNetwork module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qnetworkcookie.h"
43 #include "qnetworkcookie_p.h"
45 #include "qnetworkrequest.h"
46 #include "qnetworkreply.h"
47 #include "QtCore/qbytearray.h"
48 #include "QtCore/qdebug.h"
49 #include "QtCore/qlist.h"
50 #include "QtCore/qlocale.h"
51 #include "QtCore/qstring.h"
52 #include "QtCore/qstringlist.h"
53 #include "QtCore/qurl.h"
54 #include "private/qobject_p.h"
63 \brief The QNetworkCookie class holds one network cookie.
65 Cookies are small bits of information that stateless protocols
66 like HTTP use to maintain some persistent information across
69 A cookie is set by a remote server when it replies to a request
70 and it expects the same cookie to be sent back when further
73 QNetworkCookie holds one such cookie as received from the
74 network. A cookie has a name and a value, but those are opaque to
75 the application (that is, the information stored in them has no
76 meaning to the application). A cookie has an associated path name
77 and domain, which indicate when the cookie should be sent again to
80 A cookie can also have an expiration date, indicating its
81 validity. If the expiration date is not present, the cookie is
82 considered a "session cookie" and should be discarded when the
83 application exits (or when its concept of session is over).
85 QNetworkCookie provides a way of parsing a cookie from the HTTP
86 header format using the QNetworkCookie::parseCookies()
87 function. However, when received in a QNetworkReply, the cookie is
90 This class implements cookies as described by the
91 \l{Netscape Cookie Specification}{initial cookie specification by
92 Netscape}, which is somewhat similar to the \l{RFC 2109} specification,
93 plus the \l{Mitigating Cross-site Scripting With HTTP-only Cookies}
94 {"HttpOnly" extension}. The more recent \l{RFC 2965} specification
95 (which uses the Set-Cookie2 header) is not supported.
97 \sa QNetworkCookieJar, QNetworkRequest, QNetworkReply
101 Create a new QNetworkCookie object, initializing the cookie name
102 to \a name and its value to \a value.
104 A cookie is only valid if it has a name. However, the value is
105 opaque to the application and being empty may have significance to
108 QNetworkCookie::QNetworkCookie(const QByteArray &name, const QByteArray &value)
109 : d(new QNetworkCookiePrivate)
111 qRegisterMetaType<QNetworkCookie>();
112 qRegisterMetaType<QList<QNetworkCookie> >();
119 Creates a new QNetworkCookie object by copying the contents of \a
122 QNetworkCookie::QNetworkCookie(const QNetworkCookie &other)
128 Destroys this QNetworkCookie object.
130 QNetworkCookie::~QNetworkCookie()
132 // QSharedDataPointer auto deletes
137 Copies the contents of the QNetworkCookie object \a other to this
140 QNetworkCookie &QNetworkCookie::operator=(const QNetworkCookie &other)
147 \fn bool QNetworkCookie::operator!=(const QNetworkCookie &other) const
149 Returns true if this cookie is not equal to \a other.
156 Returns true if this cookie is equal to \a other. This function
157 only returns true if all fields of the cookie are the same.
159 However, in some contexts, two cookies of the same name could be
162 \sa operator!=(), hasSameIdentifier()
164 bool QNetworkCookie::operator==(const QNetworkCookie &other) const
168 return d->name == other.d->name &&
169 d->value == other.d->value &&
170 d->expirationDate.toUTC() == other.d->expirationDate.toUTC() &&
171 d->domain == other.d->domain &&
172 d->path == other.d->path &&
173 d->secure == other.d->secure &&
174 d->comment == other.d->comment;
178 Returns true if this cookie has the same identifier tuple as \a other.
179 The identifier tuple is composed of the name, domain and path.
183 bool QNetworkCookie::hasSameIdentifier(const QNetworkCookie &other) const
185 return d->name == other.d->name && d->domain == other.d->domain && d->path == other.d->path;
189 Returns true if the "secure" option was specified in the cookie
190 string, false otherwise.
192 Secure cookies may contain private information and should not be
193 resent over unencrypted connections.
197 bool QNetworkCookie::isSecure() const
203 Sets the secure flag of this cookie to \a enable.
205 Secure cookies may contain private information and should not be
206 resent over unencrypted connections.
210 void QNetworkCookie::setSecure(bool enable)
218 Returns true if the "HttpOnly" flag is enabled for this cookie.
220 A cookie that is "HttpOnly" is only set and retrieved by the
221 network requests and replies; i.e., the HTTP protocol. It is not
222 accessible from scripts running on browsers.
226 bool QNetworkCookie::isHttpOnly() const
234 Sets this cookie's "HttpOnly" flag to \a enable.
236 void QNetworkCookie::setHttpOnly(bool enable)
238 d->httpOnly = enable;
242 Returns true if this cookie is a session cookie. A session cookie
243 is a cookie which has no expiration date, which means it should be
244 discarded when the application's concept of session is over
245 (usually, when the application exits).
247 \sa expirationDate(), setExpirationDate()
249 bool QNetworkCookie::isSessionCookie() const
251 return !d->expirationDate.isValid();
255 Returns the expiration date for this cookie. If this cookie is a
256 session cookie, the QDateTime returned will not be valid. If the
257 date is in the past, this cookie has already expired and should
258 not be sent again back to a remote server.
260 The expiration date corresponds to the parameters of the "expires"
261 entry in the cookie string.
263 \sa isSessionCookie(), setExpirationDate()
265 QDateTime QNetworkCookie::expirationDate() const
267 return d->expirationDate;
271 Sets the expiration date of this cookie to \a date. Setting an
272 invalid expiration date to this cookie will mean it's a session
275 \sa isSessionCookie(), expirationDate()
277 void QNetworkCookie::setExpirationDate(const QDateTime &date)
279 d->expirationDate = date;
283 Returns the domain this cookie is associated with. This
284 corresponds to the "domain" field of the cookie string.
286 Note that the domain here may start with a dot, which is not a
287 valid hostname. However, it means this cookie matches all
288 hostnames ending with that domain name.
292 QString QNetworkCookie::domain() const
298 Sets the domain associated with this cookie to be \a domain.
302 void QNetworkCookie::setDomain(const QString &domain)
308 Returns the path associated with this cookie. This corresponds to
309 the "path" field of the cookie string.
313 QString QNetworkCookie::path() const
319 Sets the path associated with this cookie to be \a path.
323 void QNetworkCookie::setPath(const QString &path)
329 Returns the name of this cookie. The only mandatory field of a
330 cookie is its name, without which it is not considered valid.
332 \sa setName(), value()
334 QByteArray QNetworkCookie::name() const
340 Sets the name of this cookie to be \a cookieName. Note that
341 setting a cookie name to an empty QByteArray will make this cookie
346 void QNetworkCookie::setName(const QByteArray &cookieName)
348 d->name = cookieName;
352 Returns this cookies value, as specified in the cookie
353 string. Note that a cookie is still valid if its value is empty.
355 Cookie name-value pairs are considered opaque to the application:
356 that is, their values don't mean anything.
358 \sa setValue(), name()
360 QByteArray QNetworkCookie::value() const
366 Sets the value of this cookie to be \a value.
370 void QNetworkCookie::setValue(const QByteArray &value)
375 // ### move this to qnetworkcookie_p.h and share with qnetworkaccesshttpbackend
376 static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &position, bool isNameValue)
381 // (3) token = quoted-string
383 const int length = text.length();
384 position = nextNonWhitespace(text, position);
386 // parse the first part, before the equal sign
387 for (i = position; i < length; ++i) {
388 register char c = text.at(i);
389 if (c == ';' || c == '=')
393 QByteArray first = text.mid(position, i - position).trimmed();
397 return qMakePair(QByteArray(), QByteArray());
398 if (i == length || text.at(i) != '=')
399 // no equal sign, we found format (1)
400 return qMakePair(first, QByteArray());
403 second.reserve(32); // arbitrary but works for most cases
405 i = nextNonWhitespace(text, position + 1);
406 if (i < length && text.at(i) == '"') {
407 // a quote, we found format (3), where:
408 // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
409 // qdtext = <any TEXT except <">>
410 // quoted-pair = "\" CHAR
412 // If it is NAME=VALUE, retain the value as is
413 // refer to http://bugreports.qt-project.org/browse/QTBUG-17746
418 register char c = text.at(i);
420 // end of quoted text
424 } else if (c == '\\') {
430 return qMakePair(QByteArray(), QByteArray());
438 for ( ; i < length; ++i) {
439 register char c = text.at(i);
445 // no quote, we found format (2)
447 for ( ; i < length; ++i) {
448 register char c = text.at(i);
449 // for name value pairs, we want to parse until reaching the next ';'
450 // and not break when reaching a space char
451 if (c == ';' || ((isNameValue && (c == '\n' || c == '\r')) || (!isNameValue && isLWS(c))))
455 second = text.mid(position, i - position).trimmed();
460 second.resize(0); // turns into empty-but-not-null
461 return qMakePair(first, second);
465 \enum QNetworkCookie::RawForm
467 This enum is used with the toRawForm() function to declare which
468 form of a cookie shall be returned.
470 \value NameAndValueOnly makes toRawForm() return only the
471 "NAME=VALUE" part of the cookie, as suitable for sending back
472 to a server in a client request's "Cookie:" header. Multiple
473 cookies are separated by a semi-colon in the "Cookie:" header
476 \value Full makes toRawForm() return the full
477 cookie contents, as suitable for sending to a client in a
478 server's "Set-Cookie:" header.
480 Note that only the Full form of the cookie can be parsed back into
481 its original contents.
483 \sa toRawForm(), parseCookies()
487 Returns the raw form of this QNetworkCookie. The QByteArray
488 returned by this function is suitable for an HTTP header, either
489 in a server response (the Set-Cookie header) or the client request
490 (the Cookie header). You can choose from one of two formats, using
495 QByteArray QNetworkCookie::toRawForm(RawForm form) const
498 if (d->name.isEmpty())
499 return result; // not a valid cookie
503 if ((d->value.contains(';') ||
504 d->value.contains('"')) &&
505 (!d->value.startsWith('"') &&
506 !d->value.endsWith('"'))) {
509 QByteArray value = d->value;
510 value.replace('"', "\\\"");
519 // same as above, but encoding everything back
521 result += "; secure";
523 result += "; HttpOnly";
524 if (!isSessionCookie()) {
525 result += "; expires=";
526 result += QLocale::c().toString(d->expirationDate.toUTC(),
527 QLatin1String("ddd, dd-MMM-yyyy hh:mm:ss 'GMT")).toLatin1();
529 if (!d->domain.isEmpty()) {
530 result += "; domain=";
531 QString domainNoDot = d->domain;
532 if (domainNoDot.startsWith(QLatin1Char('.'))) {
534 domainNoDot = domainNoDot.mid(1);
536 result += QUrl::toAce(domainNoDot);
538 if (!d->path.isEmpty()) {
540 result += QUrl::toPercentEncoding(d->path, "/");
546 static const char zones[] =
564 static int zoneOffsets[] = {-8, -8, -7, -7, -6, -6, -5, -5, -4, -3, 0, 0, 0, 1, 2, 9 };
566 static const char months[] =
581 static inline bool isNumber(char s)
582 { return s >= '0' && s <= '9'; }
584 static inline bool isTerminator(char c)
585 { return c == '\n' || c == '\r'; }
587 static inline bool isValueSeparator(char c)
588 { return isTerminator(c) || c == ';'; }
590 static inline bool isWhitespace(char c)
591 { return c == ' ' || c == '\t'; }
593 static bool checkStaticArray(int &val, const QByteArray &dateString, int at, const char *array, int size)
595 if (dateString[at] < 'a' || dateString[at] > 'z')
597 if (val == -1 && dateString.length() >= at + 3) {
601 const char *str = array + i;
602 if (str[0] == dateString[at]
603 && str[1] == dateString[at + 1]
604 && str[2] == dateString[at + 2]) {
608 i += int(strlen(str)) + 1;
615 //#define PARSEDATESTRINGDEBUG
622 Parse all the date formats that Firefox can.
624 The official format is:
625 expires=ddd(d)?, dd-MMM-yyyy hh:mm:ss GMT
627 But browsers have been supporting a very wide range of date
628 strings. To work on many sites we need to support more then
629 just the official date format.
631 For reference see Firefox's PR_ParseTimeStringToExplodedTime in
632 prtime.c. The Firefox date parser is coded in a very complex way
633 and is slightly over ~700 lines long. While this implementation
634 will be slightly slower for the non standard dates it is smaller,
635 more readable, and maintainable.
637 Or in their own words:
638 "} // else what the hell is this."
640 static QDateTime parseDateString(const QByteArray &dateString)
643 // placeholders for values when we are not sure it is a year, month or day
644 int unknown[3] = {-1, -1, -1};
650 // hour:minute:second.ms pm
651 QRegExp timeRx(QLatin1String("(\\d{1,2}):(\\d{1,2})(:(\\d{1,2})|)(\\.(\\d{1,3})|)((\\s{0,}(am|pm))|)"));
654 while (at < dateString.length()) {
655 #ifdef PARSEDATESTRINGDEBUG
656 qDebug() << dateString.mid(at);
658 bool isNum = isNumber(dateString[at]);
662 && checkStaticArray(month, dateString, at, months, sizeof(months)- 1)) {
664 #ifdef PARSEDATESTRINGDEBUG
665 qDebug() << "Month:" << month;
673 && checkStaticArray(zoneOffset, dateString, at, zones, sizeof(zones)- 1)) {
674 int sign = (at >= 0 && dateString[at - 1] == '-') ? -1 : 1;
675 zoneOffset = sign * zoneOffsets[zoneOffset] * 60 * 60;
676 #ifdef PARSEDATESTRINGDEBUG
677 qDebug() << "Zone:" << month;
684 && (zoneOffset == -1 || zoneOffset == 0) // Can only go after gmt
685 && (dateString[at] == '+' || dateString[at] == '-')
687 || isWhitespace(dateString[at - 1])
688 || dateString[at - 1] == ','
690 && (dateString[at - 3] == 'g')
691 && (dateString[at - 2] == 'm')
692 && (dateString[at - 1] == 't')))) {
695 while (end < 5 && dateString.length() > at+end
696 && dateString[at + end] >= '0' && dateString[at + end] <= '9')
702 minutes = atoi(dateString.mid(at + 3, 2).constData());
705 hours = atoi(dateString.mid(at + 1, 2).constData());
708 hours = atoi(dateString.mid(at + 1, 1).constData());
715 int sign = dateString[at] == '-' ? -1 : 1;
716 zoneOffset = sign * ((minutes * 60) + (hours * 60 * 60));
717 #ifdef PARSEDATESTRINGDEBUG
718 qDebug() << "Zone offset:" << zoneOffset << hours << minutes;
726 if (isNum && time.isNull()
727 && dateString.length() >= at + 3
728 && (dateString[at + 2] == ':' || dateString[at + 1] == ':')) {
729 // While the date can be found all over the string the format
730 // for the time is set and a nice regexp can be used.
731 int pos = timeRx.indexIn(QLatin1String(dateString), at);
733 QStringList list = timeRx.capturedTexts();
734 int h = atoi(list.at(1).toLatin1().constData());
735 int m = atoi(list.at(2).toLatin1().constData());
736 int s = atoi(list.at(4).toLatin1().constData());
737 int ms = atoi(list.at(6).toLatin1().constData());
738 if (h < 12 && !list.at(9).isEmpty())
739 if (list.at(9) == QLatin1String("pm"))
741 time = QTime(h, m, s, ms);
742 #ifdef PARSEDATESTRINGDEBUG
743 qDebug() << "Time:" << list << timeRx.matchedLength();
745 at += timeRx.matchedLength();
753 && dateString.length() > at + 3) {
754 if (isNumber(dateString[at + 1])
755 && isNumber(dateString[at + 2])
756 && isNumber(dateString[at + 3])) {
757 year = atoi(dateString.mid(at, 4).constData());
759 #ifdef PARSEDATESTRINGDEBUG
760 qDebug() << "Year:" << year;
766 // a one or two digit number
767 // Could be month, day or year
770 if (dateString.length() > at + 1
771 && isNumber(dateString[at + 1]))
773 int x = atoi(dateString.mid(at, length).constData());
774 if (year == -1 && (x > 31 || x == 0)) {
777 if (unknown[0] == -1) unknown[0] = x;
778 else if (unknown[1] == -1) unknown[1] = x;
779 else if (unknown[2] == -1) unknown[2] = x;
782 #ifdef PARSEDATESTRINGDEBUG
783 qDebug() << "Saving" << x;
788 // Unknown character, typically a weekday such as 'Mon'
792 // Once we are done parsing the string take the digits in unknown
793 // and determine which is the unknown year/month/day
795 int couldBe[3] = { 0, 0, 0 };
796 int unknownCount = 3;
797 for (int i = 0; i < unknownCount; ++i) {
798 if (unknown[i] == -1) {
799 couldBe[i] = ADAY | AYEAR | AMONTH;
807 if (month == -1 && unknown[i] >= 1 && unknown[i] <= 12)
808 couldBe[i] |= AMONTH;
814 // For any possible day make sure one of the values that could be a month
815 // can contain that day.
816 // For any possible month make sure one of the values that can be a
817 // day that month can have.
819 // 31 can't be a day because 11 and 6 don't have 31 days
820 for (int i = 0; i < unknownCount; ++i) {
821 int currentValue = unknown[i];
822 bool findMatchingMonth = couldBe[i] & ADAY && currentValue >= 29;
823 bool findMatchingDay = couldBe[i] & AMONTH;
824 if (!findMatchingMonth || !findMatchingDay)
826 for (int j = 0; j < 3; ++j) {
829 for (int k = 0; k < 2; ++k) {
830 if (k == 0 && !(findMatchingMonth && (couldBe[j] & AMONTH)))
832 else if (k == 1 && !(findMatchingDay && (couldBe[j] & ADAY)))
834 int m = currentValue;
838 if (m == -1) m = month;
842 // When we get 29 and the year ends up having only 28
843 // See date.isValid below
844 // Example: 29 23 Feb
848 case 4: case 6: case 9: case 11:
853 if (d > 0 && d <= 31)
856 if (k == 0) findMatchingMonth = found;
857 else if (k == 1) findMatchingDay = found;
860 if (findMatchingMonth)
863 couldBe[i] &= ~AMONTH;
866 // First set the year/month/day that have been deduced
867 // and reduce the set as we go along to deduce more
868 for (int i = 0; i < unknownCount; ++i) {
870 for (int j = 0; j < 3; ++j) {
871 if (couldBe[j] == ADAY && day == -1) {
874 } else if (couldBe[j] == AMONTH && month == -1) {
877 } else if (couldBe[j] == AYEAR && year == -1) {
884 couldBe[j] &= ~unset;
888 // Now fallback to a standardized order to fill in the rest with
889 for (int i = 0; i < unknownCount; ++i) {
890 if (couldBe[i] & AMONTH && month == -1) month = unknown[i];
891 else if (couldBe[i] & ADAY && day == -1) day = unknown[i];
892 else if (couldBe[i] & AYEAR && year == -1) year = unknown[i];
894 #ifdef PARSEDATESTRINGDEBUG
895 qDebug() << "Final set" << year << month << day;
898 if (year == -1 || month == -1 || day == -1) {
899 #ifdef PARSEDATESTRINGDEBUG
900 qDebug() << "Parser failure" << year << month << day;
912 QDate date(year + y2k, month, day);
914 // When we were given a bad cookie that when parsed
915 // set the day to 29 and the year to one that doesn't
916 // have the 29th of Feb rather then adding the extra
917 // complicated checking earlier just swap here.
918 // Example: 29 23 Feb
920 date = QDate(day + y2k, month, year);
922 QDateTime dateTime(date, time, Qt::UTC);
924 if (zoneOffset != -1) {
925 dateTime = dateTime.addSecs(zoneOffset);
927 if (!dateTime.isValid())
933 Parses the cookie string \a cookieString as received from a server
934 response in the "Set-Cookie:" header. If there's a parsing error,
935 this function returns an empty list.
937 Since the HTTP header can set more than one cookie at the same
938 time, this function returns a QList<QNetworkCookie>, one for each
939 cookie that is parsed.
943 QList<QNetworkCookie> QNetworkCookie::parseCookies(const QByteArray &cookieString)
945 // cookieString can be a number of set-cookie header strings joined together
946 // by \n, parse each line separately.
947 QList<QNetworkCookie> cookies;
948 QList<QByteArray> list = cookieString.split('\n');
949 for (int a = 0; a < list.size(); a++)
950 cookies += QNetworkCookiePrivate::parseSetCookieHeaderLine(list.at(a));
954 QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByteArray &cookieString)
956 // According to http://wp.netscape.com/newsref/std/cookie_spec.html,<
957 // the Set-Cookie response header is of the format:
959 // Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure
961 // where only the NAME=VALUE part is mandatory
963 // We do not support RFC 2965 Set-Cookie2-style cookies
965 QList<QNetworkCookie> result;
966 QDateTime now = QDateTime::currentDateTime().toUTC();
969 const int length = cookieString.length();
970 while (position < length) {
971 QNetworkCookie cookie;
973 // The first part is always the "NAME=VALUE" part
974 QPair<QByteArray,QByteArray> field = nextField(cookieString, position, true);
975 if (field.first.isEmpty() || field.second.isNull())
978 cookie.setName(field.first);
979 cookie.setValue(field.second);
981 position = nextNonWhitespace(cookieString, position);
982 while (position < length) {
983 switch (cookieString.at(position++)) {
985 // new field in the cookie
986 field = nextField(cookieString, position, false);
987 field.first = field.first.toLower(); // everything but the NAME=VALUE is case-insensitive
989 if (field.first == "expires") {
990 position -= field.second.length();
992 for (end = position; end < length; ++end)
993 if (isValueSeparator(cookieString.at(end)))
996 QByteArray dateString = cookieString.mid(position, end - position).trimmed();
998 QDateTime dt = parseDateString(dateString.toLower());
1002 cookie.setExpirationDate(dt);
1003 } else if (field.first == "domain") {
1004 QByteArray rawDomain = field.second;
1005 QString maybeLeadingDot;
1006 if (rawDomain.startsWith('.')) {
1007 maybeLeadingDot = QLatin1Char('.');
1008 rawDomain = rawDomain.mid(1);
1011 QString normalizedDomain = QUrl::fromAce(QUrl::toAce(QString::fromUtf8(rawDomain)));
1012 if (normalizedDomain.isEmpty() && !rawDomain.isEmpty())
1014 cookie.setDomain(maybeLeadingDot + normalizedDomain);
1015 } else if (field.first == "max-age") {
1017 int secs = field.second.toInt(&ok);
1020 cookie.setExpirationDate(now.addSecs(secs));
1021 } else if (field.first == "path") {
1022 QString path = QUrl::fromPercentEncoding(field.second);
1023 cookie.setPath(path);
1024 } else if (field.first == "secure") {
1025 cookie.setSecure(true);
1026 } else if (field.first == "httponly") {
1027 cookie.setHttpOnly(true);
1029 // ignore unknown fields in the cookie (RFC6265 section 5.2, rule 6)
1032 position = nextNonWhitespace(cookieString, position);
1036 if (!cookie.name().isEmpty())
1045 This functions normalizes the path and domain of the cookie if they were previously empty.
1046 The \a url parameter is used to determine the correct domain and path.
1048 void QNetworkCookie::normalize(const QUrl &url)
1050 // don't do path checking. See http://bugreports.qt-project.org/browse/QTBUG-5815
1051 if (d->path.isEmpty()) {
1052 QString pathAndFileName = url.path();
1053 QString defaultPath = pathAndFileName.left(pathAndFileName.lastIndexOf(QLatin1Char('/'))+1);
1054 if (defaultPath.isEmpty())
1055 defaultPath = QLatin1Char('/');
1056 d->path = defaultPath;
1059 if (d->domain.isEmpty())
1060 d->domain = url.host();
1061 else if (!d->domain.startsWith(QLatin1Char('.')))
1062 // Ensure the domain starts with a dot if its field was not empty
1063 // in the HTTP header. There are some servers that forget the
1064 // leading dot and this is actually forbidden according to RFC 2109,
1065 // but all browsers accept it anyway so we do that as well.
1066 d->domain.prepend(QLatin1Char('.'));
1069 #ifndef QT_NO_DEBUG_STREAM
1070 QDebug operator<<(QDebug s, const QNetworkCookie &cookie)
1072 s.nospace() << "QNetworkCookie(" << cookie.toRawForm(QNetworkCookie::Full) << ')';