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