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 |
|
---|
56 | QT_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 | */
|
---|
106 | QNetworkCookie::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 | */
|
---|
120 | QNetworkCookie::QNetworkCookie(const QNetworkCookie &other)
|
---|
121 | : d(other.d)
|
---|
122 | {
|
---|
123 | }
|
---|
124 |
|
---|
125 | /*!
|
---|
126 | Destroys this QNetworkCookie object.
|
---|
127 | */
|
---|
128 | QNetworkCookie::~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 | */
|
---|
138 | QNetworkCookie &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 | */
|
---|
161 | bool 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 | */
|
---|
183 | bool 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 | */
|
---|
196 | void 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 | */
|
---|
212 | bool 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 | */
|
---|
222 | void 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 | */
|
---|
235 | bool 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 | */
|
---|
251 | QDateTime 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 | */
|
---|
263 | void 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 | */
|
---|
278 | QString 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 | */
|
---|
288 | void 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 | */
|
---|
299 | QString 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 | */
|
---|
309 | void 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 | */
|
---|
320 | QByteArray 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 | */
|
---|
332 | void 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 | */
|
---|
346 | QByteArray 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 | */
|
---|
356 | void QNetworkCookie::setValue(const QByteArray &value)
|
---|
357 | {
|
---|
358 | d->value = value;
|
---|
359 | }
|
---|
360 |
|
---|
361 | // ### move this to qnetworkcookie_p.h and share with qnetworkaccesshttpbackend
|
---|
362 | static 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 | */
|
---|
471 | QByteArray 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 |
|
---|
517 | static 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";
|
---|
535 | static int zoneOffsets[] = {-8, -8, -7, -7, -6, -6, -5, -5, -4, -3, 0, 0, 0, 1, 2, 9 };
|
---|
536 |
|
---|
537 | static 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 |
|
---|
552 | static inline bool isNumber(char s)
|
---|
553 | { return s >= '0' && s <= '9'; }
|
---|
554 |
|
---|
555 | static inline bool isTerminator(char c)
|
---|
556 | { return c == '\n' || c == '\r'; }
|
---|
557 |
|
---|
558 | static inline bool isValueSeparator(char c)
|
---|
559 | { return isTerminator(c) || c == ';'; }
|
---|
560 |
|
---|
561 | static inline bool isWhitespace(char c)
|
---|
562 | { return c == ' ' || c == '\t'; }
|
---|
563 |
|
---|
564 | static 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 | */
|
---|
611 | static 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 | */
|
---|
914 | QList<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
|
---|
1028 | QDebug 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 |
|
---|
1037 | class QNetworkCookieJarPrivate: public QObjectPrivate
|
---|
1038 | {
|
---|
1039 | public:
|
---|
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 | */
|
---|
1088 | QNetworkCookieJar::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 | */
|
---|
1101 | QNetworkCookieJar::~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 | */
|
---|
1112 | QList<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 | */
|
---|
1126 | void QNetworkCookieJar::setAllCookies(const QList<QNetworkCookie> &cookieList)
|
---|
1127 | {
|
---|
1128 | Q_D(QNetworkCookieJar);
|
---|
1129 | d->allCookies = cookieList;
|
---|
1130 | }
|
---|
1131 |
|
---|
1132 | static 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 |
|
---|
1141 | static 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 | */
|
---|
1170 | bool 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 | */
|
---|
1242 | QList<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 |
|
---|
1284 | QT_END_NAMESPACE
|
---|