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

Last change on this file since 428 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 40.1 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the QtNetwork module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** 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 are unsure which license is appropriate for your use, please
37** contact the sales department 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 result += QUrl::toAce(d->domain);
508 }
509 if (!d->path.isEmpty()) {
510 result += "; path=";
511 result += QUrl::toPercentEncoding(d->path, "/");
512 }
513 }
514 return result;
515}
516
517static const char zones[] =
518 "pst\0" // -8
519 "pdt\0"
520 "mst\0" // -7
521 "mdt\0"
522 "cst\0" // -6
523 "cdt\0"
524 "est\0" // -5
525 "edt\0"
526 "ast\0" // -4
527 "nst\0" // -3
528 "gmt\0" // 0
529 "utc\0"
530 "bst\0"
531 "met\0" // 1
532 "eet\0" // 2
533 "jst\0" // 9
534 "\0";
535static int zoneOffsets[] = {-8, -8, -7, -7, -6, -6, -5, -5, -4, -3, 0, 0, 0, 1, 2, 9 };
536
537static const char months[] =
538 "jan\0"
539 "feb\0"
540 "mar\0"
541 "apr\0"
542 "may\0"
543 "jun\0"
544 "jul\0"
545 "aug\0"
546 "sep\0"
547 "oct\0"
548 "nov\0"
549 "dec\0"
550 "\0";
551
552static inline bool isNumber(char s)
553{ return s >= '0' && s <= '9'; }
554
555static inline bool isTerminator(char c)
556{ return c == '\n' || c == '\r'; }
557
558static inline bool isValueSeparator(char c)
559{ return isTerminator(c) || c == ';'; }
560
561static inline bool isWhitespace(char c)
562{ return c == ' ' || c == '\t'; }
563
564static bool checkStaticArray(int &val, const QByteArray &dateString, int at, const char *array, int size)
565{
566 if (dateString[at] < 'a' || dateString[at] > 'z')
567 return false;
568 if (val == -1 && dateString.length() >= at + 3) {
569 int j = 0;
570 int i = 0;
571 while (i <= size) {
572 const char *str = array + i;
573 if (str[0] == dateString[at]
574 && str[1] == dateString[at + 1]
575 && str[2] == dateString[at + 2]) {
576 val = j;
577 return true;
578 }
579 i += strlen(str) + 1;
580 ++j;
581 }
582 }
583 return false;
584}
585
586//#define PARSEDATESTRINGDEBUG
587
588#define ADAY 1
589#define AMONTH 2
590#define AYEAR 4
591
592/*
593 Parse all the date formats that Firefox can.
594
595 The official format is:
596 expires=ddd(d)?, dd-MMM-yyyy hh:mm:ss GMT
597
598 But browsers have been supporting a very wide range of date
599 strings. To work on many sites we need to support more then
600 just the official date format.
601
602 For reference see Firefox's PR_ParseTimeStringToExplodedTime in
603 prtime.c. The Firefox date parser is coded in a very complex way
604 and is slightly over ~700 lines long. While this implementation
605 will be slightly slower for the non standard dates it is smaller,
606 more readable, and maintainable.
607
608 Or in their own words:
609 "} // else what the hell is this."
610*/
611static QDateTime parseDateString(const QByteArray &dateString)
612{
613 QTime time;
614 // placeholders for values when we are not sure it is a year, month or day
615 int unknown[3] = {-1, -1, -1};
616 int month = -1;
617 int day = -1;
618 int year = -1;
619 int zoneOffset = -1;
620
621 // hour:minute:second.ms pm
622 QRegExp timeRx(QLatin1String("(\\d{1,2}):(\\d{1,2})(:(\\d{1,2})|)(\\.(\\d{1,3})|)((\\s{0,}(am|pm))|)"));
623
624 int at = 0;
625 while (at < dateString.length()) {
626#ifdef PARSEDATESTRINGDEBUG
627 qDebug() << dateString.mid(at);
628#endif
629 bool isNum = isNumber(dateString[at]);
630
631 // Month
632 if (!isNum
633 && checkStaticArray(month, dateString, at, months, sizeof(months)- 1)) {
634 ++month;
635#ifdef PARSEDATESTRINGDEBUG
636 qDebug() << "Month:" << month;
637#endif
638 at += 3;
639 continue;
640 }
641 // Zone
642 if (!isNum
643 && zoneOffset == -1
644 && checkStaticArray(zoneOffset, dateString, at, zones, sizeof(zones)- 1)) {
645 int sign = (at >= 0 && dateString[at - 1] == '-') ? -1 : 1;
646 zoneOffset = sign * zoneOffsets[zoneOffset] * 60 * 60;
647#ifdef PARSEDATESTRINGDEBUG
648 qDebug() << "Zone:" << month;
649#endif
650 at += 3;
651 continue;
652 }
653 // Zone offset
654 if (!isNum
655 && (zoneOffset == -1 || zoneOffset == 0) // Can only go after gmt
656 && (dateString[at] == '+' || dateString[at] == '-')
657 && (at == 0
658 || isWhitespace(dateString[at - 1])
659 || dateString[at - 1] == ','
660 || (at >= 3
661 && (dateString[at - 3] == 'g')
662 && (dateString[at - 2] == 'm')
663 && (dateString[at - 1] == 't')))) {
664
665 int end = 1;
666 while (end < 5 && dateString.length() > at+end
667 && dateString[at + end] >= '0' && dateString[at + end] <= '9')
668 ++end;
669 int minutes = 0;
670 int hours = 0;
671 switch (end - 1) {
672 case 4:
673 minutes = atoi(dateString.mid(at + 3, 2).constData());
674 // fall through
675 case 2:
676 hours = atoi(dateString.mid(at + 1, 2).constData());
677 break;
678 case 1:
679 hours = atoi(dateString.mid(at + 1, 1).constData());
680 break;
681 default:
682 at += end;
683 continue;
684 }
685 if (end != 1) {
686 int sign = dateString[at] == '-' ? -1 : 1;
687 zoneOffset = sign * ((minutes * 60) + (hours * 60 * 60));
688#ifdef PARSEDATESTRINGDEBUG
689 qDebug() << "Zone offset:" << zoneOffset << hours << minutes;
690#endif
691 at += end;
692 continue;
693 }
694 }
695
696 // Time
697 if (isNum && time.isNull()
698 && dateString.length() >= at + 3
699 && (dateString[at + 2] == ':' || dateString[at + 1] == ':')) {
700 // While the date can be found all over the string the format
701 // for the time is set and a nice regexp can be used.
702 int pos = timeRx.indexIn(QLatin1String(dateString), at);
703 if (pos != -1) {
704 QStringList list = timeRx.capturedTexts();
705 int h = atoi(list.at(1).toLatin1().constData());
706 int m = atoi(list.at(2).toLatin1().constData());
707 int s = atoi(list.at(4).toLatin1().constData());
708 int ms = atoi(list.at(6).toLatin1().constData());
709 if (h < 12 && !list.at(9).isEmpty())
710 if (list.at(9) == QLatin1String("pm"))
711 h += 12;
712 time = QTime(h, m, s, ms);
713#ifdef PARSEDATESTRINGDEBUG
714 qDebug() << "Time:" << list << timeRx.matchedLength();
715#endif
716 at += timeRx.matchedLength();
717 continue;
718 }
719 }
720
721 // 4 digit Year
722 if (isNum
723 && year == -1
724 && dateString.length() >= at + 3) {
725 if (isNumber(dateString[at + 1])
726 && isNumber(dateString[at + 2])
727 && isNumber(dateString[at + 3])) {
728 year = atoi(dateString.mid(at, 4).constData());
729 at += 4;
730#ifdef PARSEDATESTRINGDEBUG
731 qDebug() << "Year:" << year;
732#endif
733 continue;
734 }
735 }
736
737 // a one or two digit number
738 // Could be month, day or year
739 if (isNum) {
740 int length = 1;
741 if (dateString.length() > at + 1
742 && isNumber(dateString[at + 1]))
743 ++length;
744 int x = atoi(dateString.mid(at, length).constData());
745 if (year == -1 && (x > 31 || x == 0)) {
746 year = x;
747 } else {
748 if (unknown[0] == -1) unknown[0] = x;
749 else if (unknown[1] == -1) unknown[1] = x;
750 else if (unknown[2] == -1) unknown[2] = x;
751 }
752 at += length;
753#ifdef PARSEDATESTRINGDEBUG
754 qDebug() << "Saving" << x;
755#endif
756 continue;
757 }
758
759 // Unknown character, typically a weekday such as 'Mon'
760 ++at;
761 }
762
763 // Once we are done parsing the string take the digits in unknown
764 // and determine which is the unknown year/month/day
765
766 int couldBe[3] = { 0, 0, 0 };
767 int unknownCount = 3;
768 for (int i = 0; i < unknownCount; ++i) {
769 if (unknown[i] == -1) {
770 couldBe[i] = ADAY | AYEAR | AMONTH;
771 unknownCount = i;
772 continue;
773 }
774
775 if (unknown[i] >= 1)
776 couldBe[i] = ADAY;
777
778 if (month == -1 && unknown[i] >= 1 && unknown[i] <= 12)
779 couldBe[i] |= AMONTH;
780
781 if (year == -1)
782 couldBe[i] |= AYEAR;
783 }
784
785 // For any possible day make sure one of the values that could be a month
786 // can contain that day.
787 // For any possible month make sure one of the values that can be a
788 // day that month can have.
789 // Example: 31 11 06
790 // 31 can't be a day because 11 and 6 don't have 31 days
791 for (int i = 0; i < unknownCount; ++i) {
792 int currentValue = unknown[i];
793 bool findMatchingMonth = couldBe[i] & ADAY && currentValue >= 29;
794 bool findMatchingDay = couldBe[i] & AMONTH;
795 if (!findMatchingMonth || !findMatchingDay)
796 continue;
797 for (int j = 0; j < 3; ++j) {
798 if (j == i)
799 continue;
800 for (int k = 0; k < 2; ++k) {
801 if (k == 0 && !(findMatchingMonth && (couldBe[j] & AMONTH)))
802 continue;
803 else if (k == 1 && !(findMatchingDay && (couldBe[j] & ADAY)))
804 continue;
805 int m = currentValue;
806 int d = unknown[j];
807 if (k == 0)
808 qSwap(m, d);
809 if (m == -1) m = month;
810 bool found = true;
811 switch(m) {
812 case 2:
813 // When we get 29 and the year ends up having only 28
814 // See date.isValid below
815 // Example: 29 23 Feb
816 if (d <= 29)
817 found = false;
818 break;
819 case 4: case 6: case 9: case 11:
820 if (d <= 30)
821 found = false;
822 break;
823 default:
824 if (d > 0 && d <= 31)
825 found = false;
826 }
827 if (k == 0) findMatchingMonth = found;
828 else if (k == 1) findMatchingDay = found;
829 }
830 }
831 if (findMatchingMonth)
832 couldBe[i] &= ~ADAY;
833 if (findMatchingDay)
834 couldBe[i] &= ~AMONTH;
835 }
836
837 // First set the year/month/day that have been deduced
838 // and reduce the set as we go along to deduce more
839 for (int i = 0; i < unknownCount; ++i) {
840 int unset = 0;
841 for (int j = 0; j < 3; ++j) {
842 if (couldBe[j] == ADAY && day == -1) {
843 day = unknown[j];
844 unset |= ADAY;
845 } else if (couldBe[j] == AMONTH && month == -1) {
846 month = unknown[j];
847 unset |= AMONTH;
848 } else if (couldBe[j] == AYEAR && year == -1) {
849 year = unknown[j];
850 unset |= AYEAR;
851 } else {
852 // common case
853 break;
854 }
855 couldBe[j] &= ~unset;
856 }
857 }
858
859 // Now fallback to a standardized order to fill in the rest with
860 for (int i = 0; i < unknownCount; ++i) {
861 if (couldBe[i] & AMONTH && month == -1) month = unknown[i];
862 else if (couldBe[i] & ADAY && day == -1) day = unknown[i];
863 else if (couldBe[i] & AYEAR && year == -1) year = unknown[i];
864 }
865#ifdef PARSEDATESTRINGDEBUG
866 qDebug() << "Final set" << year << month << day;
867#endif
868
869 if (year == -1 || month == -1 || day == -1) {
870#ifdef PARSEDATESTRINGDEBUG
871 qDebug() << "Parser failure" << year << month << day;
872#endif
873 return QDateTime();
874 }
875
876 // Y2k behavior
877 int y2k = 0;
878 if (year < 70)
879 y2k = 2000;
880 else if (year < 100)
881 y2k = 1900;
882
883 QDate date(year + y2k, month, day);
884
885 // When we were given a bad cookie that when parsed
886 // set the day to 29 and the year to one that doesn't
887 // have the 29th of Feb rather then adding the extra
888 // complicated checking earlier just swap here.
889 // Example: 29 23 Feb
890 if (!date.isValid())
891 date = QDate(day + y2k, month, year);
892
893 QDateTime dateTime(date, time, Qt::UTC);
894
895 if (zoneOffset != -1) {
896 dateTime = dateTime.addSecs(zoneOffset);
897 }
898 if (!dateTime.isValid())
899 return QDateTime();
900 return dateTime;
901}
902
903/*!
904 Parses the cookie string \a cookieString as received from a server
905 response in the "Set-Cookie:" header. If there's a parsing error,
906 this function returns an empty list.
907
908 Since the HTTP header can set more than one cookie at the same
909 time, this function returns a QList<QNetworkCookie>, one for each
910 cookie that is parsed.
911
912 \sa toRawForm()
913*/
914QList<QNetworkCookie> QNetworkCookie::parseCookies(const QByteArray &cookieString)
915{
916 // According to http://wp.netscape.com/newsref/std/cookie_spec.html,<
917 // the Set-Cookie response header is of the format:
918 //
919 // Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure
920 //
921 // where only the NAME=VALUE part is mandatory
922 //
923 // We do not support RFC 2965 Set-Cookie2-style cookies
924
925 QList<QNetworkCookie> result;
926 QDateTime now = QDateTime::currentDateTime().toUTC();
927
928 int position = 0;
929 const int length = cookieString.length();
930 while (position < length) {
931 QNetworkCookie cookie;
932
933 // When there are multiple SetCookie headers they are join with a new line
934 // \n will always be the start of a new cookie
935 int endOfSetCookie = cookieString.indexOf('\n', position);
936 if (endOfSetCookie == -1)
937 endOfSetCookie = length;
938
939 // The first part is always the "NAME=VALUE" part
940 QPair<QByteArray,QByteArray> field = nextField(cookieString, position);
941 if (field.first.isEmpty() || field.second.isNull())
942 // parsing error
943 break;
944 cookie.setName(field.first);
945 cookie.setValue(field.second);
946
947 position = nextNonWhitespace(cookieString, position);
948 bool endOfCookie = false;
949 while (!endOfCookie && position < endOfSetCookie)
950 switch (cookieString.at(position++)) {
951 case ',':
952 // end of the cookie
953 endOfCookie = true;
954 break;
955
956 case ';':
957 // new field in the cookie
958 field = nextField(cookieString, position);
959 field.first = field.first.toLower(); // everything but the NAME=VALUE is case-insensitive
960
961 if (field.first == "expires") {
962 position -= field.second.length();
963 int end;
964 for (end = position; end < length; ++end)
965 if (isValueSeparator(cookieString.at(end)))
966 break;
967
968 QByteArray dateString = cookieString.mid(position, end - position).trimmed();
969 position = end;
970 QDateTime dt = parseDateString(dateString.toLower());
971 if (!dt.isValid()) {
972 cookie = QNetworkCookie();
973 endOfCookie = true;
974 continue;
975 }
976 cookie.setExpirationDate(dt);
977 } else if (field.first == "domain") {
978 QByteArray rawDomain = field.second;
979 QString maybeLeadingDot;
980 if (rawDomain.startsWith('.')) {
981 maybeLeadingDot = QLatin1Char('.');
982 rawDomain = rawDomain.mid(1);
983 }
984
985 QString normalizedDomain = QUrl::fromAce(QUrl::toAce(QString::fromUtf8(rawDomain)));
986 cookie.setDomain(maybeLeadingDot + normalizedDomain);
987 } else if (field.first == "max-age") {
988 bool ok = false;
989 int secs = field.second.toInt(&ok);
990 if (!ok)
991 // invalid cookie string
992 return QList<QNetworkCookie>();
993 cookie.setExpirationDate(now.addSecs(secs));
994 } else if (field.first == "path") {
995 QString path = QUrl::fromPercentEncoding(field.second);
996 cookie.setPath(path);
997 } else if (field.first == "secure") {
998 cookie.setSecure(true);
999 } else if (field.first == "httponly") {
1000 cookie.setHttpOnly(true);
1001 } else if (field.first == "comment") {
1002 //cookie.setComment(QString::fromUtf8(field.second));
1003 } else if (field.first == "version") {
1004 if (field.second != "1") {
1005 // oops, we don't know how to handle this cookie
1006 cookie = QNetworkCookie();
1007 endOfCookie = true;
1008 continue;
1009 }
1010 } else {
1011 // got an unknown field in the cookie
1012 // what do we do?
1013 }
1014
1015 position = nextNonWhitespace(cookieString, position);
1016 if (position > endOfSetCookie)
1017 endOfCookie = true;
1018 }
1019
1020 if (!cookie.name().isEmpty())
1021 result += cookie;
1022 }
1023
1024 return result;
1025}
1026
1027#ifndef QT_NO_DEBUG_STREAM
1028QDebug operator<<(QDebug s, const QNetworkCookie &cookie)
1029{
1030 s.nospace() << "QNetworkCookie(" << cookie.toRawForm(QNetworkCookie::Full) << ")";
1031 return s.space();
1032}
1033#endif
1034
1035
1036
1037class QNetworkCookieJarPrivate: public QObjectPrivate
1038{
1039public:
1040 QList<QNetworkCookie> allCookies;
1041
1042 Q_DECLARE_PUBLIC(QNetworkCookieJar)
1043};
1044
1045/*!
1046 \class QNetworkCookieJar
1047 \brief The QNetworkCookieJar class implements a simple jar of QNetworkCookie objects
1048 \since 4.4
1049
1050 Cookies are small bits of information that stateless protocols
1051 like HTTP use to maintain some persistent information across
1052 requests.
1053
1054 A cookie is set by a remote server when it replies to a request
1055 and it expects the same cookie to be sent back when further
1056 requests are sent.
1057
1058 The cookie jar is the object that holds all cookies set in
1059 previous requests. Web browsers save their cookie jars to disk in
1060 order to conserve permanent cookies across invocations of the
1061 application.
1062
1063 QNetworkCookieJar does not implement permanent storage: it only
1064 keeps the cookies in memory. Once the QNetworkCookieJar object is
1065 deleted, all cookies it held will be discarded as well. If you
1066 want to save the cookies, you should derive from this class and
1067 implement the saving to disk to your own storage format.
1068
1069 This class implements only the basic security recommended by the
1070 cookie specifications and does not implement any cookie acceptance
1071 policy (it accepts all cookies set by any requests). In order to
1072 override those rules, you should reimplement the
1073 cookiesForUrl() and setCookiesFromUrl() virtual
1074 functions. They are called by QNetworkReply and
1075 QNetworkAccessManager when they detect new cookies and when they
1076 require cookies.
1077
1078 \sa QNetworkCookie, QNetworkAccessManager, QNetworkReply,
1079 QNetworkRequest, QNetworkAccessManager::setCookieJar()
1080*/
1081
1082/*!
1083 Creates a QNetworkCookieJar object and sets the parent object to
1084 be \a parent.
1085
1086 The cookie jar is initialized to empty.
1087*/
1088QNetworkCookieJar::QNetworkCookieJar(QObject *parent)
1089 : QObject(*new QNetworkCookieJarPrivate, parent)
1090{
1091}
1092
1093/*!
1094 Destroys this cookie jar object and discards all cookies stored in
1095 it. Cookies are not saved to disk in the QNetworkCookieJar default
1096 implementation.
1097
1098 If you need to save the cookies to disk, you have to derive from
1099 QNetworkCookieJar and save the cookies to disk yourself.
1100*/
1101QNetworkCookieJar::~QNetworkCookieJar()
1102{
1103}
1104
1105/*!
1106 Returns all cookies stored in this cookie jar. This function is
1107 suitable for derived classes to save cookies to disk, as well as
1108 to implement cookie expiration and other policies.
1109
1110 \sa setAllCookies(), cookiesForUrl()
1111*/
1112QList<QNetworkCookie> QNetworkCookieJar::allCookies() const
1113{
1114 return d_func()->allCookies;
1115}
1116
1117/*!
1118 Sets the internal list of cookies held by this cookie jar to be \a
1119 cookieList. This function is suitable for derived classes to
1120 implement loading cookies from permanent storage, or their own
1121 cookie acceptance policies by reimplementing
1122 setCookiesFromUrl().
1123
1124 \sa allCookies(), setCookiesFromUrl()
1125*/
1126void QNetworkCookieJar::setAllCookies(const QList<QNetworkCookie> &cookieList)
1127{
1128 Q_D(QNetworkCookieJar);
1129 d->allCookies = cookieList;
1130}
1131
1132static inline bool isParentPath(QString path, QString reference)
1133{
1134 if (!path.endsWith(QLatin1Char('/')))
1135 path += QLatin1Char('/');
1136 if (!reference.endsWith(QLatin1Char('/')))
1137 reference += QLatin1Char('/');
1138 return path.startsWith(reference);
1139}
1140
1141static inline bool isParentDomain(QString domain, QString reference)
1142{
1143 if (!reference.startsWith(QLatin1Char('.')))
1144 return domain == reference;
1145
1146 return domain.endsWith(reference) || domain == reference.mid(1);
1147}
1148
1149/*!
1150 Adds the cookies in the list \a cookieList to this cookie
1151 jar. Default values for path and domain are taken from the \a
1152 url object.
1153
1154 Returns true if one or more cookes are set for url otherwise false.
1155
1156 If a cookie already exists in the cookie jar, it will be
1157 overridden by those in \a cookieList.
1158
1159 The default QNetworkCookieJar class implements only a very basic
1160 security policy (it makes sure that the cookies' domain and path
1161 match the reply's). To enhance the security policy with your own
1162 algorithms, override setCookiesFromUrl().
1163
1164 Also, QNetworkCookieJar does not have a maximum cookie jar
1165 size. Reimplement this function to discard older cookies to create
1166 room for new ones.
1167
1168 \sa cookiesForUrl(), QNetworkAccessManager::setCookieJar()
1169*/
1170bool QNetworkCookieJar::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList,
1171 const QUrl &url)
1172{
1173 Q_D(QNetworkCookieJar);
1174 QString defaultDomain = url.host();
1175 QString pathAndFileName = url.path();
1176 QString defaultPath = pathAndFileName.left(pathAndFileName.lastIndexOf(QLatin1Char('/'))+1);
1177 if (defaultPath.isEmpty())
1178 defaultPath = QLatin1Char('/');
1179
1180 int added = 0;
1181 QDateTime now = QDateTime::currentDateTime();
1182 foreach (QNetworkCookie cookie, cookieList) {
1183 bool isDeletion = !cookie.isSessionCookie() &&
1184 cookie.expirationDate() < now;
1185
1186 // validate the cookie & set the defaults if unset
1187 // (RFC 2965: "The request-URI MUST path-match the Path attribute of the cookie.")
1188 if (cookie.path().isEmpty())
1189 cookie.setPath(defaultPath);
1190 else if (!isParentPath(pathAndFileName, cookie.path()))
1191 continue; // not accepted
1192
1193 if (cookie.domain().isEmpty()) {
1194 cookie.setDomain(defaultDomain);
1195 } else {
1196 QString domain = cookie.domain();
1197 if (!(isParentDomain(domain, defaultDomain)
1198 || isParentDomain(defaultDomain, domain))) {
1199 continue; // not accepted
1200 }
1201 }
1202
1203 QList<QNetworkCookie>::Iterator it = d->allCookies.begin(),
1204 end = d->allCookies.end();
1205 for ( ; it != end; ++it)
1206 // does this cookie already exist?
1207 if (cookie.name() == it->name() &&
1208 cookie.domain() == it->domain() &&
1209 cookie.path() == it->path()) {
1210 // found a match
1211 d->allCookies.erase(it);
1212 break;
1213 }
1214
1215 // did not find a match
1216 if (!isDeletion) {
1217 d->allCookies += cookie;
1218 ++added;
1219 }
1220 }
1221 return (added > 0);
1222}
1223
1224/*!
1225 Returns the cookies to be added to when a request is sent to
1226 \a url. This function is called by the default
1227 QNetworkAccessManager::createRequest(), which adds the
1228 cookies returned by this function to the request being sent.
1229
1230 If more than one cookie with the same name is found, but with
1231 differing paths, the one with longer path is returned before the
1232 one with shorter path. In other words, this function returns
1233 cookies sorted by path length.
1234
1235 The default QNetworkCookieJar class implements only a very basic
1236 security policy (it makes sure that the cookies' domain and path
1237 match the reply's). To enhance the security policy with your own
1238 algorithms, override cookiesForUrl().
1239
1240 \sa setCookiesFromUrl(), QNetworkAccessManager::setCookieJar()
1241*/
1242QList<QNetworkCookie> QNetworkCookieJar::cookiesForUrl(const QUrl &url) const
1243{
1244// \b Warning! This is only a dumb implementation!
1245// It does NOT follow all of the recommendations from
1246// http://wp.netscape.com/newsref/std/cookie_spec.html
1247// It does not implement a very good cross-domain verification yet.
1248
1249 Q_D(const QNetworkCookieJar);
1250 QDateTime now = QDateTime::currentDateTime();
1251 QList<QNetworkCookie> result;
1252
1253 // scan our cookies for something that matches
1254 QList<QNetworkCookie>::ConstIterator it = d->allCookies.constBegin(),
1255 end = d->allCookies.constEnd();
1256 for ( ; it != end; ++it) {
1257 if (!isParentDomain(url.host(), it->domain()))
1258 continue;
1259 if (!isParentPath(url.path(), it->path()))
1260 continue;
1261 if (!(*it).isSessionCookie() && (*it).expirationDate() < now)
1262 continue;
1263
1264 // insert this cookie into result, sorted by path
1265 QList<QNetworkCookie>::Iterator insertIt = result.begin();
1266 while (insertIt != result.end()) {
1267 if (insertIt->path().length() < it->path().length()) {
1268 // insert here
1269 insertIt = result.insert(insertIt, *it);
1270 break;
1271 } else {
1272 ++insertIt;
1273 }
1274 }
1275
1276 // this is the shortest path yet, just append
1277 if (insertIt == result.end())
1278 result += *it;
1279 }
1280
1281 return result;
1282}
1283
1284QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.