source: trunk/src/network/access/qnetworkcookie.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

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