choke uploadProgress signals
[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 == ',' || 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 == ',' || 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 == ',' || 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. Multiple cookies are separated
479         by commas in a "Set-Cookie:" header.
480
481     Note that only the Full form of the cookie can be parsed back into
482     its original contents.
483
484     \sa toRawForm(), parseCookies()
485 */
486
487 /*!
488     Returns the raw form of this QNetworkCookie. The QByteArray
489     returned by this function is suitable for an HTTP header, either
490     in a server response (the Set-Cookie header) or the client request
491     (the Cookie header). You can choose from one of two formats, using
492     \a form.
493
494     \sa parseCookies()
495 */
496 QByteArray QNetworkCookie::toRawForm(RawForm form) const
497 {
498     QByteArray result;
499     if (d->name.isEmpty())
500         return result;          // not a valid cookie
501
502     result = d->name;
503     result += '=';
504     if ((d->value.contains(';') ||
505         d->value.contains(',') ||
506         d->value.contains('"')) &&
507         (!d->value.startsWith('"') &&
508         !d->value.endsWith('"'))) {
509         result += '"';
510
511         QByteArray value = d->value;
512         value.replace('"', "\\\"");
513         result += value;
514
515         result += '"';
516     } else {
517         result += d->value;
518     }
519
520     if (form == Full) {
521         // same as above, but encoding everything back
522         if (isSecure())
523             result += "; secure";
524         if (isHttpOnly())
525             result += "; HttpOnly";
526         if (!isSessionCookie()) {
527             result += "; expires=";
528             result += QLocale::c().toString(d->expirationDate.toUTC(),
529                                             QLatin1String("ddd, dd-MMM-yyyy hh:mm:ss 'GMT")).toLatin1();
530         }
531         if (!d->domain.isEmpty()) {
532             result += "; domain=";
533             QString domainNoDot = d->domain;
534             if (domainNoDot.startsWith(QLatin1Char('.'))) {
535                 result += '.';
536                 domainNoDot = domainNoDot.mid(1);
537             }
538             result += QUrl::toAce(domainNoDot);
539         }
540         if (!d->path.isEmpty()) {
541             result += "; path=";
542             result += QUrl::toPercentEncoding(d->path, "/");
543         }
544     }
545     return result;
546 }
547
548 static const char zones[] =
549     "pst\0" // -8
550     "pdt\0"
551     "mst\0" // -7
552     "mdt\0"
553     "cst\0" // -6
554     "cdt\0"
555     "est\0" // -5
556     "edt\0"
557     "ast\0" // -4
558     "nst\0" // -3
559     "gmt\0" // 0
560     "utc\0"
561     "bst\0"
562     "met\0" // 1
563     "eet\0" // 2
564     "jst\0" // 9
565     "\0";
566 static int zoneOffsets[] = {-8, -8, -7, -7, -6, -6, -5, -5, -4, -3, 0, 0, 0, 1, 2, 9 };
567
568 static const char months[] =
569     "jan\0"
570     "feb\0"
571     "mar\0"
572     "apr\0"
573     "may\0"
574     "jun\0"
575     "jul\0"
576     "aug\0"
577     "sep\0"
578     "oct\0"
579     "nov\0"
580     "dec\0"
581     "\0";
582
583 static inline bool isNumber(char s)
584 { return s >= '0' && s <= '9'; }
585
586 static inline bool isTerminator(char c)
587 { return c == '\n' || c == '\r'; }
588
589 static inline bool isValueSeparator(char c)
590 { return isTerminator(c) || c == ';'; }
591
592 static inline bool isWhitespace(char c)
593 { return c == ' '  || c == '\t'; }
594
595 static bool checkStaticArray(int &val, const QByteArray &dateString, int at, const char *array, int size)
596 {
597     if (dateString[at] < 'a' || dateString[at] > 'z')
598         return false;
599     if (val == -1 && dateString.length() >= at + 3) {
600         int j = 0;
601         int i = 0;
602         while (i <= size) {
603             const char *str = array + i;
604             if (str[0] == dateString[at]
605                 && str[1] == dateString[at + 1]
606                 && str[2] == dateString[at + 2]) {
607                 val = j;
608                 return true;
609             }
610             i += int(strlen(str)) + 1;
611             ++j;
612         }
613     }
614     return false;
615 }
616
617 //#define PARSEDATESTRINGDEBUG
618
619 #define ADAY   1
620 #define AMONTH 2
621 #define AYEAR  4
622
623 /*
624     Parse all the date formats that Firefox can.
625
626     The official format is:
627     expires=ddd(d)?, dd-MMM-yyyy hh:mm:ss GMT
628
629     But browsers have been supporting a very wide range of date
630     strings. To work on many sites we need to support more then
631     just the official date format.
632
633     For reference see Firefox's PR_ParseTimeStringToExplodedTime in
634     prtime.c. The Firefox date parser is coded in a very complex way
635     and is slightly over ~700 lines long.  While this implementation
636     will be slightly slower for the non standard dates it is smaller,
637     more readable, and maintainable.
638
639     Or in their own words:
640         "} // else what the hell is this."
641 */
642 static QDateTime parseDateString(const QByteArray &dateString)
643 {
644     QTime time;
645     // placeholders for values when we are not sure it is a year, month or day
646     int unknown[3] = {-1, -1, -1};
647     int month = -1;
648     int day = -1;
649     int year = -1;
650     int zoneOffset = -1;
651
652     // hour:minute:second.ms pm
653     QRegExp timeRx(QLatin1String("(\\d{1,2}):(\\d{1,2})(:(\\d{1,2})|)(\\.(\\d{1,3})|)((\\s{0,}(am|pm))|)"));
654
655     int at = 0;
656     while (at < dateString.length()) {
657 #ifdef PARSEDATESTRINGDEBUG
658         qDebug() << dateString.mid(at);
659 #endif
660         bool isNum = isNumber(dateString[at]);
661
662         // Month
663         if (!isNum
664             && checkStaticArray(month, dateString, at, months, sizeof(months)- 1)) {
665             ++month;
666 #ifdef PARSEDATESTRINGDEBUG
667             qDebug() << "Month:" << month;
668 #endif
669             at += 3;
670             continue;
671         }
672         // Zone
673         if (!isNum
674             && zoneOffset == -1
675             && checkStaticArray(zoneOffset, dateString, at, zones, sizeof(zones)- 1)) {
676             int sign = (at >= 0 && dateString[at - 1] == '-') ? -1 : 1;
677             zoneOffset = sign * zoneOffsets[zoneOffset] * 60 * 60;
678 #ifdef PARSEDATESTRINGDEBUG
679             qDebug() << "Zone:" << month;
680 #endif
681             at += 3;
682             continue;
683         }
684         // Zone offset
685         if (!isNum
686             && (zoneOffset == -1 || zoneOffset == 0) // Can only go after gmt
687             && (dateString[at] == '+' || dateString[at] == '-')
688             && (at == 0
689                 || isWhitespace(dateString[at - 1])
690                 || dateString[at - 1] == ','
691                 || (at >= 3
692                     && (dateString[at - 3] == 'g')
693                     && (dateString[at - 2] == 'm')
694                     && (dateString[at - 1] == 't')))) {
695
696             int end = 1;
697             while (end < 5 && dateString.length() > at+end
698                    && dateString[at + end] >= '0' && dateString[at + end] <= '9')
699                 ++end;
700             int minutes = 0;
701             int hours = 0;
702             switch (end - 1) {
703             case 4:
704                 minutes = atoi(dateString.mid(at + 3, 2).constData());
705                 // fall through
706             case 2:
707                 hours = atoi(dateString.mid(at + 1, 2).constData());
708                 break;
709             case 1:
710                 hours = atoi(dateString.mid(at + 1, 1).constData());
711                 break;
712             default:
713                 at += end;
714                 continue;
715             }
716             if (end != 1) {
717                 int sign = dateString[at] == '-' ? -1 : 1;
718                 zoneOffset = sign * ((minutes * 60) + (hours * 60 * 60));
719 #ifdef PARSEDATESTRINGDEBUG
720                 qDebug() << "Zone offset:" << zoneOffset << hours << minutes;
721 #endif
722                 at += end;
723                 continue;
724             }
725         }
726
727         // Time
728         if (isNum && time.isNull()
729             && dateString.length() >= at + 3
730             && (dateString[at + 2] == ':' || dateString[at + 1] == ':')) {
731             // While the date can be found all over the string the format
732             // for the time is set and a nice regexp can be used.
733             int pos = timeRx.indexIn(QLatin1String(dateString), at);
734             if (pos != -1) {
735                 QStringList list = timeRx.capturedTexts();
736                 int h = atoi(list.at(1).toLatin1().constData());
737                 int m = atoi(list.at(2).toLatin1().constData());
738                 int s = atoi(list.at(4).toLatin1().constData());
739                 int ms = atoi(list.at(6).toLatin1().constData());
740                 if (h < 12 && !list.at(9).isEmpty())
741                     if (list.at(9) == QLatin1String("pm"))
742                         h += 12;
743                 time = QTime(h, m, s, ms);
744 #ifdef PARSEDATESTRINGDEBUG
745                 qDebug() << "Time:" << list << timeRx.matchedLength();
746 #endif
747                 at += timeRx.matchedLength();
748                 continue;
749             }
750         }
751
752         // 4 digit Year
753         if (isNum
754             && year == -1
755             && dateString.length() > at + 3) {
756             if (isNumber(dateString[at + 1])
757                 && isNumber(dateString[at + 2])
758                 && isNumber(dateString[at + 3])) {
759                 year = atoi(dateString.mid(at, 4).constData());
760                 at += 4;
761 #ifdef PARSEDATESTRINGDEBUG
762                 qDebug() << "Year:" << year;
763 #endif
764                 continue;
765             }
766         }
767
768         // a one or two digit number
769         // Could be month, day or year
770         if (isNum) {
771             int length = 1;
772             if (dateString.length() > at + 1
773                 && isNumber(dateString[at + 1]))
774                 ++length;
775             int x = atoi(dateString.mid(at, length).constData());
776             if (year == -1 && (x > 31 || x == 0)) {
777                 year = x;
778             } else {
779                 if (unknown[0] == -1) unknown[0] = x;
780                 else if (unknown[1] == -1) unknown[1] = x;
781                 else if (unknown[2] == -1) unknown[2] = x;
782             }
783             at += length;
784 #ifdef PARSEDATESTRINGDEBUG
785             qDebug() << "Saving" << x;
786 #endif
787             continue;
788         }
789
790         // Unknown character, typically a weekday such as 'Mon'
791         ++at;
792     }
793
794     // Once we are done parsing the string take the digits in unknown
795     // and determine which is the unknown year/month/day
796
797     int couldBe[3] = { 0, 0, 0 };
798     int unknownCount = 3;
799     for (int i = 0; i < unknownCount; ++i) {
800         if (unknown[i] == -1) {
801             couldBe[i] = ADAY | AYEAR | AMONTH;
802             unknownCount = i;
803             continue;
804         }
805
806         if (unknown[i] >= 1)
807             couldBe[i] = ADAY;
808
809         if (month == -1 && unknown[i] >= 1 && unknown[i] <= 12)
810             couldBe[i] |= AMONTH;
811
812         if (year == -1)
813             couldBe[i] |= AYEAR;
814     }
815
816     // For any possible day make sure one of the values that could be a month
817     // can contain that day.
818     // For any possible month make sure one of the values that can be a
819     // day that month can have.
820     // Example: 31 11 06
821     // 31 can't be a day because 11 and 6 don't have 31 days
822     for (int i = 0; i < unknownCount; ++i) {
823         int currentValue = unknown[i];
824         bool findMatchingMonth = couldBe[i] & ADAY && currentValue >= 29;
825         bool findMatchingDay = couldBe[i] & AMONTH;
826         if (!findMatchingMonth || !findMatchingDay)
827             continue;
828         for (int j = 0; j < 3; ++j) {
829             if (j == i)
830                 continue;
831             for (int k = 0; k < 2; ++k) {
832                 if (k == 0 && !(findMatchingMonth && (couldBe[j] & AMONTH)))
833                     continue;
834                 else if (k == 1 && !(findMatchingDay && (couldBe[j] & ADAY)))
835                     continue;
836                 int m = currentValue;
837                 int d = unknown[j];
838                 if (k == 0)
839                     qSwap(m, d);
840                 if (m == -1) m = month;
841                 bool found = true;
842                 switch(m) {
843                     case 2:
844                         // When we get 29 and the year ends up having only 28
845                         // See date.isValid below
846                         // Example: 29 23 Feb
847                         if (d <= 29)
848                             found = false;
849                         break;
850                     case 4: case 6: case 9: case 11:
851                         if (d <= 30)
852                             found = false;
853                         break;
854                     default:
855                         if (d > 0 && d <= 31)
856                             found = false;
857                 }
858                 if (k == 0) findMatchingMonth = found;
859                 else if (k == 1) findMatchingDay = found;
860             }
861         }
862         if (findMatchingMonth)
863             couldBe[i] &= ~ADAY;
864         if (findMatchingDay)
865             couldBe[i] &= ~AMONTH;
866     }
867
868     // First set the year/month/day that have been deduced
869     // and reduce the set as we go along to deduce more
870     for (int i = 0; i < unknownCount; ++i) {
871         int unset = 0;
872         for (int j = 0; j < 3; ++j) {
873             if (couldBe[j] == ADAY && day == -1) {
874                 day = unknown[j];
875                 unset |= ADAY;
876             } else if (couldBe[j] == AMONTH && month == -1) {
877                 month = unknown[j];
878                 unset |= AMONTH;
879             } else if (couldBe[j] == AYEAR && year == -1) {
880                 year = unknown[j];
881                 unset |= AYEAR;
882             } else {
883                 // common case
884                 break;
885             }
886             couldBe[j] &= ~unset;
887         }
888     }
889
890     // Now fallback to a standardized order to fill in the rest with
891     for (int i = 0; i < unknownCount; ++i) {
892         if (couldBe[i] & AMONTH && month == -1) month = unknown[i];
893         else if (couldBe[i] & ADAY && day == -1) day = unknown[i];
894         else if (couldBe[i] & AYEAR && year == -1) year = unknown[i];
895     }
896 #ifdef PARSEDATESTRINGDEBUG
897         qDebug() << "Final set" << year << month << day;
898 #endif
899
900     if (year == -1 || month == -1 || day == -1) {
901 #ifdef PARSEDATESTRINGDEBUG
902         qDebug() << "Parser failure" << year << month << day;
903 #endif
904         return QDateTime();
905     }
906
907     // Y2k behavior
908     int y2k = 0;
909     if (year < 70)
910         y2k = 2000;
911     else if (year < 100)
912         y2k = 1900;
913
914     QDate date(year + y2k, month, day);
915
916     // When we were given a bad cookie that when parsed
917     // set the day to 29 and the year to one that doesn't
918     // have the 29th of Feb rather then adding the extra
919     // complicated checking earlier just swap here.
920     // Example: 29 23 Feb
921     if (!date.isValid())
922         date = QDate(day + y2k, month, year);
923
924     QDateTime dateTime(date, time, Qt::UTC);
925
926     if (zoneOffset != -1) {
927         dateTime = dateTime.addSecs(zoneOffset);
928     }
929     if (!dateTime.isValid())
930         return QDateTime();
931     return dateTime;
932 }
933
934 /*!
935     Parses the cookie string \a cookieString as received from a server
936     response in the "Set-Cookie:" header. If there's a parsing error,
937     this function returns an empty list.
938
939     Since the HTTP header can set more than one cookie at the same
940     time, this function returns a QList<QNetworkCookie>, one for each
941     cookie that is parsed.
942
943     \sa toRawForm()
944 */
945 QList<QNetworkCookie> QNetworkCookie::parseCookies(const QByteArray &cookieString)
946 {
947     // cookieString can be a number of set-cookie header strings joined together
948     // by \n, parse each line separately.
949     QList<QNetworkCookie> cookies;
950     QList<QByteArray> list = cookieString.split('\n');
951     for (int a = 0; a < list.size(); a++)
952         cookies += QNetworkCookiePrivate::parseSetCookieHeaderLine(list.at(a));
953     return cookies;
954 }
955
956 QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByteArray &cookieString)
957 {
958     // According to http://wp.netscape.com/newsref/std/cookie_spec.html,<
959     // the Set-Cookie response header is of the format:
960     //
961     //   Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure
962     //
963     // where only the NAME=VALUE part is mandatory
964     //
965     // We do not support RFC 2965 Set-Cookie2-style cookies
966
967     QList<QNetworkCookie> result;
968     QDateTime now = QDateTime::currentDateTime().toUTC();
969
970     int position = 0;
971     const int length = cookieString.length();
972     while (position < length) {
973         QNetworkCookie cookie;
974
975         // The first part is always the "NAME=VALUE" part
976         QPair<QByteArray,QByteArray> field = nextField(cookieString, position, true);
977         if (field.first.isEmpty() || field.second.isNull())
978             // parsing error
979             break;
980         cookie.setName(field.first);
981         cookie.setValue(field.second);
982
983         position = nextNonWhitespace(cookieString, position);
984         bool endOfCookie = false;
985         while (!endOfCookie && position < length) {
986             switch (cookieString.at(position++)) {
987             case ',':
988                 // end of the cookie
989                 endOfCookie = true;
990                 break;
991
992             case ';':
993                 // new field in the cookie
994                 field = nextField(cookieString, position, false);
995                 field.first = field.first.toLower(); // everything but the NAME=VALUE is case-insensitive
996
997                 if (field.first == "expires") {
998                     position -= field.second.length();
999                     int end;
1000                     for (end = position; end < length; ++end)
1001                         if (isValueSeparator(cookieString.at(end)))
1002                             break;
1003
1004                     QByteArray dateString = cookieString.mid(position, end - position).trimmed();
1005                     position = end;
1006                     QDateTime dt = parseDateString(dateString.toLower());
1007                     if (!dt.isValid()) {
1008                         return result;
1009                     }
1010                     cookie.setExpirationDate(dt);
1011                 } else if (field.first == "domain") {
1012                     QByteArray rawDomain = field.second;
1013                     QString maybeLeadingDot;
1014                     if (rawDomain.startsWith('.')) {
1015                         maybeLeadingDot = QLatin1Char('.');
1016                         rawDomain = rawDomain.mid(1);
1017                     }
1018
1019                     QString normalizedDomain = QUrl::fromAce(QUrl::toAce(QString::fromUtf8(rawDomain)));
1020                     if (normalizedDomain.isEmpty() && !rawDomain.isEmpty())
1021                         return result;
1022                     cookie.setDomain(maybeLeadingDot + normalizedDomain);
1023                 } else if (field.first == "max-age") {
1024                     bool ok = false;
1025                     int secs = field.second.toInt(&ok);
1026                     if (!ok)
1027                         return result;
1028                     cookie.setExpirationDate(now.addSecs(secs));
1029                 } else if (field.first == "path") {
1030                     QString path = QUrl::fromPercentEncoding(field.second);
1031                     cookie.setPath(path);
1032                 } else if (field.first == "secure") {
1033                     cookie.setSecure(true);
1034                 } else if (field.first == "httponly") {
1035                     cookie.setHttpOnly(true);
1036                 } else if (field.first == "comment") {
1037                     //cookie.setComment(QString::fromUtf8(field.second));
1038                 } else if (field.first == "version") {
1039                     if (field.second != "1") {
1040                         // oops, we don't know how to handle this cookie
1041                         return result;
1042                     }
1043                 } else {
1044                     // got an unknown field in the cookie
1045                     // what do we do?
1046                 }
1047
1048                 position = nextNonWhitespace(cookieString, position);
1049             }
1050         }
1051
1052         if (!cookie.name().isEmpty())
1053             result += cookie;
1054     }
1055
1056     return result;
1057 }
1058
1059 /*!
1060     \since 5.0
1061     This functions normalizes the path and domain of the cookie if they were previously empty.
1062     The \a url parameter is used to determine the correct domain and path.
1063 */
1064 void QNetworkCookie::normalize(const QUrl &url)
1065 {
1066     // don't do path checking. See http://bugreports.qt-project.org/browse/QTBUG-5815
1067     if (d->path.isEmpty()) {
1068         QString pathAndFileName = url.path();
1069         QString defaultPath = pathAndFileName.left(pathAndFileName.lastIndexOf(QLatin1Char('/'))+1);
1070         if (defaultPath.isEmpty())
1071             defaultPath = QLatin1Char('/');
1072         d->path = defaultPath;
1073     }
1074
1075     if (d->domain.isEmpty())
1076         d->domain = url.host();
1077     else if (!d->domain.startsWith(QLatin1Char('.')))
1078         // Ensure the domain starts with a dot if its field was not empty
1079         // in the HTTP header. There are some servers that forget the
1080         // leading dot and this is actually forbidden according to RFC 2109,
1081         // but all browsers accept it anyway so we do that as well.
1082         d->domain.prepend(QLatin1Char('.'));
1083 }
1084
1085 #ifndef QT_NO_DEBUG_STREAM
1086 QDebug operator<<(QDebug s, const QNetworkCookie &cookie)
1087 {
1088     s.nospace() << "QNetworkCookie(" << cookie.toRawForm(QNetworkCookie::Full) << ')';
1089     return s.space();
1090 }
1091 #endif
1092
1093 QT_END_NAMESPACE