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