Coverage Report

Created: 2026-04-29 07:00

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