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 }