Warning, file /frameworks/kio/src/kioworkers/http/kcookiejar/kcookiejar.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     This file is part of the KDE File Manager
0003     SPDX-FileCopyrightText: 1998-2000 Waldo Bastian <bastian@kde.org>
0004     SPDX-FileCopyrightText: 2000, 2001 Dawit Alemayehu <adawit@kde.org>
0005 
0006     SPDX-License-Identifier: MIT
0007 */
0008 
0009 // KDE File Manager -- HTTP Cookies
0010 
0011 //
0012 // The cookie protocol is a mess. RFC2109 is a joke since nobody seems to
0013 // use it. Apart from that it is badly written.
0014 // We try to implement Netscape Cookies and try to behave us according to
0015 // RFC2109 as much as we can.
0016 //
0017 // We assume cookies do not contain any spaces (Netscape spec.)
0018 // According to RFC2109 this is allowed though.
0019 //
0020 
0021 #include "kcookiejar.h"
0022 
0023 #include <KConfig>
0024 #include <KConfigGroup>
0025 #include <QDebug>
0026 #include <QSaveFile>
0027 
0028 #include <QFile>
0029 #include <QLocale>
0030 #include <QRegularExpression>
0031 #include <QString>
0032 #include <QTextStream>
0033 #include <QUrl>
0034 
0035 Q_LOGGING_CATEGORY(KIO_COOKIEJAR, "kf.kio.workers.http.cookiejar")
0036 
0037 static constexpr int s_readBufferSize = 8192;
0038 static constexpr char s_ipAddressExpression[] = "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
0039 
0040 // Note with respect to QLatin1String( )....
0041 // Cookies are stored as 8 bit data and passed to kio_http as Latin1
0042 // regardless of their actual encoding.
0043 
0044 static QString removeWeekday(const QString &value)
0045 {
0046     const int index = value.indexOf(QLatin1Char(' '));
0047     if (index > -1) {
0048         int pos = 0;
0049         const auto weekday = QStringView(value).left(index);
0050         const QLocale cLocale = QLocale::c();
0051         for (int i = 1; i < 8; ++i) {
0052             // No need to check for long names since the short names are
0053             // prefixes of the long names
0054             if (weekday.startsWith(cLocale.dayName(i, QLocale::ShortFormat), Qt::CaseInsensitive)) {
0055                 pos = index + 1;
0056                 break;
0057             }
0058         }
0059         if (pos > 0) {
0060             return value.mid(pos);
0061         }
0062     }
0063     return value;
0064 }
0065 
0066 static QDateTime parseDate(const QString &_value)
0067 {
0068     // Handle sites sending invalid weekday as part of the date. #298660
0069     QString value(removeWeekday(_value));
0070 
0071     // Check if expiration date matches RFC dates as specified under
0072     // RFC 2616 sec 3.3.1 & RFC 6265 sec 4.1.1
0073     QDateTime dt = QDateTime::fromString(value, Qt::RFC2822Date);
0074 
0075     // Q6DateTime::fromString() will return an invalid datetime if the "GMT"
0076     // part is included
0077 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0078     if (!dt.isValid()) {
0079         const QLatin1String tail(" GMT");
0080         if (value.endsWith(tail)) {
0081             value.chop(tail.size());
0082         }
0083         dt = QDateTime::fromString(value, Qt::RFC2822Date);
0084     }
0085 #endif
0086 
0087     if (!dt.isValid()) {
0088         static const char *const date_formats[] = {// Other formats documented in RFC 2616 sec 3.3.1
0089                                                    // Note: the RFC says timezone information MUST be "GMT", hence the hardcoded timezone string
0090                                                    "MMM dd HH:mm:ss yyyy", /* ANSI C's asctime() format (#145244): Jan 01 00:00:00 1970 GMT */
0091                                                    "dd-MMM-yy HH:mm:ss 'GMT'", /* RFC 850 date: 06-Dec-39 00:30:42 GMT */
0092                                                    // Non-standard formats
0093                                                    "MMM dd yyyy HH:mm:ss", /* A variation on ANSI C format seen @ amazon.com: Jan 01 1970 00:00:00 GMT */
0094                                                    "dd-MMM-yyyy HH:mm:ss 'GMT'", /* cookies.test: Y2K38 problem: 06-Dec-2039 00:30:42 GMT */
0095                                                    "MMM dd HH:mm:ss yyyy 'GMT'", /* cookies.test: Non-standard expiration dates: Sep 12 07:00:00 2020 GMT */
0096                                                    "MMM dd yyyy HH:mm:ss 'GMT'", /* cookies.test: Non-standard expiration dates: Sep 12 2020 07:00:00 GMT */
0097                                                    nullptr};
0098 
0099         // Only English month names are allowed, thus use the C locale.
0100         const QLocale cLocale = QLocale::c();
0101         for (int i = 0; date_formats[i]; ++i) {
0102             dt = cLocale.toDateTime(value, QLatin1String(date_formats[i]));
0103             if (dt.isValid()) {
0104                 dt.setTimeSpec(Qt::UTC);
0105                 break;
0106             }
0107         }
0108     }
0109 
0110     return dt.toUTC(); // Per RFC 2616 sec 3.3.1 always convert to UTC.
0111 }
0112 
0113 static qint64 toEpochSecs(const QDateTime &dt)
0114 {
0115     return (dt.toMSecsSinceEpoch() / 1000); // convert to seconds...
0116 }
0117 
0118 static qint64 epoch()
0119 {
0120     return toEpochSecs(QDateTime::currentDateTimeUtc());
0121 }
0122 
0123 QString KCookieJar::adviceToStr(KCookieAdvice _advice)
0124 {
0125     switch (_advice) {
0126     case KCookieAccept:
0127         return QStringLiteral("Accept");
0128     case KCookieAcceptForSession:
0129         return QStringLiteral("AcceptForSession");
0130     case KCookieReject:
0131         return QStringLiteral("Reject");
0132     case KCookieAsk:
0133         return QStringLiteral("Ask");
0134     default:
0135         return QStringLiteral("Dunno");
0136     }
0137 }
0138 
0139 KCookieAdvice KCookieJar::strToAdvice(const QString &_str)
0140 {
0141     if (_str.isEmpty()) {
0142         return KCookieDunno;
0143     }
0144 
0145     QString advice = _str.toLower().remove(QLatin1Char(' '));
0146 
0147     if (advice == QLatin1String("accept")) {
0148         return KCookieAccept;
0149     } else if (advice == QLatin1String("acceptforsession")) {
0150         return KCookieAcceptForSession;
0151     } else if (advice == QLatin1String("reject")) {
0152         return KCookieReject;
0153     } else if (advice == QLatin1String("ask")) {
0154         return KCookieAsk;
0155     }
0156 
0157     return KCookieDunno;
0158 }
0159 
0160 // KHttpCookie
0161 ///////////////////////////////////////////////////////////////////////////
0162 
0163 //
0164 // Cookie constructor
0165 //
0166 KHttpCookie::KHttpCookie(const QString &_host,
0167                          const QString &_domain,
0168                          const QString &_path,
0169                          const QString &_name,
0170                          const QString &_value,
0171                          qint64 _expireDate,
0172                          int _protocolVersion,
0173                          bool _secure,
0174                          bool _httpOnly,
0175                          bool _explicitPath)
0176     : mHost(_host)
0177     , mDomain(_domain)
0178     , mPath(_path.isEmpty() ? QString() : _path)
0179     , mName(_name)
0180     , mValue(_value)
0181     , mExpireDate(_expireDate)
0182     , mProtocolVersion(_protocolVersion)
0183     , mSecure(_secure)
0184     , mCrossDomain(false)
0185     , mHttpOnly(_httpOnly)
0186     , mExplicitPath(_explicitPath)
0187     , mUserSelectedAdvice(KCookieDunno)
0188 {
0189 }
0190 
0191 //
0192 // Checks if a cookie has been expired
0193 //
0194 bool KHttpCookie::isExpired(qint64 currentDate) const
0195 {
0196     if (currentDate == -1) {
0197         currentDate = epoch();
0198     }
0199 
0200     return (mExpireDate != 0) && (mExpireDate < currentDate);
0201 }
0202 
0203 //
0204 // Returns a string for a HTTP-header
0205 //
0206 QString KHttpCookie::cookieStr(bool useDOMFormat) const
0207 {
0208     QString result;
0209 
0210     if (useDOMFormat || (mProtocolVersion == 0)) {
0211         if (mName.isEmpty()) {
0212             result = mValue;
0213         } else {
0214             result = mName + QLatin1Char('=') + mValue;
0215         }
0216     } else {
0217         result = mName + QLatin1Char('=') + mValue;
0218         if (mExplicitPath) {
0219             result += QLatin1String("; $Path=\"") + mPath + QLatin1Char('"');
0220         }
0221         if (!mDomain.isEmpty()) {
0222             result += QLatin1String("; $Domain=\"") + mDomain + QLatin1Char('"');
0223         }
0224         if (!mPorts.isEmpty()) {
0225             if (mPorts.length() == 2 && mPorts.at(0) == -1) {
0226                 result += QLatin1String("; $Port");
0227             } else {
0228                 QString portNums;
0229                 for (int port : std::as_const(mPorts)) {
0230                     portNums += QString::number(port) + QLatin1Char(' ');
0231                 }
0232                 result += QLatin1String("; $Port=\"") + portNums.trimmed() + QLatin1Char('"');
0233             }
0234         }
0235     }
0236     return result;
0237 }
0238 
0239 //
0240 // Returns whether this cookie should be send to this location.
0241 bool KHttpCookie::match(const QString &fqdn, const QStringList &domains, const QString &path, int port) const
0242 {
0243     // Cookie domain match check
0244     if (mDomain.isEmpty()) {
0245         if (fqdn != mHost) {
0246             return false;
0247         }
0248     } else if (!domains.contains(mDomain)) {
0249         if (mDomain[0] == QLatin1Char('.')) {
0250             return false;
0251         }
0252 
0253         // Maybe the domain needs an extra dot.
0254         const QString domain = QLatin1Char('.') + mDomain;
0255         if (!domains.contains(domain)) {
0256             if (fqdn != mDomain) {
0257                 return false;
0258             }
0259         }
0260     } else if (mProtocolVersion != 0 && port != -1 && !mPorts.isEmpty() && !mPorts.contains(port)) {
0261         return false;
0262     }
0263 
0264     // Cookie path match check
0265     if (mPath.isEmpty()) {
0266         return true;
0267     }
0268 
0269     // According to the netscape spec http://www.acme.com/foobar,
0270     // http://www.acme.com/foo.bar and http://www.acme.com/foo/bar
0271     // should all match http://www.acme.com/foo...
0272     // We only match http://www.acme.com/foo/bar
0273     if (path.startsWith(mPath)
0274         && ((path.length() == mPath.length()) || // Paths are exact match
0275             mPath.endsWith(QLatin1Char('/')) || // mPath ended with a slash
0276             (path[mPath.length()] == QLatin1Char('/')) // A slash follows
0277             )) {
0278         return true; // Path of URL starts with cookie-path
0279     }
0280 
0281     return false;
0282 }
0283 
0284 // KCookieJar
0285 ///////////////////////////////////////////////////////////////////////////
0286 
0287 //
0288 // Constructs a new cookie jar
0289 //
0290 // One jar should be enough for all cookies.
0291 //
0292 KCookieJar::KCookieJar()
0293 {
0294     m_globalAdvice = KCookieDunno;
0295     m_configChanged = false;
0296     m_cookiesChanged = false;
0297 
0298     KConfig cfg(QStringLiteral("kf5/kcookiejar/domain_info"), KConfig::NoGlobals, QStandardPaths::GenericDataLocation);
0299     KConfigGroup group(&cfg, QString());
0300 
0301     const QStringList tldList = group.readEntry("gTLDs", QStringList());
0302     const QStringList twoLevelTldList = group.readEntry("twoLevelTLD", QStringList());
0303     m_gTLDs = QSet<QString>(tldList.begin(), tldList.end());
0304     m_twoLevelTLD = QSet<QString>(twoLevelTldList.begin(), twoLevelTldList.end());
0305 }
0306 
0307 //
0308 // Destructs the cookie jar
0309 //
0310 // Poor little cookies, they will all be eaten by the cookie monster!
0311 //
0312 KCookieJar::~KCookieJar()
0313 {
0314     qDeleteAll(m_cookieDomains);
0315     // Not much to do here
0316 }
0317 
0318 // cookiePtr is modified: the window ids of the existing cookie in the list are added to it
0319 static void removeDuplicateFromList(KHttpCookieList *list, KHttpCookie &cookiePtr, bool nameMatchOnly = false, bool updateWindowId = false)
0320 {
0321     QString domain1 = cookiePtr.domain();
0322     if (domain1.isEmpty()) {
0323         domain1 = cookiePtr.host();
0324     }
0325 
0326     QMutableListIterator<KHttpCookie> cookieIterator(*list);
0327     while (cookieIterator.hasNext()) {
0328         const KHttpCookie &cookie = cookieIterator.next();
0329         QString domain2 = cookie.domain();
0330         if (domain2.isEmpty()) {
0331             domain2 = cookie.host();
0332         }
0333 
0334         if (cookiePtr.name() == cookie.name() && (nameMatchOnly || (domain1 == domain2 && cookiePtr.path() == cookie.path()))) {
0335             if (updateWindowId) {
0336                 for (WId windowId : cookie.windowIds()) {
0337                     if (windowId && (!cookiePtr.windowIds().contains(windowId))) {
0338                         cookiePtr.windowIds().append(windowId);
0339                     }
0340                 }
0341             }
0342             cookieIterator.remove();
0343             break;
0344         }
0345     }
0346 }
0347 
0348 //
0349 // Looks for cookies in the cookie jar which are appropriate for _url.
0350 // Returned is a string containing all appropriate cookies in a format
0351 // which can be added to a HTTP-header without any additional processing.
0352 //
0353 QString KCookieJar::findCookies(const QString &_url, bool useDOMFormat, WId windowId, KHttpCookieList *pendingCookies)
0354 {
0355     QString cookieStr;
0356     QString fqdn;
0357     QString path;
0358     QStringList domains;
0359     int port = -1;
0360 
0361     if (!parseUrl(_url, fqdn, path, &port)) {
0362         return cookieStr;
0363     }
0364 
0365     const bool secureRequest =
0366         (_url.startsWith(QLatin1String("https://"), Qt::CaseInsensitive) || _url.startsWith(QLatin1String("webdavs://"), Qt::CaseInsensitive));
0367     if (port == -1) {
0368         port = (secureRequest ? 443 : 80);
0369     }
0370 
0371     extractDomains(fqdn, domains);
0372 
0373     KHttpCookieList allCookies;
0374     for (QStringList::ConstIterator it = domains.constBegin(), itEnd = domains.constEnd();; ++it) {
0375         KHttpCookieList *cookieList = nullptr;
0376         if (it == itEnd) {
0377             cookieList = pendingCookies; // Add pending cookies
0378             pendingCookies = nullptr;
0379             if (!cookieList) {
0380                 break;
0381             }
0382         } else {
0383             if ((*it).isNull()) {
0384                 cookieList = m_cookieDomains.value(QLatin1String(""));
0385             } else {
0386                 cookieList = m_cookieDomains.value(*it);
0387             }
0388 
0389             if (!cookieList) {
0390                 continue; // No cookies for this domain
0391             }
0392         }
0393 
0394         QMutableListIterator<KHttpCookie> cookieIt(*cookieList);
0395         while (cookieIt.hasNext()) {
0396             KHttpCookie &cookie = cookieIt.next();
0397             if (cookieAdvice(cookie) == KCookieReject) {
0398                 continue;
0399             }
0400 
0401             if (!cookie.match(fqdn, domains, path, port)) {
0402                 continue;
0403             }
0404 
0405             if (cookie.isSecure() && !secureRequest) {
0406                 continue;
0407             }
0408 
0409             if (cookie.isHttpOnly() && useDOMFormat) {
0410                 continue;
0411             }
0412 
0413             // Do not send expired cookies.
0414             if (cookie.isExpired()) {
0415                 // NOTE: there is no need to delete the cookie here because the
0416                 // cookieserver will invoke its saveCookieJar function as a result
0417                 // of the state change below. This will then result in the cookie
0418                 // being deleting at that point.
0419                 m_cookiesChanged = true;
0420                 continue;
0421             }
0422 
0423             if (windowId && (cookie.windowIds().indexOf(windowId) == -1)) {
0424                 cookie.windowIds().append(windowId);
0425             }
0426 
0427             if (it == itEnd) { // Only needed when processing pending cookies
0428                 removeDuplicateFromList(&allCookies, cookie);
0429             }
0430 
0431             allCookies.append(cookie);
0432         }
0433 
0434         if (it == itEnd) {
0435             break; // Finished.
0436         }
0437     }
0438 
0439     int protVersion = 0;
0440     for (const KHttpCookie &cookie : std::as_const(allCookies)) {
0441         if (cookie.protocolVersion() > protVersion) {
0442             protVersion = cookie.protocolVersion();
0443         }
0444     }
0445 
0446     if (!allCookies.isEmpty()) {
0447         if (!useDOMFormat) {
0448             cookieStr = QStringLiteral("Cookie: ");
0449         }
0450 
0451         if (protVersion > 0) {
0452             cookieStr = cookieStr + QLatin1String("$Version=") + QString::number(protVersion) + QLatin1String("; ");
0453         }
0454 
0455         for (const KHttpCookie &cookie : std::as_const(allCookies)) {
0456             cookieStr = cookieStr + cookie.cookieStr(useDOMFormat) + QStringLiteral("; ");
0457         }
0458 
0459         cookieStr.chop(2); // Remove the trailing '; '
0460     }
0461 
0462     return cookieStr;
0463 }
0464 
0465 //
0466 // This function parses a string like 'my_name="my_value";' and returns
0467 // 'my_name' in Name and 'my_value' in Value.
0468 //
0469 // A pointer to the end of the parsed part is returned.
0470 // This pointer points either to:
0471 // '\0' - The end of the string has reached.
0472 // ';'  - Another my_name="my_value" pair follows
0473 // ','  - Another cookie follows
0474 // '\n' - Another header follows
0475 static const char *parseNameValue(const char *header, QString &Name, QString &Value, bool keepQuotes = false, bool rfcQuotes = false)
0476 {
0477     const char *s = header;
0478     // Parse 'my_name' part
0479     for (; (*s != '='); s++) {
0480         if ((*s == '\0') || (*s == ';') || (*s == '\n')) {
0481             // No '=' sign -> use string as the value, name is empty
0482             // (behavior found in Mozilla and IE)
0483             Name = QLatin1String("");
0484             Value = QLatin1String(header);
0485             Value.truncate(s - header);
0486             Value = Value.trimmed();
0487             return s;
0488         }
0489     }
0490 
0491     Name = QLatin1String(header);
0492     Name.truncate(s - header);
0493     Name = Name.trimmed();
0494 
0495     // *s == '='
0496     s++;
0497 
0498     // Skip any whitespace
0499     for (; (*s == ' ') || (*s == '\t'); s++) {
0500         if ((*s == '\0') || (*s == ';') || (*s == '\n')) {
0501             // End of Name
0502             Value = QLatin1String("");
0503             return s;
0504         }
0505     }
0506 
0507     if ((rfcQuotes || !keepQuotes) && (*s == '\"')) {
0508         // Parse '"my_value"' part (quoted value)
0509         if (keepQuotes) {
0510             header = s++;
0511         } else {
0512             header = ++s; // skip "
0513         }
0514         for (; (*s != '\"'); s++) {
0515             if ((*s == '\0') || (*s == '\n')) {
0516                 // End of Name
0517                 Value = QLatin1String(header);
0518                 Value.truncate(s - header);
0519                 return s;
0520             }
0521         }
0522         Value = QLatin1String(header);
0523         // *s == '\"';
0524         if (keepQuotes) {
0525             Value.truncate(++s - header);
0526         } else {
0527             Value.truncate(s++ - header);
0528         }
0529 
0530         // Skip any remaining garbage
0531         for (;; s++) {
0532             if ((*s == '\0') || (*s == ';') || (*s == '\n')) {
0533                 break;
0534             }
0535         }
0536     } else {
0537         // Parse 'my_value' part (unquoted value)
0538         header = s;
0539         while ((*s != '\0') && (*s != ';') && (*s != '\n')) {
0540             s++;
0541         }
0542         // End of Name
0543         Value = QLatin1String(header);
0544         Value.truncate(s - header);
0545         Value = Value.trimmed();
0546     }
0547     return s;
0548 }
0549 
0550 void KCookieJar::stripDomain(const QString &_fqdn, QString &_domain) const
0551 {
0552     QStringList domains;
0553     extractDomains(_fqdn, domains);
0554     if (domains.count() > 3) {
0555         _domain = domains[3];
0556     } else if (!domains.isEmpty()) {
0557         _domain = domains[0];
0558     } else {
0559         _domain = QLatin1String("");
0560     }
0561 }
0562 
0563 QString KCookieJar::stripDomain(const KHttpCookie &cookie) const
0564 {
0565     QString domain; // We file the cookie under this domain.
0566     if (cookie.domain().isEmpty()) {
0567         stripDomain(cookie.host(), domain);
0568     } else {
0569         domain = cookie.domain();
0570     }
0571     return domain;
0572 }
0573 
0574 bool KCookieJar::parseUrl(const QString &_url, QString &_fqdn, QString &_path, int *port)
0575 {
0576     QUrl kurl(_url);
0577     if (!kurl.isValid() || kurl.scheme().isEmpty()) {
0578         return false;
0579     }
0580 
0581     _fqdn = kurl.host().toLower();
0582     // Cookie spoofing protection.  Since there is no way a path separator,
0583     // a space or the escape encoding character is allowed in the hostname
0584     // according to RFC 2396, reject attempts to include such things there!
0585     if (_fqdn.contains(QLatin1Char('/')) || _fqdn.contains(QLatin1Char('%'))) {
0586         return false; // deny everything!!
0587     }
0588 
0589     // Set the port number from the protocol when one is found...
0590     if (port) {
0591         *port = kurl.port();
0592     }
0593 
0594     _path = kurl.path();
0595     if (_path.isEmpty()) {
0596         _path = QStringLiteral("/");
0597     }
0598 
0599     return true;
0600 }
0601 
0602 // not static because it uses m_twoLevelTLD
0603 void KCookieJar::extractDomains(const QString &_fqdn, QStringList &_domains) const
0604 {
0605     if (_fqdn.isEmpty()) {
0606         _domains.append(QStringLiteral("localhost"));
0607         return;
0608     }
0609 
0610     // Return numeric IPv6 addresses as is...
0611     if (_fqdn[0] == QLatin1Char('[')) {
0612         _domains.append(_fqdn);
0613         return;
0614     }
0615     // Return numeric IPv4 addresses as is...
0616     if (_fqdn[0] >= QLatin1Char('0') && _fqdn[0] <= QLatin1Char('9') && _fqdn.indexOf(QRegularExpression(QString::fromLatin1(s_ipAddressExpression))) > -1) {
0617         _domains.append(_fqdn);
0618         return;
0619     }
0620 
0621     // Always add the FQDN at the start of the list for
0622     // hostname == cookie-domainname checks!
0623     _domains.append(_fqdn);
0624     _domains.append(QLatin1Char('.') + _fqdn);
0625 
0626     QStringList partList = _fqdn.split(QLatin1Char('.'), Qt::SkipEmptyParts);
0627 
0628     if (!partList.isEmpty()) {
0629         partList.erase(partList.begin()); // Remove hostname
0630     }
0631 
0632     while (partList.count()) {
0633         if (partList.count() == 1) {
0634             break; // We only have a TLD left.
0635         }
0636 
0637         if ((partList.count() == 2) && m_twoLevelTLD.contains(partList[1].toLower())) {
0638             // This domain uses two-level TLDs in the form xxxx.yy
0639             break;
0640         }
0641 
0642         if ((partList.count() == 2) && (partList[1].length() == 2)) {
0643             // If this is a TLD, we should stop. (e.g. co.uk)
0644             // We assume this is a TLD if it ends with .xx.yy or .x.yy
0645             if (partList[0].length() <= 2) {
0646                 break; // This is a TLD.
0647             }
0648 
0649             // Catch some TLDs that we miss with the previous check
0650             // e.g. com.au, org.uk, mil.co
0651             if (m_gTLDs.contains(partList[0].toLower())) {
0652                 break;
0653             }
0654         }
0655 
0656         QString domain = partList.join(QLatin1Char('.'));
0657         _domains.append(domain);
0658         _domains.append(QLatin1Char('.') + domain);
0659         partList.erase(partList.begin()); // Remove part
0660     }
0661 }
0662 
0663 //
0664 // This function parses cookie_headers and returns a linked list of
0665 // KHttpCookie objects for all cookies found in cookie_headers.
0666 // If no cookies could be found 0 is returned.
0667 //
0668 // cookie_headers should be a concatenation of all lines of a HTTP-header
0669 // which start with "Set-Cookie". The lines should be separated by '\n's.
0670 //
0671 KHttpCookieList KCookieJar::makeCookies(const QString &_url, const QByteArray &cookie_headers, WId windowId)
0672 {
0673     QString fqdn;
0674     QString path;
0675 
0676     if (!parseUrl(_url, fqdn, path)) {
0677         return KHttpCookieList(); // Error parsing _url
0678     }
0679 
0680     QString Name;
0681     QString Value;
0682     KHttpCookieList cookieList;
0683     KHttpCookieList cookieList2;
0684 
0685     bool isRFC2965 = false;
0686     bool crossDomain = false;
0687     const char *cookieStr = cookie_headers.constData();
0688 
0689     QString defaultPath;
0690     const int index = path.lastIndexOf(QLatin1Char('/'));
0691     if (index > 0) {
0692         defaultPath = path.left(index);
0693     }
0694 
0695     // Check for cross-domain flag from kio_http
0696     if (qstrncmp(cookieStr, "Cross-Domain\n", 13) == 0) {
0697         cookieStr += 13;
0698         crossDomain = true;
0699     }
0700 
0701     //  The hard stuff :)
0702     for (;;) {
0703         // check for "Set-Cookie"
0704         if (qstrnicmp(cookieStr, "Set-Cookie:", 11) == 0) {
0705             cookieStr = parseNameValue(cookieStr + 11, Name, Value, true);
0706 
0707             // Host = FQDN
0708             // Default domain = ""
0709             // Default path according to rfc2109
0710 
0711             KHttpCookie cookie(fqdn, QLatin1String(""), defaultPath, Name, Value);
0712             if (windowId) {
0713                 cookie.mWindowIds.append(windowId);
0714             }
0715             cookie.mCrossDomain = crossDomain;
0716 
0717             // Insert cookie in chain
0718             cookieList.append(cookie);
0719         } else if (qstrnicmp(cookieStr, "Set-Cookie2:", 12) == 0) {
0720             // Attempt to follow rfc2965
0721             isRFC2965 = true;
0722             cookieStr = parseNameValue(cookieStr + 12, Name, Value, true, true);
0723 
0724             // Host = FQDN
0725             // Default domain = ""
0726             // Default path according to rfc2965
0727 
0728             KHttpCookie cookie(fqdn, QLatin1String(""), defaultPath, Name, Value);
0729             if (windowId) {
0730                 cookie.mWindowIds.append(windowId);
0731             }
0732             cookie.mCrossDomain = crossDomain;
0733 
0734             // Insert cookie in chain
0735             cookieList2.append(cookie);
0736         } else {
0737             // This is not the start of a cookie header, skip till next line.
0738             while (*cookieStr && *cookieStr != '\n') {
0739                 cookieStr++;
0740             }
0741 
0742             if (*cookieStr == '\n') {
0743                 cookieStr++;
0744             }
0745 
0746             if (!*cookieStr) {
0747                 break; // End of cookie_headers
0748             } else {
0749                 continue; // end of this header, continue with next.
0750             }
0751         }
0752 
0753         while ((*cookieStr == ';') || (*cookieStr == ' ')) {
0754             cookieStr++;
0755 
0756             // Name-Value pair follows
0757             cookieStr = parseNameValue(cookieStr, Name, Value);
0758             KHttpCookie &lastCookie = (isRFC2965 ? cookieList2.last() : cookieList.last());
0759 
0760             if (Name.compare(QLatin1String("domain"), Qt::CaseInsensitive) == 0) {
0761                 QString dom = Value.toLower();
0762                 // RFC2965 3.2.2: If an explicitly specified value does not
0763                 // start with a dot, the user agent supplies a leading dot
0764                 if (dom.length() > 0 && dom[0] != QLatin1Char('.')) {
0765                     dom.prepend(QLatin1Char('.'));
0766                 }
0767                 // remove a trailing dot
0768                 if (dom.length() > 2 && dom[dom.length() - 1] == QLatin1Char('.')) {
0769                     dom.chop(1);
0770                 }
0771 
0772                 if (dom.count(QLatin1Char('.')) > 1 || dom == QLatin1String(".local")) {
0773                     lastCookie.mDomain = dom;
0774                 }
0775             } else if (Name.compare(QLatin1String("max-age"), Qt::CaseInsensitive) == 0) {
0776                 int max_age = Value.toInt();
0777                 if (max_age == 0) {
0778                     lastCookie.mExpireDate = 1;
0779                 } else {
0780                     lastCookie.mExpireDate = toEpochSecs(QDateTime::currentDateTimeUtc().addSecs(max_age));
0781                 }
0782             } else if (Name.compare(QLatin1String("expires"), Qt::CaseInsensitive) == 0) {
0783                 const QDateTime dt = parseDate(Value);
0784 
0785                 if (dt.isValid()) {
0786                     lastCookie.mExpireDate = toEpochSecs(dt);
0787                     if (lastCookie.mExpireDate == 0) {
0788                         lastCookie.mExpireDate = 1;
0789                     }
0790                 }
0791             } else if (Name.compare(QLatin1String("path"), Qt::CaseInsensitive) == 0) {
0792                 if (Value.isEmpty()) {
0793                     lastCookie.mPath.clear(); // Catch "" <> QString()
0794                 } else {
0795                     lastCookie.mPath = QUrl::fromPercentEncoding(Value.toLatin1());
0796                 }
0797                 lastCookie.mExplicitPath = true;
0798             } else if (Name.compare(QLatin1String("version"), Qt::CaseInsensitive) == 0) {
0799                 lastCookie.mProtocolVersion = Value.toInt();
0800             } else if (Name.compare(QLatin1String("secure"), Qt::CaseInsensitive) == 0
0801                        || (Name.isEmpty() && Value.compare(QLatin1String("secure"), Qt::CaseInsensitive) == 0)) {
0802                 lastCookie.mSecure = true;
0803             } else if (Name.compare(QLatin1String("httponly"), Qt::CaseInsensitive) == 0
0804                        || (Name.isEmpty() && Value.compare(QLatin1String("httponly"), Qt::CaseInsensitive) == 0)) {
0805                 lastCookie.mHttpOnly = true;
0806             } else if (isRFC2965
0807                        && (Name.compare(QLatin1String("port"), Qt::CaseInsensitive) == 0
0808                            || (Name.isEmpty() && Value.compare(QLatin1String("port"), Qt::CaseInsensitive) == 0))) {
0809                 // Based on the port selection rule of RFC 2965 section 3.3.4...
0810                 if (Name.isEmpty()) {
0811                     // We intentionally append a -1 first in order to distinguish
0812                     // between only a 'Port' vs a 'Port="80 443"' in the sent cookie.
0813                     lastCookie.mPorts.append(-1);
0814                     const bool secureRequest =
0815                         (_url.startsWith(QLatin1String("https://"), Qt::CaseInsensitive) || _url.startsWith(QLatin1String("webdavs://"), Qt::CaseInsensitive));
0816                     if (secureRequest) {
0817                         lastCookie.mPorts.append(443);
0818                     } else {
0819                         lastCookie.mPorts.append(80);
0820                     }
0821                 } else {
0822                     bool ok;
0823                     const QStringList portNums = Value.split(QLatin1Char(' '), Qt::SkipEmptyParts);
0824                     for (const QString &portNum : portNums) {
0825                         const int port = portNum.toInt(&ok);
0826                         if (ok) {
0827                             lastCookie.mPorts.append(port);
0828                         }
0829                     }
0830                 }
0831             }
0832         }
0833 
0834         if (*cookieStr == '\0') {
0835             break; // End of header
0836         }
0837 
0838         // Skip ';' or '\n'
0839         cookieStr++;
0840     }
0841 
0842     // RFC2965 cookies come last so that they override netscape cookies.
0843     while (!cookieList2.isEmpty()) {
0844         KHttpCookie &lastCookie = cookieList2.first();
0845         removeDuplicateFromList(&cookieList, lastCookie, true);
0846         cookieList.append(lastCookie);
0847         cookieList2.removeFirst();
0848     }
0849 
0850     return cookieList;
0851 }
0852 
0853 /**
0854  * Parses cookie_domstr and returns a linked list of KHttpCookie objects.
0855  * cookie_domstr should be a semicolon-delimited list of "name=value"
0856  * pairs. Any whitespace before "name" or around '=' is discarded.
0857  * If no cookies are found, 0 is returned.
0858  */
0859 KHttpCookieList KCookieJar::makeDOMCookies(const QString &_url, const QByteArray &cookie_domstring, WId windowId)
0860 {
0861     // A lot copied from above
0862     KHttpCookieList cookieList;
0863 
0864     const char *cookieStr = cookie_domstring.data();
0865     QString fqdn;
0866     QString path;
0867 
0868     if (!parseUrl(_url, fqdn, path)) {
0869         // Error parsing _url
0870         return KHttpCookieList();
0871     }
0872 
0873     QString Name;
0874     QString Value;
0875     //  This time it's easy
0876     while (*cookieStr) {
0877         cookieStr = parseNameValue(cookieStr, Name, Value);
0878 
0879         // Host = FQDN
0880         // Default domain = ""
0881         // Default path = ""
0882         KHttpCookie cookie(fqdn, QString(), QString(), Name, Value);
0883         if (windowId) {
0884             cookie.mWindowIds.append(windowId);
0885         }
0886 
0887         cookieList.append(cookie);
0888 
0889         if (*cookieStr != '\0') {
0890             cookieStr++; // Skip ';' or '\n'
0891         }
0892     }
0893 
0894     return cookieList;
0895 }
0896 
0897 // KHttpCookieList sorting
0898 ///////////////////////////////////////////////////////////////////////////
0899 
0900 // We want the longest path first
0901 static bool compareCookies(const KHttpCookie &item1, const KHttpCookie &item2)
0902 {
0903     return item1.path().length() > item2.path().length();
0904 }
0905 
0906 //
0907 // This function hands a KHttpCookie object over to the cookie jar.
0908 //
0909 void KCookieJar::addCookie(KHttpCookie &cookie)
0910 {
0911     QStringList domains;
0912     // We always need to do this to make sure that the
0913     // that cookies of type hostname == cookie-domainname
0914     // are properly removed and/or updated as necessary!
0915     extractDomains(cookie.host(), domains);
0916 
0917     // If the cookie specifies a domain, check whether it is valid. Otherwise,
0918     // accept the cookie anyways but removes the domain="" value to prevent
0919     // cross-site cookie injection.
0920     if (!cookie.domain().isEmpty()) {
0921         if (!domains.contains(cookie.domain()) && !cookie.domain().endsWith(QLatin1Char('.') + cookie.host())) {
0922             cookie.fixDomain(QString());
0923         }
0924     }
0925 
0926     for (const QString &key : std::as_const(domains)) {
0927         KHttpCookieList *list;
0928 
0929         if (key.isNull()) {
0930             list = m_cookieDomains.value(QLatin1String(""));
0931         } else {
0932             list = m_cookieDomains.value(key);
0933         }
0934 
0935         if (list) {
0936             removeDuplicateFromList(list, cookie, false, true);
0937         }
0938     }
0939 
0940     const QString domain = stripDomain(cookie);
0941     KHttpCookieList *cookieList;
0942     if (domain.isNull()) {
0943         cookieList = m_cookieDomains.value(QLatin1String(""));
0944     } else {
0945         cookieList = m_cookieDomains.value(domain);
0946     }
0947 
0948     if (!cookieList) {
0949         // Make a new cookie list
0950         cookieList = new KHttpCookieList();
0951 
0952         // All cookies whose domain is not already
0953         // known to us should be added with KCookieDunno.
0954         // KCookieDunno means that we use the global policy.
0955         cookieList->setAdvice(KCookieDunno);
0956 
0957         m_cookieDomains.insert(domain, cookieList);
0958 
0959         // Update the list of domains
0960         m_domainList.append(domain);
0961     }
0962 
0963     // Add the cookie to the cookie list
0964     // The cookie list is sorted 'longest path first'
0965     if (!cookie.isExpired()) {
0966         cookieList->push_back(cookie);
0967         // Use a stable sort so that unit tests are reliable.
0968         // In practice it doesn't matter though.
0969         std::stable_sort(cookieList->begin(), cookieList->end(), compareCookies);
0970 
0971         m_cookiesChanged = true;
0972     }
0973 }
0974 
0975 //
0976 // This function advises whether a single KHttpCookie object should
0977 // be added to the cookie jar.
0978 //
0979 KCookieAdvice KCookieJar::cookieAdvice(const KHttpCookie &cookie) const
0980 {
0981     if (m_rejectCrossDomainCookies && cookie.isCrossDomain()) {
0982         return KCookieReject;
0983     }
0984 
0985     if (cookie.getUserSelectedAdvice() != KCookieDunno) {
0986         return cookie.getUserSelectedAdvice();
0987     }
0988 
0989     if (m_autoAcceptSessionCookies && cookie.expireDate() == 0) {
0990         return KCookieAccept;
0991     }
0992 
0993     QStringList domains;
0994     extractDomains(cookie.host(), domains);
0995 
0996     KCookieAdvice advice = KCookieDunno;
0997     QStringListIterator it(domains);
0998     while (advice == KCookieDunno && it.hasNext()) {
0999         const QString &domain = it.next();
1000         if (domain.startsWith(QLatin1Char('.')) || cookie.host() == domain) {
1001             KHttpCookieList *cookieList = m_cookieDomains.value(domain);
1002             if (cookieList) {
1003                 advice = cookieList->getAdvice();
1004             }
1005         }
1006     }
1007 
1008     if (advice == KCookieDunno) {
1009         advice = m_globalAdvice;
1010     }
1011 
1012     return advice;
1013 }
1014 
1015 //
1016 // This function tells whether a single KHttpCookie object should
1017 // be considered persistent. Persistent cookies do not get deleted
1018 // at the end of the session and are saved on disk.
1019 //
1020 bool KCookieJar::cookieIsPersistent(const KHttpCookie &cookie) const
1021 {
1022     if (cookie.expireDate() == 0) {
1023         return false;
1024     }
1025 
1026     KCookieAdvice advice = cookieAdvice(cookie);
1027 
1028     if (advice == KCookieReject || advice == KCookieAcceptForSession) {
1029         return false;
1030     }
1031 
1032     return true;
1033 }
1034 
1035 //
1036 // This function gets the advice for all cookies originating from
1037 // _domain.
1038 //
1039 KCookieAdvice KCookieJar::getDomainAdvice(const QString &_domain) const
1040 {
1041     KHttpCookieList *cookieList = m_cookieDomains.value(_domain);
1042     KCookieAdvice advice;
1043 
1044     if (cookieList) {
1045         advice = cookieList->getAdvice();
1046     } else {
1047         advice = KCookieDunno;
1048     }
1049 
1050     return advice;
1051 }
1052 
1053 //
1054 // This function sets the advice for all cookies originating from
1055 // _domain.
1056 //
1057 void KCookieJar::setDomainAdvice(const QString &domain, KCookieAdvice _advice)
1058 {
1059     KHttpCookieList *cookieList = m_cookieDomains.value(domain);
1060 
1061     if (cookieList) {
1062         if (cookieList->getAdvice() != _advice) {
1063             m_configChanged = true;
1064             // domain is already known
1065             cookieList->setAdvice(_advice);
1066         }
1067 
1068         if ((cookieList->isEmpty()) && (_advice == KCookieDunno)) {
1069             // This deletes cookieList!
1070             delete m_cookieDomains.take(domain);
1071             m_domainList.removeAll(domain);
1072         }
1073     } else {
1074         // domain is not yet known
1075         if (_advice != KCookieDunno) {
1076             // We should create a domain entry
1077             m_configChanged = true;
1078             // Make a new cookie list
1079             cookieList = new KHttpCookieList();
1080             cookieList->setAdvice(_advice);
1081             m_cookieDomains.insert(domain, cookieList);
1082             // Update the list of domains
1083             m_domainList.append(domain);
1084         }
1085     }
1086 }
1087 
1088 //
1089 // This function sets the advice for all cookies originating from
1090 // the same domain as _cookie
1091 //
1092 void KCookieJar::setDomainAdvice(const KHttpCookie &cookie, KCookieAdvice _advice)
1093 {
1094     QString domain;
1095     stripDomain(cookie.host(), domain); // We file the cookie under this domain.
1096     setDomainAdvice(domain, _advice);
1097 }
1098 
1099 //
1100 // This function sets the global advice for cookies
1101 //
1102 void KCookieJar::setGlobalAdvice(KCookieAdvice _advice)
1103 {
1104     if (m_globalAdvice != _advice) {
1105         m_configChanged = true;
1106     }
1107     m_globalAdvice = _advice;
1108 }
1109 
1110 //
1111 // Get a list of all domains known to the cookie jar.
1112 //
1113 const QStringList &KCookieJar::getDomainList()
1114 {
1115     return m_domainList;
1116 }
1117 
1118 //
1119 // Get a list of all cookies in the cookie jar originating from _domain.
1120 //
1121 KHttpCookieList *KCookieJar::getCookieList(const QString &_domain, const QString &_fqdn)
1122 {
1123     QString domain;
1124 
1125     if (_domain.isEmpty()) {
1126         stripDomain(_fqdn, domain);
1127     } else {
1128         domain = _domain;
1129     }
1130 
1131     return m_cookieDomains.value(domain);
1132 }
1133 
1134 //
1135 // Eat a cookie out of the jar.
1136 // cookieIterator should be one of the cookies returned by getCookieList()
1137 //
1138 void KCookieJar::eatCookie(const KHttpCookieList::iterator &cookieIterator)
1139 {
1140     const KHttpCookie &cookie = *cookieIterator;
1141     const QString domain = stripDomain(cookie); // We file the cookie under this domain.
1142     KHttpCookieList *cookieList = m_cookieDomains.value(domain);
1143 
1144     if (cookieList) {
1145         // This deletes cookie!
1146         cookieList->erase(cookieIterator);
1147 
1148         if ((cookieList->isEmpty()) && (cookieList->getAdvice() == KCookieDunno)) {
1149             // This deletes cookieList!
1150             delete m_cookieDomains.take(domain);
1151             m_domainList.removeAll(domain);
1152         }
1153     }
1154 }
1155 
1156 void KCookieJar::eatCookiesForDomain(const QString &domain)
1157 {
1158     KHttpCookieList *cookieList = m_cookieDomains.value(domain);
1159     if (!cookieList || cookieList->isEmpty()) {
1160         return;
1161     }
1162 
1163     cookieList->clear();
1164     if (cookieList->getAdvice() == KCookieDunno) {
1165         // This deletes cookieList!
1166         delete m_cookieDomains.take(domain);
1167         m_domainList.removeAll(domain);
1168     }
1169     m_cookiesChanged = true;
1170 }
1171 
1172 void KCookieJar::eatSessionCookies(long windowId)
1173 {
1174     if (!windowId) {
1175         return;
1176     }
1177 
1178     for (const QString &domain : std::as_const(m_domainList)) {
1179         eatSessionCookies(domain, windowId, false);
1180     }
1181 }
1182 
1183 void KCookieJar::eatAllCookies()
1184 {
1185     // we need a copy as eatCookiesForDomain() might remove domain from m_domainList
1186     const QStringList list = m_domainList;
1187     for (const QString &domain : list) {
1188         eatCookiesForDomain(domain);
1189     }
1190 }
1191 
1192 void KCookieJar::eatSessionCookies(const QString &fqdn, WId windowId, bool isFQDN)
1193 {
1194     KHttpCookieList *cookieList;
1195     if (!isFQDN) {
1196         cookieList = m_cookieDomains.value(fqdn);
1197     } else {
1198         QString domain;
1199         stripDomain(fqdn, domain);
1200         cookieList = m_cookieDomains.value(domain);
1201     }
1202 
1203     if (cookieList) {
1204         QMutableListIterator<KHttpCookie> cookieIterator(*cookieList);
1205         while (cookieIterator.hasNext()) {
1206             KHttpCookie &cookie = cookieIterator.next();
1207 
1208             if (cookieIsPersistent(cookie)) {
1209                 continue;
1210             }
1211 
1212             QList<WId> &ids = cookie.windowIds();
1213 
1214 #ifndef NDEBUG
1215             if (ids.contains(windowId)) {
1216                 if (ids.count() > 1) {
1217                     qCDebug(KIO_COOKIEJAR) << "removing window id" << windowId << "from session cookie";
1218                 } else {
1219                     qCDebug(KIO_COOKIEJAR) << "deleting session cookie";
1220                 }
1221             }
1222 #endif
1223             if (!ids.removeAll(windowId) || !ids.isEmpty()) {
1224                 continue;
1225             }
1226             cookieIterator.remove();
1227         }
1228     }
1229 }
1230 
1231 static QString hostWithPort(const KHttpCookie *cookie)
1232 {
1233     const QList<int> &ports = cookie->ports();
1234 
1235     if (ports.isEmpty()) {
1236         return cookie->host();
1237     }
1238 
1239     QStringList portList;
1240     for (int port : ports) {
1241         portList << QString::number(port);
1242     }
1243 
1244     return (cookie->host() + QLatin1Char(':') + portList.join(QLatin1Char(',')));
1245 }
1246 
1247 //
1248 // Saves all cookies to the file '_filename'.
1249 // On success 'true' is returned.
1250 // On failure 'false' is returned.
1251 bool KCookieJar::saveCookies(const QString &_filename)
1252 {
1253     QSaveFile cookieFile(_filename);
1254 
1255     if (!cookieFile.open(QIODevice::WriteOnly)) {
1256         return false;
1257     }
1258 
1259     QTextStream ts(&cookieFile);
1260 
1261     ts << "# KDE Cookie File v2\n#\n";
1262 
1263     const QString str =
1264         QString::asprintf("%-20s %-20s %-12s %-10s %-4s %-20s %-4s %s\n", "# Host", "Domain", "Path", "Exp.date", "Prot", "Name", "Sec", "Value");
1265     ts << str;
1266 
1267     for (const QString &domainName : std::as_const(m_domainList)) {
1268         bool domainPrinted = false;
1269 
1270         KHttpCookieList *cookieList = m_cookieDomains.value(domainName);
1271         if (!cookieList) {
1272             continue;
1273         }
1274 
1275         QMutableListIterator<KHttpCookie> cookieIterator(*cookieList);
1276         while (cookieIterator.hasNext()) {
1277             const KHttpCookie &cookie = cookieIterator.next();
1278 
1279             if (cookie.isExpired()) {
1280                 // Delete expired cookies
1281                 cookieIterator.remove();
1282                 continue;
1283             }
1284             if (cookieIsPersistent(cookie)) {
1285                 // Only save cookies that are not "session-only cookies"
1286                 if (!domainPrinted) {
1287                     domainPrinted = true;
1288                     ts << '[' << domainName.toLocal8Bit().data() << "]\n";
1289                 }
1290                 // Store persistent cookies
1291                 const QString path = QLatin1Char('"') + cookie.path() + QLatin1Char('"');
1292                 const QString domain = QLatin1Char('"') + cookie.domain() + QLatin1Char('"');
1293                 const QString host = hostWithPort(&cookie);
1294 
1295                 // TODO: replace with direct QTextStream output ?
1296                 const QString fullStr = QString::asprintf(
1297                     "%-20s %-20s %-12s %10lld  %3d %-20s %-4i %s\n",
1298                     host.toLatin1().constData(),
1299                     domain.toLatin1().constData(),
1300                     path.toLatin1().constData(),
1301                     cookie.expireDate(),
1302                     cookie.protocolVersion(),
1303                     cookie.name().isEmpty() ? cookie.value().toLatin1().constData() : cookie.name().toLatin1().constData(),
1304                     (cookie.isSecure() ? 1 : 0) + (cookie.isHttpOnly() ? 2 : 0) + (cookie.hasExplicitPath() ? 4 : 0) + (cookie.name().isEmpty() ? 8 : 0),
1305                     cookie.value().toLatin1().constData());
1306                 ts << fullStr.toLatin1();
1307             }
1308         }
1309     }
1310 
1311     if (cookieFile.commit()) {
1312         QFile::setPermissions(_filename, QFile::ReadUser | QFile::WriteUser);
1313         return true;
1314     }
1315     return false;
1316 }
1317 
1318 static const char *parseField(char *&buffer, bool keepQuotes = false)
1319 {
1320     char *result;
1321     if (!keepQuotes && (*buffer == '\"')) {
1322         // Find terminating "
1323         buffer++;
1324         result = buffer;
1325         while ((*buffer != '\"') && (*buffer)) {
1326             buffer++;
1327         }
1328     } else {
1329         // Find first white space
1330         result = buffer;
1331         while ((*buffer != ' ') && (*buffer != '\t') && (*buffer != '\n') && (*buffer)) {
1332             buffer++;
1333         }
1334     }
1335 
1336     if (!*buffer) {
1337         return result; //
1338     }
1339     *buffer++ = '\0';
1340 
1341     // Skip white-space
1342     while ((*buffer == ' ') || (*buffer == '\t') || (*buffer == '\n')) {
1343         buffer++;
1344     }
1345 
1346     return result;
1347 }
1348 
1349 static QString extractHostAndPorts(const QString &str, QList<int> *ports = nullptr)
1350 {
1351     if (str.isEmpty()) {
1352         return str;
1353     }
1354 
1355     const int index = str.indexOf(QLatin1Char(':'));
1356     if (index == -1) {
1357         return str;
1358     }
1359 
1360     const QString host = str.left(index);
1361     if (ports) {
1362         bool ok;
1363         const QStringList list = str.mid(index + 1).split(QLatin1Char(','));
1364         for (const QString &portStr : list) {
1365             const int portNum = portStr.toInt(&ok);
1366             if (ok) {
1367                 ports->append(portNum);
1368             }
1369         }
1370     }
1371 
1372     return host;
1373 }
1374 
1375 //
1376 // Reloads all cookies from the file '_filename'.
1377 // On success 'true' is returned.
1378 // On failure 'false' is returned.
1379 bool KCookieJar::loadCookies(const QString &_filename)
1380 {
1381     QFile cookieFile(_filename);
1382 
1383     if (!cookieFile.open(QIODevice::ReadOnly)) {
1384         return false;
1385     }
1386 
1387     int version = 1;
1388     bool success = false;
1389     char *buffer = new char[s_readBufferSize];
1390     qint64 len = cookieFile.readLine(buffer, s_readBufferSize - 1);
1391 
1392     if (len != -1) {
1393         if (qstrcmp(buffer, "# KDE Cookie File\n") == 0) {
1394             success = true;
1395         } else if (qstrcmp(buffer, "# KDE Cookie File v") > 0) {
1396             bool ok = false;
1397             const int verNum = QByteArray(buffer + 19, len - 19).trimmed().toInt(&ok);
1398             if (ok) {
1399                 version = verNum;
1400                 success = true;
1401             }
1402         }
1403     }
1404 
1405     if (success) {
1406         const qint64 currentTime = epoch();
1407         QList<int> ports;
1408 
1409         while (cookieFile.readLine(buffer, s_readBufferSize - 1) != -1) {
1410             char *line = buffer;
1411             // Skip lines which begin with '#' or '['
1412             if ((line[0] == '#') || (line[0] == '[')) {
1413                 continue;
1414             }
1415 
1416             const QString host = extractHostAndPorts(QLatin1String(parseField(line)), &ports);
1417             const QString domain = QLatin1String(parseField(line));
1418             if (host.isEmpty() && domain.isEmpty()) {
1419                 continue;
1420             }
1421             const QString path = QLatin1String(parseField(line));
1422             const QString expStr = QLatin1String(parseField(line));
1423             if (expStr.isEmpty()) {
1424                 continue;
1425             }
1426             const qint64 expDate = expStr.toLongLong();
1427             const QString verStr = QLatin1String(parseField(line));
1428             if (verStr.isEmpty()) {
1429                 continue;
1430             }
1431             int protVer = verStr.toInt();
1432             QString name = QLatin1String(parseField(line));
1433             bool secure = false;
1434             bool httpOnly = false;
1435             bool explicitPath = false;
1436             const char *value = nullptr;
1437             if ((version == 2) || (protVer >= 200)) {
1438                 if (protVer >= 200) {
1439                     protVer -= 200;
1440                 }
1441                 int i = atoi(parseField(line));
1442                 secure = i & 1;
1443                 httpOnly = i & 2;
1444                 explicitPath = i & 4;
1445                 if (i & 8) {
1446                     name = QLatin1String("");
1447                 }
1448                 line[strlen(line) - 1] = '\0'; // Strip LF.
1449                 value = line;
1450             } else {
1451                 bool keepQuotes = false;
1452                 if (protVer >= 100) {
1453                     protVer -= 100;
1454                     keepQuotes = true;
1455                 }
1456                 value = parseField(line, keepQuotes);
1457                 secure = QByteArray(parseField(line)).toShort();
1458             }
1459 
1460             // Expired or parse error
1461             if (!value || expDate == 0 || expDate < currentTime) {
1462                 continue;
1463             }
1464 
1465             KHttpCookie cookie(host, domain, path, name, QString::fromUtf8(value), expDate, protVer, secure, httpOnly, explicitPath);
1466             if (!ports.isEmpty()) {
1467                 cookie.mPorts = ports;
1468             }
1469             addCookie(cookie);
1470         }
1471     }
1472 
1473     delete[] buffer;
1474     m_cookiesChanged = false;
1475     return success;
1476 }
1477 
1478 //
1479 // Save the cookie configuration
1480 //
1481 
1482 void KCookieJar::saveConfig(KConfig *_config)
1483 {
1484     if (!m_configChanged) {
1485         return;
1486     }
1487 
1488     KConfigGroup dlgGroup(_config, "Cookie Dialog");
1489     dlgGroup.writeEntry("PreferredPolicy", static_cast<int>(m_preferredPolicy));
1490     dlgGroup.writeEntry("ShowCookieDetails", m_showCookieDetails);
1491     KConfigGroup policyGroup(_config, "Cookie Policy");
1492     policyGroup.writeEntry("CookieGlobalAdvice", adviceToStr(m_globalAdvice));
1493 
1494     QStringList domainSettings;
1495     for (const QString &domain : std::as_const(m_domainList)) {
1496         KCookieAdvice advice = getDomainAdvice(domain);
1497         if (advice != KCookieDunno) {
1498             const QString value = domain + QLatin1Char(':') + adviceToStr(advice);
1499             domainSettings.append(value);
1500         }
1501     }
1502     policyGroup.writeEntry("CookieDomainAdvice", domainSettings);
1503     _config->sync();
1504     m_configChanged = false;
1505 }
1506 
1507 //
1508 // Load the cookie configuration
1509 //
1510 
1511 void KCookieJar::loadConfig(KConfig *_config, bool reparse)
1512 {
1513     if (reparse) {
1514         _config->reparseConfiguration();
1515     }
1516 
1517     KConfigGroup dlgGroup(_config, "Cookie Dialog");
1518     m_showCookieDetails = dlgGroup.readEntry("ShowCookieDetails", false);
1519     m_preferredPolicy = static_cast<KCookieDefaultPolicy>(dlgGroup.readEntry("PreferredPolicy", 0));
1520 
1521     KConfigGroup policyGroup(_config, "Cookie Policy");
1522     const QStringList domainSettings = policyGroup.readEntry("CookieDomainAdvice", QStringList());
1523     // Warning: those default values are duplicated in the kcm (kio/kcookiespolicies.cpp)
1524     m_rejectCrossDomainCookies = policyGroup.readEntry("RejectCrossDomainCookies", true);
1525     m_autoAcceptSessionCookies = policyGroup.readEntry("AcceptSessionCookies", true);
1526     m_globalAdvice = strToAdvice(policyGroup.readEntry("CookieGlobalAdvice", QStringLiteral("Accept")));
1527 
1528     // Reset current domain settings first.
1529     // We need a copy as setDomainAdvice() may modify m_domainList
1530     const QStringList list = m_domainList;
1531     for (const QString &domain : list) {
1532         setDomainAdvice(domain, KCookieDunno);
1533     }
1534 
1535     // Now apply the domain settings read from config file...
1536     for (QStringList::ConstIterator it = domainSettings.constBegin(), itEnd = domainSettings.constEnd(); it != itEnd; ++it) {
1537         const QString &value = *it;
1538         const int sepPos = value.lastIndexOf(QLatin1Char(':'));
1539         if (sepPos <= 0) {
1540             continue;
1541         }
1542 
1543         const QString domain(value.left(sepPos));
1544         KCookieAdvice advice = strToAdvice(value.mid(sepPos + 1));
1545         setDomainAdvice(domain, advice);
1546     }
1547 }
1548 
1549 QDebug operator<<(QDebug dbg, const KHttpCookie &cookie)
1550 {
1551     dbg.nospace() << cookie.cookieStr(false);
1552     return dbg.space();
1553 }
1554 
1555 QDebug operator<<(QDebug dbg, const KHttpCookieList &list)
1556 {
1557     for (const KHttpCookie &cookie : list) {
1558         dbg << cookie;
1559     }
1560     return dbg;
1561 }