QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByteArray &cookieString) { // According to http://wp.netscape.com/newsref/std/cookie_spec.html,< // the Set-Cookie response header is of the format: // // Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure // // where only the NAME=VALUE part is mandatory // // We do not support RFC 2965 Set-Cookie2-style cookies QList<QNetworkCookie> result; QDateTime now = QDateTime::currentDateTime().toUTC(); int position = 0; const int length = cookieString.length(); while (position < length) { QNetworkCookie cookie; // The first part is always the "NAME=VALUE" part QPair<QByteArray,QByteArray> field = nextField(cookieString, position, true); if (field.first.isEmpty() || field.second.isNull()) // parsing error break; cookie.setName(field.first); cookie.setValue(field.second); position = nextNonWhitespace(cookieString, position); bool endOfCookie = false; while (!endOfCookie && position < length) { switch (cookieString.at(position++)) { case ',': // end of the cookie endOfCookie = true; break; case ';': // new field in the cookie field = nextField(cookieString, position, false); field.first = field.first.toLower(); // everything but the NAME=VALUE is case-insensitive if (field.first == "expires") { position -= field.second.length(); int end; for (end = position; end < length; ++end) if (isValueSeparator(cookieString.at(end))) break; QByteArray dateString = cookieString.mid(position, end - position).trimmed(); position = end; QDateTime dt = parseDateString(dateString.toLower()); if (!dt.isValid()) { return result; } cookie.setExpirationDate(dt); } else if (field.first == "domain") { QByteArray rawDomain = field.second; QString maybeLeadingDot; if (rawDomain.startsWith('.')) { maybeLeadingDot = QLatin1Char('.'); rawDomain = rawDomain.mid(1); } QString normalizedDomain = QUrl::fromAce(QUrl::toAce(QString::fromUtf8(rawDomain))); if (normalizedDomain.isEmpty() && !rawDomain.isEmpty()) return result; cookie.setDomain(maybeLeadingDot + normalizedDomain); } else if (field.first == "max-age") { bool ok = false; int secs = field.second.toInt(&ok); if (!ok) return result; cookie.setExpirationDate(now.addSecs(secs)); } else if (field.first == "path") { QString path = QUrl::fromPercentEncoding(field.second); cookie.setPath(path); } else if (field.first == "secure") { cookie.setSecure(true); } else if (field.first == "httponly") { cookie.setHttpOnly(true); } else if (field.first == "comment") { //cookie.setComment(QString::fromUtf8(field.second)); } else if (field.first == "version") { if (field.second != "1") { // oops, we don't know how to handle this cookie return result; } } else { // got an unknown field in the cookie // what do we do? } position = nextNonWhitespace(cookieString, position); } } if (!cookie.name().isEmpty()) result += cookie; } return result; }
QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByteArray &cookieString) { // According to http://wp.netscape.com/newsref/std/cookie_spec.html,< // the Set-Cookie response header is of the format: // // Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure // // where only the NAME=VALUE part is mandatory // // We do not support RFC 2965 Set-Cookie2-style cookies QList<QNetworkCookie> result; const QDateTime now = QDateTime::currentDateTimeUtc(); int position = 0; const int length = cookieString.length(); while (position < length) { QNetworkCookie cookie; // The first part is always the "NAME=VALUE" part QPair<QByteArray,QByteArray> field = nextField(cookieString, position, true); if (field.first.isEmpty()) // parsing error break; cookie.setName(field.first); cookie.setValue(field.second); position = nextNonWhitespace(cookieString, position); while (position < length) { switch (cookieString.at(position++)) { case ';': // new field in the cookie field = nextField(cookieString, position, false); field.first = field.first.toLower(); // everything but the NAME=VALUE is case-insensitive if (field.first == "expires") { position -= field.second.length(); int end; for (end = position; end < length; ++end) if (isValueSeparator(cookieString.at(end))) break; QByteArray dateString = cookieString.mid(position, end - position).trimmed(); position = end; QDateTime dt = parseDateString(dateString.toLower()); if (dt.isValid()) cookie.setExpirationDate(dt); //if unparsed, ignore the attribute but not the whole cookie (RFC6265 section 5.2.1) } else if (field.first == "domain") { QByteArray rawDomain = field.second; //empty domain should be ignored (RFC6265 section 5.2.3) if (!rawDomain.isEmpty()) { QString maybeLeadingDot; if (rawDomain.startsWith('.')) { maybeLeadingDot = QLatin1Char('.'); rawDomain = rawDomain.mid(1); } //IDN domains are required by RFC6265, accepting utf8 as well doesn't break any test cases. QString normalizedDomain = QUrl::fromAce(QUrl::toAce(QString::fromUtf8(rawDomain))); if (!normalizedDomain.isEmpty()) { cookie.setDomain(maybeLeadingDot + normalizedDomain); } else { //Normalization fails for malformed domains, e.g. "..example.org", reject the cookie now //rather than accepting it but never sending it due to domain match failure, as the //strict reading of RFC6265 would indicate. return result; } } } else if (field.first == "max-age") { bool ok = false; int secs = field.second.toInt(&ok); if (ok) { if (secs <= 0) { //earliest representable time (RFC6265 section 5.2.2) cookie.setExpirationDate(QDateTime::fromSecsSinceEpoch(0)); } else { cookie.setExpirationDate(now.addSecs(secs)); } } //if unparsed, ignore the attribute but not the whole cookie (RFC6265 section 5.2.2) } else if (field.first == "path") { if (field.second.startsWith('/')) { // ### we should treat cookie paths as an octet sequence internally // However RFC6265 says we should assume UTF-8 for presentation as a string cookie.setPath(QString::fromUtf8(field.second)); } else { // if the path doesn't start with '/' then set the default path (RFC6265 section 5.2.4) // and also IETF test case path0030 which has valid and empty path in the same cookie cookie.setPath(QString()); } } else if (field.first == "secure") { cookie.setSecure(true); } else if (field.first == "httponly") { cookie.setHttpOnly(true); } else { // ignore unknown fields in the cookie (RFC6265 section 5.2, rule 6) } position = nextNonWhitespace(cookieString, position); } } if (!cookie.name().isEmpty()) result += cookie; } return result; }