src/network/access/qnetworkcookie.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the QtNetwork module of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    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 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "qnetworkcookie.h"
       
    43 #include "qnetworkcookie_p.h"
       
    44 
       
    45 #include "qnetworkrequest.h"
       
    46 #include "qnetworkreply.h"
       
    47 #include "QtCore/qbytearray.h"
       
    48 #include "QtCore/qdebug.h"
       
    49 #include "QtCore/qlist.h"
       
    50 #include "QtCore/qlocale.h"
       
    51 #include "QtCore/qstring.h"
       
    52 #include "QtCore/qstringlist.h"
       
    53 #include "QtCore/qurl.h"
       
    54 #include "private/qobject_p.h"
       
    55 
       
    56 QT_BEGIN_NAMESPACE
       
    57 
       
    58 /*!
       
    59     \class QNetworkCookie
       
    60     \since 4.4
       
    61     \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             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 
       
   522 static 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";
       
   540 static int zoneOffsets[] = {-8, -8, -7, -7, -6, -6, -5, -5, -4, -3, 0, 0, 0, 1, 2, 9 };
       
   541 
       
   542 static 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 
       
   557 static inline bool isNumber(char s)
       
   558 { return s >= '0' && s <= '9'; }
       
   559 
       
   560 static inline bool isTerminator(char c)
       
   561 { return c == '\n' || c == '\r'; }
       
   562 
       
   563 static inline bool isValueSeparator(char c)
       
   564 { return isTerminator(c) || c == ';'; }
       
   565 
       
   566 static inline bool isWhitespace(char c)
       
   567 { return c == ' '  || c == '\t'; }
       
   568 
       
   569 static 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 */
       
   616 static 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 */
       
   919 QList<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 
       
   930 QList<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                     if (rawDomain.startsWith('.')) {
       
   988                         rawDomain = rawDomain.mid(1);
       
   989                     }
       
   990                     QString normalizedDomain = QUrl::fromAce(QUrl::toAce(QString::fromUtf8(rawDomain)));
       
   991                     // always add the dot, there are some servers that forget the
       
   992                     // leading dot. This is actually forbidden according to RFC 2109,
       
   993                     // but all browsers accept it anyway so we do that as well
       
   994                     cookie.setDomain(QLatin1Char('.') + normalizedDomain);
       
   995                 } else if (field.first == "max-age") {
       
   996                     bool ok = false;
       
   997                     int secs = field.second.toInt(&ok);
       
   998                     if (!ok)
       
   999                         return result;
       
  1000                     cookie.setExpirationDate(now.addSecs(secs));
       
  1001                 } else if (field.first == "path") {
       
  1002                     QString path = QUrl::fromPercentEncoding(field.second);
       
  1003                     cookie.setPath(path);
       
  1004                 } else if (field.first == "secure") {
       
  1005                     cookie.setSecure(true);
       
  1006                 } else if (field.first == "httponly") {
       
  1007                     cookie.setHttpOnly(true);
       
  1008                 } else if (field.first == "comment") {
       
  1009                     //cookie.setComment(QString::fromUtf8(field.second));
       
  1010                 } else if (field.first == "version") {
       
  1011                     if (field.second != "1") {
       
  1012                         // oops, we don't know how to handle this cookie
       
  1013                         return result;
       
  1014                     }
       
  1015                 } else {
       
  1016                     // got an unknown field in the cookie
       
  1017                     // what do we do?
       
  1018                 }
       
  1019 
       
  1020                 position = nextNonWhitespace(cookieString, position);
       
  1021             }
       
  1022         }
       
  1023 
       
  1024         if (!cookie.name().isEmpty())
       
  1025             result += cookie;
       
  1026     }
       
  1027 
       
  1028     return result;
       
  1029 }
       
  1030 
       
  1031 #ifndef QT_NO_DEBUG_STREAM
       
  1032 QDebug operator<<(QDebug s, const QNetworkCookie &cookie)
       
  1033 {
       
  1034     s.nospace() << "QNetworkCookie(" << cookie.toRawForm(QNetworkCookie::Full) << ')';
       
  1035     return s.space();
       
  1036 }
       
  1037 #endif
       
  1038 
       
  1039 QT_END_NAMESPACE