QNetworkCookie - ignore unknown attributes
[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     \since 5.0
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.
158
159     However, in some contexts, two cookies of the same name could be
160     considered equal.
161
162     \sa operator!=(), hasSameIdentifier()
163 */
164 bool QNetworkCookie::operator==(const QNetworkCookie &other) const
165 {
166     if (d == other.d)
167         return true;
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;
175 }
176
177 /*!
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.
180
181     \sa operator==()
182 */
183 bool QNetworkCookie::hasSameIdentifier(const QNetworkCookie &other) const
184 {
185     return d->name == other.d->name && d->domain == other.d->domain && d->path == other.d->path;
186 }
187
188 /*!
189     Returns true if the "secure" option was specified in the cookie
190     string, false otherwise.
191
192     Secure cookies may contain private information and should not be
193     resent over unencrypted connections.
194
195     \sa setSecure()
196 */
197 bool QNetworkCookie::isSecure() const
198 {
199     return d->secure;
200 }
201
202 /*!
203     Sets the secure flag of this cookie to \a enable.
204
205     Secure cookies may contain private information and should not be
206     resent over unencrypted connections.
207
208     \sa isSecure()
209 */
210 void QNetworkCookie::setSecure(bool enable)
211 {
212     d->secure = enable;
213 }
214
215 /*!
216     \since 4.5
217
218     Returns true if the "HttpOnly" flag is enabled for this cookie.
219
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.
223
224     \sa isSecure()
225 */
226 bool QNetworkCookie::isHttpOnly() const
227 {
228     return d->httpOnly;
229 }
230
231 /*!
232     \since 4.5
233
234     Sets this cookie's "HttpOnly" flag to \a enable.
235 */
236 void QNetworkCookie::setHttpOnly(bool enable)
237 {
238     d->httpOnly = enable;
239 }
240
241 /*!
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).
246
247     \sa expirationDate(), setExpirationDate()
248 */
249 bool QNetworkCookie::isSessionCookie() const
250 {
251     return !d->expirationDate.isValid();
252 }
253
254 /*!
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.
259
260     The expiration date corresponds to the parameters of the "expires"
261     entry in the cookie string.
262
263     \sa isSessionCookie(), setExpirationDate()
264 */
265 QDateTime QNetworkCookie::expirationDate() const
266 {
267     return d->expirationDate;
268 }
269
270 /*!
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
273     cookie.
274
275     \sa isSessionCookie(), expirationDate()
276 */
277 void QNetworkCookie::setExpirationDate(const QDateTime &date)
278 {
279     d->expirationDate = date;
280 }
281
282 /*!
283     Returns the domain this cookie is associated with. This
284     corresponds to the "domain" field of the cookie string.
285
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.
289
290     \sa setDomain()
291 */
292 QString QNetworkCookie::domain() const
293 {
294     return d->domain;
295 }
296
297 /*!
298     Sets the domain associated with this cookie to be \a domain.
299
300     \sa domain()
301 */
302 void QNetworkCookie::setDomain(const QString &domain)
303 {
304     d->domain = domain;
305 }
306
307 /*!
308     Returns the path associated with this cookie. This corresponds to
309     the "path" field of the cookie string.
310
311     \sa setPath()
312 */
313 QString QNetworkCookie::path() const
314 {
315     return d->path;
316 }
317
318 /*!
319     Sets the path associated with this cookie to be \a path.
320
321     \sa path()
322 */
323 void QNetworkCookie::setPath(const QString &path)
324 {
325     d->path = path;
326 }
327
328 /*!
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.
331
332     \sa setName(), value()
333 */
334 QByteArray QNetworkCookie::name() const
335 {
336     return d->name;
337 }
338
339 /*!
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
342     invalid.
343
344     \sa name(), value()
345 */
346 void QNetworkCookie::setName(const QByteArray &cookieName)
347 {
348     d->name = cookieName;
349 }
350
351 /*!
352     Returns this cookies value, as specified in the cookie
353     string. Note that a cookie is still valid if its value is empty.
354
355     Cookie name-value pairs are considered opaque to the application:
356     that is, their values don't mean anything.
357
358     \sa setValue(), name()
359 */
360 QByteArray QNetworkCookie::value() const
361 {
362     return d->value;
363 }
364
365 /*!
366     Sets the value of this cookie to be \a value.
367
368     \sa value(), name()
369 */
370 void QNetworkCookie::setValue(const QByteArray &value)
371 {
372     d->value = value;
373 }
374
375 // ### move this to qnetworkcookie_p.h and share with qnetworkaccesshttpbackend
376 static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &position, bool isNameValue)
377 {
378     // format is one of:
379     //    (1)  token
380     //    (2)  token = token
381     //    (3)  token = quoted-string
382     int i;
383     const int length = text.length();
384     position = nextNonWhitespace(text, position);
385
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 == '=')
390             break;
391     }
392
393     QByteArray first = text.mid(position, i - position).trimmed();
394     position = i;
395
396     if (first.isEmpty())
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());
401
402     QByteArray second;
403     second.reserve(32);         // arbitrary but works for most cases
404
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
411
412         // If it is NAME=VALUE, retain the value as is
413         // refer to http://bugreports.qt-project.org/browse/QTBUG-17746
414         if (isNameValue)
415             second += '"';
416         ++i;
417         while (i < length) {
418             register char c = text.at(i);
419             if (c == '"') {
420                 // end of quoted text
421                 if (isNameValue)
422                     second += '"';
423                 break;
424             } else if (c == '\\') {
425                 if (isNameValue)
426                     second += '\\';
427                 ++i;
428                 if (i >= length)
429                     // broken line
430                     return qMakePair(QByteArray(), QByteArray());
431                 c = text.at(i);
432             }
433
434             second += c;
435             ++i;
436         }
437
438         for ( ; i < length; ++i) {
439             register char c = text.at(i);
440             if (c == ';')
441                 break;
442         }
443         position = i;
444     } else {
445         // no quote, we found format (2)
446         position = i;
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))))
452                 break;
453         }
454
455         second = text.mid(position, i - position).trimmed();
456         position = i;
457     }
458
459     if (second.isNull())
460         second.resize(0); // turns into empty-but-not-null
461     return qMakePair(first, second);
462 }
463
464 /*!
465     \enum QNetworkCookie::RawForm
466
467     This enum is used with the toRawForm() function to declare which
468     form of a cookie shall be returned.
469
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
474         field.
475
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.
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.startsWith('"') &&
506         !d->value.endsWith('"'))) {
507         result += '"';
508
509         QByteArray value = d->value;
510         value.replace('"', "\\\"");
511         result += value;
512
513         result += '"';
514     } else {
515         result += d->value;
516     }
517
518     if (form == Full) {
519         // same as above, but encoding everything back
520         if (isSecure())
521             result += "; secure";
522         if (isHttpOnly())
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();
528         }
529         if (!d->domain.isEmpty()) {
530             result += "; domain=";
531             QString domainNoDot = d->domain;
532             if (domainNoDot.startsWith(QLatin1Char('.'))) {
533                 result += '.';
534                 domainNoDot = domainNoDot.mid(1);
535             }
536             result += QUrl::toAce(domainNoDot);
537         }
538         if (!d->path.isEmpty()) {
539             result += "; path=";
540             result += QUrl::toPercentEncoding(d->path, "/");
541         }
542     }
543     return result;
544 }
545
546 static const char zones[] =
547     "pst\0" // -8
548     "pdt\0"
549     "mst\0" // -7
550     "mdt\0"
551     "cst\0" // -6
552     "cdt\0"
553     "est\0" // -5
554     "edt\0"
555     "ast\0" // -4
556     "nst\0" // -3
557     "gmt\0" // 0
558     "utc\0"
559     "bst\0"
560     "met\0" // 1
561     "eet\0" // 2
562     "jst\0" // 9
563     "\0";
564 static int zoneOffsets[] = {-8, -8, -7, -7, -6, -6, -5, -5, -4, -3, 0, 0, 0, 1, 2, 9 };
565
566 static const char months[] =
567     "jan\0"
568     "feb\0"
569     "mar\0"
570     "apr\0"
571     "may\0"
572     "jun\0"
573     "jul\0"
574     "aug\0"
575     "sep\0"
576     "oct\0"
577     "nov\0"
578     "dec\0"
579     "\0";
580
581 static inline bool isNumber(char s)
582 { return s >= '0' && s <= '9'; }
583
584 static inline bool isTerminator(char c)
585 { return c == '\n' || c == '\r'; }
586
587 static inline bool isValueSeparator(char c)
588 { return isTerminator(c) || c == ';'; }
589
590 static inline bool isWhitespace(char c)
591 { return c == ' '  || c == '\t'; }
592
593 static bool checkStaticArray(int &val, const QByteArray &dateString, int at, const char *array, int size)
594 {
595     if (dateString[at] < 'a' || dateString[at] > 'z')
596         return false;
597     if (val == -1 && dateString.length() >= at + 3) {
598         int j = 0;
599         int i = 0;
600         while (i <= size) {
601             const char *str = array + i;
602             if (str[0] == dateString[at]
603                 && str[1] == dateString[at + 1]
604                 && str[2] == dateString[at + 2]) {
605                 val = j;
606                 return true;
607             }
608             i += int(strlen(str)) + 1;
609             ++j;
610         }
611     }
612     return false;
613 }
614
615 //#define PARSEDATESTRINGDEBUG
616
617 #define ADAY   1
618 #define AMONTH 2
619 #define AYEAR  4
620
621 /*
622     Parse all the date formats that Firefox can.
623
624     The official format is:
625     expires=ddd(d)?, dd-MMM-yyyy hh:mm:ss GMT
626
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.
630
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.
636
637     Or in their own words:
638         "} // else what the hell is this."
639 */
640 static QDateTime parseDateString(const QByteArray &dateString)
641 {
642     QTime time;
643     // placeholders for values when we are not sure it is a year, month or day
644     int unknown[3] = {-1, -1, -1};
645     int month = -1;
646     int day = -1;
647     int year = -1;
648     int zoneOffset = -1;
649
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))|)"));
652
653     int at = 0;
654     while (at < dateString.length()) {
655 #ifdef PARSEDATESTRINGDEBUG
656         qDebug() << dateString.mid(at);
657 #endif
658         bool isNum = isNumber(dateString[at]);
659
660         // Month
661         if (!isNum
662             && checkStaticArray(month, dateString, at, months, sizeof(months)- 1)) {
663             ++month;
664 #ifdef PARSEDATESTRINGDEBUG
665             qDebug() << "Month:" << month;
666 #endif
667             at += 3;
668             continue;
669         }
670         // Zone
671         if (!isNum
672             && zoneOffset == -1
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;
678 #endif
679             at += 3;
680             continue;
681         }
682         // Zone offset
683         if (!isNum
684             && (zoneOffset == -1 || zoneOffset == 0) // Can only go after gmt
685             && (dateString[at] == '+' || dateString[at] == '-')
686             && (at == 0
687                 || isWhitespace(dateString[at - 1])
688                 || dateString[at - 1] == ','
689                 || (at >= 3
690                     && (dateString[at - 3] == 'g')
691                     && (dateString[at - 2] == 'm')
692                     && (dateString[at - 1] == 't')))) {
693
694             int end = 1;
695             while (end < 5 && dateString.length() > at+end
696                    && dateString[at + end] >= '0' && dateString[at + end] <= '9')
697                 ++end;
698             int minutes = 0;
699             int hours = 0;
700             switch (end - 1) {
701             case 4:
702                 minutes = atoi(dateString.mid(at + 3, 2).constData());
703                 // fall through
704             case 2:
705                 hours = atoi(dateString.mid(at + 1, 2).constData());
706                 break;
707             case 1:
708                 hours = atoi(dateString.mid(at + 1, 1).constData());
709                 break;
710             default:
711                 at += end;
712                 continue;
713             }
714             if (end != 1) {
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;
719 #endif
720                 at += end;
721                 continue;
722             }
723         }
724
725         // Time
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);
732             if (pos != -1) {
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"))
740                         h += 12;
741                 time = QTime(h, m, s, ms);
742 #ifdef PARSEDATESTRINGDEBUG
743                 qDebug() << "Time:" << list << timeRx.matchedLength();
744 #endif
745                 at += timeRx.matchedLength();
746                 continue;
747             }
748         }
749
750         // 4 digit Year
751         if (isNum
752             && year == -1
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());
758                 at += 4;
759 #ifdef PARSEDATESTRINGDEBUG
760                 qDebug() << "Year:" << year;
761 #endif
762                 continue;
763             }
764         }
765
766         // a one or two digit number
767         // Could be month, day or year
768         if (isNum) {
769             int length = 1;
770             if (dateString.length() > at + 1
771                 && isNumber(dateString[at + 1]))
772                 ++length;
773             int x = atoi(dateString.mid(at, length).constData());
774             if (year == -1 && (x > 31 || x == 0)) {
775                 year = x;
776             } else {
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;
780             }
781             at += length;
782 #ifdef PARSEDATESTRINGDEBUG
783             qDebug() << "Saving" << x;
784 #endif
785             continue;
786         }
787
788         // Unknown character, typically a weekday such as 'Mon'
789         ++at;
790     }
791
792     // Once we are done parsing the string take the digits in unknown
793     // and determine which is the unknown year/month/day
794
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;
800             unknownCount = i;
801             continue;
802         }
803
804         if (unknown[i] >= 1)
805             couldBe[i] = ADAY;
806
807         if (month == -1 && unknown[i] >= 1 && unknown[i] <= 12)
808             couldBe[i] |= AMONTH;
809
810         if (year == -1)
811             couldBe[i] |= AYEAR;
812     }
813
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.
818     // Example: 31 11 06
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)
825             continue;
826         for (int j = 0; j < 3; ++j) {
827             if (j == i)
828                 continue;
829             for (int k = 0; k < 2; ++k) {
830                 if (k == 0 && !(findMatchingMonth && (couldBe[j] & AMONTH)))
831                     continue;
832                 else if (k == 1 && !(findMatchingDay && (couldBe[j] & ADAY)))
833                     continue;
834                 int m = currentValue;
835                 int d = unknown[j];
836                 if (k == 0)
837                     qSwap(m, d);
838                 if (m == -1) m = month;
839                 bool found = true;
840                 switch(m) {
841                     case 2:
842                         // When we get 29 and the year ends up having only 28
843                         // See date.isValid below
844                         // Example: 29 23 Feb
845                         if (d <= 29)
846                             found = false;
847                         break;
848                     case 4: case 6: case 9: case 11:
849                         if (d <= 30)
850                             found = false;
851                         break;
852                     default:
853                         if (d > 0 && d <= 31)
854                             found = false;
855                 }
856                 if (k == 0) findMatchingMonth = found;
857                 else if (k == 1) findMatchingDay = found;
858             }
859         }
860         if (findMatchingMonth)
861             couldBe[i] &= ~ADAY;
862         if (findMatchingDay)
863             couldBe[i] &= ~AMONTH;
864     }
865
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) {
869         int unset = 0;
870         for (int j = 0; j < 3; ++j) {
871             if (couldBe[j] == ADAY && day == -1) {
872                 day = unknown[j];
873                 unset |= ADAY;
874             } else if (couldBe[j] == AMONTH && month == -1) {
875                 month = unknown[j];
876                 unset |= AMONTH;
877             } else if (couldBe[j] == AYEAR && year == -1) {
878                 year = unknown[j];
879                 unset |= AYEAR;
880             } else {
881                 // common case
882                 break;
883             }
884             couldBe[j] &= ~unset;
885         }
886     }
887
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];
893     }
894 #ifdef PARSEDATESTRINGDEBUG
895         qDebug() << "Final set" << year << month << day;
896 #endif
897
898     if (year == -1 || month == -1 || day == -1) {
899 #ifdef PARSEDATESTRINGDEBUG
900         qDebug() << "Parser failure" << year << month << day;
901 #endif
902         return QDateTime();
903     }
904
905     // Y2k behavior
906     int y2k = 0;
907     if (year < 70)
908         y2k = 2000;
909     else if (year < 100)
910         y2k = 1900;
911
912     QDate date(year + y2k, month, day);
913
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
919     if (!date.isValid())
920         date = QDate(day + y2k, month, year);
921
922     QDateTime dateTime(date, time, Qt::UTC);
923
924     if (zoneOffset != -1) {
925         dateTime = dateTime.addSecs(zoneOffset);
926     }
927     if (!dateTime.isValid())
928         return QDateTime();
929     return dateTime;
930 }
931
932 /*!
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.
936
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.
940
941     \sa toRawForm()
942 */
943 QList<QNetworkCookie> QNetworkCookie::parseCookies(const QByteArray &cookieString)
944 {
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));
951     return cookies;
952 }
953
954 QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByteArray &cookieString)
955 {
956     // According to http://wp.netscape.com/newsref/std/cookie_spec.html,<
957     // the Set-Cookie response header is of the format:
958     //
959     //   Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure
960     //
961     // where only the NAME=VALUE part is mandatory
962     //
963     // We do not support RFC 2965 Set-Cookie2-style cookies
964
965     QList<QNetworkCookie> result;
966     QDateTime now = QDateTime::currentDateTime().toUTC();
967
968     int position = 0;
969     const int length = cookieString.length();
970     while (position < length) {
971         QNetworkCookie cookie;
972
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())
976             // parsing error
977             break;
978         cookie.setName(field.first);
979         cookie.setValue(field.second);
980
981         position = nextNonWhitespace(cookieString, position);
982         while (position < length) {
983             switch (cookieString.at(position++)) {
984             case ';':
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
988
989                 if (field.first == "expires") {
990                     position -= field.second.length();
991                     int end;
992                     for (end = position; end < length; ++end)
993                         if (isValueSeparator(cookieString.at(end)))
994                             break;
995
996                     QByteArray dateString = cookieString.mid(position, end - position).trimmed();
997                     position = end;
998                     QDateTime dt = parseDateString(dateString.toLower());
999                     if (!dt.isValid()) {
1000                         return result;
1001                     }
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);
1009                     }
1010
1011                     QString normalizedDomain = QUrl::fromAce(QUrl::toAce(QString::fromUtf8(rawDomain)));
1012                     if (normalizedDomain.isEmpty() && !rawDomain.isEmpty())
1013                         return result;
1014                     cookie.setDomain(maybeLeadingDot + normalizedDomain);
1015                 } else if (field.first == "max-age") {
1016                     bool ok = false;
1017                     int secs = field.second.toInt(&ok);
1018                     if (!ok)
1019                         return result;
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);
1028                 } else {
1029                     // ignore unknown fields in the cookie (RFC6265 section 5.2, rule 6)
1030                 }
1031
1032                 position = nextNonWhitespace(cookieString, position);
1033             }
1034         }
1035
1036         if (!cookie.name().isEmpty())
1037             result += cookie;
1038     }
1039
1040     return result;
1041 }
1042
1043 /*!
1044     \since 5.0
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.
1047 */
1048 void QNetworkCookie::normalize(const QUrl &url)
1049 {
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;
1057     }
1058
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('.'));
1067 }
1068
1069 #ifndef QT_NO_DEBUG_STREAM
1070 QDebug operator<<(QDebug s, const QNetworkCookie &cookie)
1071 {
1072     s.nospace() << "QNetworkCookie(" << cookie.toRawForm(QNetworkCookie::Full) << ')';
1073     return s.space();
1074 }
1075 #endif
1076
1077 QT_END_NAMESPACE