diff options
Diffstat (limited to 'kioslave/http/kcookiejar')
20 files changed, 7503 insertions, 0 deletions
diff --git a/kioslave/http/kcookiejar/Makefile.am b/kioslave/http/kcookiejar/Makefile.am new file mode 100644 index 000000000..933de5e13 --- /dev/null +++ b/kioslave/http/kcookiejar/Makefile.am @@ -0,0 +1,31 @@ +# Makefile.am of kdebase/kioslave/http + +SUBDIRS=tests +INCLUDES= $(all_includes) + +####### Files + +bin_PROGRAMS = +lib_LTLIBRARIES = +kdeinit_LTLIBRARIES = kcookiejar.la +kde_module_LTLIBRARIES = kded_kcookiejar.la + +kcookiejar_la_SOURCES = main.cpp +METASOURCES = AUTO +kcookiejar_la_LDFLAGS = $(all_libraries) -module -avoid-version +kcookiejar_la_LIBADD = $(LIB_KDECORE) + +kded_kcookiejar_la_SOURCES = kcookiejar.cpp kcookieserver.cpp \ + kcookieserver.skel kcookiewin.cpp +kded_kcookiejar_la_LDFLAGS = $(all_libraries) -module -avoid-version +kded_kcookiejar_la_LIBADD = $(LIB_KIO) $(LIB_KDED) + +kded_DATA = kcookiejar.desktop +kdeddir = $(kde_servicesdir)/kded + +update_DATA = kcookiescfg.upd +updatedir = $(kde_datadir)/kconf_update + +cookie_DATA = domain_info +cookiedir = $(kde_datadir)/khtml + diff --git a/kioslave/http/kcookiejar/domain_info b/kioslave/http/kcookiejar/domain_info new file mode 100644 index 000000000..94baf8dae --- /dev/null +++ b/kioslave/http/kcookiejar/domain_info @@ -0,0 +1 @@ +twoLevelTLD=name,ai,au,bd,bh,ck,eg,et,fk,il,in,kh,kr,mk,mt,na,np,nz,pg,pk,qa,sa,sb,sg,sv,ua,ug,uk,uy,vn,za,zw diff --git a/kioslave/http/kcookiejar/kcookiejar.cpp b/kioslave/http/kcookiejar/kcookiejar.cpp new file mode 100644 index 000000000..5b5f78f6b --- /dev/null +++ b/kioslave/http/kcookiejar/kcookiejar.cpp @@ -0,0 +1,1558 @@ +/* This file is part of the KDE File Manager + + Copyright (C) 1998-2000 Waldo Bastian (bastian@kde.org) + Copyright (C) 2000,2001 Dawit Alemayehu (adawit@kde.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, and/or sell copies of the + Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +//---------------------------------------------------------------------------- +// +// KDE File Manager -- HTTP Cookies +// $Id$ + +// +// The cookie protocol is a mess. RFC2109 is a joke since nobody seems to +// use it. Apart from that it is badly written. +// We try to implement Netscape Cookies and try to behave us according to +// RFC2109 as much as we can. +// +// We assume cookies do not contain any spaces (Netscape spec.) +// According to RFC2109 this is allowed though. +// + +#include <config.h> +#include <sys/types.h> +#include <sys/stat.h> +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#include <fcntl.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> + +#ifdef USE_SOLARIS +#include <strings.h> +#endif + +#include <stdlib.h> + +//#include <netinet/in.h> +//#include <arpa/inet.h> + +#include <qstring.h> +#include <qstrlist.h> +#include <qptrlist.h> +#include <qptrdict.h> +#include <qfile.h> +#include <qdir.h> +#include <qregexp.h> + +#include <kurl.h> +#include <krfcdate.h> +#include <kconfig.h> +#include <ksavefile.h> +#include <kdebug.h> + +#include "kcookiejar.h" + + +// BR87227 +// Waba: Should the number of cookies be limited? +// I am not convinced of the need of such limit +// Mozilla seems to limit to 20 cookies / domain +// but it is unclear which policy it uses to expire +// cookies when it exceeds that amount +#undef MAX_COOKIE_LIMIT + +#define MAX_COOKIES_PER_HOST 25 +#define READ_BUFFER_SIZE 8192 +#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]?)" + +// Note with respect to QString::fromLatin1( ) +// Cookies are stored as 8 bit data and passed to kio_http as +// latin1 regardless of their actual encoding. + +// L1 is used to indicate latin1 constants +#define L1(x) QString::fromLatin1(x) + +template class QPtrList<KHttpCookie>; +template class QPtrDict<KHttpCookieList>; + +QString KCookieJar::adviceToStr(KCookieAdvice _advice) +{ + switch( _advice ) + { + case KCookieAccept: return L1("Accept"); + case KCookieReject: return L1("Reject"); + case KCookieAsk: return L1("Ask"); + default: return L1("Dunno"); + } +} + +KCookieAdvice KCookieJar::strToAdvice(const QString &_str) +{ + if (_str.isEmpty()) + return KCookieDunno; + + QCString advice = _str.lower().latin1(); + + if (advice == "accept") + return KCookieAccept; + else if (advice == "reject") + return KCookieReject; + else if (advice == "ask") + return KCookieAsk; + + return KCookieDunno; +} + +// KHttpCookie +/////////////////////////////////////////////////////////////////////////// + +// +// Cookie constructor +// +KHttpCookie::KHttpCookie(const QString &_host, + const QString &_domain, + const QString &_path, + const QString &_name, + const QString &_value, + time_t _expireDate, + int _protocolVersion, + bool _secure, + bool _httpOnly, + bool _explicitPath) : + mHost(_host), + mDomain(_domain), + mPath(_path.isEmpty() ? QString::null : _path), + mName(_name), + mValue(_value), + mExpireDate(_expireDate), + mProtocolVersion(_protocolVersion), + mSecure(_secure), + mHttpOnly(_httpOnly), + mExplicitPath(_explicitPath) +{ +} + +// +// Checks if a cookie has been expired +// +bool KHttpCookie::isExpired(time_t currentDate) +{ + return (mExpireDate != 0) && (mExpireDate < currentDate); +} + +// +// Returns a string for a HTTP-header +// +QString KHttpCookie::cookieStr(bool useDOMFormat) +{ + QString result; + + if (useDOMFormat || (mProtocolVersion == 0)) + { + if ( !mName.isEmpty() ) + result = mName + '='; + result += mValue; + } + else + { + result = mName + '=' + mValue; + if (mExplicitPath) + result += L1("; $Path=\"") + mPath + L1("\""); + if (!mDomain.isEmpty()) + result += L1("; $Domain=\"") + mDomain + L1("\""); + } + return result; +} + +// +// Returns whether this cookie should be send to this location. +bool KHttpCookie::match(const QString &fqdn, const QStringList &domains, + const QString &path) +{ + // Cookie domain match check + if (mDomain.isEmpty()) + { + if (fqdn != mHost) + return false; + } + else if (!domains.contains(mDomain)) + { + if (mDomain[0] == '.') + return false; + + // Maybe the domain needs an extra dot. + QString domain = '.' + mDomain; + if ( !domains.contains( domain ) ) + if ( fqdn != mDomain ) + return false; + } + + // Cookie path match check + if (mPath.isEmpty()) + return true; + + // According to the netscape spec both http://www.acme.com/foobar, + // http://www.acme.com/foo.bar and http://www.acme.com/foo/bar + // match http://www.acme.com/foo. + // We only match http://www.acme.com/foo/bar + + if( path.startsWith(mPath) && + ( + (path.length() == mPath.length() ) || // Paths are exact match + (path[mPath.length()-1] == '/') || // mPath ended with a slash + (path[mPath.length()] == '/') // A slash follows. + )) + return true; // Path of URL starts with cookie-path + + return false; +} + +// KHttpCookieList +/////////////////////////////////////////////////////////////////////////// + +int KHttpCookieList::compareItems( void * item1, void * item2) +{ + int pathLen1 = ((KHttpCookie *)item1)->path().length(); + int pathLen2 = ((KHttpCookie *)item2)->path().length(); + if (pathLen1 > pathLen2) + return -1; + if (pathLen1 < pathLen2) + return 1; + return 0; +} + + +// KCookieJar +/////////////////////////////////////////////////////////////////////////// + +// +// Constructs a new cookie jar +// +// One jar should be enough for all cookies. +// +KCookieJar::KCookieJar() +{ + m_cookieDomains.setAutoDelete( true ); + m_globalAdvice = KCookieDunno; + m_configChanged = false; + m_cookiesChanged = false; + + KConfig cfg("khtml/domain_info", true, false, "data"); + QStringList countries = cfg.readListEntry("twoLevelTLD"); + for(QStringList::ConstIterator it = countries.begin(); + it != countries.end(); ++it) + { + m_twoLevelTLD.replace(*it, (int *) 1); + } +} + +// +// Destructs the cookie jar +// +// Poor little cookies, they will all be eaten by the cookie monster! +// +KCookieJar::~KCookieJar() +{ + // Not much to do here +} + +static void removeDuplicateFromList(KHttpCookieList *list, KHttpCookie *cookiePtr, bool nameMatchOnly=false, bool updateWindowId=false) +{ + QString domain1 = cookiePtr->domain(); + if (domain1.isEmpty()) + domain1 = cookiePtr->host(); + + for ( KHttpCookiePtr cookie=list->first(); cookie != 0; ) + { + QString domain2 = cookie->domain(); + if (domain2.isEmpty()) + domain2 = cookie->host(); + + if ( + (cookiePtr->name() == cookie->name()) && + ( + nameMatchOnly || + ( (domain1 == domain2) && (cookiePtr->path() == cookie->path()) ) + ) + ) + { + if (updateWindowId) + { + for(QValueList<long>::ConstIterator it = cookie->windowIds().begin(); + it != cookie->windowIds().end(); ++it) + { + long windowId = *it; + if (windowId && (cookiePtr->windowIds().find(windowId) == cookiePtr->windowIds().end())) + { + cookiePtr->windowIds().append(windowId); + } + } + } + KHttpCookiePtr old_cookie = cookie; + cookie = list->next(); + list->removeRef( old_cookie ); + break; + } + else + { + cookie = list->next(); + } + } +} + + +// +// Looks for cookies in the cookie jar which are appropriate for _url. +// Returned is a string containing all appropriate cookies in a format +// which can be added to a HTTP-header without any additional processing. +// +QString KCookieJar::findCookies(const QString &_url, bool useDOMFormat, long windowId, KHttpCookieList *pendingCookies) +{ + QString cookieStr; + QStringList domains; + QString fqdn; + QString path; + KHttpCookiePtr cookie; + KCookieAdvice advice = m_globalAdvice; + + if (!parseURL(_url, fqdn, path)) + return cookieStr; + + bool secureRequest = (_url.find( L1("https://"), 0, false) == 0 || + _url.find( L1("webdavs://"), 0, false) == 0); + + // kdDebug(7104) << "findCookies: URL= " << _url << ", secure = " << secureRequest << endl; + + extractDomains(fqdn, domains); + + KHttpCookieList allCookies; + + for(QStringList::ConstIterator it = domains.begin(); + true; + ++it) + { + KHttpCookieList *cookieList; + if (it == domains.end()) + { + cookieList = pendingCookies; // Add pending cookies + pendingCookies = 0; + if (!cookieList) + break; + } + else + { + QString key = (*it).isNull() ? L1("") : (*it); + cookieList = m_cookieDomains[key]; + if (!cookieList) + continue; // No cookies for this domain + } + + if (cookieList->getAdvice() != KCookieDunno) + advice = cookieList->getAdvice(); + + for ( cookie=cookieList->first(); cookie != 0; cookie=cookieList->next() ) + { + // If the we are setup to automatically accept all session cookies and to + // treat all cookies as session cookies or the current cookie is a session + // cookie, then send the cookie back regardless of either policy. + if (advice == KCookieReject && + !(m_autoAcceptSessionCookies && + (m_ignoreCookieExpirationDate || cookie->expireDate() == 0))) + continue; + + if (!cookie->match(fqdn, domains, path)) + continue; + + if( cookie->isSecure() && !secureRequest ) + continue; + + if( cookie->isHttpOnly() && useDOMFormat ) + continue; + + // Do not send expired cookies. + if ( cookie->isExpired (time(0)) ) + { + // Note there is no need to actually delete the cookie here + // since the cookieserver will invoke ::saveCookieJar because + // of the state change below. This will then do the job of + // deleting the cookie for us. + m_cookiesChanged = true; + continue; + } + + if (windowId && (cookie->windowIds().find(windowId) == cookie->windowIds().end())) + { + cookie->windowIds().append(windowId); + } + + if (it == domains.end()) // Only needed when processing pending cookies + removeDuplicateFromList(&allCookies, cookie); + + allCookies.append(cookie); + } + if (it == domains.end()) + break; // Finished. + } + + int cookieCount = 0; + + int protVersion=0; + for ( cookie=allCookies.first(); cookie != 0; cookie=allCookies.next() ) + { + if (cookie->protocolVersion() > protVersion) + protVersion = cookie->protocolVersion(); + } + + for ( cookie=allCookies.first(); cookie != 0; cookie=allCookies.next() ) + { + if (useDOMFormat) + { + if (cookieCount > 0) + cookieStr += L1("; "); + cookieStr += cookie->cookieStr(true); + } + else + { + if (cookieCount == 0) + { + cookieStr += L1("Cookie: "); + if (protVersion > 0) + { + QString version; + version.sprintf("$Version=%d; ", protVersion); // Without quotes + cookieStr += version; + } + } + else + { + cookieStr += L1("; "); + } + cookieStr += cookie->cookieStr(false); + } + cookieCount++; + } + + return cookieStr; +} + +// +// This function parses a string like 'my_name="my_value";' and returns +// 'my_name' in Name and 'my_value' in Value. +// +// A pointer to the end of the parsed part is returned. +// This pointer points either to: +// '\0' - The end of the string has reached. +// ';' - Another my_name="my_value" pair follows +// ',' - Another cookie follows +// '\n' - Another header follows +static const char * parseNameValue(const char *header, + QString &Name, + QString &Value, + bool keepQuotes=false, + bool rfcQuotes=false) +{ + const char *s = header; + // Parse 'my_name' part + for(; (*s != '='); s++) + { + if ((*s=='\0') || (*s==';') || (*s=='\n')) + { + // No '=' sign -> use string as the value, name is empty + // (behavior found in Mozilla and IE) + Name = ""; + Value = QString::fromLatin1(header); + Value.truncate( s - header ); + Value = Value.stripWhiteSpace(); + return (s); + } + } + + Name = header; + Name.truncate( s - header ); + Name = Name.stripWhiteSpace(); + + // *s == '=' + s++; + + // Skip any whitespace + for(; (*s == ' ') || (*s == '\t'); s++) + { + if ((*s=='\0') || (*s==';') || (*s=='\n')) + { + // End of Name + Value = ""; + return (s); + } + } + + if ((rfcQuotes || !keepQuotes) && (*s == '\"')) + { + // Parse '"my_value"' part (quoted value) + if (keepQuotes) + header = s++; + else + header = ++s; // skip " + for(;(*s != '\"');s++) + { + if ((*s=='\0') || (*s=='\n')) + { + // End of Name + Value = QString::fromLatin1(header); + Value.truncate(s - header); + return (s); + } + } + Value = QString::fromLatin1(header); + // *s == '\"'; + if (keepQuotes) + Value.truncate( ++s - header ); + else + Value.truncate( s++ - header ); + + // Skip any remaining garbage + for(;; s++) + { + if ((*s=='\0') || (*s==';') || (*s=='\n')) + break; + } + } + else + { + // Parse 'my_value' part (unquoted value) + header = s; + while ((*s != '\0') && (*s != ';') && (*s != '\n')) + s++; + // End of Name + Value = QString::fromLatin1(header); + Value.truncate( s - header ); + Value = Value.stripWhiteSpace(); + } + return (s); + +} + +void KCookieJar::stripDomain(const QString &_fqdn, QString &_domain) +{ + QStringList domains; + extractDomains(_fqdn, domains); + if (domains.count() > 3) + _domain = domains[3]; + else + _domain = domains[0]; +} + +QString KCookieJar::stripDomain( KHttpCookiePtr cookiePtr) +{ + QString domain; // We file the cookie under this domain. + if (cookiePtr->domain().isEmpty()) + stripDomain( cookiePtr->host(), domain); + else + stripDomain (cookiePtr->domain(), domain); + return domain; +} + +bool KCookieJar::parseURL(const QString &_url, + QString &_fqdn, + QString &_path) +{ + KURL kurl(_url); + if (!kurl.isValid()) + return false; + + _fqdn = kurl.host().lower(); + if (kurl.port()) + { + if (((kurl.protocol() == L1("http")) && (kurl.port() != 80)) || + ((kurl.protocol() == L1("https")) && (kurl.port() != 443))) + { + _fqdn = L1("%1:%2").arg(kurl.port()).arg(_fqdn); + } + } + + // Cookie spoofing protection. Since there is no way a path separator + // or escape encoded character is allowed in the hostname according + // to RFC 2396, reject attempts to include such things there! + if(_fqdn.find('/') > -1 || _fqdn.find('%') > -1) + { + return false; // deny everything!! + } + + _path = kurl.path(); + if (_path.isEmpty()) + _path = L1("/"); + + QRegExp exp(L1("[\\\\/]\\.\\.[\\\\/]")); + // Weird path, cookie stealing attempt? + if (exp.search(_path) != -1) + return false; // Deny everything!! + + return true; +} + +void KCookieJar::extractDomains(const QString &_fqdn, + QStringList &_domains) +{ + // Return numeric IPv6 addresses as is... + if (_fqdn[0] == '[') + { + _domains.append( _fqdn ); + return; + } + // Return numeric IPv4 addresses as is... + if ((_fqdn[0] >= '0') && (_fqdn[0] <= '9')) + { + if (_fqdn.find(QRegExp(IP_ADDRESS_EXPRESSION)) > -1) + { + _domains.append( _fqdn ); + return; + } + } + + QStringList partList = QStringList::split('.', _fqdn, false); + + if (partList.count()) + partList.remove(partList.begin()); // Remove hostname + + while(partList.count()) + { + + if (partList.count() == 1) + break; // We only have a TLD left. + + if ((partList.count() == 2) && (m_twoLevelTLD[partList[1].lower()])) + { + // This domain uses two-level TLDs in the form xxxx.yy + break; + } + + if ((partList.count() == 2) && (partList[1].length() == 2)) + { + // If this is a TLD, we should stop. (e.g. co.uk) + // We assume this is a TLD if it ends with .xx.yy or .x.yy + if (partList[0].length() <= 2) + break; // This is a TLD. + + // Catch some TLDs that we miss with the previous check + // e.g. com.au, org.uk, mil.co + QCString t = partList[0].lower().utf8(); + if ((t == "com") || (t == "net") || (t == "org") || (t == "gov") || (t == "edu") || (t == "mil") || (t == "int")) + break; + } + + QString domain = partList.join(L1(".")); + _domains.append(domain); + _domains.append('.' + domain); + partList.remove(partList.begin()); // Remove part + } + + // Always add the FQDN at the start of the list for + // hostname == cookie-domainname checks! + _domains.prepend( '.' + _fqdn ); + _domains.prepend( _fqdn ); +} + + +/* + Changes dates in from the following format + + Wed Sep 12 07:00:00 2007 GMT + to + Wed Sep 12 2007 07:00:00 GMT + + to allow KRFCDate::parseDate to properly parse expiration date formats + used in cookies by some servers such as amazon.com. See BR# 145244. +*/ +static QString fixupDateTime(const QString& dt) +{ + const int index = dt.find(QRegExp("[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}")); + + if (index > -1) + { + QStringList dateStrList = QStringList::split(' ', dt.mid(index)); + if (dateStrList.count() > 1) + { + QString date = dateStrList[0]; + dateStrList[0] = dateStrList[1]; + dateStrList[1] = date; + date = dt; + return date.replace(index, date.length(), dateStrList.join(" ")); + } + } + + return dt; +} + +// +// This function parses cookie_headers and returns a linked list of +// KHttpCookie objects for all cookies found in cookie_headers. +// If no cookies could be found 0 is returned. +// +// cookie_headers should be a concatenation of all lines of a HTTP-header +// which start with "Set-Cookie". The lines should be separated by '\n's. +// +KHttpCookieList KCookieJar::makeCookies(const QString &_url, + const QCString &cookie_headers, + long windowId) +{ + KHttpCookieList cookieList; + KHttpCookieList cookieList2; + KHttpCookiePtr lastCookie = 0; + const char *cookieStr = cookie_headers.data(); + QString Name; + QString Value; + QString fqdn; + QString path; + bool crossDomain = false; + + if (!parseURL(_url, fqdn, path)) + { + // Error parsing _url + return KHttpCookieList(); + } + QString defaultPath; + int i = path.findRev('/'); + if (i > 0) + defaultPath = path.left(i); + + // The hard stuff :) + for(;;) + { + // check for "Set-Cookie" + if (strncmp(cookieStr, "Cross-Domain\n", 13) == 0) + { + cookieStr += 13; + crossDomain = true; + } + else if (strncasecmp(cookieStr, "Set-Cookie:", 11) == 0) + { + cookieStr = parseNameValue(cookieStr+11, Name, Value, true); + + // Host = FQDN + // Default domain = "" + // Default path according to rfc2109 + + KHttpCookie *cookie = new KHttpCookie(fqdn, L1(""), defaultPath, Name, Value); + if (windowId) + cookie->mWindowIds.append(windowId); + cookie->mCrossDomain = crossDomain; + + // Insert cookie in chain + cookieList.append(cookie); + lastCookie = cookie; + } + else if (strncasecmp(cookieStr, "Set-Cookie2:", 12) == 0) + { + // Attempt to follow rfc2965 + cookieStr = parseNameValue(cookieStr+12, Name, Value, true, true); + + // Host = FQDN + // Default domain = "" + // Default path according to rfc2965 + + KHttpCookie *cookie = new KHttpCookie(fqdn, L1(""), defaultPath, Name, Value); + if (windowId) + cookie->mWindowIds.append(windowId); + cookie->mCrossDomain = crossDomain; + + // Insert cookie in chain + cookieList2.append(cookie); + lastCookie = cookie; + } + else + { + // This is not the start of a cookie header, skip till next line. + while (*cookieStr && *cookieStr != '\n') + cookieStr++; + + if (*cookieStr == '\n') + cookieStr++; + + if (!*cookieStr) + break; // End of cookie_headers + else + continue; // end of this header, continue with next. + } + + while ((*cookieStr == ';') || (*cookieStr == ' ')) + { + cookieStr++; + + // Name-Value pair follows + cookieStr = parseNameValue(cookieStr, Name, Value); + + QCString cName = Name.lower().latin1(); + if (cName == "domain") + { + QString dom = Value.lower(); + // RFC2965 3.2.2: If an explicitly specified value does not + // start with a dot, the user agent supplies a leading dot + if(dom.length() && dom[0] != '.') + dom.prepend("."); + // remove a trailing dot + if(dom.length() > 2 && dom[dom.length()-1] == '.') + dom = dom.left(dom.length()-1); + + if(dom.contains('.') > 1 || dom == ".local") + lastCookie->mDomain = dom; + } + else if (cName == "max-age") + { + int max_age = Value.toInt(); + if (max_age == 0) + lastCookie->mExpireDate = 1; + else + lastCookie->mExpireDate = time(0)+max_age; + } + else if (cName == "expires") + { + // Parse brain-dead netscape cookie-format + lastCookie->mExpireDate = KRFCDate::parseDate(Value); + + // Workaround for servers that send the expiration date in + // 'Wed Sep 12 07:00:00 2007 GMT' format. See BR# 145244. + if (lastCookie->mExpireDate == 0) + lastCookie->mExpireDate = KRFCDate::parseDate(fixupDateTime(Value)); + } + else if (cName == "path") + { + if (Value.isEmpty()) + lastCookie->mPath = QString::null; // Catch "" <> QString::null + else + lastCookie->mPath = KURL::decode_string(Value); + lastCookie->mExplicitPath = true; + } + else if (cName == "version") + { + lastCookie->mProtocolVersion = Value.toInt(); + } + else if ((cName == "secure") || + (cName.isEmpty() && Value.lower() == L1("secure"))) + { + lastCookie->mSecure = true; + } + else if ((cName == "httponly") || + (cName.isEmpty() && Value.lower() == L1("httponly"))) + { + lastCookie->mHttpOnly = true; + } + } + + if (*cookieStr == '\0') + break; // End of header + + // Skip ';' or '\n' + cookieStr++; + } + + // RFC2965 cookies come last so that they override netscape cookies. + while( !cookieList2.isEmpty() && (lastCookie = cookieList2.take(0)) ) + { + removeDuplicateFromList(&cookieList, lastCookie, true); + cookieList.append(lastCookie); + } + + return cookieList; +} + +/** +* Parses cookie_domstr and returns a linked list of KHttpCookie objects. +* cookie_domstr should be a semicolon-delimited list of "name=value" +* pairs. Any whitespace before "name" or around '=' is discarded. +* If no cookies are found, 0 is returned. +*/ +KHttpCookieList KCookieJar::makeDOMCookies(const QString &_url, + const QCString &cookie_domstring, + long windowId) +{ + // A lot copied from above + KHttpCookieList cookieList; + KHttpCookiePtr lastCookie = 0; + + const char *cookieStr = cookie_domstring.data(); + QString Name; + QString Value; + QString fqdn; + QString path; + + if (!parseURL(_url, fqdn, path)) + { + // Error parsing _url + return KHttpCookieList(); + } + + // This time it's easy + while(*cookieStr) + { + cookieStr = parseNameValue(cookieStr, Name, Value); + + // Host = FQDN + // Default domain = "" + // Default path = "" + KHttpCookie *cookie = new KHttpCookie(fqdn, QString::null, QString::null, + Name, Value ); + if (windowId) + cookie->mWindowIds.append(windowId); + + cookieList.append(cookie); + lastCookie = cookie; + + if (*cookieStr != '\0') + cookieStr++; // Skip ';' or '\n' + } + + return cookieList; +} + +#ifdef MAX_COOKIE_LIMIT +static void makeRoom(KHttpCookieList *cookieList, KHttpCookiePtr &cookiePtr) +{ + // Too much cookies: throw one away, try to be somewhat clever + KHttpCookiePtr lastCookie = 0; + for(KHttpCookiePtr cookie = cookieList->first(); cookie; cookie = cookieList->next()) + { + if (cookieList->compareItems(cookie, cookiePtr) < 0) + break; + lastCookie = cookie; + } + if (!lastCookie) + lastCookie = cookieList->first(); + cookieList->removeRef(lastCookie); +} +#endif + +// +// This function hands a KHttpCookie object over to the cookie jar. +// +// On return cookiePtr is set to 0. +// +void KCookieJar::addCookie(KHttpCookiePtr &cookiePtr) +{ + QStringList domains; + KHttpCookieList *cookieList = 0L; + + // We always need to do this to make sure that the + // that cookies of type hostname == cookie-domainname + // are properly removed and/or updated as necessary! + extractDomains( cookiePtr->host(), domains ); + for ( QStringList::ConstIterator it = domains.begin(); + (it != domains.end() && !cookieList); + ++it ) + { + QString key = (*it).isNull() ? L1("") : (*it); + KHttpCookieList *list= m_cookieDomains[key]; + if ( !list ) continue; + + removeDuplicateFromList(list, cookiePtr, false, true); + } + + QString domain = stripDomain( cookiePtr ); + QString key = domain.isNull() ? L1("") : domain; + cookieList = m_cookieDomains[ key ]; + if (!cookieList) + { + // Make a new cookie list + cookieList = new KHttpCookieList(); + cookieList->setAutoDelete(true); + + // All cookies whose domain is not already + // known to us should be added with KCookieDunno. + // KCookieDunno means that we use the global policy. + cookieList->setAdvice( KCookieDunno ); + + m_cookieDomains.insert( domain, cookieList); + + // Update the list of domains + m_domainList.append(domain); + } + + // Add the cookie to the cookie list + // The cookie list is sorted 'longest path first' + if (!cookiePtr->isExpired(time(0))) + { +#ifdef MAX_COOKIE_LIMIT + if (cookieList->count() >= MAX_COOKIES_PER_HOST) + makeRoom(cookieList, cookiePtr); // Delete a cookie +#endif + cookieList->inSort( cookiePtr ); + m_cookiesChanged = true; + } + else + { + delete cookiePtr; + } + cookiePtr = 0; +} + +// +// This function advices whether a single KHttpCookie object should +// be added to the cookie jar. +// +KCookieAdvice KCookieJar::cookieAdvice(KHttpCookiePtr cookiePtr) +{ + if (m_rejectCrossDomainCookies && cookiePtr->isCrossDomain()) + return KCookieReject; + + QStringList domains; + + extractDomains(cookiePtr->host(), domains); + + // If the cookie specifies a domain, check whether it is valid. Otherwise, + // accept the cookie anyways but remove the domain="" value to prevent + // cross-site cookie injection. + if (!cookiePtr->domain().isEmpty()) + { + if (!domains.contains(cookiePtr->domain()) && + !cookiePtr->domain().endsWith("."+cookiePtr->host())) + cookiePtr->fixDomain(QString::null); + } + + if (m_autoAcceptSessionCookies && (cookiePtr->expireDate() == 0 || + m_ignoreCookieExpirationDate)) + return KCookieAccept; + + KCookieAdvice advice = KCookieDunno; + bool isFQDN = true; // First is FQDN + QStringList::Iterator it = domains.begin(); // Start with FQDN which first in the list. + while( (advice == KCookieDunno) && (it != domains.end())) + { + QString domain = *it; + // Check if a policy for the FQDN/domain is set. + if ( domain[0] == '.' || isFQDN ) + { + isFQDN = false; + KHttpCookieList *cookieList = m_cookieDomains[domain]; + if (cookieList) + advice = cookieList->getAdvice(); + } + domains.remove(it); + it = domains.begin(); // Continue from begin of remaining list + } + + if (advice == KCookieDunno) + advice = m_globalAdvice; + + return advice; +} + +// +// This function gets the advice for all cookies originating from +// _domain. +// +KCookieAdvice KCookieJar::getDomainAdvice(const QString &_domain) +{ + KHttpCookieList *cookieList = m_cookieDomains[_domain]; + KCookieAdvice advice; + + if (cookieList) + { + advice = cookieList->getAdvice(); + } + else + { + advice = KCookieDunno; + } + + return advice; +} + +// +// This function sets the advice for all cookies originating from +// _domain. +// +void KCookieJar::setDomainAdvice(const QString &_domain, KCookieAdvice _advice) +{ + QString domain(_domain); + KHttpCookieList *cookieList = m_cookieDomains[domain]; + + if (cookieList) + { + if (cookieList->getAdvice() != _advice) + { + m_configChanged = true; + // domain is already known + cookieList->setAdvice( _advice); + } + + if ((cookieList->isEmpty()) && + (_advice == KCookieDunno)) + { + // This deletes cookieList! + m_cookieDomains.remove(domain); + m_domainList.remove(domain); + } + } + else + { + // domain is not yet known + if (_advice != KCookieDunno) + { + // We should create a domain entry + m_configChanged = true; + // Make a new cookie list + cookieList = new KHttpCookieList(); + cookieList->setAutoDelete(true); + cookieList->setAdvice( _advice); + m_cookieDomains.insert( domain, cookieList); + // Update the list of domains + m_domainList.append( domain); + } + } +} + +// +// This function sets the advice for all cookies originating from +// the same domain as _cookie +// +void KCookieJar::setDomainAdvice(KHttpCookiePtr cookiePtr, KCookieAdvice _advice) +{ + QString domain; + stripDomain(cookiePtr->host(), domain); // We file the cookie under this domain. + + setDomainAdvice(domain, _advice); +} + +// +// This function sets the global advice for cookies +// +void KCookieJar::setGlobalAdvice(KCookieAdvice _advice) +{ + if (m_globalAdvice != _advice) + m_configChanged = true; + m_globalAdvice = _advice; +} + +// +// Get a list of all domains known to the cookie jar. +// +const QStringList& KCookieJar::getDomainList() +{ + return m_domainList; +} + +// +// Get a list of all cookies in the cookie jar originating from _domain. +// +const KHttpCookieList *KCookieJar::getCookieList(const QString & _domain, + const QString & _fqdn ) +{ + QString domain; + + if (_domain.isEmpty()) + stripDomain( _fqdn, domain ); + else + domain = _domain; + + return m_cookieDomains[domain]; +} + +// +// Eat a cookie out of the jar. +// cookiePtr should be one of the cookies returned by getCookieList() +// +void KCookieJar::eatCookie(KHttpCookiePtr cookiePtr) +{ + QString domain = stripDomain(cookiePtr); // We file the cookie under this domain. + KHttpCookieList *cookieList = m_cookieDomains[domain]; + + if (cookieList) + { + // This deletes cookiePtr! + if (cookieList->removeRef( cookiePtr )) + m_cookiesChanged = true; + + if ((cookieList->isEmpty()) && + (cookieList->getAdvice() == KCookieDunno)) + { + // This deletes cookieList! + m_cookieDomains.remove(domain); + + m_domainList.remove(domain); + } + } +} + +void KCookieJar::eatCookiesForDomain(const QString &domain) +{ + KHttpCookieList *cookieList = m_cookieDomains[domain]; + if (!cookieList || cookieList->isEmpty()) return; + + cookieList->clear(); + if (cookieList->getAdvice() == KCookieDunno) + { + // This deletes cookieList! + m_cookieDomains.remove(domain); + m_domainList.remove(domain); + } + m_cookiesChanged = true; +} + +void KCookieJar::eatSessionCookies( long windowId ) +{ + if (!windowId) + return; + + QStringList::Iterator it=m_domainList.begin(); + for ( ; it != m_domainList.end(); ++it ) + eatSessionCookies( *it, windowId, false ); +} + +void KCookieJar::eatAllCookies() +{ + for ( QStringList::Iterator it=m_domainList.begin(); + it != m_domainList.end();) + { + QString domain = *it++; + // This might remove domain from domainList! + eatCookiesForDomain(domain); + } +} + +void KCookieJar::eatSessionCookies( const QString& fqdn, long windowId, + bool isFQDN ) +{ + KHttpCookieList* cookieList; + if ( !isFQDN ) + cookieList = m_cookieDomains[fqdn]; + else + { + QString domain; + stripDomain( fqdn, domain ); + cookieList = m_cookieDomains[domain]; + } + + if ( cookieList ) + { + KHttpCookiePtr cookie=cookieList->first(); + for (; cookie != 0;) + { + if ((cookie->expireDate() != 0) && !m_ignoreCookieExpirationDate) + { + cookie = cookieList->next(); + continue; + } + + QValueList<long> &ids = cookie->windowIds(); + if (!ids.remove(windowId) || !ids.isEmpty()) + { + cookie = cookieList->next(); + continue; + } + KHttpCookiePtr old_cookie = cookie; + cookie = cookieList->next(); + cookieList->removeRef( old_cookie ); + } + } +} + +// +// Saves all cookies to the file '_filename'. +// On succes 'true' is returned. +// On failure 'false' is returned. +bool KCookieJar::saveCookies(const QString &_filename) +{ + KSaveFile saveFile(_filename, 0600); + + if (saveFile.status() != 0) + return false; + + FILE *fStream = saveFile.fstream(); + + time_t curTime = time(0); + + fprintf(fStream, "# KDE Cookie File v2\n#\n"); + + fprintf(fStream, "%-20s %-20s %-12s %-10s %-4s %-20s %-4s %s\n", + "# Host", "Domain", "Path", "Exp.date", "Prot", + "Name", "Sec", "Value"); + + for ( QStringList::Iterator it=m_domainList.begin(); it != m_domainList.end(); + it++ ) + { + const QString &domain = *it; + bool domainPrinted = false; + + KHttpCookieList *cookieList = m_cookieDomains[domain]; + KHttpCookiePtr cookie=cookieList->last(); + + for (; cookie != 0;) + { + if (cookie->isExpired(curTime)) + { + // Delete expired cookies + KHttpCookiePtr old_cookie = cookie; + cookie = cookieList->prev(); + cookieList->removeRef( old_cookie ); + } + else if (cookie->expireDate() != 0 && !m_ignoreCookieExpirationDate) + { + if (!domainPrinted) + { + domainPrinted = true; + fprintf(fStream, "[%s]\n", domain.local8Bit().data()); + } + // Store persistent cookies + QString path = L1("\""); + path += cookie->path(); + path += '"'; + QString domain = L1("\""); + domain += cookie->domain(); + domain += '"'; + fprintf(fStream, "%-20s %-20s %-12s %10lu %3d %-20s %-4i %s\n", + cookie->host().latin1(), domain.latin1(), + path.latin1(), (unsigned long) cookie->expireDate(), + cookie->protocolVersion(), + cookie->name().isEmpty() ? cookie->value().latin1() : cookie->name().latin1(), + (cookie->isSecure() ? 1 : 0) + (cookie->isHttpOnly() ? 2 : 0) + + (cookie->hasExplicitPath() ? 4 : 0) + (cookie->name().isEmpty() ? 8 : 0), + cookie->value().latin1()); + cookie = cookieList->prev(); + } + else + { + // Skip session-only cookies + cookie = cookieList->prev(); + } + } + } + + return saveFile.close(); +} + +typedef char *charPtr; + +static const char *parseField(charPtr &buffer, bool keepQuotes=false) +{ + char *result; + if (!keepQuotes && (*buffer == '\"')) + { + // Find terminating " + buffer++; + result = buffer; + while((*buffer != '\"') && (*buffer)) + buffer++; + } + else + { + // Find first white space + result = buffer; + while((*buffer != ' ') && (*buffer != '\t') && (*buffer != '\n') && (*buffer)) + buffer++; + } + + if (!*buffer) + return result; // + *buffer++ = '\0'; + + // Skip white-space + while((*buffer == ' ') || (*buffer == '\t') || (*buffer == '\n')) + buffer++; + + return result; +} + + +// +// Reloads all cookies from the file '_filename'. +// On succes 'true' is returned. +// On failure 'false' is returned. +bool KCookieJar::loadCookies(const QString &_filename) +{ + FILE *fStream = fopen( QFile::encodeName(_filename), "r"); + if (fStream == 0) + { + return false; + } + + time_t curTime = time(0); + + char *buffer = new char[READ_BUFFER_SIZE]; + + bool err = false; + err = (fgets(buffer, READ_BUFFER_SIZE, fStream) == 0); + + int version = 1; + if (!err) + { + if (strcmp(buffer, "# KDE Cookie File\n") == 0) + { + // version 1 + } + else if (sscanf(buffer, "# KDE Cookie File v%d\n", &version) != 1) + { + err = true; + } + } + + if (!err) + { + while(fgets(buffer, READ_BUFFER_SIZE, fStream) != 0) + { + char *line = buffer; + // Skip lines which begin with '#' or '[' + if ((line[0] == '#') || (line[0] == '[')) + continue; + + const char *host( parseField(line) ); + const char *domain( parseField(line) ); + const char *path( parseField(line) ); + const char *expStr( parseField(line) ); + if (!expStr) continue; + int expDate = (time_t) strtoul(expStr, 0, 10); + const char *verStr( parseField(line) ); + if (!verStr) continue; + int protVer = (time_t) strtoul(verStr, 0, 10); + const char *name( parseField(line) ); + bool keepQuotes = false; + bool secure = false; + bool httpOnly = false; + bool explicitPath = false; + const char *value = 0; + if ((version == 2) || (protVer >= 200)) + { + if (protVer >= 200) + protVer -= 200; + int i = atoi( parseField(line) ); + secure = i & 1; + httpOnly = i & 2; + explicitPath = i & 4; + if (i & 8) + name = ""; + line[strlen(line)-1] = '\0'; // Strip LF. + value = line; + } + else + { + if (protVer >= 100) + { + protVer -= 100; + keepQuotes = true; + } + value = parseField(line, keepQuotes); + secure = atoi( parseField(line) ); + } + + // Parse error + if (!value) continue; + + // Expired or parse error + if ((expDate == 0) || (expDate < curTime)) + continue; + + KHttpCookie *cookie = new KHttpCookie(QString::fromLatin1(host), + QString::fromLatin1(domain), + QString::fromLatin1(path), + QString::fromLatin1(name), + QString::fromLatin1(value), + expDate, protVer, + secure, httpOnly, explicitPath); + addCookie(cookie); + } + } + delete [] buffer; + m_cookiesChanged = false; + + fclose( fStream); + return err; +} + +// +// Save the cookie configuration +// + +void KCookieJar::saveConfig(KConfig *_config) +{ + if (!m_configChanged) + return; + + _config->setGroup("Cookie Dialog"); + _config->writeEntry("PreferredPolicy", m_preferredPolicy); + _config->writeEntry("ShowCookieDetails", m_showCookieDetails ); + _config->setGroup("Cookie Policy"); + _config->writeEntry("CookieGlobalAdvice", adviceToStr( m_globalAdvice)); + + QStringList domainSettings; + for ( QStringList::Iterator it=m_domainList.begin(); + it != m_domainList.end(); + it++ ) + { + const QString &domain = *it; + KCookieAdvice advice = getDomainAdvice( domain); + if (advice != KCookieDunno) + { + QString value(domain); + value += ':'; + value += adviceToStr(advice); + domainSettings.append(value); + } + } + _config->writeEntry("CookieDomainAdvice", domainSettings); + _config->sync(); + m_configChanged = false; +} + + +// +// Load the cookie configuration +// + +void KCookieJar::loadConfig(KConfig *_config, bool reparse ) +{ + if ( reparse ) + _config->reparseConfiguration(); + + _config->setGroup("Cookie Dialog"); + m_showCookieDetails = _config->readBoolEntry( "ShowCookieDetails" ); + m_preferredPolicy = _config->readNumEntry( "PreferredPolicy", 0 ); + + _config->setGroup("Cookie Policy"); + QStringList domainSettings = _config->readListEntry("CookieDomainAdvice"); + m_rejectCrossDomainCookies = _config->readBoolEntry( "RejectCrossDomainCookies", true ); + m_autoAcceptSessionCookies = _config->readBoolEntry( "AcceptSessionCookies", true ); + m_ignoreCookieExpirationDate = _config->readBoolEntry( "IgnoreExpirationDate", false ); + QString value = _config->readEntry("CookieGlobalAdvice", L1("Ask")); + m_globalAdvice = strToAdvice(value); + + // Reset current domain settings first. + for ( QStringList::Iterator it=m_domainList.begin(); it != m_domainList.end(); ) + { + // Make sure to update iterator before calling setDomainAdvice() + // setDomainAdvice() might delete the domain from domainList. + QString domain = *it++; + setDomainAdvice(domain, KCookieDunno); + } + + // Now apply the domain settings read from config file... + for ( QStringList::Iterator it=domainSettings.begin(); + it != domainSettings.end(); ) + { + const QString &value = *it++; + + int sepPos = value.findRev(':'); + + if (sepPos <= 0) + continue; + + QString domain(value.left(sepPos)); + KCookieAdvice advice = strToAdvice( value.mid(sepPos + 1) ); + setDomainAdvice(domain, advice); + } +} diff --git a/kioslave/http/kcookiejar/kcookiejar.desktop b/kioslave/http/kcookiejar/kcookiejar.desktop new file mode 100644 index 000000000..54421225a --- /dev/null +++ b/kioslave/http/kcookiejar/kcookiejar.desktop @@ -0,0 +1,157 @@ +[Desktop Entry] +Type=Service +Name=KDED Cookie Jar Module +Name[af]=Kded Koekie Houer Module +Name[ar]=وحدة Jar لكعكة KDED +Name[az]=KDED Kökə Jar Modulu +Name[be]=Модуль "печыва" KDED +Name[bg]=Модул KDED Cookie Jar +Name[bn]=KDED কুকি জার মডিউল +Name[bs]=KDED modul "Tegla sa keksima" +Name[ca]=Mòdul Jar de cookies per a KDED +Name[cs]=KDED modul pro cookies +Name[csb]=Sprôwianié kùszkama +Name[cy]=Modiwl Jar Cwci KDED +Name[da]=KDED-cookie-jar-modul +Name[de]=Cookie-Verwaltung +Name[el]=Άρθρωμα Cookie Jar του KDED +Name[eo]=KDED-kuketotraktila modulo +Name[es]=Módulo Jar de cookies de KDED +Name[et]=KDED Cookie Jar moodul +Name[eu]=KDED Cookie Jar modulua +Name[fa]=پیمانۀ ظرف کوکی KDED +Name[fi]=KDED-evästemoduuli +Name[fr]=Module Jar de cookie KDED +Name[fy]=KDED-module foar it bewarjen fan Koekjes +Name[gl]=Módulo Jar de cookies de KDED +Name[he]=מודול צנצנת העוגיות של KDED +Name[hi]=KDED कुकी जार मॉड्यूल +Name[hr]=KDED modul za čuvanje kolačića +Name[hu]=KDED cookie-modul +Name[id]=Modul Penyimpanan Cookies KDED +Name[is]=KDED smákökukrukka +Name[it]=Modulo Jar dei cookie per KDED +Name[ja]=KDED クッキー Jar モジュール +Name[ka]=KDED-ის ბმულების Jar მოდული +Name[kk]=KDE cookie модулі +Name[km]=ម៉ូឌុល Jar នៃខូគី KDED +Name[ko]=KDED 쿠키 JAR 모듈 +Name[lb]=KDED-Modul fir d'Verwaltung vun de Cookien +Name[lt]=KDED slapukų rinkinio modulis +Name[lv]=KDED Cepumu Jar modulis +Name[mk]=KDED модул Тегла со колачиња +Name[ms]=Modul Balang Cecikut KDED +Name[mt]=Modulu tal-"cookies" KDED +Name[nb]=KDEDs modul for informasjonskapsler (Cookie Jar) +Name[nds]=KDED-Kookjepleeg +Name[ne]=KDED कुकी जार मोड्युल +Name[nl]=KDED-module voor het opslaan van cookies +Name[nn]=KDED-informasjonskapselmodul +Name[nso]=Seripa sa Jar ya Cookie ya KDED +Name[pa]=KDED ਕੂਕੀਜ਼ Jar ਮੈਡੀਊਲ +Name[pl]=Zarządzanie ciasteczkami +Name[pt]=Módulo de 'Cookies' do KDED +Name[pt_BR]=Módulo de Cookie Jar do KDE +Name[ro]=Modul Cookie JAR pentru KDED +Name[ru]=Служба cookie +Name[rw]=Igice Jar Inyandikonyakwirema KDED +Name[se]=KDED gáhkošlihtti-moduvla +Name[sk]=Modul pre cookies KDED +Name[sl]=Modul posode za piškotke KDED +Name[sq]=Modul i KDED-it për Qyp të keksave nga KDED +Name[sr]=KDED модул тегле за колачиће +Name[sr@Latn]=KDED modul tegle za kolačiće +Name[sv]=KDED-kakburksmodul +Name[ta]=KDED தற்காலிக நினைவக சாடி பகுதி +Name[te]=కెడిఈడి కుకీ జాడి మాడ్యూల్ +Name[tg]=Модули KDED Cookie Jar +Name[th]=โมดูลโถคุกกี KDED +Name[tr]=KDED Cookie Jar Modülü +Name[tt]=KDED'nıñ Cookie Modulı +Name[uk]=Модуль глечика з куками KDED +Name[uz]=KDED kuki idish moduli +Name[uz@cyrillic]=KDED куки идиш модули +Name[ven]=Modulu wa Jar wa Cookie ya KDED +Name[vi]=Mô-đun Cookie Jar của KDED +Name[xh]=Isicatshulwa se KDED Cookie Jar +Name[zh_CN]=KDED Cookie Jar 模块 +Name[zh_HK]=KDED Cookie Jar 模組 +Name[zh_TW]=KDED Cookie Jar 模組 +Name[zu]=Ingxenye Yojeke ye-Cookie ye-KDED +Comment=Keeps track of all cookies in the system +Comment[af]=Hou tred van al die koekies in die stelsel +Comment[ar]=يراقب جميع الكعكات الموجودة على النظام +Comment[be]=Захоўвае звесткі пра "печыва" +Comment[bg]=Контрол над всички бисквитки в системата +Comment[bn]=সিস্টেমে সমস্ত কুকি-র খোঁজখবর রাখে +Comment[bs]=Prati sve kolačiće (cookije) na sistemu +Comment[ca]=Segueix totes les galetes en el sistema +Comment[cs]=Spravuje Cookies v počítači +Comment[csb]=Trzëmô wszëtczé kùszczi w systemie +Comment[da]=Holder styr på alle cookier på systemet +Comment[de]=Verwaltet die Cookies in KDE +Comment[el]=Διατηρεί αρχείο από όλα τα cookies στο σύστημα +Comment[eo]=Registras ĉiujn kuketojn en la sistemo +Comment[es]=Mantiene registro todas las cookies en el sistema +Comment[et]=Hoiab silma peal kõigil süsteemi küpsistel +Comment[eu]=Sistemaren cookie guztien jarraipena egiten du +Comment[fa]=رد همۀ کوکیها را در سیستم نگه میدارد +Comment[fi]=Seuraa järjestelmän evästeitä +Comment[fr]=Conserve une trace de tous les cookies dans le système +Comment[fy]=Hâld by wer alle koekjes binne +Comment[gl]=Manter as pegadas de todas as Cookies no sistema +Comment[he]=מבצע מעקב אחרי כל העוגיות במערכת +Comment[hi]=तंत्र की सभी कुकी की जानकारी रखता है +Comment[hr]=Vođenje evidencije o svim kolačićima na sustavu +Comment[hu]=Nyomon követi a rendszerben létrejövő cookie-kat +Comment[id]=Menyimpan semua cookies pada sistem +Comment[is]=Heldur utanum allar smákökur í kerfinu +Comment[it]=Tiene traccia di tutti i cookie del sistema +Comment[ja]=システムのすべてのクッキーを管理します +Comment[ka]=სისტემის ყველა ბმულის თვალმიდევნება +Comment[kk]=Жүйедегі бүкіл cookie файлдарды бақылау +Comment[km]=រក្សាការតាមដានខូគីទាំងអស់ក្នុងប្រព័ន្ធ +Comment[lb]=Iwwerwaacht all d'Cookie vum System +Comment[lt]=Seka visus slapukus sistemoje +Comment[lv]=Seko visiem sistēmā esošajiem cepumiem +Comment[mk]=Води сметка за сите колачиња во системот +Comment[ms]=Memerhati semua cecikut dalam sistem +Comment[nb]=Holder rede på alle informasjonskapsler i systemet +Comment[nds]=Passt all Kookjes in't Systeem +Comment[ne]=प्रणालीमा सबै कुकीहरूको पदचिन्ह राख्दछ +Comment[nl]=Houdt alle cookies in het systeem bij +Comment[nn]=Held greie på informasjonskapslane +Comment[pa]=ਸਿਸਟਮ ਦੇ ਸਾਰੇ ਕੂਕੀਜ਼ ਦਾ ਰਿਕਾਰਡ ਰੱਖੋ +Comment[pl]=Przechowuje wszystkie ciasteczka w systemie +Comment[pt]=Mantém um registo de todos os 'cookies' no sistema +Comment[pt_BR]=Mantém informações sobre todos os cookies do sistema +Comment[ro]=Administrează toate "cookie"-urile din sistem +Comment[ru]=Управление закладками-cookie в KDE +Comment[rw]=Iguma inzira y'inyandikonyakwirema zose muri sisitemu +Comment[se]=Halddaša buot diehtočoahkuid +Comment[sk]=Sleduje všetky cookie v systéme +Comment[sl]=Opazuje vse piškotke v sistemu +Comment[sr]=Води евиденцију о свим колачићима на систему +Comment[sr@Latn]=Vodi evidenciju o svim kolačićima na sistemu +Comment[sv]=Håller ordning på alla kakor i systemet +Comment[ta]=கணினியின் எல்லா தற்காலிக நினைவகங்களையும் கண்காணிக்கிறது +Comment[te]=వ్యవస్థలోని అన్ని కుకీల జాడని వుంచుకుంటుంది +Comment[tg]=Гузаргоҳи ҳамаша Cookies дар система муҳофизат кунед +Comment[th]=ใช้ติดตามคุกกีทั้งหมดในระบบ +Comment[tr]=Sistemdeki tüm çerezleri izler +Comment[tt]=Sistemdäge bar cookie'larnı küz astında tota +Comment[uk]=Стежить за всіма куками в системі +Comment[uz]=Tizimdagi hamma kukilarni kuzatadi +Comment[uz@cyrillic]=Тизимдаги ҳамма кукиларни кузатади +Comment[vi]=Theo dõi các tập tin cookie trong hệ thống. +Comment[zh_CN]=将全部 cookies 的记录保存在系统中 +Comment[zh_TW]=追蹤系統所有的 cookies +ServiceTypes=KDEDModule +Exec=kcookiejar +X-DCOP-ServiceType=Unique +X-KDE-StartupNotify=false +X-KDE-ModuleType=Library +X-KDE-Library=kcookiejar +X-KDE-FactoryName=kcookiejar +X-KDE-Kded-autoload=false +X-KDE-Kded-load-on-demand=true diff --git a/kioslave/http/kcookiejar/kcookiejar.h b/kioslave/http/kcookiejar/kcookiejar.h new file mode 100644 index 000000000..c73708bea --- /dev/null +++ b/kioslave/http/kcookiejar/kcookiejar.h @@ -0,0 +1,365 @@ +/* + This file is part of the KDE File Manager + + Copyright (C) 1998 Waldo Bastian (bastian@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + version 2 as published by the Free Software Foundation. + + This software is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +//---------------------------------------------------------------------------- +// +// KDE File Manager -- HTTP Cookies +// $Id$ + +#ifndef KCOOKIEJAR_H +#define KCOOKIEJAR_H + +#include <qstring.h> +#include <qstringlist.h> +#include <qdict.h> +#include <qptrlist.h> +#include <time.h> + +class KConfig; +class KCookieJar; +class KHttpCookie; +class KHttpCookieList; + +typedef KHttpCookie *KHttpCookiePtr; + +enum KCookieAdvice +{ + KCookieDunno=0, + KCookieAccept, + KCookieReject, + KCookieAsk +}; + +class KHttpCookie +{ + friend class KCookieJar; + friend class KHttpCookieList; + +protected: + QString mHost; + QString mDomain; + QString mPath; + QString mName; + QString mValue; + time_t mExpireDate; + int mProtocolVersion; + bool mSecure; + bool mCrossDomain; + bool mHttpOnly; + bool mExplicitPath; + QValueList<long> mWindowIds; + + QString cookieStr(bool useDOMFormat); + +public: + KHttpCookie(const QString &_host=QString::null, + const QString &_domain=QString::null, + const QString &_path=QString::null, + const QString &_name=QString::null, + const QString &_value=QString::null, + time_t _expireDate=0, + int _protocolVersion=0, + bool _secure = false, + bool _httpOnly = false, + bool _explicitPath = false); + + QString domain(void) { return mDomain; } + QString host(void) { return mHost; } + QString path(void) { return mPath; } + QString name(void) { return mName; } + QString value(void) { return mValue; } + QValueList<long> &windowIds(void) { return mWindowIds; } + void fixDomain(const QString &domain) { mDomain = domain; } + time_t expireDate(void) { return mExpireDate; } + int protocolVersion(void) { return mProtocolVersion; } + bool isSecure(void) { return mSecure; } + bool isExpired(time_t currentDate); + bool isCrossDomain(void) { return mCrossDomain; } + bool isHttpOnly(void) { return mHttpOnly; } + bool hasExplicitPath(void) { return mExplicitPath; } + bool match(const QString &fqdn, const QStringList &domainList, const QString &path); +}; + +class KHttpCookieList : public QPtrList<KHttpCookie> +{ +public: + KHttpCookieList() : QPtrList<KHttpCookie>(), advice( KCookieDunno ) + { } + virtual ~KHttpCookieList() { } + + virtual int compareItems( void * item1, void * item2); + KCookieAdvice getAdvice(void) { return advice; } + void setAdvice(KCookieAdvice _advice) { advice = _advice; } + +private: + KCookieAdvice advice; +}; + +class KCookieJar +{ +public: + /** + * Constructs a new cookie jar + * + * One jar should be enough for all cookies. + */ + KCookieJar(); + + /** + * Destructs the cookie jar + * + * Poor little cookies, they will all be eaten by the cookie monster! + */ + ~KCookieJar(); + + /** + * Returns whether the cookiejar has been changed + */ + bool changed() const { return m_cookiesChanged || m_configChanged; } + + /** + * Store all the cookies in a safe(?) place + */ + bool saveCookies(const QString &_filename); + + /** + * Load all the cookies from file and add them to the cookie jar. + */ + bool loadCookies(const QString &_filename); + + /** + * Save the cookie configuration + */ + void saveConfig(KConfig *_config); + + /** + * Load the cookie configuration + */ + void loadConfig(KConfig *_config, bool reparse = false); + + /** + * Looks for cookies in the cookie jar which are appropriate for _url. + * Returned is a string containing all appropriate cookies in a format + * which can be added to a HTTP-header without any additional processing. + * + * If @p useDOMFormat is true, the string is formatted in a format + * in compliance with the DOM standard. + * @p pendingCookies contains a list of cookies that have not been + * approved yet by the user but that will be included in the result + * none the less. + */ + QString findCookies(const QString &_url, bool useDOMFormat, long windowId, KHttpCookieList *pendingCookies=0); + + /** + * This function parses cookie_headers and returns a linked list of + * valid KHttpCookie objects for all cookies found in cookie_headers. + * If no cookies could be found 0 is returned. + * + * cookie_headers should be a concatenation of all lines of a HTTP-header + * which start with "Set-Cookie". The lines should be separated by '\n's. + */ + KHttpCookieList makeCookies(const QString &_url, const QCString &cookie_headers, long windowId); + + /** + * This function parses cookie_headers and returns a linked list of + * valid KHttpCookie objects for all cookies found in cookie_headers. + * If no cookies could be found 0 is returned. + * + * cookie_domstr should be a concatenation of "name=value" pairs, separated + * by a semicolon ';'. + */ + KHttpCookieList makeDOMCookies(const QString &_url, const QCString &cookie_domstr, long windowId); + + /** + * This function hands a KHttpCookie object over to the cookie jar. + * + * On return cookiePtr is set to 0. + */ + void addCookie(KHttpCookiePtr &cookiePtr); + + /** + * This function advices whether a single KHttpCookie object should + * be added to the cookie jar. + * + * Possible return values are: + * - KCookieAccept, the cookie should be added + * - KCookieReject, the cookie should not be added + * - KCookieAsk, the user should decide what to do + */ + KCookieAdvice cookieAdvice(KHttpCookiePtr cookiePtr); + + /** + * This function gets the advice for all cookies originating from + * _domain. + * + * - KCookieDunno, no specific advice for _domain + * - KCookieAccept, accept all cookies for _domain + * - KCookieReject, reject all cookies for _domain + * - KCookieAsk, the user decides what to do with cookies for _domain + */ + KCookieAdvice getDomainAdvice(const QString &_domain); + + /** + * This function sets the advice for all cookies originating from + * _domain. + * + * _advice can have the following values: + * - KCookieDunno, no specific advice for _domain + * - KCookieAccept, accept all cookies for _domain + * - KCookieReject, reject all cookies for _domain + * - KCookieAsk, the user decides what to do with cookies for _domain + */ + void setDomainAdvice(const QString &_domain, KCookieAdvice _advice); + + /** + * This function sets the advice for all cookies originating from + * the same domain as _cookie + * + * _advice can have the following values: + * - KCookieDunno, no specific advice for _domain + * - KCookieAccept, accept all cookies for _domain + * - KCookieReject, reject all cookies for _domain + * - KCookieAsk, the user decides what to do with cookies for _domain + */ + void setDomainAdvice(KHttpCookiePtr _cookie, KCookieAdvice _advice); + + /** + * Get the global advice for cookies + * + * The returned advice can have the following values: + * - KCookieAccept, accept cookies + * - KCookieReject, reject cookies + * - KCookieAsk, the user decides what to do with cookies + * + * The global advice is used if the domain has no advice set. + */ + KCookieAdvice getGlobalAdvice() { return m_globalAdvice; } + + /** + * This function sets the global advice for cookies + * + * _advice can have the following values: + * - KCookieAccept, accept cookies + * - KCookieReject, reject cookies + * - KCookieAsk, the user decides what to do with cookies + * + * The global advice is used if the domain has no advice set. + */ + void setGlobalAdvice(KCookieAdvice _advice); + + /** + * Get a list of all domains known to the cookie jar. + * A domain is known to the cookie jar if: + * - It has a cookie originating from the domain + * - It has a specific advice set for the domain + */ + const QStringList& getDomainList(); + + /** + * Get a list of all cookies in the cookie jar originating from _domain. + */ + const KHttpCookieList *getCookieList(const QString & _domain, + const QString& _fqdn ); + + /** + * Remove & delete a cookie from the jar. + * + * cookiePtr should be one of the entries in a KHttpCookieList. + * Update your KHttpCookieList by calling getCookieList after + * calling this function. + */ + void eatCookie(KHttpCookiePtr cookiePtr); + + /** + * Remove & delete all cookies for @p domain. + */ + void eatCookiesForDomain(const QString &domain); + + /** + * Remove & delete all cookies + */ + void eatAllCookies(); + + /** + * Removes all end of session cookies set by the + * session @p windId. + */ + void eatSessionCookies( long windowId ); + + /** + * Removes all end of session cookies set by the + * session @p windId. + */ + void eatSessionCookies( const QString& fqdn, long windowId, bool isFQDN = true ); + + /** + * Parses _url and returns the FQDN (_fqdn) and path (_path). + */ + static bool parseURL(const QString &_url, + QString &_fqdn, + QString &_path); + + /** + * Returns a list of domains in @p _domainList relevant for this host. + * The list is sorted with the FQDN listed first and the top-most + * domain listed last + */ + void extractDomains(const QString &_fqdn, + QStringList &_domainList); + + static QString adviceToStr(KCookieAdvice _advice); + static KCookieAdvice strToAdvice(const QString &_str); + + /** Returns the */ + int preferredDefaultPolicy() const { return m_preferredPolicy; } + + /** Returns the */ + bool showCookieDetails () const { return m_showCookieDetails; } + + /** + * Sets the user's default preference cookie policy. + */ + void setPreferredDefaultPolicy (int value) { m_preferredPolicy = value; } + + /** + * Sets the user's preference of level of detail displayed + * by the cookie dialog. + */ + void setShowCookieDetails (bool value) { m_showCookieDetails = value; } + +protected: + void stripDomain(const QString &_fqdn, QString &_domain); + QString stripDomain( KHttpCookiePtr cookiePtr); + +protected: + QStringList m_domainList; + KCookieAdvice m_globalAdvice; + QDict<KHttpCookieList> m_cookieDomains; + QDict<int> m_twoLevelTLD; + + bool m_configChanged; + bool m_cookiesChanged; + bool m_showCookieDetails; + bool m_rejectCrossDomainCookies; + bool m_autoAcceptSessionCookies; + bool m_ignoreCookieExpirationDate; + + int m_preferredPolicy; +}; +#endif diff --git a/kioslave/http/kcookiejar/kcookiescfg.upd b/kioslave/http/kcookiejar/kcookiescfg.upd new file mode 100644 index 000000000..3c1cd028d --- /dev/null +++ b/kioslave/http/kcookiejar/kcookiescfg.upd @@ -0,0 +1,16 @@ +# Update for old cookie config files, if present +Id=kde2.2/b1 +File=kcookiejarrc +Group=Browser Settings/HTTP,Cookie Policy + +# Update cookies config file... +Id=kde3.1/cvs +File=kcookiejarrc +Group=<default>,Cookie Dialog +Key=DefaultRadioButton,PreferredPolicy +Key=ShowCookieDetails +Group=Cookie Policy +Key=AcceptTempCookies,AcceptSessionCookies +Key=AutoAcceptSessionCookies,AcceptSessionCookies +Key=RejectCrossDomain,RejectCrossDomainCookies +Key=IgnoreCookieExpirationDate,IgnoreExpirationDate diff --git a/kioslave/http/kcookiejar/kcookieserver.cpp b/kioslave/http/kcookiejar/kcookieserver.cpp new file mode 100644 index 000000000..365f15e79 --- /dev/null +++ b/kioslave/http/kcookiejar/kcookieserver.cpp @@ -0,0 +1,606 @@ +/* +This file is part of KDE + + Copyright (C) 1998-2000 Waldo Bastian (bastian@kde.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +//---------------------------------------------------------------------------- +// +// KDE Cookie Server +// $Id$ + +#define SAVE_DELAY 3 // Save after 3 minutes + +#include <unistd.h> + +#include <qtimer.h> +#include <qptrlist.h> +#include <qfile.h> + +#include <dcopclient.h> + +#include <kconfig.h> +#include <kdebug.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <kstandarddirs.h> + +#include "kcookiejar.h" +#include "kcookiewin.h" +#include "kcookieserver.h" + +extern "C" { + KDE_EXPORT KDEDModule *create_kcookiejar(const QCString &name) + { + return new KCookieServer(name); + } +} + + +// Cookie field indexes +enum CookieDetails { CF_DOMAIN=0, CF_PATH, CF_NAME, CF_HOST, + CF_VALUE, CF_EXPIRE, CF_PROVER, CF_SECURE }; + + +class CookieRequest { +public: + DCOPClient *client; + DCOPClientTransaction *transaction; + QString url; + bool DOM; + long windowId; +}; + +template class QPtrList<CookieRequest>; + +class RequestList : public QPtrList<CookieRequest> +{ +public: + RequestList() : QPtrList<CookieRequest>() { } +}; + +KCookieServer::KCookieServer(const QCString &name) + :KDEDModule(name) +{ + mOldCookieServer = new DCOPClient(); // backwards compatibility. + mOldCookieServer->registerAs("kcookiejar", false); + mOldCookieServer->setDaemonMode( true ); + mCookieJar = new KCookieJar; + mPendingCookies = new KHttpCookieList; + mPendingCookies->setAutoDelete(true); + mRequestList = new RequestList; + mAdvicePending = false; + mTimer = new QTimer(); + connect( mTimer, SIGNAL( timeout()), SLOT( slotSave())); + mConfig = new KConfig("kcookiejarrc"); + mCookieJar->loadConfig( mConfig ); + + QString filename = locateLocal("data", "kcookiejar/cookies"); + + // Stay backwards compatible! + QString filenameOld = locate("data", "kfm/cookies"); + if (!filenameOld.isEmpty()) + { + mCookieJar->loadCookies( filenameOld ); + if (mCookieJar->saveCookies( filename)) + { + unlink(QFile::encodeName(filenameOld)); // Remove old kfm cookie file + } + } + else + { + mCookieJar->loadCookies( filename); + } + connect(this, SIGNAL(windowUnregistered(long)), + this, SLOT(slotDeleteSessionCookies(long))); +} + +KCookieServer::~KCookieServer() +{ + if (mCookieJar->changed()) + slotSave(); + delete mOldCookieServer; + delete mCookieJar; + delete mTimer; + delete mPendingCookies; + delete mConfig; +} + +bool KCookieServer::cookiesPending( const QString &url, KHttpCookieList *cookieList ) +{ + QString fqdn; + QStringList domains; + QString path; + // Check whether 'url' has cookies on the pending list + if (mPendingCookies->isEmpty()) + return false; + if (!KCookieJar::parseURL(url, fqdn, path)) + return false; + + mCookieJar->extractDomains( fqdn, domains ); + for( KHttpCookie *cookie = mPendingCookies->first(); + cookie != 0L; + cookie = mPendingCookies->next()) + { + if (cookie->match( fqdn, domains, path)) + { + if (!cookieList) + return true; + cookieList->append(cookie); + } + } + if (!cookieList) + return false; + return cookieList->isEmpty(); +} + +void KCookieServer::addCookies( const QString &url, const QCString &cookieHeader, + long windowId, bool useDOMFormat ) +{ + KHttpCookieList cookieList; + if (useDOMFormat) + cookieList = mCookieJar->makeDOMCookies(url, cookieHeader, windowId); + else + cookieList = mCookieJar->makeCookies(url, cookieHeader, windowId); + + checkCookies(&cookieList); + + for(KHttpCookiePtr cookie = cookieList.first(); cookie; cookie = cookieList.first()) + mPendingCookies->append(cookieList.take()); + + if (!mAdvicePending) + { + mAdvicePending = true; + while (!mPendingCookies->isEmpty()) + { + checkCookies(0); + } + mAdvicePending = false; + } +} + +void KCookieServer::checkCookies( KHttpCookieList *cookieList) +{ + KHttpCookieList *list; + + if (cookieList) + list = cookieList; + else + list = mPendingCookies; + + KHttpCookiePtr cookie = list->first(); + while (cookie) + { + kdDebug(7104) << "checkCookies: Asking cookie advice for " << cookie->host() << endl; + KCookieAdvice advice = mCookieJar->cookieAdvice(cookie); + switch(advice) + { + case KCookieAccept: + list->take(); + mCookieJar->addCookie(cookie); + cookie = list->current(); + break; + + case KCookieReject: + list->take(); + delete cookie; + cookie = list->current(); + break; + + default: + cookie = list->next(); + break; + } + } + + if (cookieList || list->isEmpty()) + return; + + KHttpCookiePtr currentCookie = mPendingCookies->first(); + + KHttpCookieList currentList; + currentList.append(currentCookie); + QString currentHost = currentCookie->host(); + + cookie = mPendingCookies->next(); + while (cookie) + { + if (cookie->host() == currentHost) + { + currentList.append(cookie); + } + cookie = mPendingCookies->next(); + } + + KCookieWin *kw = new KCookieWin( 0L, currentList, + mCookieJar->preferredDefaultPolicy(), + mCookieJar->showCookieDetails() ); + KCookieAdvice userAdvice = kw->advice(mCookieJar, currentCookie); + delete kw; + // Save the cookie config if it has changed + mCookieJar->saveConfig( mConfig ); + + // Apply the user's choice to all cookies that are currently + // queued for this host. + cookie = mPendingCookies->first(); + while (cookie) + { + if (cookie->host() == currentHost) + { + switch(userAdvice) + { + case KCookieAccept: + mPendingCookies->take(); + mCookieJar->addCookie(cookie); + cookie = mPendingCookies->current(); + break; + + case KCookieReject: + mPendingCookies->take(); + delete cookie; + cookie = mPendingCookies->current(); + break; + + default: + qWarning(__FILE__":%d Problem!", __LINE__); + cookie = mPendingCookies->next(); + break; + } + } + else + { + cookie = mPendingCookies->next(); + } + } + + + // Check if we can handle any request + for ( CookieRequest *request = mRequestList->first(); request;) + { + if (!cookiesPending( request->url )) + { + QCString replyType; + QByteArray replyData; + QString res = mCookieJar->findCookies( request->url, request->DOM, request->windowId ); + + QDataStream stream2(replyData, IO_WriteOnly); + stream2 << res; + replyType = "QString"; + request->client->endTransaction( request->transaction, + replyType, replyData); + CookieRequest *tmp = request; + request = mRequestList->next(); + mRequestList->removeRef( tmp ); + delete tmp; + } + else + { + request = mRequestList->next(); + } + } + if (mCookieJar->changed()) + saveCookieJar(); +} + +void KCookieServer::slotSave() +{ + QString filename = locateLocal("data", "kcookiejar/cookies"); + mCookieJar->saveCookies(filename); +} + +void KCookieServer::saveCookieJar() +{ + if( mTimer->isActive() ) + return; + + mTimer->start( 1000*60*SAVE_DELAY, true ); +} + +void KCookieServer::putCookie( QStringList& out, KHttpCookie *cookie, + const QValueList<int>& fields ) +{ + QValueList<int>::ConstIterator i = fields.begin(); + for ( ; i != fields.end(); ++i ) + { + switch(*i) + { + case CF_DOMAIN : + out << cookie->domain(); + break; + case CF_NAME : + out << cookie->name(); + break; + case CF_PATH : + out << cookie->path(); + break; + case CF_HOST : + out << cookie->host(); + break; + case CF_VALUE : + out << cookie->value(); + break; + case CF_EXPIRE : + out << QString::number(cookie->expireDate()); + break; + case CF_PROVER : + out << QString::number(cookie->protocolVersion()); + break; + case CF_SECURE : + out << QString::number( cookie->isSecure() ? 1 : 0 ); + break; + default : + out << QString::null; + } + } +} + +bool KCookieServer::cookieMatches( KHttpCookiePtr c, + QString domain, QString fqdn, + QString path, QString name ) +{ + if( c ) + { + bool hasDomain = !domain.isEmpty(); + return + ((hasDomain && c->domain() == domain) || + fqdn == c->host()) && + (c->path() == path) && + (c->name() == name) && + (!c->isExpired(time(0))); + } + return false; +} + +// DCOP function +QString +KCookieServer::findCookies(QString url) +{ + return findCookies(url, 0); +} + +// DCOP function +QString +KCookieServer::findCookies(QString url, long windowId) +{ + if (cookiesPending(url)) + { + CookieRequest *request = new CookieRequest; + request->client = callingDcopClient(); + request->transaction = request->client->beginTransaction(); + request->url = url; + request->DOM = false; + request->windowId = windowId; + mRequestList->append( request ); + return QString::null; // Talk to you later :-) + } + + QString cookies = mCookieJar->findCookies(url, false, windowId); + + if (mCookieJar->changed()) + saveCookieJar(); + + return cookies; +} + +// DCOP function +QStringList +KCookieServer::findDomains() +{ + QStringList result; + const QStringList domains = mCookieJar->getDomainList(); + for ( QStringList::ConstIterator domIt = domains.begin(); + domIt != domains.end(); ++domIt ) + { + // Ignore domains that have policy set for but contain + // no cookies whatsoever... + const KHttpCookieList* list = mCookieJar->getCookieList(*domIt, ""); + if ( list && !list->isEmpty() ) + result << *domIt; + } + return result; +} + +// DCOP function +QStringList +KCookieServer::findCookies(QValueList<int> fields, + QString domain, + QString fqdn, + QString path, + QString name) +{ + QStringList result; + bool allDomCookies = name.isEmpty(); + + const KHttpCookieList* list = mCookieJar->getCookieList(domain, fqdn); + if ( list && !list->isEmpty() ) + { + QPtrListIterator<KHttpCookie>it( *list ); + for ( ; it.current(); ++it ) + { + if ( !allDomCookies ) + { + if ( cookieMatches(it.current(), domain, fqdn, path, name) ) + { + putCookie(result, it.current(), fields); + break; + } + } + else + putCookie(result, it.current(), fields); + } + } + return result; +} + +// DCOP function +QString +KCookieServer::findDOMCookies(QString url) +{ + return findDOMCookies(url, 0); +} + +// DCOP function +QString +KCookieServer::findDOMCookies(QString url, long windowId) +{ + // We don't wait for pending cookies because it locks up konqueror + // which can cause a deadlock if it happens to have a popup-menu up. + // Instead we just return pending cookies as if they had been accepted already. + KHttpCookieList pendingCookies; + cookiesPending(url, &pendingCookies); + + return mCookieJar->findCookies(url, true, windowId, &pendingCookies); +} + +// DCOP function +void +KCookieServer::addCookies(QString arg1, QCString arg2, long arg3) +{ + addCookies(arg1, arg2, arg3, false); +} + +// DCOP function +void +KCookieServer::deleteCookie(QString domain, QString fqdn, + QString path, QString name) +{ + const KHttpCookieList* list = mCookieJar->getCookieList( domain, fqdn ); + if ( list && !list->isEmpty() ) + { + QPtrListIterator<KHttpCookie>it (*list); + for ( ; it.current(); ++it ) + { + if( cookieMatches(it.current(), domain, fqdn, path, name) ) + { + mCookieJar->eatCookie( it.current() ); + saveCookieJar(); + break; + } + } + } +} + +// DCOP function +void +KCookieServer::deleteCookiesFromDomain(QString domain) +{ + mCookieJar->eatCookiesForDomain(domain); + saveCookieJar(); +} + + +// Qt function +void +KCookieServer::slotDeleteSessionCookies( long windowId ) +{ + deleteSessionCookies(windowId); +} + +// DCOP function +void +KCookieServer::deleteSessionCookies( long windowId ) +{ + mCookieJar->eatSessionCookies( windowId ); + saveCookieJar(); +} + +void +KCookieServer::deleteSessionCookiesFor(QString fqdn, long windowId) +{ + mCookieJar->eatSessionCookies( fqdn, windowId ); + saveCookieJar(); +} + +// DCOP function +void +KCookieServer::deleteAllCookies() +{ + mCookieJar->eatAllCookies(); + saveCookieJar(); +} + +// DCOP function +void +KCookieServer::addDOMCookies(QString arg1, QCString arg2, long arg3) +{ + addCookies(arg1, arg2, arg3, true); +} + +// DCOP function +void +KCookieServer::setDomainAdvice(QString url, QString advice) +{ + QString fqdn; + QString dummy; + if (KCookieJar::parseURL(url, fqdn, dummy)) + { + QStringList domains; + mCookieJar->extractDomains(fqdn, domains); + + mCookieJar->setDomainAdvice(domains[domains.count() > 3 ? 3 : 0], + KCookieJar::strToAdvice(advice)); + // Save the cookie config if it has changed + mCookieJar->saveConfig( mConfig ); + } +} + +// DCOP function +QString +KCookieServer::getDomainAdvice(QString url) +{ + KCookieAdvice advice = KCookieDunno; + QString fqdn; + QString dummy; + if (KCookieJar::parseURL(url, fqdn, dummy)) + { + QStringList domains; + mCookieJar->extractDomains(fqdn, domains); + + QStringList::ConstIterator it = domains.begin(); + while ( (advice == KCookieDunno) && (it != domains.end()) ) + { + // Always check advice in both ".domain" and "domain". Note + // that we only want to check "domain" if it matches the + // fqdn of the requested URL. + if ( (*it)[0] == '.' || (*it) == fqdn ) + advice = mCookieJar->getDomainAdvice(*it); + ++it; + } + if (advice == KCookieDunno) + advice = mCookieJar->getGlobalAdvice(); + } + return KCookieJar::adviceToStr(advice); +} + +// DCOP function +void +KCookieServer::reloadPolicy() +{ + mCookieJar->loadConfig( mConfig, true ); +} + +// DCOP function +void +KCookieServer::shutdown() +{ + deleteLater(); +} + +#include "kcookieserver.moc" + diff --git a/kioslave/http/kcookiejar/kcookieserver.h b/kioslave/http/kcookiejar/kcookieserver.h new file mode 100644 index 000000000..bcd7fa530 --- /dev/null +++ b/kioslave/http/kcookiejar/kcookieserver.h @@ -0,0 +1,98 @@ +/* + This file is part of the KDE File Manager + + Copyright (C) 1998 Waldo Bastian (bastian@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + version 2 as published by the Free Software Foundation. + + This software is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +//---------------------------------------------------------------------------- +// +// KDE Cookie Server +// $Id$ + +#ifndef KCOOKIESERVER_H +#define KCOOKIESERVER_H + +#include <qstringlist.h> +#include <kded/kdedmodule.h> + +class KHttpCookieList; +class KCookieJar; +class KHttpCookie; +class QTimer; +class RequestList; +class DCOPClient; +class KConfig; + +class KCookieServer : public KDEDModule +{ + Q_OBJECT + K_DCOP +public: + KCookieServer(const QCString &); + ~KCookieServer(); + +k_dcop: + QString findCookies(QString); + QString findCookies(QString, long); + QStringList findDomains(); + QStringList findCookies(QValueList<int>,QString,QString,QString,QString); + QString findDOMCookies(QString); + QString findDOMCookies(QString, long); + void addCookies(QString, QCString, long); + void deleteCookie(QString, QString, QString, QString); + void deleteCookiesFromDomain(QString); + void deleteSessionCookies(long); + void deleteSessionCookiesFor(QString, long); + void deleteAllCookies(); + void addDOMCookies(QString, QCString, long); + /** + * Sets the cookie policy for the domain associated with the specified URL. + */ + void setDomainAdvice(QString url, QString advice); + /** + * Returns the cookie policy in effect for the specified URL. + */ + QString getDomainAdvice(QString url); + void reloadPolicy(); + void shutdown(); + +public: + bool cookiesPending(const QString &url, KHttpCookieList *cookieList=0); + void addCookies(const QString &url, const QCString &cookieHeader, + long windowId, bool useDOMFormat); + void checkCookies(KHttpCookieList *cookieList); + +public slots: + void slotSave(); + void slotDeleteSessionCookies(long); + +protected: + KCookieJar *mCookieJar; + KHttpCookieList *mPendingCookies; + RequestList *mRequestList; + QTimer *mTimer; + bool mAdvicePending; + DCOPClient *mOldCookieServer; + KConfig *mConfig; + +private: + virtual int newInstance(QValueList<QCString>) { return 0; } + bool cookieMatches(KHttpCookie*, QString, QString, QString, QString); + void putCookie(QStringList&, KHttpCookie*, const QValueList<int>&); + void saveCookieJar(); +}; + +#endif diff --git a/kioslave/http/kcookiejar/kcookiewin.cpp b/kioslave/http/kcookiejar/kcookiewin.cpp new file mode 100644 index 000000000..5c68f8c1e --- /dev/null +++ b/kioslave/http/kcookiejar/kcookiewin.cpp @@ -0,0 +1,382 @@ +/* +This file is part of KDE + + Copyright (C) 2000- Waldo Bastian <bastian@kde.org> + Copyright (C) 2000- Dawit Alemayehu <adawit@kde.org> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +//---------------------------------------------------------------------------- +// +// KDE File Manager -- HTTP Cookie Dialogs +// $Id$ + +// The purpose of the QT_NO_TOOLTIP and QT_NO_WHATSTHIS ifdefs is because +// this file is also used in Konqueror/Embedded. One of the aims of +// Konqueror/Embedded is to be a small as possible to fit on embedded +// devices. For this it's also useful to strip out unneeded features of +// Qt, like for example QToolTip or QWhatsThis. The availability (or the +// lack thereof) can be determined using these preprocessor defines. +// The same applies to the QT_NO_ACCEL ifdef below. I hope it doesn't make +// too much trouble... (Simon) + +#include <qhbox.h> +#include <qvbox.h> +#include <qaccel.h> +#include <qlabel.h> +#include <qwidget.h> +#include <qlayout.h> +#include <qgroupbox.h> +#include <qdatetime.h> +#include <qmessagebox.h> +#include <qpushbutton.h> +#include <qradiobutton.h> +#include <qvbuttongroup.h> + +#ifndef QT_NO_TOOLTIP +#include <qtooltip.h> +#endif + +#ifndef QT_NO_WHATSTHIS +#include <qwhatsthis.h> +#endif + +#include <kidna.h> +#include <kwin.h> +#include <klocale.h> +#include <kglobal.h> +#include <kurllabel.h> +#include <klineedit.h> +#include <kiconloader.h> +#include <kapplication.h> + +#ifdef Q_WS_X11 +#include <X11/Xlib.h> +#endif + +#include "kcookiejar.h" +#include "kcookiewin.h" + +KCookieWin::KCookieWin( QWidget *parent, KHttpCookieList cookieList, + int defaultButton, bool showDetails ) + :KDialog( parent, "cookiealert", true ) +{ +#ifndef Q_WS_QWS //FIXME(E): Implement for Qt Embedded + setCaption( i18n("Cookie Alert") ); + setIcon( SmallIcon("cookie") ); + // all cookies in the list should have the same window at this time, so let's take the first +# ifdef Q_WS_X11 + if( cookieList.first()->windowIds().count() > 0 ) + { + XSetTransientForHint( qt_xdisplay(), winId(), cookieList.first()->windowIds().first()); + } + else + { + // No window associated... make sure the user notices our dialog. + KWin::setState( winId(), NET::KeepAbove ); + kapp->updateUserTimestamp(); + } +# endif +#endif + // Main widget's layout manager... + QVBoxLayout* vlayout = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() ); + vlayout->setResizeMode( QLayout::Fixed ); + + // Cookie image and message to user + QHBox* hBox = new QHBox( this ); + hBox->setSpacing( KDialog::spacingHint() ); + QLabel* icon = new QLabel( hBox ); + icon->setPixmap( QMessageBox::standardIcon(QMessageBox::Warning) ); + icon->setAlignment( Qt::AlignCenter ); + icon->setFixedSize( 2*icon->sizeHint() ); + + int count = cookieList.count(); + + QVBox* vBox = new QVBox( hBox ); + QString txt = i18n("You received a cookie from", + "You received %n cookies from", count); + QLabel* lbl = new QLabel( txt, vBox ); + lbl->setAlignment( Qt::AlignCenter ); + KHttpCookiePtr cookie = cookieList.first(); + + QString host (cookie->host()); + int pos = host.find(':'); + if ( pos > 0 ) + { + QString portNum = host.left(pos); + host.remove(0, pos+1); + host += ':'; + host += portNum; + } + + txt = QString("<b>%1</b>").arg( KIDNA::toUnicode(host) ); + if (cookie->isCrossDomain()) + txt += i18n(" <b>[Cross Domain!]</b>"); + lbl = new QLabel( txt, vBox ); + lbl->setAlignment( Qt::AlignCenter ); + lbl = new QLabel( i18n("Do you want to accept or reject?"), vBox ); + lbl->setAlignment( Qt::AlignCenter ); + vlayout->addWidget( hBox, 0, Qt::AlignLeft ); + + // Cookie Details dialog... + m_detailView = new KCookieDetail( cookieList, count, this ); + vlayout->addWidget( m_detailView ); + m_showDetails = showDetails; + m_showDetails ? m_detailView->show():m_detailView->hide(); + + // Cookie policy choice... + m_btnGrp = new QVButtonGroup( i18n("Apply Choice To"), this ); + m_btnGrp->setRadioButtonExclusive( true ); + + txt = (count == 1)? i18n("&Only this cookie") : i18n("&Only these cookies"); + QRadioButton* rb = new QRadioButton( txt, m_btnGrp ); +#ifndef QT_NO_WHATSTHIS + QWhatsThis::add( rb, i18n("Select this option to accept/reject only this cookie. " + "You will be prompted if another cookie is received. " + "<em>(see WebBrowsing/Cookies in the Control Center)</em>." ) ); +#endif + m_btnGrp->insert( rb ); + rb = new QRadioButton( i18n("All cookies from this do&main"), m_btnGrp ); +#ifndef QT_NO_WHATSTHIS + QWhatsThis::add( rb, i18n("Select this option to accept/reject all cookies from " + "this site. Choosing this option will add a new policy for " + "the site this cookie originated from. This policy will be " + "permanent until you manually change it from the Control Center " + "<em>(see WebBrowsing/Cookies in the Control Center)</em>.") ); +#endif + m_btnGrp->insert( rb ); + rb = new QRadioButton( i18n("All &cookies"), m_btnGrp ); +#ifndef QT_NO_WHATSTHIS + QWhatsThis::add( rb, i18n("Select this option to accept/reject all cookies from " + "anywhere. Choosing this option will change the global " + "cookie policy set in the Control Center for all cookies " + "<em>(see WebBrowsing/Cookies in the Control Center)</em>.") ); +#endif + m_btnGrp->insert( rb ); + vlayout->addWidget( m_btnGrp ); + + if ( defaultButton > -1 && defaultButton < 3 ) + m_btnGrp->setButton( defaultButton ); + else + m_btnGrp->setButton( 1 ); + + // Accept/Reject buttons + QWidget* bbox = new QWidget( this ); + QBoxLayout* bbLay = new QHBoxLayout( bbox ); + bbLay->setSpacing( KDialog::spacingHint() ); + QPushButton* btn = new QPushButton( i18n("&Accept"), bbox ); + btn->setDefault( true ); + btn->setFocus(); + connect( btn, SIGNAL(clicked()), SLOT(accept()) ); + bbLay->addWidget( btn ); + btn = new QPushButton( i18n("&Reject"), bbox ); + connect( btn, SIGNAL(clicked()), SLOT(reject()) ); + bbLay->addWidget( btn ); + bbLay->addStretch( 1 ); +#ifndef QT_NO_ACCEL + QAccel* a = new QAccel( this ); + a->connectItem( a->insertItem(Qt::Key_Escape), btn, SLOT(animateClick()) ); +#endif + + m_button = new QPushButton( bbox ); + m_button->setText( m_showDetails ? i18n("&Details <<"):i18n("&Details >>") ); + connect( m_button, SIGNAL(clicked()), SLOT(slotCookieDetails()) ); + bbLay->addWidget( m_button ); +#ifndef QT_NO_WHATSTHIS + QWhatsThis::add( m_button, i18n("See or modify the cookie information") ); +#endif + + + vlayout->addWidget( bbox ); + setFixedSize( sizeHint() ); +} + +KCookieWin::~KCookieWin() +{ +} + +void KCookieWin::slotCookieDetails() +{ + if ( m_detailView->isVisible() ) + { + m_detailView->setMaximumSize( 0, 0 ); + m_detailView->adjustSize(); + m_detailView->hide(); + m_button->setText( i18n( "&Details >>" ) ); + m_showDetails = false; + } + else + { + m_detailView->setMaximumSize( 1000, 1000 ); + m_detailView->adjustSize(); + m_detailView->show(); + m_button->setText( i18n( "&Details <<" ) ); + m_showDetails = true; + } +} + +KCookieAdvice KCookieWin::advice( KCookieJar *cookiejar, KHttpCookie* cookie ) +{ + int result = exec(); + + cookiejar->setShowCookieDetails ( m_showDetails ); + + KCookieAdvice advice = (result==QDialog::Accepted) ? KCookieAccept:KCookieReject; + + int preferredPolicy = m_btnGrp->id( m_btnGrp->selected() ); + cookiejar->setPreferredDefaultPolicy( preferredPolicy ); + + switch ( preferredPolicy ) + { + case 2: + cookiejar->setGlobalAdvice( advice ); + break; + case 1: + cookiejar->setDomainAdvice( cookie, advice ); + break; + case 0: + default: + break; + } + return advice; +} + +KCookieDetail::KCookieDetail( KHttpCookieList cookieList, int cookieCount, + QWidget* parent, const char* name ) + :QGroupBox( parent, name ) +{ + setTitle( i18n("Cookie Details") ); + QGridLayout* grid = new QGridLayout( this, 9, 2, + KDialog::spacingHint(), + KDialog::marginHint() ); + grid->addRowSpacing( 0, fontMetrics().lineSpacing() ); + grid->setColStretch( 1, 3 ); + + QLabel* label = new QLabel( i18n("Name:"), this ); + grid->addWidget( label, 1, 0 ); + m_name = new KLineEdit( this ); + m_name->setReadOnly( true ); + m_name->setMaximumWidth( fontMetrics().maxWidth() * 25 ); + grid->addWidget( m_name, 1 ,1 ); + + //Add the value + label = new QLabel( i18n("Value:"), this ); + grid->addWidget( label, 2, 0 ); + m_value = new KLineEdit( this ); + m_value->setReadOnly( true ); + m_value->setMaximumWidth( fontMetrics().maxWidth() * 25 ); + grid->addWidget( m_value, 2, 1); + + label = new QLabel( i18n("Expires:"), this ); + grid->addWidget( label, 3, 0 ); + m_expires = new KLineEdit( this ); + m_expires->setReadOnly( true ); + m_expires->setMaximumWidth(fontMetrics().maxWidth() * 25 ); + grid->addWidget( m_expires, 3, 1); + + label = new QLabel( i18n("Path:"), this ); + grid->addWidget( label, 4, 0 ); + m_path = new KLineEdit( this ); + m_path->setReadOnly( true ); + m_path->setMaximumWidth( fontMetrics().maxWidth() * 25 ); + grid->addWidget( m_path, 4, 1); + + label = new QLabel( i18n("Domain:"), this ); + grid->addWidget( label, 5, 0 ); + m_domain = new KLineEdit( this ); + m_domain->setReadOnly( true ); + m_domain->setMaximumWidth( fontMetrics().maxWidth() * 25 ); + grid->addWidget( m_domain, 5, 1); + + label = new QLabel( i18n("Exposure:"), this ); + grid->addWidget( label, 6, 0 ); + m_secure = new KLineEdit( this ); + m_secure->setReadOnly( true ); + m_secure->setMaximumWidth( fontMetrics().maxWidth() * 25 ); + grid->addWidget( m_secure, 6, 1 ); + + if ( cookieCount > 1 ) + { + QPushButton* btnNext = new QPushButton( i18n("Next cookie","&Next >>"), this ); + btnNext->setFixedSize( btnNext->sizeHint() ); + grid->addMultiCellWidget( btnNext, 8, 8, 0, 1 ); + connect( btnNext, SIGNAL(clicked()), SLOT(slotNextCookie()) ); +#ifndef QT_NO_TOOLTIP + QToolTip::add( btnNext, i18n("Show details of the next cookie") ); +#endif + } + m_cookieList = cookieList; + m_cookie = 0; + slotNextCookie(); +} + +KCookieDetail::~KCookieDetail() +{ +} + +void KCookieDetail::slotNextCookie() +{ + KHttpCookiePtr cookie = m_cookieList.first(); + if (m_cookie) while(cookie) + { + if (cookie == m_cookie) + { + cookie = m_cookieList.next(); + break; + } + cookie = m_cookieList.next(); + } + m_cookie = cookie; + if (!m_cookie) + m_cookie = m_cookieList.first(); + + if ( m_cookie ) + { + m_name->setText( m_cookie->name() ); + m_value->setText( ( m_cookie->value() ) ); + if ( m_cookie->domain().isEmpty() ) + m_domain->setText( i18n("Not specified") ); + else + m_domain->setText( m_cookie->domain() ); + m_path->setText( m_cookie->path() ); + QDateTime cookiedate; + cookiedate.setTime_t( m_cookie->expireDate() ); + if ( m_cookie->expireDate() ) + m_expires->setText( KGlobal::locale()->formatDateTime(cookiedate) ); + else + m_expires->setText( i18n("End of Session") ); + QString sec; + if (m_cookie->isSecure()) + { + if (m_cookie->isHttpOnly()) + sec = i18n("Secure servers only"); + else + sec = i18n("Secure servers, page scripts"); + } + else + { + if (m_cookie->isHttpOnly()) + sec = i18n("Servers"); + else + sec = i18n("Servers, page scripts"); + } + m_secure->setText( sec ); + } +} + +#include "kcookiewin.moc" diff --git a/kioslave/http/kcookiejar/kcookiewin.h b/kioslave/http/kcookiejar/kcookiewin.h new file mode 100644 index 000000000..30e92e7e0 --- /dev/null +++ b/kioslave/http/kcookiejar/kcookiewin.h @@ -0,0 +1,84 @@ +/* + This file is part of the KDE File Manager + + Copyright (C) 1998- Waldo Bastian (bastian@kde.org) + Copyright (C) 2000- Dawit Alemayehu (adawit@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This software is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +//---------------------------------------------------------------------------- +// +// KDE File Manager -- HTTP Cookie Dialogs +// $Id$ + +#ifndef _KCOOKIEWIN_H_ +#define _KCOOKIEWIN_H_ + +#include <qgroupbox.h> + +#include <kdialog.h> +#include "kcookiejar.h" + +class KLineEdit; +class QPushButton; +class QVButtonGroup; +class KURLLabel; + +class KCookieDetail : public QGroupBox +{ + Q_OBJECT + +public : + KCookieDetail( KHttpCookieList cookieList, int cookieCount, QWidget *parent=0, + const char *name=0 ); + ~KCookieDetail(); + +private : + KLineEdit* m_name; + KLineEdit* m_value; + KLineEdit* m_expires; + KLineEdit* m_domain; + KLineEdit* m_path; + KLineEdit* m_secure; + + KHttpCookieList m_cookieList; + KHttpCookiePtr m_cookie; + +private slots: + void slotNextCookie(); +}; + +class KCookieWin : public KDialog +{ + Q_OBJECT + +public : + KCookieWin( QWidget *parent, KHttpCookieList cookieList, int defaultButton=0, + bool showDetails=false ); + ~KCookieWin(); + + KCookieAdvice advice( KCookieJar *cookiejar, KHttpCookie* cookie ); + +private : + QPushButton* m_button; + QVButtonGroup* m_btnGrp; + KCookieDetail* m_detailView; + bool m_showDetails; + +private slots: + void slotCookieDetails(); +}; +#endif diff --git a/kioslave/http/kcookiejar/main.cpp b/kioslave/http/kcookiejar/main.cpp new file mode 100644 index 000000000..1e943b939 --- /dev/null +++ b/kioslave/http/kcookiejar/main.cpp @@ -0,0 +1,92 @@ +/* +This file is part of KDE + + Copyright (C) 1998-2000 Waldo Bastian (bastian@kde.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include <dcopclient.h> +#include <kcmdlineargs.h> +#include <klocale.h> +#include <kapplication.h> + +static const char description[] = + I18N_NOOP("HTTP Cookie Daemon"); + +static const char version[] = "1.0"; + +static const KCmdLineOptions options[] = +{ + { "shutdown", I18N_NOOP("Shut down cookie jar"), 0 }, + { "remove <domain>", I18N_NOOP("Remove cookies for domain"), 0 }, + { "remove-all", I18N_NOOP("Remove all cookies"), 0 }, + { "reload-config", I18N_NOOP("Reload configuration file"), 0 }, + KCmdLineLastOption +}; + +extern "C" KDE_EXPORT int kdemain(int argc, char *argv[]) +{ + KLocale::setMainCatalogue("kdelibs"); + KCmdLineArgs::init(argc, argv, "kcookiejar", I18N_NOOP("HTTP cookie daemon"), + description, version); + + KCmdLineArgs::addCmdLineOptions( options ); + + KInstance a("kcookiejar"); + + kapp->dcopClient()->attach(); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + QCString replyType; + QByteArray replyData; + if (args->isSet("remove-all")) + { + kapp->dcopClient()->call( "kded", "kcookiejar", "deleteAllCookies()", QByteArray(), replyType, replyData); + } + if (args->isSet("remove")) + { + QString domain = args->getOption("remove"); + QByteArray params; + QDataStream stream(params, IO_WriteOnly); + stream << domain; + kapp->dcopClient()->call( "kded", "kcookiejar", "deleteCookiesFromDomain(QString)", params, replyType, replyData); + } + if (args->isSet("shutdown")) + { + QCString module = "kcookiejar"; + QByteArray params; + QDataStream stream(params, IO_WriteOnly); + stream << module; + kapp->dcopClient()->call( "kded", "kded", "unloadModule(QCString)", params, replyType, replyData); + } + else if(args->isSet("reload-config")) + { + kapp->dcopClient()->call( "kded", "kcookiejar", "reloadPolicy()", QByteArray(), replyType, replyData); + } + else + { + QCString module = "kcookiejar"; + QByteArray params; + QDataStream stream(params, IO_WriteOnly); + stream << module; + kapp->dcopClient()->call( "kded", "kded", "loadModule(QCString)", params, replyType, replyData); + } + + return 0; +} diff --git a/kioslave/http/kcookiejar/netscape_cookie_spec.html b/kioslave/http/kcookiejar/netscape_cookie_spec.html new file mode 100644 index 000000000..eb190f2e3 --- /dev/null +++ b/kioslave/http/kcookiejar/netscape_cookie_spec.html @@ -0,0 +1,331 @@ +<HTML> +<HEAD> +<TITLE>Client Side State - HTTP Cookies</TITLE> +</HEAD> + +<BODY BGCOLOR="#ffffff" LINK="#0000ff" VLINK="#ff0000" ALINK="#ff0000" TEXT="#000000" > + + +<CENTER> +<!-- BANNER:s3 --> +<A HREF="/maps/banners/documentation_s3.map"><IMG SRC="/images/banners/documentation_s3.gif" ALT="Documentation" BORDER=0 WIDTH=612 HEIGHT=50 ISMAP USEMAP="#banner_nav"></A> +<MAP NAME="banner_nav"> +<AREA SHAPE=RECT COORDS="62,11,91,40" HREF="/"> +<AREA SHAPE=RECT COORDS="153,41,221,50" HREF="/"> +<AREA SHAPE=RECT COORDS="298,8,374,34" HREF="/support/index.html"> +<AREA SHAPE=RECT COORDS="381,15,586,43" HREF="http://help.netscape.com/browse/index.html"> +<AREA SHAPE=default NOHREF> +</MAP> + +<!-- BANNER:s3 --> + +<H2> +<FONT SIZE=+3>P</FONT>ERSISTENT +<FONT SIZE=+3>C</FONT>LIENT +<FONT SIZE=+3>S</FONT>TATE<BR> +<FONT SIZE=+3>HTTP C</FONT>OOKIES +</H2> + +<H3>Preliminary Specification - Use with caution</H3> +</CENTER> + +<HR SIZE=4> + +<CENTER> +<H3> +<FONT SIZE=+2>I</FONT>NTRODUCTION +</H3> +</CENTER> + +Cookies are a general mechanism which server side connections (such as +CGI scripts) can use to both store and retrieve information on the +client side of the connection. The addition of a simple, persistent, +client-side state significantly extends the capabilities of Web-based +client/server applications.<P> + +<CENTER> +<H3> +<FONT SIZE=+2>O</FONT>VERVIEW +</H3> +</CENTER> + +A server, when returning an HTTP object to a client, may also send a +piece of state information which the client will store. Included in that +state object is a description of the range of URLs for which that state is +valid. Any future HTTP requests made by the client which fall in that +range will include a transmittal of the current value of the state +object from the client back to the server. The state object is called +a <B>cookie</B>, for no compelling reason. <P> +This simple mechanism provides a powerful new tool which enables a host +of new types of applications to be written for web-based environments. +Shopping applications can now store information about the currently +selected items, for fee services can send back registration information +and free the client from retyping a user-id on next connection, +sites can store per-user preferences on the client, and have the client supply +those preferences every time that site is connected to. + +<CENTER> +<H3> +<FONT SIZE=+2>S</FONT>PECIFICATION +</H3> +</CENTER> + +A cookie is introduced to the client by including a <B>Set-Cookie</B> +header as part of an HTTP response, typically this will be generated +by a CGI script. + +<H3>Syntax of the Set-Cookie HTTP Response Header</H3> + +This is the format a CGI script would use to add to the HTTP headers +a new piece of data which is to be stored by the client for later retrieval. + +<PRE> +Set-Cookie: <I>NAME</I>=<I>VALUE</I>; expires=<I>DATE</I>; +path=<I>PATH</I>; domain=<I>DOMAIN_NAME</I>; secure +</PRE> +<DL> +<DT> <I>NAME</I>=<I>VALUE</I><DD> +This string is a sequence of characters excluding semi-colon, comma and white +space. If there is a need to place such data in the name or value, some +encoding method such as URL style %XX encoding is recommended, though no +encoding is defined or required. <P> This is the only required attribute +on the <B>Set-Cookie</B> header. <P> +<DT><B>expires</B>=<I>DATE</I> +<DD> +The <B>expires</B> attribute specifies a date string that +defines the valid life time of that cookie. Once the expiration +date has been reached, the cookie will no longer be stored or +given out. <P> +The date string is formatted as: +<BLOCKQUOTE> <TT>Wdy, DD-Mon-YYYY HH:MM:SS GMT</TT></BLOCKQUOTE> +This is based on +<A TARGET="_top" HREF="http://ds.internic.net/rfc/rfc822.txt">RFC 822</A>, +<A TARGET="_top" HREF="http://ds.internic.net/rfc/rfc850.txt">RFC 850</A>, +<A TARGET="_top" HREF="http://www.w3.org/hypertext/WWW/Protocols/rfc1036/rfc1036.html#z6"> +RFC 1036</A>, and +<A TARGET="_top" HREF="http://ds1.internic.net/rfc/rfc1123.txt"> +RFC 1123</A>, +with the variations that the only legal time zone is <B>GMT</B> and +the separators between the elements of the date must be dashes. +<P> +<B>expires</B> is an optional attribute. If not specified, the cookie will +expire when the user's session ends. <P> +<B>Note:</B> There is a bug in Netscape Navigator version 1.1 and earlier. +Only cookies whose <B>path</B> attribute is set explicitly to "/" will +be properly saved between sessions if they have an <B>expires</B> +attribute.<P> + +<DT> <B>domain</B>=<I>DOMAIN_NAME</I> +<DD> +When searching the cookie list for valid cookies, a comparison of the +<B>domain</B> +attributes of the cookie is made with the Internet domain name of the +host from which the URL will be fetched. If there is a tail match, +then the cookie will go through <B>path</B> matching to see if it +should be sent. "Tail matching" means that <B>domain</B> attribute +is matched against the tail of the fully qualified domain name of +the host. A <B>domain</B> attribute of "acme.com" would match +host names "anvil.acme.com" as well as "shipping.crate.acme.com". <P> + +Only hosts within the specified domain +can set a cookie for a domain and domains must have at least two (2) +or three (3) periods in them to prevent domains of the form: +".com", ".edu", and "va.us". Any domain that fails within +one of the seven special top level domains listed below only require +two periods. Any other domain requires at least three. The +seven special top level domains are: "COM", "EDU", "NET", "ORG", +"GOV", "MIL", and "INT". + + <P> +The default value of <B>domain</B> is the host name of the server +which generated the cookie response. <P> +<DT> <B>path</B>=<I>PATH</I> +<DD> +The <B>path</B> attribute is used to specify the subset of URLs in a +domain for +which the cookie is valid. If a cookie has already passed <B>domain</B> +matching, then the pathname component +of the URL is compared with the path attribute, and if there is +a match, the cookie is considered valid and is sent along with +the URL request. The path "/foo" +would match "/foobar" and "/foo/bar.html". The path "/" is the most +general path. <P> +If the <B>path</B> is not specified, it as assumed to be the same path +as the document being described by the header which contains the cookie. +<P> +<DT> <B>secure</B> +<DD> +If a cookie is marked <B>secure</B>, it will only be transmitted if the +communications channel with the host is a secure one. Currently +this means that secure cookies will only be sent to HTTPS (HTTP over SSL) +servers. <P> +If <B>secure</B> is not specified, a cookie is considered safe to be sent +in the clear over unsecured channels. +</DL> + +<H3>Syntax of the Cookie HTTP Request Header</H3> + +When requesting a URL from an HTTP server, the browser will match +the URL against all cookies and if any of them match, a line +containing the name/value pairs of all matching cookies will +be included in the HTTP request. Here is the format of that line: +<PRE> +Cookie: <I>NAME1=OPAQUE_STRING1</I>; <I>NAME2=OPAQUE_STRING2 ...</I> +</PRE> + +<H3>Additional Notes</H3> + +<UL> +<LI>Multiple <B>Set-Cookie</B> headers can be issued in a single server +response. +<p> +<LI>Instances of the same path and name will overwrite each other, with the +latest instance taking precedence. Instances of the same path but +different names will add additional mappings. +<p> +<LI>Setting the path to a higher-level value does not override other more +specific path mappings. If there are multiple matches for a given cookie +name, but with separate paths, all the matching cookies will be sent. +(See examples below.) +<p> +<LI>The +expires header lets the client know when it is safe to purge the mapping +but the client is not required to do so. A client may also delete a +cookie before it's expiration date arrives if the number of cookies +exceeds its internal limits. +<p> +<LI>When sending cookies to a server, all cookies with a more specific +path mapping should be sent before cookies with less specific path +mappings. For example, a cookie "name1=foo" with a path mapping +of "/" should be sent after a cookie "name1=foo2" with +a path mapping of "/bar" if they are both to be sent. +<p> +<LI>There are limitations on the number of cookies that a client +can store at any one time. This is a specification of the minimum +number of cookies that a client should be prepared to receive and +store. + +<UL> + <LI>300 total cookies + <LI>4 kilobytes per cookie, where the name and the OPAQUE_STRING + combine to form the 4 kilobyte limit. + <LI>20 cookies per server or domain. (note that completely + specified hosts and domains are treated as separate entities + and have a 20 cookie limitation for each, not combined) +</UL> +Servers should not expect clients to be able to exceed these limits. +When the 300 cookie limit or the 20 cookie per server limit +is exceeded, clients should delete the least recently used cookie. +When a cookie larger than 4 kilobytes is encountered the cookie +should be trimmed to fit, but the name should remain intact +as long as it is less than 4 kilobytes. + <P> +<LI>If a CGI script wishes to delete a cookie, it can do so by +returning a cookie with the same name, and an <B>expires</B> time +which is in the past. The path and name must match exactly +in order for the expiring cookie to replace the valid cookie. +This requirement makes it difficult for anyone but the originator +of a cookie to delete a cookie. +<P><LI>When caching HTTP, as a proxy server might do, the <B>Set-cookie</B> +response header should never be cached. +<P><LI>If a proxy server receives a response which +contains a <B>Set-cookie</B> header, it should propagate the <B>Set-cookie</B> +header to the client, regardless of whether the response was 304 +(Not Modified) or 200 (OK). +<P>Similarly, if a client request contains a Cookie: header, it +should be forwarded through a proxy, even if the conditional +If-modified-since request is being made. +</UL> + +<CENTER> +<H3> +<FONT SIZE=+2>E</FONT>XAMPLES +</H3> +</CENTER> + +Here are some sample exchanges which are designed to illustrate the use +of cookies. +<H3>First Example transaction sequence:</H3> +<DL> +<dt>Client requests a document, and receives in the response:<dd> +<PRE> +Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT</PRE> +<dt>When client requests a URL in path "/" on this server, it sends:<DD> +<PRE>Cookie: CUSTOMER=WILE_E_COYOTE</PRE> +<dt>Client requests a document, and receives in the response:<dd> +<PRE>Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/</PRE> +<dt>When client requests a URL in path "/" on this server, it sends:<dd> +<PRE>Cookie: CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001</PRE> +<dt>Client receives:<dd> +<PRE>Set-Cookie: SHIPPING=FEDEX; path=/foo</PRE> +<dt>When client requests a URL in path "/" on this server, it sends:<dd> +<PRE>Cookie: CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001</PRE> +<dt>When client requests a URL in path "/foo" on this server, it sends:<dd> +<PRE>Cookie: CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001; SHIPPING=FEDEX</PRE> +</DL> +<H3>Second Example transaction sequence:</H3> +<DL> +<dt>Assume all mappings from above have been cleared.<p> +<dt>Client receives:<dd> +<PRE>Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/</PRE> +<dt>When client requests a URL in path "/" on this server, it sends:<dd> +<PRE>Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001</PRE> +<dt>Client receives:<dd> +<PRE>Set-Cookie: PART_NUMBER=RIDING_ROCKET_0023; path=/ammo</PRE> +<dt>When client requests a URL in path "/ammo" on this server, it sends:<dd> +<PRE>Cookie: PART_NUMBER=RIDING_ROCKET_0023; PART_NUMBER=ROCKET_LAUNCHER_0001</PRE> +<dd>NOTE: There are two name/value pairs named "PART_NUMBER" due to the +inheritance +of the "/" mapping in addition to the "/ammo" mapping. +</DL> + +<HR SIZE=4> +<P> + +<CENTER> + + +<!-- footer --> +<TABLE WIDTH=600 BORDER=0 CELLPADDING=0 CELLSPACING=0> +<TR> +<TD WIDTH=600 HEIGHT=8><HR SIZE=1 NOSHADE></TD></TR> +<TR><TD ALIGN=LEFT VALIGN=TOP><FONT FACE="sans-serif, Arial, Helvetica" SIZE=-2><A HREF="http://home.netscape.com/misc/nav_redir/help.html" TARGET="_top">Help</A> | <A +HREF="http://home.netscape.com/misc/nav_redir/site_map.html" TARGET="_top">Site Map</A> | <A +HREF="http://home.netscape.com/misc/nav_redir/howtoget.html" TARGET="_top">How to Get Netscape Products</A> | <A HREF="http://home.netscape.com/misc/nav_redir/ad.html" TARGET="_top">Advertise With Us</A> | <A HREF="http://home.netscape.com/misc/nav_redir/addsite.html" TARGET="_top">Add Site</A> | <A HREF="http://home.netscape.com/misc/nav_redir/custom_browser.html" TARGET="_top">Custom Browser Program</A></FONT></TD></TR> +<TR> +<TD WIDTH=600 HEIGHT=8 COLSPAN=0></TD> +</TR> + +<TR> +<TD ALIGN=LEFT VALIGN=TOP> +<!-- Channels --> +<FONT FACE="sans-serif, Arial, Helvetica" SIZE=-2><A HREF="http://home.netscape.com/misc/nav_redir/channels/autos.html" TARGET="_top">Autos</A> | <A +HREF="http://home.netscape.com/misc/nav_redir/channels/business.html" TARGET="_top">Business</A> | <A HREF="http://home.netscape.com/misc/nav_redir/channels/computers_internet.html" TARGET="_top">Computing & Internet</A> | <A HREF="http://home.netscape.com/misc/nav_redir/channels/entertainment.html" TARGET="_top">Entertainment</A> | <A +HREF="http://home.netscape.com/misc/nav_redir/channels/kids_family.html" TARGET="_top">Family</A> | <A +HREF="http://home.netscape.com/misc/nav_redir/channels/games.html" TARGET="_top">Games</A> | <A HREF="http://home.netscape.com/misc/nav_redir/channels/health.html" TARGET="_top">Health</A> | <A HREF="http://home.netscape.com/misc/nav_redir/channels/lifestyles.html" TARGET="_top">Lifestyles</A> | <A +HREF="http://home.netscape.com/misc/nav_redir/channels/local.html" TARGET="_top">Local</A> | <A HREF="http://home.netscape.com/misc/nav_redir/channels/netscape.html" TARGET="_top">Netscape</A> | <A HREF="http://home.netscape.com/misc/nav_redir/channels/open_directory.html">Netscape Open Directory</A> | <A +HREF="http://home.netscape.com/misc/nav_redir/channels/news.html" TARGET="_top">News</A> | <A HREF="http://home.netscape.com/misc/nav_redir/channels/personalize_finance.html" TARGET="_top">Personal Finance</A> | <A +HREF="http://home.netscape.com/misc/nav_redir/channels/real_estate.html" TARGET="_top">Real Estate</A> | <A HREF="http://home.netscape.com/misc/nav_redir/channels/education.html" TARGET="_top">Research & Learn</A> | <A HREF="http://home.netscape.com/misc/nav_redir/channels/shopping.html" TARGET="_top">Shopping</A> | <A HREF="http://home.netscape.com/misc/nav_redir/channels/smallbiz.html" TARGET="_top">Small Business</A> | <A +HREF="http://home.netscape.com/misc/nav_redir/channels/sports.html" TARGET="_top">Sports</A> | <A HREF="http://home.netscape.com/misc/nav_redir/channels/travel.html" TARGET="_top">Travel</A></FONT></TD></TR> +</TABLE> + +<TABLE WIDTH=600 BORDER=0 CELLPADDING=0 CELLSPACING=0> +<TR><TD WIDTH=600 HEIGHT=8 COLSPAN=0></TD></TR> +<TR> +<TD WIDTH=600 COLSPAN=5 VALIGN=TOP ALIGN=LEFT> +<FONT FACE="sans-serif, Arial, Helvetica" SIZE=-2> +© 1999 Netscape, All Rights Reserved. <A HREF="http://home.netscape.com/legal_notices/index.html">Legal & Privacy Notices</A><BR>This site powered by <A HREF="http://home.netscape.com/comprod/server_central/index.html" TARGET="_top">Netscape SuiteSpot servers</A>.</FONT></TD> +</TR> +</TABLE> +<!-- end footer --> + + + + +</CENTER> +<P> + + + +</BODY> +</HTML>
\ No newline at end of file diff --git a/kioslave/http/kcookiejar/rfc2109 b/kioslave/http/kcookiejar/rfc2109 new file mode 100644 index 000000000..432fdcc6e --- /dev/null +++ b/kioslave/http/kcookiejar/rfc2109 @@ -0,0 +1,1179 @@ + + + + + + +Network Working Group D. Kristol +Request for Comments: 2109 Bell Laboratories, Lucent Technologies +Category: Standards Track L. Montulli + Netscape Communications + February 1997 + + + HTTP State Management Mechanism + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +1. ABSTRACT + + This document specifies a way to create a stateful session with HTTP + requests and responses. It describes two new headers, Cookie and + Set-Cookie, which carry state information between participating + origin servers and user agents. The method described here differs + from Netscape's Cookie proposal, but it can interoperate with + HTTP/1.0 user agents that use Netscape's method. (See the HISTORICAL + section.) + +2. TERMINOLOGY + + The terms user agent, client, server, proxy, and origin server have + the same meaning as in the HTTP/1.0 specification. + + Fully-qualified host name (FQHN) means either the fully-qualified + domain name (FQDN) of a host (i.e., a completely specified domain + name ending in a top-level domain such as .com or .uk), or the + numeric Internet Protocol (IP) address of a host. The fully + qualified domain name is preferred; use of numeric IP addresses is + strongly discouraged. + + The terms request-host and request-URI refer to the values the client + would send to the server as, respectively, the host (but not port) + and abs_path portions of the absoluteURI (http_URL) of the HTTP + request line. Note that request-host must be a FQHN. + + + + + + + + +Kristol & Montulli Standards Track [Page 1] + +RFC 2109 HTTP State Management Mechanism February 1997 + + + Hosts names can be specified either as an IP address or a FQHN + string. Sometimes we compare one host name with another. Host A's + name domain-matches host B's if + + * both host names are IP addresses and their host name strings match + exactly; or + + * both host names are FQDN strings and their host name strings match + exactly; or + + * A is a FQDN string and has the form NB, where N is a non-empty name + string, B has the form .B', and B' is a FQDN string. (So, x.y.com + domain-matches .y.com but not y.com.) + + Note that domain-match is not a commutative operation: a.b.c.com + domain-matches .c.com, but not the reverse. + + Because it was used in Netscape's original implementation of state + management, we will use the term cookie to refer to the state + information that passes between an origin server and user agent, and + that gets stored by the user agent. + +3. STATE AND SESSIONS + + This document describes a way to create stateful sessions with HTTP + requests and responses. Currently, HTTP servers respond to each + client request without relating that request to previous or + subsequent requests; the technique allows clients and servers that + wish to exchange state information to place HTTP requests and + responses within a larger context, which we term a "session". This + context might be used to create, for example, a "shopping cart", in + which user selections can be aggregated before purchase, or a + magazine browsing system, in which a user's previous reading affects + which offerings are presented. + + There are, of course, many different potential contexts and thus many + different potential types of session. The designers' paradigm for + sessions created by the exchange of cookies has these key attributes: + + 1. Each session has a beginning and an end. + + 2. Each session is relatively short-lived. + + 3. Either the user agent or the origin server may terminate a + session. + + 4. The session is implicit in the exchange of state information. + + + + +Kristol & Montulli Standards Track [Page 2] + +RFC 2109 HTTP State Management Mechanism February 1997 + + +4. OUTLINE + + We outline here a way for an origin server to send state information + to the user agent, and for the user agent to return the state + information to the origin server. The goal is to have a minimal + impact on HTTP and user agents. Only origin servers that need to + maintain sessions would suffer any significant impact, and that + impact can largely be confined to Common Gateway Interface (CGI) + programs, unless the server provides more sophisticated state + management support. (See Implementation Considerations, below.) + +4.1 Syntax: General + + The two state management headers, Set-Cookie and Cookie, have common + syntactic properties involving attribute-value pairs. The following + grammar uses the notation, and tokens DIGIT (decimal digits) and + token (informally, a sequence of non-special, non-white space + characters) from the HTTP/1.1 specification [RFC 2068] to describe + their syntax. + + av-pairs = av-pair *(";" av-pair) + av-pair = attr ["=" value] ; optional value + attr = token + value = word + word = token | quoted-string + + Attributes (names) (attr) are case-insensitive. White space is + permitted between tokens. Note that while the above syntax + description shows value as optional, most attrs require them. + + NOTE: The syntax above allows whitespace between the attribute and + the = sign. + +4.2 Origin Server Role + +4.2.1 General + + The origin server initiates a session, if it so desires. (Note that + "session" here does not refer to a persistent network connection but + to a logical session created from HTTP requests and responses. The + presence or absence of a persistent connection should have no effect + on the use of cookie-derived sessions). To initiate a session, the + origin server returns an extra response header to the client, Set- + Cookie. (The details follow later.) + + A user agent returns a Cookie request header (see below) to the + origin server if it chooses to continue a session. The origin server + may ignore it or use it to determine the current state of the + + + +Kristol & Montulli Standards Track [Page 3] + +RFC 2109 HTTP State Management Mechanism February 1997 + + + session. It may send back to the client a Set-Cookie response header + with the same or different information, or it may send no Set-Cookie + header at all. The origin server effectively ends a session by + sending the client a Set-Cookie header with Max-Age=0. + + Servers may return a Set-Cookie response headers with any response. + User agents should send Cookie request headers, subject to other + rules detailed below, with every request. + + An origin server may include multiple Set-Cookie headers in a + response. Note that an intervening gateway could fold multiple such + headers into a single header. + +4.2.2 Set-Cookie Syntax + + The syntax for the Set-Cookie response header is + + set-cookie = "Set-Cookie:" cookies + cookies = 1#cookie + cookie = NAME "=" VALUE *(";" cookie-av) + NAME = attr + VALUE = value + cookie-av = "Comment" "=" value + | "Domain" "=" value + | "Max-Age" "=" value + | "Path" "=" value + | "Secure" + | "Version" "=" 1*DIGIT + + Informally, the Set-Cookie response header comprises the token Set- + Cookie:, followed by a comma-separated list of one or more cookies. + Each cookie begins with a NAME=VALUE pair, followed by zero or more + semi-colon-separated attribute-value pairs. The syntax for + attribute-value pairs was shown earlier. The specific attributes and + the semantics of their values follows. The NAME=VALUE attribute- + value pair must come first in each cookie. The others, if present, + can occur in any order. If an attribute appears more than once in a + cookie, the behavior is undefined. + + NAME=VALUE + Required. The name of the state information ("cookie") is NAME, + and its value is VALUE. NAMEs that begin with $ are reserved for + other uses and must not be used by applications. + + + + + + + + +Kristol & Montulli Standards Track [Page 4] + +RFC 2109 HTTP State Management Mechanism February 1997 + + + The VALUE is opaque to the user agent and may be anything the + origin server chooses to send, possibly in a server-selected + printable ASCII encoding. "Opaque" implies that the content is of + interest and relevance only to the origin server. The content + may, in fact, be readable by anyone that examines the Set-Cookie + header. + + Comment=comment + Optional. Because cookies can contain private information about a + user, the Cookie attribute allows an origin server to document its + intended use of a cookie. The user can inspect the information to + decide whether to initiate or continue a session with this cookie. + + Domain=domain + Optional. The Domain attribute specifies the domain for which the + cookie is valid. An explicitly specified domain must always start + with a dot. + + Max-Age=delta-seconds + Optional. The Max-Age attribute defines the lifetime of the + cookie, in seconds. The delta-seconds value is a decimal non- + negative integer. After delta-seconds seconds elapse, the client + should discard the cookie. A value of zero means the cookie + should be discarded immediately. + + Path=path + Optional. The Path attribute specifies the subset of URLs to + which this cookie applies. + + Secure + Optional. The Secure attribute (with no value) directs the user + agent to use only (unspecified) secure means to contact the origin + server whenever it sends back this cookie. + + The user agent (possibly under the user's control) may determine + what level of security it considers appropriate for "secure" + cookies. The Secure attribute should be considered security + advice from the server to the user agent, indicating that it is in + the session's interest to protect the cookie contents. + + Version=version + Required. The Version attribute, a decimal integer, identifies to + which version of the state management specification the cookie + conforms. For this specification, Version=1 applies. + + + + + + + +Kristol & Montulli Standards Track [Page 5] + +RFC 2109 HTTP State Management Mechanism February 1997 + + +4.2.3 Controlling Caching + + An origin server must be cognizant of the effect of possible caching + of both the returned resource and the Set-Cookie header. Caching + "public" documents is desirable. For example, if the origin server + wants to use a public document such as a "front door" page as a + sentinel to indicate the beginning of a session for which a Set- + Cookie response header must be generated, the page should be stored + in caches "pre-expired" so that the origin server will see further + requests. "Private documents", for example those that contain + information strictly private to a session, should not be cached in + shared caches. + + If the cookie is intended for use by a single user, the Set-cookie + header should not be cached. A Set-cookie header that is intended to + be shared by multiple users may be cached. + + The origin server should send the following additional HTTP/1.1 + response headers, depending on circumstances: + + * To suppress caching of the Set-Cookie header: Cache-control: no- + cache="set-cookie". + + and one of the following: + + * To suppress caching of a private document in shared caches: Cache- + control: private. + + * To allow caching of a document and require that it be validated + before returning it to the client: Cache-control: must-revalidate. + + * To allow caching of a document, but to require that proxy caches + (not user agent caches) validate it before returning it to the + client: Cache-control: proxy-revalidate. + + * To allow caching of a document and request that it be validated + before returning it to the client (by "pre-expiring" it): + Cache-control: max-age=0. Not all caches will revalidate the + document in every case. + + HTTP/1.1 servers must send Expires: old-date (where old-date is a + date long in the past) on responses containing Set-Cookie response + headers unless they know for certain (by out of band means) that + there are no downsteam HTTP/1.0 proxies. HTTP/1.1 servers may send + other Cache-Control directives that permit caching by HTTP/1.1 + proxies in addition to the Expires: old-date directive; the Cache- + Control directive will override the Expires: old-date for HTTP/1.1 + proxies. + + + +Kristol & Montulli Standards Track [Page 6] + +RFC 2109 HTTP State Management Mechanism February 1997 + + +4.3 User Agent Role + +4.3.1 Interpreting Set-Cookie + + The user agent keeps separate track of state information that arrives + via Set-Cookie response headers from each origin server (as + distinguished by name or IP address and port). The user agent + applies these defaults for optional attributes that are missing: + + VersionDefaults to "old cookie" behavior as originally specified by + Netscape. See the HISTORICAL section. + + Domain Defaults to the request-host. (Note that there is no dot at + the beginning of request-host.) + + Max-AgeThe default behavior is to discard the cookie when the user + agent exits. + + Path Defaults to the path of the request URL that generated the + Set-Cookie response, up to, but not including, the + right-most /. + + Secure If absent, the user agent may send the cookie over an + insecure channel. + +4.3.2 Rejecting Cookies + + To prevent possible security or privacy violations, a user agent + rejects a cookie (shall not store its information) if any of the + following is true: + + * The value for the Path attribute is not a prefix of the request- + URI. + + * The value for the Domain attribute contains no embedded dots or + does not start with a dot. + + * The value for the request-host does not domain-match the Domain + attribute. + + * The request-host is a FQDN (not IP address) and has the form HD, + where D is the value of the Domain attribute, and H is a string + that contains one or more dots. + + Examples: + + * A Set-Cookie from request-host y.x.foo.com for Domain=.foo.com + would be rejected, because H is y.x and contains a dot. + + + +Kristol & Montulli Standards Track [Page 7] + +RFC 2109 HTTP State Management Mechanism February 1997 + + + * A Set-Cookie from request-host x.foo.com for Domain=.foo.com would + be accepted. + + * A Set-Cookie with Domain=.com or Domain=.com., will always be + rejected, because there is no embedded dot. + + * A Set-Cookie with Domain=ajax.com will be rejected because the + value for Domain does not begin with a dot. + +4.3.3 Cookie Management + + If a user agent receives a Set-Cookie response header whose NAME is + the same as a pre-existing cookie, and whose Domain and Path + attribute values exactly (string) match those of a pre-existing + cookie, the new cookie supersedes the old. However, if the Set- + Cookie has a value for Max-Age of zero, the (old and new) cookie is + discarded. Otherwise cookies accumulate until they expire (resources + permitting), at which time they are discarded. + + Because user agents have finite space in which to store cookies, they + may also discard older cookies to make space for newer ones, using, + for example, a least-recently-used algorithm, along with constraints + on the maximum number of cookies that each origin server may set. + + If a Set-Cookie response header includes a Comment attribute, the + user agent should store that information in a human-readable form + with the cookie and should display the comment text as part of a + cookie inspection user interface. + + User agents should allow the user to control cookie destruction. An + infrequently-used cookie may function as a "preferences file" for + network applications, and a user may wish to keep it even if it is + the least-recently-used cookie. One possible implementation would be + an interface that allows the permanent storage of a cookie through a + checkbox (or, conversely, its immediate destruction). + + Privacy considerations dictate that the user have considerable + control over cookie management. The PRIVACY section contains more + information. + +4.3.4 Sending Cookies to the Origin Server + + When it sends a request to an origin server, the user agent sends a + Cookie request header to the origin server if it has cookies that are + applicable to the request, based on + + * the request-host; + + + + +Kristol & Montulli Standards Track [Page 8] + +RFC 2109 HTTP State Management Mechanism February 1997 + + + * the request-URI; + + * the cookie's age. + + The syntax for the header is: + + cookie = "Cookie:" cookie-version + 1*((";" | ",") cookie-value) + cookie-value = NAME "=" VALUE [";" path] [";" domain] + cookie-version = "$Version" "=" value + NAME = attr + VALUE = value + path = "$Path" "=" value + domain = "$Domain" "=" value + + The value of the cookie-version attribute must be the value from the + Version attribute, if any, of the corresponding Set-Cookie response + header. Otherwise the value for cookie-version is 0. The value for + the path attribute must be the value from the Path attribute, if any, + of the corresponding Set-Cookie response header. Otherwise the + attribute should be omitted from the Cookie request header. The + value for the domain attribute must be the value from the Domain + attribute, if any, of the corresponding Set-Cookie response header. + Otherwise the attribute should be omitted from the Cookie request + header. + + Note that there is no Comment attribute in the Cookie request header + corresponding to the one in the Set-Cookie response header. The user + agent does not return the comment information to the origin server. + + The following rules apply to choosing applicable cookie-values from + among all the cookies the user agent has. + + Domain Selection + The origin server's fully-qualified host name must domain-match + the Domain attribute of the cookie. + + Path Selection + The Path attribute of the cookie must match a prefix of the + request-URI. + + Max-Age Selection + Cookies that have expired should have been discarded and thus + are not forwarded to an origin server. + + + + + + + +Kristol & Montulli Standards Track [Page 9] + +RFC 2109 HTTP State Management Mechanism February 1997 + + + If multiple cookies satisfy the criteria above, they are ordered in + the Cookie header such that those with more specific Path attributes + precede those with less specific. Ordering with respect to other + attributes (e.g., Domain) is unspecified. + + Note: For backward compatibility, the separator in the Cookie header + is semi-colon (;) everywhere. A server should also accept comma (,) + as the separator between cookie-values for future compatibility. + +4.3.5 Sending Cookies in Unverifiable Transactions + + Users must have control over sessions in order to ensure privacy. + (See PRIVACY section below.) To simplify implementation and to + prevent an additional layer of complexity where adequate safeguards + exist, however, this document distinguishes between transactions that + are verifiable and those that are unverifiable. A transaction is + verifiable if the user has the option to review the request-URI prior + to its use in the transaction. A transaction is unverifiable if the + user does not have that option. Unverifiable transactions typically + arise when a user agent automatically requests inlined or embedded + entities or when it resolves redirection (3xx) responses from an + origin server. Typically the origin transaction, the transaction + that the user initiates, is verifiable, and that transaction may + directly or indirectly induce the user agent to make unverifiable + transactions. + + When it makes an unverifiable transaction, a user agent must enable a + session only if a cookie with a domain attribute D was sent or + received in its origin transaction, such that the host name in the + Request-URI of the unverifiable transaction domain-matches D. + + This restriction prevents a malicious service author from using + unverifiable transactions to induce a user agent to start or continue + a session with a server in a different domain. The starting or + continuation of such sessions could be contrary to the privacy + expectations of the user, and could also be a security problem. + + User agents may offer configurable options that allow the user agent, + or any autonomous programs that the user agent executes, to ignore + the above rule, so long as these override options default to "off". + + Many current user agents already provide a review option that would + render many links verifiable. For instance, some user agents display + the URL that would be referenced for a particular link when the mouse + pointer is placed over that link. The user can therefore determine + whether to visit that site before causing the browser to do so. + (Though not implemented on current user agents, a similar technique + could be used for a button used to submit a form -- the user agent + + + +Kristol & Montulli Standards Track [Page 10] + +RFC 2109 HTTP State Management Mechanism February 1997 + + + could display the action to be taken if the user were to select that + button.) However, even this would not make all links verifiable; for + example, links to automatically loaded images would not normally be + subject to "mouse pointer" verification. + + Many user agents also provide the option for a user to view the HTML + source of a document, or to save the source to an external file where + it can be viewed by another application. While such an option does + provide a crude review mechanism, some users might not consider it + acceptable for this purpose. + +4.4 How an Origin Server Interprets the Cookie Header + + A user agent returns much of the information in the Set-Cookie header + to the origin server when the Path attribute matches that of a new + request. When it receives a Cookie header, the origin server should + treat cookies with NAMEs whose prefix is $ specially, as an attribute + for the adjacent cookie. The value for such a NAME is to be + interpreted as applying to the lexically (left-to-right) most recent + cookie whose name does not have the $ prefix. If there is no + previous cookie, the value applies to the cookie mechanism as a + whole. For example, consider the cookie + + Cookie: $Version="1"; Customer="WILE_E_COYOTE"; + $Path="/acme" + + $Version applies to the cookie mechanism as a whole (and gives the + version number for the cookie mechanism). $Path is an attribute + whose value (/acme) defines the Path attribute that was used when the + Customer cookie was defined in a Set-Cookie response header. + +4.5 Caching Proxy Role + + One reason for separating state information from both a URL and + document content is to facilitate the scaling that caching permits. + To support cookies, a caching proxy must obey these rules already in + the HTTP specification: + + * Honor requests from the cache, if possible, based on cache validity + rules. + + * Pass along a Cookie request header in any request that the proxy + must make of another server. + + * Return the response to the client. Include any Set-Cookie response + header. + + + + + +Kristol & Montulli Standards Track [Page 11] + +RFC 2109 HTTP State Management Mechanism February 1997 + + + * Cache the received response subject to the control of the usual + headers, such as Expires, Cache-control: no-cache, and Cache- + control: private, + + * Cache the Set-Cookie subject to the control of the usual header, + Cache-control: no-cache="set-cookie". (The Set-Cookie header + should usually not be cached.) + + Proxies must not introduce Set-Cookie (Cookie) headers of their own + in proxy responses (requests). + +5. EXAMPLES + +5.1 Example 1 + + Most detail of request and response headers has been omitted. Assume + the user agent has no stored cookies. + + 1. User Agent -> Server + + POST /acme/login HTTP/1.1 + [form data] + + User identifies self via a form. + + 2. Server -> User Agent + + HTTP/1.1 200 OK + Set-Cookie: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme" + + Cookie reflects user's identity. + + 3. User Agent -> Server + + POST /acme/pickitem HTTP/1.1 + Cookie: $Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme" + [form data] + + User selects an item for "shopping basket." + + 4. Server -> User Agent + + HTTP/1.1 200 OK + Set-Cookie: Part_Number="Rocket_Launcher_0001"; Version="1"; + Path="/acme" + + Shopping basket contains an item. + + + + +Kristol & Montulli Standards Track [Page 12] + +RFC 2109 HTTP State Management Mechanism February 1997 + + + 5. User Agent -> Server + + POST /acme/shipping HTTP/1.1 + Cookie: $Version="1"; + Customer="WILE_E_COYOTE"; $Path="/acme"; + Part_Number="Rocket_Launcher_0001"; $Path="/acme" + [form data] + + User selects shipping method from form. + + 6. Server -> User Agent + + HTTP/1.1 200 OK + Set-Cookie: Shipping="FedEx"; Version="1"; Path="/acme" + + New cookie reflects shipping method. + + 7. User Agent -> Server + + POST /acme/process HTTP/1.1 + Cookie: $Version="1"; + Customer="WILE_E_COYOTE"; $Path="/acme"; + Part_Number="Rocket_Launcher_0001"; $Path="/acme"; + Shipping="FedEx"; $Path="/acme" + [form data] + + User chooses to process order. + + 8. Server -> User Agent + + HTTP/1.1 200 OK + + Transaction is complete. + + The user agent makes a series of requests on the origin server, after + each of which it receives a new cookie. All the cookies have the + same Path attribute and (default) domain. Because the request URLs + all have /acme as a prefix, and that matches the Path attribute, each + request contains all the cookies received so far. + +5.2 Example 2 + + This example illustrates the effect of the Path attribute. All + detail of request and response headers has been omitted. Assume the + user agent has no stored cookies. + + Imagine the user agent has received, in response to earlier requests, + the response headers + + + +Kristol & Montulli Standards Track [Page 13] + +RFC 2109 HTTP State Management Mechanism February 1997 + + + Set-Cookie: Part_Number="Rocket_Launcher_0001"; Version="1"; + Path="/acme" + + and + + Set-Cookie: Part_Number="Riding_Rocket_0023"; Version="1"; + Path="/acme/ammo" + + A subsequent request by the user agent to the (same) server for URLs + of the form /acme/ammo/... would include the following request + header: + + Cookie: $Version="1"; + Part_Number="Riding_Rocket_0023"; $Path="/acme/ammo"; + Part_Number="Rocket_Launcher_0001"; $Path="/acme" + + Note that the NAME=VALUE pair for the cookie with the more specific + Path attribute, /acme/ammo, comes before the one with the less + specific Path attribute, /acme. Further note that the same cookie + name appears more than once. + + A subsequent request by the user agent to the (same) server for a URL + of the form /acme/parts/ would include the following request header: + + Cookie: $Version="1"; Part_Number="Rocket_Launcher_0001"; $Path="/acme" + + Here, the second cookie's Path attribute /acme/ammo is not a prefix + of the request URL, /acme/parts/, so the cookie does not get + forwarded to the server. + +6. IMPLEMENTATION CONSIDERATIONS + + Here we speculate on likely or desirable details for an origin server + that implements state management. + +6.1 Set-Cookie Content + + An origin server's content should probably be divided into disjoint + application areas, some of which require the use of state + information. The application areas can be distinguished by their + request URLs. The Set-Cookie header can incorporate information + about the application areas by setting the Path attribute for each + one. + + The session information can obviously be clear or encoded text that + describes state. However, if it grows too large, it can become + unwieldy. Therefore, an implementor might choose for the session + information to be a key to a server-side resource. Of course, using + + + +Kristol & Montulli Standards Track [Page 14] + +RFC 2109 HTTP State Management Mechanism February 1997 + + + a database creates some problems that this state management + specification was meant to avoid, namely: + + 1. keeping real state on the server side; + + 2. how and when to garbage-collect the database entry, in case the + user agent terminates the session by, for example, exiting. + +6.2 Stateless Pages + + Caching benefits the scalability of WWW. Therefore it is important + to reduce the number of documents that have state embedded in them + inherently. For example, if a shopping-basket-style application + always displays a user's current basket contents on each page, those + pages cannot be cached, because each user's basket's contents would + be different. On the other hand, if each page contains just a link + that allows the user to "Look at My Shopping Basket", the page can be + cached. + +6.3 Implementation Limits + + Practical user agent implementations have limits on the number and + size of cookies that they can store. In general, user agents' cookie + support should have no fixed limits. They should strive to store as + many frequently-used cookies as possible. Furthermore, general-use + user agents should provide each of the following minimum capabilities + individually, although not necessarily simultaneously: + + * at least 300 cookies + + * at least 4096 bytes per cookie (as measured by the size of the + characters that comprise the cookie non-terminal in the syntax + description of the Set-Cookie header) + + * at least 20 cookies per unique host or domain name + + User agents created for specific purposes or for limited-capacity + devices should provide at least 20 cookies of 4096 bytes, to ensure + that the user can interact with a session-based origin server. + + The information in a Set-Cookie response header must be retained in + its entirety. If for some reason there is inadequate space to store + the cookie, it must be discarded, not truncated. + + Applications should use as few and as small cookies as possible, and + they should cope gracefully with the loss of a cookie. + + + + + +Kristol & Montulli Standards Track [Page 15] + +RFC 2109 HTTP State Management Mechanism February 1997 + + +6.3.1 Denial of Service Attacks + + User agents may choose to set an upper bound on the number of cookies + to be stored from a given host or domain name or on the size of the + cookie information. Otherwise a malicious server could attempt to + flood a user agent with many cookies, or large cookies, on successive + responses, which would force out cookies the user agent had received + from other servers. However, the minima specified above should still + be supported. + +7. PRIVACY + +7.1 User Agent Control + + An origin server could create a Set-Cookie header to track the path + of a user through the server. Users may object to this behavior as + an intrusive accumulation of information, even if their identity is + not evident. (Identity might become evident if a user subsequently + fills out a form that contains identifying information.) This state + management specification therefore requires that a user agent give + the user control over such a possible intrusion, although the + interface through which the user is given this control is left + unspecified. However, the control mechanisms provided shall at least + allow the user + + * to completely disable the sending and saving of cookies. + + * to determine whether a stateful session is in progress. + + * to control the saving of a cookie on the basis of the cookie's + Domain attribute. + + Such control could be provided by, for example, mechanisms + + * to notify the user when the user agent is about to send a cookie + to the origin server, offering the option not to begin a session. + + * to display a visual indication that a stateful session is in + progress. + + * to let the user decide which cookies, if any, should be saved + when the user concludes a window or user agent session. + + * to let the user examine the contents of a cookie at any time. + + A user agent usually begins execution with no remembered state + information. It should be possible to configure a user agent never + to send Cookie headers, in which case it can never sustain state with + + + +Kristol & Montulli Standards Track [Page 16] + +RFC 2109 HTTP State Management Mechanism February 1997 + + + an origin server. (The user agent would then behave like one that is + unaware of how to handle Set-Cookie response headers.) + + When the user agent terminates execution, it should let the user + discard all state information. Alternatively, the user agent may ask + the user whether state information should be retained; the default + should be "no". If the user chooses to retain state information, it + would be restored the next time the user agent runs. + + NOTE: User agents should probably be cautious about using files to + store cookies long-term. If a user runs more than one instance of + the user agent, the cookies could be commingled or otherwise messed + up. + +7.2 Protocol Design + + The restrictions on the value of the Domain attribute, and the rules + concerning unverifiable transactions, are meant to reduce the ways + that cookies can "leak" to the "wrong" site. The intent is to + restrict cookies to one, or a closely related set of hosts. + Therefore a request-host is limited as to what values it can set for + Domain. We consider it acceptable for hosts host1.foo.com and + host2.foo.com to share cookies, but not a.com and b.com. + + Similarly, a server can only set a Path for cookies that are related + to the request-URI. + +8. SECURITY CONSIDERATIONS + +8.1 Clear Text + + The information in the Set-Cookie and Cookie headers is unprotected. + Two consequences are: + + 1. Any sensitive information that is conveyed in them is exposed + to intruders. + + 2. A malicious intermediary could alter the headers as they travel + in either direction, with unpredictable results. + + These facts imply that information of a personal and/or financial + nature should only be sent over a secure channel. For less sensitive + information, or when the content of the header is a database key, an + origin server should be vigilant to prevent a bad Cookie value from + causing failures. + + + + + + +Kristol & Montulli Standards Track [Page 17] + +RFC 2109 HTTP State Management Mechanism February 1997 + + +8.2 Cookie Spoofing + + Proper application design can avoid spoofing attacks from related + domains. Consider: + + 1. User agent makes request to victim.cracker.edu, gets back + cookie session_id="1234" and sets the default domain + victim.cracker.edu. + + 2. User agent makes request to spoof.cracker.edu, gets back + cookie session-id="1111", with Domain=".cracker.edu". + + 3. User agent makes request to victim.cracker.edu again, and + passes + + Cookie: $Version="1"; + session_id="1234"; + session_id="1111"; $Domain=".cracker.edu" + + The server at victim.cracker.edu should detect that the second + cookie was not one it originated by noticing that the Domain + attribute is not for itself and ignore it. + +8.3 Unexpected Cookie Sharing + + A user agent should make every attempt to prevent the sharing of + session information between hosts that are in different domains. + Embedded or inlined objects may cause particularly severe privacy + problems if they can be used to share cookies between disparate + hosts. For example, a malicious server could embed cookie + information for host a.com in a URI for a CGI on host b.com. User + agent implementors are strongly encouraged to prevent this sort of + exchange whenever possible. + +9. OTHER, SIMILAR, PROPOSALS + + Three other proposals have been made to accomplish similar goals. + This specification is an amalgam of Kristol's State-Info proposal and + Netscape's Cookie proposal. + + Brian Behlendorf proposed a Session-ID header that would be user- + agent-initiated and could be used by an origin server to track + "clicktrails". It would not carry any origin-server-defined state, + however. Phillip Hallam-Baker has proposed another client-defined + session ID mechanism for similar purposes. + + + + + + +Kristol & Montulli Standards Track [Page 18] + +RFC 2109 HTTP State Management Mechanism February 1997 + + + While both session IDs and cookies can provide a way to sustain + stateful sessions, their intended purpose is different, and, + consequently, the privacy requirements for them are different. A + user initiates session IDs to allow servers to track progress through + them, or to distinguish multiple users on a shared machine. Cookies + are server-initiated, so the cookie mechanism described here gives + users control over something that would otherwise take place without + the users' awareness. Furthermore, cookies convey rich, server- + selected information, whereas session IDs comprise user-selected, + simple information. + +10. HISTORICAL + +10.1 Compatibility With Netscape's Implementation + + HTTP/1.0 clients and servers may use Set-Cookie and Cookie headers + that reflect Netscape's original cookie proposal. These notes cover + inter-operation between "old" and "new" cookies. + +10.1.1 Extended Cookie Header + + This proposal adds attribute-value pairs to the Cookie request header + in a compatible way. An "old" client that receives a "new" cookie + will ignore attributes it does not understand; it returns what it + does understand to the origin server. A "new" client always sends + cookies in the new form. + + An "old" server that receives a "new" cookie will see what it thinks + are many cookies with names that begin with a $, and it will ignore + them. (The "old" server expects these cookies to be separated by + semi-colon, not comma.) A "new" server can detect cookies that have + passed through an "old" client, because they lack a $Version + attribute. + +10.1.2 Expires and Max-Age + + Netscape's original proposal defined an Expires header that took a + date value in a fixed-length variant format in place of Max-Age: + + Wdy, DD-Mon-YY HH:MM:SS GMT + + Note that the Expires date format contains embedded spaces, and that + "old" cookies did not have quotes around values. Clients that + implement to this specification should be aware of "old" cookies and + Expires. + + + + + + +Kristol & Montulli Standards Track [Page 19] + +RFC 2109 HTTP State Management Mechanism February 1997 + + +10.1.3 Punctuation + + In Netscape's original proposal, the values in attribute-value pairs + did not accept "-quoted strings. Origin servers should be cautious + about sending values that require quotes unless they know the + receiving user agent understands them (i.e., "new" cookies). A + ("new") user agent should only use quotes around values in Cookie + headers when the cookie's version(s) is (are) all compliant with this + specification or later. + + In Netscape's original proposal, no whitespace was permitted around + the = that separates attribute-value pairs. Therefore such + whitespace should be used with caution in new implementations. + +10.2 Caching and HTTP/1.0 + + Some caches, such as those conforming to HTTP/1.0, will inevitably + cache the Set-Cookie header, because there was no mechanism to + suppress caching of headers prior to HTTP/1.1. This caching can lead + to security problems. Documents transmitted by an origin server + along with Set-Cookie headers will usually either be uncachable, or + will be "pre-expired". As long as caches obey instructions not to + cache documents (following Expires: <a date in the past> or Pragma: + no-cache (HTTP/1.0), or Cache-control: no-cache (HTTP/1.1)) + uncachable documents present no problem. However, pre-expired + documents may be stored in caches. They require validation (a + conditional GET) on each new request, but some cache operators loosen + the rules for their caches, and sometimes serve expired documents + without first validating them. This combination of factors can lead + to cookies meant for one user later being sent to another user. The + Set-Cookie header is stored in the cache, and, although the document + is stale (expired), the cache returns the document in response to + later requests, including cached headers. + +11. ACKNOWLEDGEMENTS + + This document really represents the collective efforts of the + following people, in addition to the authors: Roy Fielding, Marc + Hedlund, Ted Hardie, Koen Holtman, Shel Kaphan, Rohit Khare. + + + + + + + + + + + + +Kristol & Montulli Standards Track [Page 20] + +RFC 2109 HTTP State Management Mechanism February 1997 + + +12. AUTHORS' ADDRESSES + + David M. Kristol + Bell Laboratories, Lucent Technologies + 600 Mountain Ave. Room 2A-227 + Murray Hill, NJ 07974 + + Phone: (908) 582-2250 + Fax: (908) 582-5809 + EMail: dmk@bell-labs.com + + + Lou Montulli + Netscape Communications Corp. + 501 E. Middlefield Rd. + Mountain View, CA 94043 + + Phone: (415) 528-2600 + EMail: montulli@netscape.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Kristol & Montulli Standards Track [Page 21] + diff --git a/kioslave/http/kcookiejar/rfc2965 b/kioslave/http/kcookiejar/rfc2965 new file mode 100644 index 000000000..8a4d02b17 --- /dev/null +++ b/kioslave/http/kcookiejar/rfc2965 @@ -0,0 +1,1459 @@ + + + + + + +Network Working Group D. Kristol +Request for Comments: 2965 Bell Laboratories, Lucent Technologies +Obsoletes: 2109 L. Montulli +Category: Standards Track Epinions.com, Inc. + October 2000 + + + HTTP State Management Mechanism + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2000). All Rights Reserved. + +IESG Note + + The IESG notes that this mechanism makes use of the .local top-level + domain (TLD) internally when handling host names that don't contain + any dots, and that this mechanism might not work in the expected way + should an actual .local TLD ever be registered. + +Abstract + + This document specifies a way to create a stateful session with + Hypertext Transfer Protocol (HTTP) requests and responses. It + describes three new headers, Cookie, Cookie2, and Set-Cookie2, which + carry state information between participating origin servers and user + agents. The method described here differs from Netscape's Cookie + proposal [Netscape], but it can interoperate with HTTP/1.0 user + agents that use Netscape's method. (See the HISTORICAL section.) + + This document reflects implementation experience with RFC 2109 and + obsoletes it. + +1. TERMINOLOGY + + The terms user agent, client, server, proxy, origin server, and + http_URL have the same meaning as in the HTTP/1.1 specification + [RFC2616]. The terms abs_path and absoluteURI have the same meaning + as in the URI Syntax specification [RFC2396]. + + + + +Kristol & Montulli Standards Track [Page 1] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + Host name (HN) means either the host domain name (HDN) or the numeric + Internet Protocol (IP) address of a host. The fully qualified domain + name is preferred; use of numeric IP addresses is strongly + discouraged. + + The terms request-host and request-URI refer to the values the client + would send to the server as, respectively, the host (but not port) + and abs_path portions of the absoluteURI (http_URL) of the HTTP + request line. Note that request-host is a HN. + + The term effective host name is related to host name. If a host name + contains no dots, the effective host name is that name with the + string .local appended to it. Otherwise the effective host name is + the same as the host name. Note that all effective host names + contain at least one dot. + + The term request-port refers to the port portion of the absoluteURI + (http_URL) of the HTTP request line. If the absoluteURI has no + explicit port, the request-port is the HTTP default, 80. The + request-port of a cookie is the request-port of the request in which + a Set-Cookie2 response header was returned to the user agent. + + Host names can be specified either as an IP address or a HDN string. + Sometimes we compare one host name with another. (Such comparisons + SHALL be case-insensitive.) Host A's name domain-matches host B's if + + * their host name strings string-compare equal; or + + * A is a HDN string and has the form NB, where N is a non-empty + name string, B has the form .B', and B' is a HDN string. (So, + x.y.com domain-matches .Y.com but not Y.com.) + + Note that domain-match is not a commutative operation: a.b.c.com + domain-matches .c.com, but not the reverse. + + The reach R of a host name H is defined as follows: + + * If + + - H is the host domain name of a host; and, + + - H has the form A.B; and + + - A has no embedded (that is, interior) dots; and + + - B has at least one embedded dot, or B is the string "local". + then the reach of H is .B. + + + + +Kristol & Montulli Standards Track [Page 2] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + * Otherwise, the reach of H is H. + + For two strings that represent paths, P1 and P2, P1 path-matches P2 + if P2 is a prefix of P1 (including the case where P1 and P2 string- + compare equal). Thus, the string /tec/waldo path-matches /tec. + + Because it was used in Netscape's original implementation of state + management, we will use the term cookie to refer to the state + information that passes between an origin server and user agent, and + that gets stored by the user agent. + +1.1 Requirements + + The key words "MAY", "MUST", "MUST NOT", "OPTIONAL", "RECOMMENDED", + "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT" in this + document are to be interpreted as described in RFC 2119 [RFC2119]. + +2. STATE AND SESSIONS + + This document describes a way to create stateful sessions with HTTP + requests and responses. Currently, HTTP servers respond to each + client request without relating that request to previous or + subsequent requests; the state management mechanism allows clients + and servers that wish to exchange state information to place HTTP + requests and responses within a larger context, which we term a + "session". This context might be used to create, for example, a + "shopping cart", in which user selections can be aggregated before + purchase, or a magazine browsing system, in which a user's previous + reading affects which offerings are presented. + + Neither clients nor servers are required to support cookies. A + server MAY refuse to provide content to a client that does not return + the cookies it sends. + +3. DESCRIPTION + + We describe here a way for an origin server to send state information + to the user agent, and for the user agent to return the state + information to the origin server. The goal is to have a minimal + impact on HTTP and user agents. + +3.1 Syntax: General + + The two state management headers, Set-Cookie2 and Cookie, have common + syntactic properties involving attribute-value pairs. The following + grammar uses the notation, and tokens DIGIT (decimal digits), token + + + + + +Kristol & Montulli Standards Track [Page 3] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + (informally, a sequence of non-special, non-white space characters), + and http_URL from the HTTP/1.1 specification [RFC2616] to describe + their syntax. + + av-pairs = av-pair *(";" av-pair) + av-pair = attr ["=" value] ; optional value + attr = token + value = token | quoted-string + + Attributes (names) (attr) are case-insensitive. White space is + permitted between tokens. Note that while the above syntax + description shows value as optional, most attrs require them. + + NOTE: The syntax above allows whitespace between the attribute and + the = sign. + +3.2 Origin Server Role + + 3.2.1 General The origin server initiates a session, if it so + desires. To do so, it returns an extra response header to the + client, Set-Cookie2. (The details follow later.) + + A user agent returns a Cookie request header (see below) to the + origin server if it chooses to continue a session. The origin server + MAY ignore it or use it to determine the current state of the + session. It MAY send back to the client a Set-Cookie2 response + header with the same or different information, or it MAY send no + Set-Cookie2 header at all. The origin server effectively ends a + session by sending the client a Set-Cookie2 header with Max-Age=0. + + Servers MAY return Set-Cookie2 response headers with any response. + User agents SHOULD send Cookie request headers, subject to other + rules detailed below, with every request. + + An origin server MAY include multiple Set-Cookie2 headers in a + response. Note that an intervening gateway could fold multiple such + headers into a single header. + + + + + + + + + + + + + + +Kristol & Montulli Standards Track [Page 4] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + 3.2.2 Set-Cookie2 Syntax The syntax for the Set-Cookie2 response + header is + + set-cookie = "Set-Cookie2:" cookies + cookies = 1#cookie + cookie = NAME "=" VALUE *(";" set-cookie-av) + NAME = attr + VALUE = value + set-cookie-av = "Comment" "=" value + | "CommentURL" "=" <"> http_URL <"> + | "Discard" + | "Domain" "=" value + | "Max-Age" "=" value + | "Path" "=" value + | "Port" [ "=" <"> portlist <"> ] + | "Secure" + | "Version" "=" 1*DIGIT + portlist = 1#portnum + portnum = 1*DIGIT + + Informally, the Set-Cookie2 response header comprises the token Set- + Cookie2:, followed by a comma-separated list of one or more cookies. + Each cookie begins with a NAME=VALUE pair, followed by zero or more + semi-colon-separated attribute-value pairs. The syntax for + attribute-value pairs was shown earlier. The specific attributes and + the semantics of their values follows. The NAME=VALUE attribute- + value pair MUST come first in each cookie. The others, if present, + can occur in any order. If an attribute appears more than once in a + cookie, the client SHALL use only the value associated with the first + appearance of the attribute; a client MUST ignore values after the + first. + + The NAME of a cookie MAY be the same as one of the attributes in this + specification. However, because the cookie's NAME must come first in + a Set-Cookie2 response header, the NAME and its VALUE cannot be + confused with an attribute-value pair. + + NAME=VALUE + REQUIRED. The name of the state information ("cookie") is NAME, + and its value is VALUE. NAMEs that begin with $ are reserved and + MUST NOT be used by applications. + + The VALUE is opaque to the user agent and may be anything the + origin server chooses to send, possibly in a server-selected + printable ASCII encoding. "Opaque" implies that the content is of + interest and relevance only to the origin server. The content + may, in fact, be readable by anyone that examines the Set-Cookie2 + header. + + + +Kristol & Montulli Standards Track [Page 5] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + Comment=value + OPTIONAL. Because cookies can be used to derive or store private + information about a user, the value of the Comment attribute + allows an origin server to document how it intends to use the + cookie. The user can inspect the information to decide whether to + initiate or continue a session with this cookie. Characters in + value MUST be in UTF-8 encoding. [RFC2279] + + CommentURL="http_URL" + OPTIONAL. Because cookies can be used to derive or store private + information about a user, the CommentURL attribute allows an + origin server to document how it intends to use the cookie. The + user can inspect the information identified by the URL to decide + whether to initiate or continue a session with this cookie. + + Discard + OPTIONAL. The Discard attribute instructs the user agent to + discard the cookie unconditionally when the user agent terminates. + + Domain=value + OPTIONAL. The value of the Domain attribute specifies the domain + for which the cookie is valid. If an explicitly specified value + does not start with a dot, the user agent supplies a leading dot. + + Max-Age=value + OPTIONAL. The value of the Max-Age attribute is delta-seconds, + the lifetime of the cookie in seconds, a decimal non-negative + integer. To handle cached cookies correctly, a client SHOULD + calculate the age of the cookie according to the age calculation + rules in the HTTP/1.1 specification [RFC2616]. When the age is + greater than delta-seconds seconds, the client SHOULD discard the + cookie. A value of zero means the cookie SHOULD be discarded + immediately. + + Path=value + OPTIONAL. The value of the Path attribute specifies the subset of + URLs on the origin server to which this cookie applies. + + Port[="portlist"] + OPTIONAL. The Port attribute restricts the port to which a cookie + may be returned in a Cookie request header. Note that the syntax + REQUIREs quotes around the OPTIONAL portlist even if there is only + one portnum in portlist. + + + + + + + + +Kristol & Montulli Standards Track [Page 6] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + Secure + OPTIONAL. The Secure attribute (with no value) directs the user + agent to use only (unspecified) secure means to contact the origin + server whenever it sends back this cookie, to protect the + confidentially and authenticity of the information in the cookie. + + The user agent (possibly with user interaction) MAY determine what + level of security it considers appropriate for "secure" cookies. + The Secure attribute should be considered security advice from the + server to the user agent, indicating that it is in the session's + interest to protect the cookie contents. When it sends a "secure" + cookie back to a server, the user agent SHOULD use no less than + the same level of security as was used when it received the cookie + from the server. + + Version=value + REQUIRED. The value of the Version attribute, a decimal integer, + identifies the version of the state management specification to + which the cookie conforms. For this specification, Version=1 + applies. + + 3.2.3 Controlling Caching An origin server must be cognizant of the + effect of possible caching of both the returned resource and the + Set-Cookie2 header. Caching "public" documents is desirable. For + example, if the origin server wants to use a public document such as + a "front door" page as a sentinel to indicate the beginning of a + session for which a Set-Cookie2 response header must be generated, + the page SHOULD be stored in caches "pre-expired" so that the origin + server will see further requests. "Private documents", for example + those that contain information strictly private to a session, SHOULD + NOT be cached in shared caches. + + If the cookie is intended for use by a single user, the Set-Cookie2 + header SHOULD NOT be cached. A Set-Cookie2 header that is intended + to be shared by multiple users MAY be cached. + + The origin server SHOULD send the following additional HTTP/1.1 + response headers, depending on circumstances: + + * To suppress caching of the Set-Cookie2 header: + + Cache-control: no-cache="set-cookie2" + + and one of the following: + + * To suppress caching of a private document in shared caches: + + Cache-control: private + + + +Kristol & Montulli Standards Track [Page 7] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + * To allow caching of a document and require that it be validated + before returning it to the client: + + Cache-Control: must-revalidate, max-age=0 + + * To allow caching of a document, but to require that proxy + caches (not user agent caches) validate it before returning it + to the client: + + Cache-Control: proxy-revalidate, max-age=0 + + * To allow caching of a document and request that it be validated + before returning it to the client (by "pre-expiring" it): + + Cache-control: max-age=0 + + Not all caches will revalidate the document in every case. + + HTTP/1.1 servers MUST send Expires: old-date (where old-date is a + date long in the past) on responses containing Set-Cookie2 response + headers unless they know for certain (by out of band means) that + there are no HTTP/1.0 proxies in the response chain. HTTP/1.1 + servers MAY send other Cache-Control directives that permit caching + by HTTP/1.1 proxies in addition to the Expires: old-date directive; + the Cache-Control directive will override the Expires: old-date for + HTTP/1.1 proxies. + +3.3 User Agent Role + + 3.3.1 Interpreting Set-Cookie2 The user agent keeps separate track + of state information that arrives via Set-Cookie2 response headers + from each origin server (as distinguished by name or IP address and + port). The user agent MUST ignore attribute-value pairs whose + attribute it does not recognize. The user agent applies these + defaults for optional attributes that are missing: + + Discard The default behavior is dictated by the presence or absence + of a Max-Age attribute. + + Domain Defaults to the effective request-host. (Note that because + there is no dot at the beginning of effective request-host, + the default Domain can only domain-match itself.) + + Max-Age The default behavior is to discard the cookie when the user + agent exits. + + Path Defaults to the path of the request URL that generated the + Set-Cookie2 response, up to and including the right-most /. + + + +Kristol & Montulli Standards Track [Page 8] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + Port The default behavior is that a cookie MAY be returned to any + request-port. + + Secure If absent, the user agent MAY send the cookie over an + insecure channel. + + 3.3.2 Rejecting Cookies To prevent possible security or privacy + violations, a user agent rejects a cookie according to rules below. + The goal of the rules is to try to limit the set of servers for which + a cookie is valid, based on the values of the Path, Domain, and Port + attributes and the request-URI, request-host and request-port. + + A user agent rejects (SHALL NOT store its information) if the Version + attribute is missing. Moreover, a user agent rejects (SHALL NOT + store its information) if any of the following is true of the + attributes explicitly present in the Set-Cookie2 response header: + + * The value for the Path attribute is not a prefix of the + request-URI. + + * The value for the Domain attribute contains no embedded dots, + and the value is not .local. + + * The effective host name that derives from the request-host does + not domain-match the Domain attribute. + + * The request-host is a HDN (not IP address) and has the form HD, + where D is the value of the Domain attribute, and H is a string + that contains one or more dots. + + * The Port attribute has a "port-list", and the request-port was + not in the list. + + Examples: + + * A Set-Cookie2 from request-host y.x.foo.com for Domain=.foo.com + would be rejected, because H is y.x and contains a dot. + + * A Set-Cookie2 from request-host x.foo.com for Domain=.foo.com + would be accepted. + + * A Set-Cookie2 with Domain=.com or Domain=.com., will always be + rejected, because there is no embedded dot. + + * A Set-Cookie2 with Domain=ajax.com will be accepted, and the + value for Domain will be taken to be .ajax.com, because a dot + gets prepended to the value. + + + + +Kristol & Montulli Standards Track [Page 9] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + * A Set-Cookie2 with Port="80,8000" will be accepted if the + request was made to port 80 or 8000 and will be rejected + otherwise. + + * A Set-Cookie2 from request-host example for Domain=.local will + be accepted, because the effective host name for the request- + host is example.local, and example.local domain-matches .local. + + 3.3.3 Cookie Management If a user agent receives a Set-Cookie2 + response header whose NAME is the same as that of a cookie it has + previously stored, the new cookie supersedes the old when: the old + and new Domain attribute values compare equal, using a case- + insensitive string-compare; and, the old and new Path attribute + values string-compare equal (case-sensitive). However, if the Set- + Cookie2 has a value for Max-Age of zero, the (old and new) cookie is + discarded. Otherwise a cookie persists (resources permitting) until + whichever happens first, then gets discarded: its Max-Age lifetime is + exceeded; or, if the Discard attribute is set, the user agent + terminates the session. + + Because user agents have finite space in which to store cookies, they + MAY also discard older cookies to make space for newer ones, using, + for example, a least-recently-used algorithm, along with constraints + on the maximum number of cookies that each origin server may set. + + If a Set-Cookie2 response header includes a Comment attribute, the + user agent SHOULD store that information in a human-readable form + with the cookie and SHOULD display the comment text as part of a + cookie inspection user interface. + + If a Set-Cookie2 response header includes a CommentURL attribute, the + user agent SHOULD store that information in a human-readable form + with the cookie, or, preferably, SHOULD allow the user to follow the + http_URL link as part of a cookie inspection user interface. + + The cookie inspection user interface may include a facility whereby a + user can decide, at the time the user agent receives the Set-Cookie2 + response header, whether or not to accept the cookie. A potentially + confusing situation could arise if the following sequence occurs: + + * the user agent receives a cookie that contains a CommentURL + attribute; + + * the user agent's cookie inspection interface is configured so + that it presents a dialog to the user before the user agent + accepts the cookie; + + + + + +Kristol & Montulli Standards Track [Page 10] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + * the dialog allows the user to follow the CommentURL link when + the user agent receives the cookie; and, + + * when the user follows the CommentURL link, the origin server + (or another server, via other links in the returned content) + returns another cookie. + + The user agent SHOULD NOT send any cookies in this context. The user + agent MAY discard any cookie it receives in this context that the + user has not, through some user agent mechanism, deemed acceptable. + + User agents SHOULD allow the user to control cookie destruction, but + they MUST NOT extend the cookie's lifetime beyond that controlled by + the Discard and Max-Age attributes. An infrequently-used cookie may + function as a "preferences file" for network applications, and a user + may wish to keep it even if it is the least-recently-used cookie. One + possible implementation would be an interface that allows the + permanent storage of a cookie through a checkbox (or, conversely, its + immediate destruction). + + Privacy considerations dictate that the user have considerable + control over cookie management. The PRIVACY section contains more + information. + + 3.3.4 Sending Cookies to the Origin Server When it sends a request + to an origin server, the user agent includes a Cookie request header + if it has stored cookies that are applicable to the request, based on + + * the request-host and request-port; + + * the request-URI; + + * the cookie's age. + + The syntax for the header is: + +cookie = "Cookie:" cookie-version 1*((";" | ",") cookie-value) +cookie-value = NAME "=" VALUE [";" path] [";" domain] [";" port] +cookie-version = "$Version" "=" value +NAME = attr +VALUE = value +path = "$Path" "=" value +domain = "$Domain" "=" value +port = "$Port" [ "=" <"> value <"> ] + + The value of the cookie-version attribute MUST be the value from the + Version attribute of the corresponding Set-Cookie2 response header. + Otherwise the value for cookie-version is 0. The value for the path + + + +Kristol & Montulli Standards Track [Page 11] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + attribute MUST be the value from the Path attribute, if one was + present, of the corresponding Set-Cookie2 response header. Otherwise + the attribute SHOULD be omitted from the Cookie request header. The + value for the domain attribute MUST be the value from the Domain + attribute, if one was present, of the corresponding Set-Cookie2 + response header. Otherwise the attribute SHOULD be omitted from the + Cookie request header. + + The port attribute of the Cookie request header MUST mirror the Port + attribute, if one was present, in the corresponding Set-Cookie2 + response header. That is, the port attribute MUST be present if the + Port attribute was present in the Set-Cookie2 header, and it MUST + have the same value, if any. Otherwise, if the Port attribute was + absent from the Set-Cookie2 header, the attribute likewise MUST be + omitted from the Cookie request header. + + Note that there is neither a Comment nor a CommentURL attribute in + the Cookie request header corresponding to the ones in the Set- + Cookie2 response header. The user agent does not return the comment + information to the origin server. + + The user agent applies the following rules to choose applicable + cookie-values to send in Cookie request headers from among all the + cookies it has received. + + Domain Selection + The origin server's effective host name MUST domain-match the + Domain attribute of the cookie. + + Port Selection + There are three possible behaviors, depending on the Port + attribute in the Set-Cookie2 response header: + + 1. By default (no Port attribute), the cookie MAY be sent to any + port. + + 2. If the attribute is present but has no value (e.g., Port), the + cookie MUST only be sent to the request-port it was received + from. + + 3. If the attribute has a port-list, the cookie MUST only be + returned if the new request-port is one of those listed in + port-list. + + Path Selection + The request-URI MUST path-match the Path attribute of the cookie. + + + + + +Kristol & Montulli Standards Track [Page 12] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + Max-Age Selection + Cookies that have expired should have been discarded and thus are + not forwarded to an origin server. + + If multiple cookies satisfy the criteria above, they are ordered in + the Cookie header such that those with more specific Path attributes + precede those with less specific. Ordering with respect to other + attributes (e.g., Domain) is unspecified. + + Note: For backward compatibility, the separator in the Cookie header + is semi-colon (;) everywhere. A server SHOULD also accept comma (,) + as the separator between cookie-values for future compatibility. + + 3.3.5 Identifying What Version is Understood: Cookie2 The Cookie2 + request header facilitates interoperation between clients and servers + that understand different versions of the cookie specification. When + the client sends one or more cookies to an origin server, if at least + one of those cookies contains a $Version attribute whose value is + different from the version that the client understands, then the + client MUST also send a Cookie2 request header, the syntax for which + is + + cookie2 = "Cookie2:" cookie-version + + Here the value for cookie-version is the highest version of cookie + specification (currently 1) that the client understands. The client + needs to send at most one such request header per request. + + 3.3.6 Sending Cookies in Unverifiable Transactions Users MUST have + control over sessions in order to ensure privacy. (See PRIVACY + section below.) To simplify implementation and to prevent an + additional layer of complexity where adequate safeguards exist, + however, this document distinguishes between transactions that are + verifiable and those that are unverifiable. A transaction is + verifiable if the user, or a user-designated agent, has the option to + review the request-URI prior to its use in the transaction. A + transaction is unverifiable if the user does not have that option. + Unverifiable transactions typically arise when a user agent + automatically requests inlined or embedded entities or when it + resolves redirection (3xx) responses from an origin server. + Typically the origin transaction, the transaction that the user + initiates, is verifiable, and that transaction may directly or + indirectly induce the user agent to make unverifiable transactions. + + An unverifiable transaction is to a third-party host if its request- + host U does not domain-match the reach R of the request-host O in the + origin transaction. + + + + +Kristol & Montulli Standards Track [Page 13] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + When it makes an unverifiable transaction, a user agent MUST disable + all cookie processing (i.e., MUST NOT send cookies, and MUST NOT + accept any received cookies) if the transaction is to a third-party + host. + + This restriction prevents a malicious service author from using + unverifiable transactions to induce a user agent to start or continue + a session with a server in a different domain. The starting or + continuation of such sessions could be contrary to the privacy + expectations of the user, and could also be a security problem. + + User agents MAY offer configurable options that allow the user agent, + or any autonomous programs that the user agent executes, to ignore + the above rule, so long as these override options default to "off". + + (N.B. Mechanisms may be proposed that will automate overriding the + third-party restrictions under controlled conditions.) + + Many current user agents already provide a review option that would + render many links verifiable. For instance, some user agents display + the URL that would be referenced for a particular link when the mouse + pointer is placed over that link. The user can therefore determine + whether to visit that site before causing the browser to do so. + (Though not implemented on current user agents, a similar technique + could be used for a button used to submit a form -- the user agent + could display the action to be taken if the user were to select that + button.) However, even this would not make all links verifiable; for + example, links to automatically loaded images would not normally be + subject to "mouse pointer" verification. + + Many user agents also provide the option for a user to view the HTML + source of a document, or to save the source to an external file where + it can be viewed by another application. While such an option does + provide a crude review mechanism, some users might not consider it + acceptable for this purpose. + +3.4 How an Origin Server Interprets the Cookie Header + + A user agent returns much of the information in the Set-Cookie2 + header to the origin server when the request-URI path-matches the + Path attribute of the cookie. When it receives a Cookie header, the + origin server SHOULD treat cookies with NAMEs whose prefix is $ + specially, as an attribute for the cookie. + + + + + + + + +Kristol & Montulli Standards Track [Page 14] + +RFC 2965 HTTP State Management Mechanism October 2000 + + +3.5 Caching Proxy Role + + One reason for separating state information from both a URL and + document content is to facilitate the scaling that caching permits. + To support cookies, a caching proxy MUST obey these rules already in + the HTTP specification: + + * Honor requests from the cache, if possible, based on cache + validity rules. + + * Pass along a Cookie request header in any request that the + proxy must make of another server. + + * Return the response to the client. Include any Set-Cookie2 + response header. + + * Cache the received response subject to the control of the usual + headers, such as Expires, + + Cache-control: no-cache + + and + + Cache-control: private + + * Cache the Set-Cookie2 subject to the control of the usual + header, + + Cache-control: no-cache="set-cookie2" + + (The Set-Cookie2 header should usually not be cached.) + + Proxies MUST NOT introduce Set-Cookie2 (Cookie) headers of their own + in proxy responses (requests). + +4. EXAMPLES + +4.1 Example 1 + + Most detail of request and response headers has been omitted. Assume + the user agent has no stored cookies. + + 1. User Agent -> Server + + POST /acme/login HTTP/1.1 + [form data] + + User identifies self via a form. + + + +Kristol & Montulli Standards Track [Page 15] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + 2. Server -> User Agent + + HTTP/1.1 200 OK + Set-Cookie2: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme" + + Cookie reflects user's identity. + + 3. User Agent -> Server + + POST /acme/pickitem HTTP/1.1 + Cookie: $Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme" + [form data] + + User selects an item for "shopping basket". + + 4. Server -> User Agent + + HTTP/1.1 200 OK + Set-Cookie2: Part_Number="Rocket_Launcher_0001"; Version="1"; + Path="/acme" + + Shopping basket contains an item. + + 5. User Agent -> Server + + POST /acme/shipping HTTP/1.1 + Cookie: $Version="1"; + Customer="WILE_E_COYOTE"; $Path="/acme"; + Part_Number="Rocket_Launcher_0001"; $Path="/acme" + [form data] + + User selects shipping method from form. + + 6. Server -> User Agent + + HTTP/1.1 200 OK + Set-Cookie2: Shipping="FedEx"; Version="1"; Path="/acme" + + New cookie reflects shipping method. + + 7. User Agent -> Server + + POST /acme/process HTTP/1.1 + Cookie: $Version="1"; + Customer="WILE_E_COYOTE"; $Path="/acme"; + Part_Number="Rocket_Launcher_0001"; $Path="/acme"; + Shipping="FedEx"; $Path="/acme" + [form data] + + + +Kristol & Montulli Standards Track [Page 16] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + User chooses to process order. + + 8. Server -> User Agent + + HTTP/1.1 200 OK + + Transaction is complete. + + The user agent makes a series of requests on the origin server, after + each of which it receives a new cookie. All the cookies have the + same Path attribute and (default) domain. Because the request-URIs + all path-match /acme, the Path attribute of each cookie, each request + contains all the cookies received so far. + +4.2 Example 2 + + This example illustrates the effect of the Path attribute. All + detail of request and response headers has been omitted. Assume the + user agent has no stored cookies. + + Imagine the user agent has received, in response to earlier requests, + the response headers + + Set-Cookie2: Part_Number="Rocket_Launcher_0001"; Version="1"; + Path="/acme" + + and + + Set-Cookie2: Part_Number="Riding_Rocket_0023"; Version="1"; + Path="/acme/ammo" + + A subsequent request by the user agent to the (same) server for URLs + of the form /acme/ammo/... would include the following request + header: + + Cookie: $Version="1"; + Part_Number="Riding_Rocket_0023"; $Path="/acme/ammo"; + Part_Number="Rocket_Launcher_0001"; $Path="/acme" + + Note that the NAME=VALUE pair for the cookie with the more specific + Path attribute, /acme/ammo, comes before the one with the less + specific Path attribute, /acme. Further note that the same cookie + name appears more than once. + + A subsequent request by the user agent to the (same) server for a URL + of the form /acme/parts/ would include the following request header: + + + + + +Kristol & Montulli Standards Track [Page 17] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + Cookie: $Version="1"; Part_Number="Rocket_Launcher_0001"; + $Path="/acme" + + Here, the second cookie's Path attribute /acme/ammo is not a prefix + of the request URL, /acme/parts/, so the cookie does not get + forwarded to the server. + +5. IMPLEMENTATION CONSIDERATIONS + + Here we provide guidance on likely or desirable details for an origin + server that implements state management. + +5.1 Set-Cookie2 Content + + An origin server's content should probably be divided into disjoint + application areas, some of which require the use of state + information. The application areas can be distinguished by their + request URLs. The Set-Cookie2 header can incorporate information + about the application areas by setting the Path attribute for each + one. + + The session information can obviously be clear or encoded text that + describes state. However, if it grows too large, it can become + unwieldy. Therefore, an implementor might choose for the session + information to be a key to a server-side resource. Of course, using + a database creates some problems that this state management + specification was meant to avoid, namely: + + 1. keeping real state on the server side; + + 2. how and when to garbage-collect the database entry, in case the + user agent terminates the session by, for example, exiting. + +5.2 Stateless Pages + + Caching benefits the scalability of WWW. Therefore it is important + to reduce the number of documents that have state embedded in them + inherently. For example, if a shopping-basket-style application + always displays a user's current basket contents on each page, those + pages cannot be cached, because each user's basket's contents would + be different. On the other hand, if each page contains just a link + that allows the user to "Look at My Shopping Basket", the page can be + cached. + + + + + + + + +Kristol & Montulli Standards Track [Page 18] + +RFC 2965 HTTP State Management Mechanism October 2000 + + +5.3 Implementation Limits + + Practical user agent implementations have limits on the number and + size of cookies that they can store. In general, user agents' cookie + support should have no fixed limits. They should strive to store as + many frequently-used cookies as possible. Furthermore, general-use + user agents SHOULD provide each of the following minimum capabilities + individually, although not necessarily simultaneously: + + * at least 300 cookies + + * at least 4096 bytes per cookie (as measured by the characters + that comprise the cookie non-terminal in the syntax description + of the Set-Cookie2 header, and as received in the Set-Cookie2 + header) + + * at least 20 cookies per unique host or domain name + + User agents created for specific purposes or for limited-capacity + devices SHOULD provide at least 20 cookies of 4096 bytes, to ensure + that the user can interact with a session-based origin server. + + The information in a Set-Cookie2 response header MUST be retained in + its entirety. If for some reason there is inadequate space to store + the cookie, it MUST be discarded, not truncated. + + Applications should use as few and as small cookies as possible, and + they should cope gracefully with the loss of a cookie. + + 5.3.1 Denial of Service Attacks User agents MAY choose to set an + upper bound on the number of cookies to be stored from a given host + or domain name or on the size of the cookie information. Otherwise a + malicious server could attempt to flood a user agent with many + cookies, or large cookies, on successive responses, which would force + out cookies the user agent had received from other servers. However, + the minima specified above SHOULD still be supported. + +6. PRIVACY + + Informed consent should guide the design of systems that use cookies. + A user should be able to find out how a web site plans to use + information in a cookie and should be able to choose whether or not + those policies are acceptable. Both the user agent and the origin + server must assist informed consent. + + + + + + + +Kristol & Montulli Standards Track [Page 19] + +RFC 2965 HTTP State Management Mechanism October 2000 + + +6.1 User Agent Control + + An origin server could create a Set-Cookie2 header to track the path + of a user through the server. Users may object to this behavior as + an intrusive accumulation of information, even if their identity is + not evident. (Identity might become evident, for example, if a user + subsequently fills out a form that contains identifying information.) + This state management specification therefore requires that a user + agent give the user control over such a possible intrusion, although + the interface through which the user is given this control is left + unspecified. However, the control mechanisms provided SHALL at least + allow the user + + * to completely disable the sending and saving of cookies. + + * to determine whether a stateful session is in progress. + + * to control the saving of a cookie on the basis of the cookie's + Domain attribute. + + Such control could be provided, for example, by mechanisms + + * to notify the user when the user agent is about to send a + cookie to the origin server, to offer the option not to begin a + session. + + * to display a visual indication that a stateful session is in + progress. + + * to let the user decide which cookies, if any, should be saved + when the user concludes a window or user agent session. + + * to let the user examine and delete the contents of a cookie at + any time. + + A user agent usually begins execution with no remembered state + information. It SHOULD be possible to configure a user agent never + to send Cookie headers, in which case it can never sustain state with + an origin server. (The user agent would then behave like one that is + unaware of how to handle Set-Cookie2 response headers.) + + When the user agent terminates execution, it SHOULD let the user + discard all state information. Alternatively, the user agent MAY ask + the user whether state information should be retained; the default + should be "no". If the user chooses to retain state information, it + would be restored the next time the user agent runs. + + + + + +Kristol & Montulli Standards Track [Page 20] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + NOTE: User agents should probably be cautious about using files to + store cookies long-term. If a user runs more than one instance of + the user agent, the cookies could be commingled or otherwise + corrupted. + +6.2 Origin Server Role + + An origin server SHOULD promote informed consent by adding CommentURL + or Comment information to the cookies it sends. CommentURL is + preferred because of the opportunity to provide richer information in + a multiplicity of languages. + +6.3 Clear Text + + The information in the Set-Cookie2 and Cookie headers is unprotected. + As a consequence: + + 1. Any sensitive information that is conveyed in them is exposed + to intruders. + + 2. A malicious intermediary could alter the headers as they travel + in either direction, with unpredictable results. + + These facts imply that information of a personal and/or financial + nature should only be sent over a secure channel. For less sensitive + information, or when the content of the header is a database key, an + origin server should be vigilant to prevent a bad Cookie value from + causing failures. + + A user agent in a shared user environment poses a further risk. + Using a cookie inspection interface, User B could examine the + contents of cookies that were saved when User A used the machine. + +7. SECURITY CONSIDERATIONS + +7.1 Protocol Design + + The restrictions on the value of the Domain attribute, and the rules + concerning unverifiable transactions, are meant to reduce the ways + that cookies can "leak" to the "wrong" site. The intent is to + restrict cookies to one host, or a closely related set of hosts. + Therefore a request-host is limited as to what values it can set for + Domain. We consider it acceptable for hosts host1.foo.com and + host2.foo.com to share cookies, but not a.com and b.com. + + Similarly, a server can set a Path only for cookies that are related + to the request-URI. + + + + +Kristol & Montulli Standards Track [Page 21] + +RFC 2965 HTTP State Management Mechanism October 2000 + + +7.2 Cookie Spoofing + + Proper application design can avoid spoofing attacks from related + domains. Consider: + + 1. User agent makes request to victim.cracker.edu, gets back + cookie session_id="1234" and sets the default domain + victim.cracker.edu. + + 2. User agent makes request to spoof.cracker.edu, gets back cookie + session-id="1111", with Domain=".cracker.edu". + + 3. User agent makes request to victim.cracker.edu again, and + passes + + Cookie: $Version="1"; session_id="1234", + $Version="1"; session_id="1111"; $Domain=".cracker.edu" + + The server at victim.cracker.edu should detect that the second + cookie was not one it originated by noticing that the Domain + attribute is not for itself and ignore it. + +7.3 Unexpected Cookie Sharing + + A user agent SHOULD make every attempt to prevent the sharing of + session information between hosts that are in different domains. + Embedded or inlined objects may cause particularly severe privacy + problems if they can be used to share cookies between disparate + hosts. For example, a malicious server could embed cookie + information for host a.com in a URI for a CGI on host b.com. User + agent implementors are strongly encouraged to prevent this sort of + exchange whenever possible. + +7.4 Cookies For Account Information + + While it is common practice to use them this way, cookies are not + designed or intended to be used to hold authentication information, + such as account names and passwords. Unless such cookies are + exchanged over an encrypted path, the account information they + contain is highly vulnerable to perusal and theft. + +8. OTHER, SIMILAR, PROPOSALS + + Apart from RFC 2109, three other proposals have been made to + accomplish similar goals. This specification began as an amalgam of + Kristol's State-Info proposal [DMK95] and Netscape's Cookie proposal + [Netscape]. + + + + +Kristol & Montulli Standards Track [Page 22] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + Brian Behlendorf proposed a Session-ID header that would be user- + agent-initiated and could be used by an origin server to track + "clicktrails". It would not carry any origin-server-defined state, + however. Phillip Hallam-Baker has proposed another client-defined + session ID mechanism for similar purposes. + + While both session IDs and cookies can provide a way to sustain + stateful sessions, their intended purpose is different, and, + consequently, the privacy requirements for them are different. A + user initiates session IDs to allow servers to track progress through + them, or to distinguish multiple users on a shared machine. Cookies + are server-initiated, so the cookie mechanism described here gives + users control over something that would otherwise take place without + the users' awareness. Furthermore, cookies convey rich, server- + selected information, whereas session IDs comprise user-selected, + simple information. + +9. HISTORICAL + +9.1 Compatibility with Existing Implementations + + Existing cookie implementations, based on the Netscape specification, + use the Set-Cookie (not Set-Cookie2) header. User agents that + receive in the same response both a Set-Cookie and Set-Cookie2 + response header for the same cookie MUST discard the Set-Cookie + information and use only the Set-Cookie2 information. Furthermore, a + user agent MUST assume, if it received a Set-Cookie2 response header, + that the sending server complies with this document and will + understand Cookie request headers that also follow this + specification. + + New cookies MUST replace both equivalent old- and new-style cookies. + That is, if a user agent that follows both this specification and + Netscape's original specification receives a Set-Cookie2 response + header, and the NAME and the Domain and Path attributes match (per + the Cookie Management section) a Netscape-style cookie, the + Netscape-style cookie MUST be discarded, and the user agent MUST + retain only the cookie adhering to this specification. + + Older user agents that do not understand this specification, but that + do understand Netscape's original specification, will not recognize + the Set-Cookie2 response header and will receive and send cookies + according to the older specification. + + + + + + + + +Kristol & Montulli Standards Track [Page 23] + +RFC 2965 HTTP State Management Mechanism October 2000 + + + A user agent that supports both this specification and Netscape-style + cookies SHOULD send a Cookie request header that follows the older + Netscape specification if it received the cookie in a Set-Cookie + response header and not in a Set-Cookie2 response header. However, + it SHOULD send the following request header as well: + + Cookie2: $Version="1" + + The Cookie2 header advises the server that the user agent understands + new-style cookies. If the server understands new-style cookies, as + well, it SHOULD continue the stateful session by sending a Set- + Cookie2 response header, rather than Set-Cookie. A server that does + not understand new-style cookies will simply ignore the Cookie2 + request header. + +9.2 Caching and HTTP/1.0 + + Some caches, such as those conforming to HTTP/1.0, will inevitably + cache the Set-Cookie2 and Set-Cookie headers, because there was no + mechanism to suppress caching of headers prior to HTTP/1.1. This + caching can lead to security problems. Documents transmitted by an + origin server along with Set-Cookie2 and Set-Cookie headers usually + either will be uncachable, or will be "pre-expired". As long as + caches obey instructions not to cache documents (following Expires: + <a date in the past> or Pragma: no-cache (HTTP/1.0), or Cache- + control: no-cache (HTTP/1.1)) uncachable documents present no + problem. However, pre-expired documents may be stored in caches. + They require validation (a conditional GET) on each new request, but + some cache operators loosen the rules for their caches, and sometimes + serve expired documents without first validating them. This + combination of factors can lead to cookies meant for one user later + being sent to another user. The Set-Cookie2 and Set-Cookie headers + are stored in the cache, and, although the document is stale + (expired), the cache returns the document in response to later + requests, including cached headers. + +10. ACKNOWLEDGEMENTS + + This document really represents the collective efforts of the HTTP + Working Group of the IETF and, particularly, the following people, in + addition to the authors: Roy Fielding, Yaron Goland, Marc Hedlund, + Ted Hardie, Koen Holtman, Shel Kaphan, Rohit Khare, Foteos Macrides, + David W. Morris. + + + + + + + + +Kristol & Montulli Standards Track [Page 24] + +RFC 2965 HTTP State Management Mechanism October 2000 + + +11. AUTHORS' ADDRESSES + + David M. Kristol + Bell Laboratories, Lucent Technologies + 600 Mountain Ave. Room 2A-333 + Murray Hill, NJ 07974 + + Phone: (908) 582-2250 + Fax: (908) 582-1239 + EMail: dmk@bell-labs.com + + + Lou Montulli + Epinions.com, Inc. + 2037 Landings Dr. + Mountain View, CA 94301 + + EMail: lou@montulli.org + +12. REFERENCES + + [DMK95] Kristol, D.M., "Proposed HTTP State-Info Mechanism", + available at <http://portal.research.bell- + labs.com/~dmk/state-info.html>, September, 1995. + + [Netscape] "Persistent Client State -- HTTP Cookies", available at + <http://www.netscape.com/newsref/std/cookie_spec.html>, + undated. + + [RFC2109] Kristol, D. and L. Montulli, "HTTP State Management + Mechanism", RFC 2109, February 1997. + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC2279] Yergeau, F., "UTF-8, a transformation format of Unicode + and ISO-10646", RFC 2279, January 1998. + + [RFC2396] Berners-Lee, T., Fielding, R. and L. Masinter, "Uniform + Resource Identifiers (URI): Generic Syntax", RFC 2396, + August 1998. + + [RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H. and T. + Berners-Lee, "Hypertext Transfer Protocol -- HTTP/1.1", + RFC 2616, June 1999. + + + + + + +Kristol & Montulli Standards Track [Page 25] + +RFC 2965 HTTP State Management Mechanism October 2000 + + +13. Full Copyright Statement + + Copyright (C) The Internet Society (2000). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + + + + + + + + + + + + + +Kristol & Montulli Standards Track [Page 26] + diff --git a/kioslave/http/kcookiejar/tests/Makefile.am b/kioslave/http/kcookiejar/tests/Makefile.am new file mode 100644 index 000000000..b79dd10fb --- /dev/null +++ b/kioslave/http/kcookiejar/tests/Makefile.am @@ -0,0 +1,18 @@ +# $Id$ +# Makefile.am of kdebase/kioslave/http + +INCLUDES= $(all_includes) + +####### Files + +check_PROGRAMS = kcookiejartest + +kcookiejartest_SOURCES = kcookiejartest.cpp +kcookiejartest_LDADD = $(LIB_KIO) +kcookiejartest_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +check-local: kcookiejartest + ./kcookiejartest $(srcdir)/cookie.test + ./kcookiejartest $(srcdir)/cookie_rfc.test + ./kcookiejartest $(srcdir)/cookie_saving.test + ./kcookiejartest $(srcdir)/cookie_settings.test diff --git a/kioslave/http/kcookiejar/tests/cookie.test b/kioslave/http/kcookiejar/tests/cookie.test new file mode 100644 index 000000000..6619bf82d --- /dev/null +++ b/kioslave/http/kcookiejar/tests/cookie.test @@ -0,0 +1,162 @@ +## Check setting of cookies +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR% +CHECK http://w.y.z/ Cookie: some_value=value1 +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value2; Path="/" +CHECK http://a.b.c/ Cookie: some_value=value2 +## Check if clearing cookie jar works +CLEAR COOKIES +CHECK http://w.y.z/ +CHECK http://a.b.c/ +## Check cookie syntax +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value with spaces +CHECK http://w.y.z/ Cookie: some_value=value with spaces +COOKIE ASK http://a.b.c/ Set-Cookie: some_value="quoted value" +CHECK http://a.b.c/ Cookie: some_value="quoted value" +# Without a = sign, the cookie gets interpreted as the value for a cookie with no name +# This is what IE and Netscape does +COOKIE ASK http://a.b.c/ Set-Cookie: some_value +CHECK http://a.b.c/ Cookie: some_value; some_value="quoted value" +COOKIE ASK http://a.b.c/ Set-Cookie: some_other_value +CHECK http://a.b.c/ Cookie: some_other_value; some_value="quoted value" +CLEAR COOKIES +# This doesn't work with old-style netscape cookies, it should work with RFC2965 cookies +COOKIE ASK http://a.b.c/ Set-Cookie: some_value="quoted value; and such" +# IE & Netscape does this: +CHECK http://a.b.c/ Cookie: some_value="quoted value +# Mozilla does: +# CHECK http://a.b.c/ Cookie: some_value="quoted value; and such" +# COOKIE ASK http://a.b.c/ Set-Cookie: some_value="quoted value; +# CHECK http://a.b.c/ Cookie: some_value= +# Note that we parse RFC2965 cookies like Mozilla does +CLEAR COOKIES +## Check if deleting cookies works +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR% +CHECK http://w.y.z/ Cookie: some_value=value1 +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/"; expires=%LASTYEAR% +CHECK http://w.y.z/ +## Check if updating cookies works +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value2; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value3; Path="/"; expires=%NEXTYEAR% +CHECK http://w.y.z/ Cookie: some_value=value3 +## Check if multiple cookies work +COOKIE ASK http://w.y.z/ Set-Cookie: some_value2=foobar; Path="/"; expires=%NEXTYEAR% +CHECK http://w.y.z/ Cookie: some_value2=foobar; some_value=value3 +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=; Path="/"; expires=%LASTYEAR% +CHECK http://w.y.z/ Cookie: some_value2=foobar +CLEAR COOKIES +## Check if path restrictions work +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/Foo"; expires=%NEXTYEAR% +CHECK http://w.y.z/ +CHECK http://w.y.z/Foo Cookie: some_value=value1 +CHECK http://w.y.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y.z/Foo/bar Cookie: some_value=value1 +CLEAR COOKIES +## Check if default path works +# RFC2965 says that we should default to the URL path, but netscape cookies default to / +COOKIE ASK http://w.y.z/Foo/ Set-Cookie: some_value=value1; expires=%NEXTYEAR% +CHECK http://w.y.z/ +CHECK http://w.y.z/Foo Cookie: some_value=value1 +CHECK http://w.y.z/FooBar +CHECK http://w.y.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y.z/Foo/bar Cookie: some_value=value1 +CLEAR COOKIES +## Check if cookies are correctly ordered based on path +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/Foo"; expires=%NEXTYEAR% +COOKIE ASK http://w.y.z/ Set-Cookie: some_value2=value2; Path="/Foo/Bar"; expires=%NEXTYEAR% +CHECK http://w.y.z/Foo/Bar Cookie: some_value2=value2; some_value=value1 +COOKIE ASK http://w.y.z/ Set-Cookie: some_value3=value3; Path="/"; expires=%NEXTYEAR% +CHECK http://w.y.z/Foo/Bar Cookie: some_value2=value2; some_value=value1; some_value3=value3 +CLEAR COOKIES +## Check cookies with same name but different paths +COOKIE ASK http://w.y.z/Foo/ Set-Cookie: some_value=value1; expires=%NEXTYEAR% +COOKIE ASK http://w.y.z/Bar/ Set-Cookie: some_value=value2; expires=%NEXTYEAR% +CHECK http://w.y.z/Foo/Bar Cookie: some_value=value1 +CHECK http://w.y.z/Bar/Foo Cookie: some_value=value2 +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value3; expires=%NEXTYEAR% +CHECK http://w.y.z/Foo/Bar Cookie: some_value=value1; some_value=value3 +## Check secure cookie handling +COOKIE ASK https://secure.y.z/ Set-Cookie: some_value2=value2; Path="/"; expires=%NEXTYEAR%; secure +CHECK https://secure.y.z/Foo/bar Cookie: some_value2=value2 +CHECK http://secure.y.z/Foo/bar +CLEAR COOKIES +COOKIE ASK http://secure.y.z/ Set-Cookie: some_value3=value3; Path="/"; expires=%NEXTYEAR%; secure +CHECK https://secure.y.z/Foo/bar Cookie: some_value3=value3 +CHECK http://secure.y.z/Foo/bar +CLEAR COOKIES +## Check domain restrictions #1 +COOKIE ASK http://www.acme.com/ Set-Cookie: some_value=value1; Domain=".acme.com"; expires=%NEXTYEAR% +CHECK http://www.acme.com/ Cookie: some_value=value1 +CHECK http://www.abc.com/ +CHECK http://frop.acme.com/ Cookie: some_value=value1 +CLEAR COOKIES +## Check domain restrictions #2 +COOKIE ASK http://novell.com/ Set-Cookie: some_value=value1; Domain=".novell.com"; expires=%NEXTYEAR% +CHECK http://novell.com/ Cookie: some_value=value1 +CHECK http://www.novell.com/ Cookie: some_value=value1 +CLEAR COOKIES +COOKIE ASK http://novell.com/ Set-Cookie: some_value=value1; Domain="novell.com"; expires=%NEXTYEAR% +CHECK http://novell.com/ Cookie: some_value=value1 +CHECK http://www.novell.com/ Cookie: some_value=value1 +CLEAR COOKIES +## Check domain restrictions #3 +COOKIE ASK http://novell.com/ Set-Cookie: some_value=value1; expires=%NEXTYEAR% +CHECK http://novell.com/ Cookie: some_value=value1 +# FIXME: Allegedly IE sends cookies to sub-domains as well! +# See e.g. https://bugzilla.mozilla.org/show_bug.cgi?id=223027 +CHECK http://www.novell.com/ +CLEAR COOKIES +## Check domain restrictions #4 +COOKIE ASK http://novell.com/ Set-Cookie: some_value=value1; Domain=".com"; expires=%NEXTYEAR% +CHECK http://novell.com/ Cookie: some_value=value1 +# If the specified domain is too broad, we default to host only +CHECK http://www.novell.com/ +CHECK http://com/ +CHECK http://sun.com/ +## Check domain restrictions #5 +CLEAR COOKIES +COOKIE ASK http://novell.co.uk/ Set-Cookie: some_value=value1; Domain=".co.uk"; expires=%NEXTYEAR% +CHECK http://novell.co.uk/ Cookie: some_value=value1 +# If the specified domain is too broad, we default to host only +CHECK http://www.novell.co.uk/ +CHECK http://co.uk/ +CHECK http://sun.co.uk/ +COOKIE ASK http://x.y.z.foobar.com/ Set-Cookie: set_by=x.y.z.foobar.com; Domain=".foobar.com"; expires=%NEXTYEAR% +CHECK http://x.y.z.foobar.com/ Cookie: set_by=x.y.z.foobar.com +CHECK http://y.z.foobar.com/ Cookie: set_by=x.y.z.foobar.com +CHECK http://z.foobar.com/ Cookie: set_by=x.y.z.foobar.com +CHECK http://www.foobar.com/ Cookie: set_by=x.y.z.foobar.com +CHECK http://foobar.com/ Cookie: set_by=x.y.z.foobar.com +CLEAR COOKIES +## Check domain restrictions #6 +COOKIE ASK http://x.y.z.frop.com/ Set-Cookie: set_by=x.y.z.frop.com; Domain=".foobar.com"; expires=%NEXTYEAR% +COOKIE ASK http://x.y.z.frop.com/ Set-Cookie: set_by2=x.y.z.frop.com; Domain=".com"; expires=%NEXTYEAR% +CHECK http://x.y.z.foobar.com/ +CHECK http://y.z.foobar.com/ +CHECK http://z.foobar.com/ +CHECK http://www.foobar.com/ +CHECK http://foobar.com/ +CLEAR COOKIES +## Check domain restrictions #7 +COOKIE ASK http://frop.com/ Set-Cookie: set_by=x.y.z.frop.com; Domain=".foobar.com"; expires=%NEXTYEAR% +COOKIE ASK http://frop.com/ Set-Cookie: set_by2=x.y.z.frop.com; Domain=".com"; expires=%NEXTYEAR% +CHECK http://x.y.z.foobar.com/ +CHECK http://y.z.foobar.com/ +CHECK http://z.foobar.com/ +CHECK http://www.foobar.com/ +CHECK http://foobar.com/ +CLEAR COOKIES +## Check domain restrictions #8 +CONFIG AcceptSessionCookies true +COOKIE ACCEPT http://www.foobar.com Set-Cookie: from=foobar.com; domain=bar.com; Path="/" +CHECK http://bar.com +CLEAR COOKIES +## Check cookies with IP address hostnames +COOKIE ASK http://192.168.0.1 Set-Cookie: name1=value1; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://192.168.0.1 Set-Cookie: name11=value11; domain="test.local"; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://192.168.0.1:8080 Set-Cookie: name2=value2; Path="/"; expires=%NEXTYEAR% +COOKIE ASK https://192.168.0.1 Set-Cookie: name3=value3; Path="/"; expires=%NEXTYEAR%; secure +CHECK http://192.168.0.1 Cookie: name11=value11; name1=value1 +CHECK http://192.168.0.1:8080 Cookie: name2=value2 +CHECK https://192.168.0.1 Cookie: name3=value3; name11=value11; name1=value1 +CHECK http://192.168.0.10 +CHECK http://192.168.0 diff --git a/kioslave/http/kcookiejar/tests/cookie_rfc.test b/kioslave/http/kcookiejar/tests/cookie_rfc.test new file mode 100644 index 000000000..e1d8a40de --- /dev/null +++ b/kioslave/http/kcookiejar/tests/cookie_rfc.test @@ -0,0 +1,148 @@ +## Check setting of cookies +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value="value1"; Version=1; Path="/"; Max-Age=3600 +# Although the examples in RFC2965 uses $Version="1" the syntax description suggests that +# such quotes are not allowed, KDE BR59990 reports that the Sun Java server fails to handle +# cookies that use $Version="1" +CHECK http://w.y.z/ Cookie: $Version=1; some_value="value1"; $Path="/" +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value="value2"; Version=1; Path="/" +CHECK http://a.b.c/ Cookie: $Version=1; some_value="value2"; $Path="/" +## Check if clearing cookie jar works +CLEAR COOKIES +CHECK http://w.y.z/ +CHECK http://a.b.c/ +## Check cookie syntax +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value="value with spaces"; Version=1 +CHECK http://w.y.z/ Cookie: $Version=1; some_value="value with spaces" +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value ="extra space 1"; Version=1 +CHECK http://w.y.z/ Cookie: $Version=1; some_value="extra space 1" +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value= "extra space 2"; Version=1 +CHECK http://w.y.z/ Cookie: $Version=1; some_value="extra space 2" +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=unquoted; Version=1 +CHECK http://a.b.c/ Cookie: $Version=1; some_value=unquoted +# Note that we parse this different for Netscape-style cookies! +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value="quoted value; and such"; Version=1; +CHECK http://a.b.c/ Cookie: $Version=1; some_value="quoted value; and such" +CLEAR COOKIES +## Check if deleting cookies works #1 +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value="value1"; Version=1; Path="/"; Max-Age=3600 +CHECK http://w.y.z/ Cookie: $Version=1; some_value="value1"; $Path="/" +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value1; Version=1; Path="/"; Max-Age=0 +CHECK http://w.y.z/ +## Check if updating cookies works +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value2; Version=1; Path="/"; Max-Age=3600 +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value3; Version=1; Path="/"; Max-Age=3600 +CHECK http://w.y.z/ Cookie: $Version=1; some_value=value3; $Path="/" +## Check if multiple cookies work +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value2=foobar; Version=1; Path="/"; Max-Age=3600 +CHECK http://w.y.z/ Cookie: $Version=1; some_value2=foobar; $Path="/"; some_value=value3; $Path="/" +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=; Version=1; Path="/"; Max-Age=0 +CHECK http://w.y.z/ Cookie: $Version=1; some_value2=foobar; $Path="/" +CLEAR COOKIES +## Check if we prepend domain with a dot +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value2; Version=1; Path="/"; Domain=.y.z; Max-Age=3600 +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value3; Version=1; Path="/"; Domain=y.z.; Max-Age=3600 +CHECK http://w.y.z/ Cookie: $Version=1; some_value=value3; $Path="/"; $Domain=".y.z" +CLEAR COOKIES +## Check if multiple cookies on a single line work +## FIXME +#COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value3; Version=1; Path="/"; Max-Age=3600, some_value2=foobar; Version=1; Path="/"; Max-Age=3600 +# CHECK http://w.y.z/ Cookie: $Version=1; some_value2=foobar; $Path="/"; some_value=value3; $Path="/" +# COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=; Version=1; Path="/"; Max-Age=0 +# CHECK http://w.y.z/ Cookie: $Version=1; some_value2=foobar; $Path="/" +CLEAR COOKIES +## Check if path restrictions work +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value1; Version=1; Path="/Foo"; Max-Age=3600 +CHECK http://w.y.z/ +CHECK http://w.y.z/Foo Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y.z/Foo/ Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y.z/Foo/bar Cookie: $Version=1; some_value=value1; $Path="/Foo" +CLEAR COOKIES +## Check if default path works +# RFC2965 says that we should default to the URL path +COOKIE ASK http://w.y.z/Foo/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600 +CHECK http://w.y.z/ +CHECK http://w.y.z/Foo Cookie: $Version=1; some_value=value1 +CHECK http://w.y.z/FooBar +CHECK http://w.y.z/Foo/ Cookie: $Version=1; some_value=value1 +CHECK http://w.y.z/Foo/bar Cookie: $Version=1; some_value=value1 +CLEAR COOKIES +## Check if cookies are correctly ordered based on path +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value1; Version=1; Path="/Foo"; Max-Age=3600 +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value2=value2; Version=1; Path="/Foo/Bar"; Max-Age=3600 +CHECK http://w.y.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo" +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value3=value3; Version=1; Path="/"; Max-Age=3600 +CHECK http://w.y.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo"; some_value3=value3; $Path="/" +CLEAR COOKIES +## Check cookies with same name but different paths +COOKIE ASK http://w.y.z/Foo/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600 +COOKIE ASK http://w.y.z/Bar/ Set-Cookie2: some_value=value2; Version=1; Max-Age=3600 +CHECK http://w.y.z/Foo/Bar Cookie: $Version=1; some_value=value1 +CHECK http://w.y.z/Bar/Foo Cookie: $Version=1; some_value=value2 +COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value3; Version=1; Max-Age=3600 +CHECK http://w.y.z/Foo/Bar Cookie: $Version=1; some_value=value1; some_value=value3 +## Check secure cookie handling +COOKIE ASK https://secure.y.z/ Set-Cookie2: some_value2=value2; Version=1; Path="/"; Max-Age=3600; Secure +CHECK https://secure.y.z/Foo/bar Cookie: $Version=1; some_value2=value2; $Path="/" +CHECK http://secure.y.z/Foo/bar +CLEAR COOKIES +COOKIE ASK http://secure.y.z/ Set-Cookie2: some_value3=value3; Version=1; Path="/"; Max-Age=3600; Secure +CHECK https://secure.y.z/Foo/bar Cookie: $Version=1; some_value3=value3; $Path="/" +CHECK http://secure.y.z/Foo/bar +CLEAR COOKIES +## Check domain restrictions #1 +COOKIE ASK http://www.acme.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".acme.com"; Max-Age=3600 +CHECK http://www.acme.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme.com" +CHECK http://www.abc.com/ +CHECK http://frop.acme.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme.com" +CLEAR COOKIES +## Check domain restrictions #2 +COOKIE ASK http://novell.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".novell.com"; Max-Age=3600 +CHECK http://novell.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell.com" +CHECK http://www.novell.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell.com" +CLEAR COOKIES +## Check domain restrictions #3 +COOKIE ASK http://novell.com/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600 +CHECK http://novell.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell.com/ +CLEAR COOKIES +## Check domain restrictions #4 +COOKIE ASK http://novell.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".com"; Max-Age=3600 +# If the specified domain is too broad, we ignore the Domain +# FIXME: RFC2965 says we should ignore the cookie completely +CHECK http://novell.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell.com/ +CHECK http://com/ +CHECK http://sun.com/ +## Check domain restrictions #5 +CLEAR COOKIES +COOKIE ASK http://novell.co.uk/ Set-Cookie2: some_value=value1; Version=1; Domain=".co.uk"; Max-Age=3600 +# If the specified domain is too broad, we default to host only +# FIXME: RFC2965 says we should ignore the cookie completely +CHECK http://novell.co.uk/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell.co.uk/ +CHECK http://co.uk/ +CHECK http://sun.co.uk/ +COOKIE ASK http://x.y.z.foobar.com/ Set-Cookie2: set_by=x.y.z.foobar.com; Version=1; Domain=".foobar.com"; Max-Age=3600 +CHECK http://x.y.z.foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com" +CHECK http://y.z.foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com" +CHECK http://z.foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com" +CHECK http://www.foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com" +CHECK http://foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com" +CLEAR COOKIES +## Check domain restrictions #6 +COOKIE ASK http://x.y.z.frop.com/ Set-Cookie2: set_by=x.y.z.frop.com; Version=1; Domain=".foobar.com"; Max-Age=3600 +COOKIE ASK http://x.y.z.frop.com/ Set-Cookie2: set_by2=x.y.z.frop.com; Version=1; Domain=".com"; Max-Age=3600 +CHECK http://x.y.z.foobar.com/ +CHECK http://y.z.foobar.com/ +CHECK http://z.foobar.com/ +CHECK http://www.foobar.com/ +CHECK http://foobar.com/ +CLEAR COOKIES +## Check domain restrictions #7 +COOKIE ASK http://frop.com/ Set-Cookie2: set_by=x.y.z.frop.com; Version=1; Domain=".foobar.com"; Max-Age=3600 +COOKIE ASK http://frop.com/ Set-Cookie2: set_by2=x.y.z.frop.com; Version=1; Domain=".com"; Max-Age=3600 +CHECK http://x.y.z.foobar.com/ +CHECK http://y.z.foobar.com/ +CHECK http://z.foobar.com/ +CHECK http://www.foobar.com/ +CHECK http://foobar.com/ diff --git a/kioslave/http/kcookiejar/tests/cookie_saving.test b/kioslave/http/kcookiejar/tests/cookie_saving.test new file mode 100644 index 000000000..cb9f34c42 --- /dev/null +++ b/kioslave/http/kcookiejar/tests/cookie_saving.test @@ -0,0 +1,430 @@ +## Check setting of cookies +COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value2; Path="/" +## Check if clearing cookie jar works +CLEAR COOKIES +## Check cookie syntax +COOKIE ASK http://w.y1.z/ Set-Cookie: some_value=value with spaces; expires=%NEXTYEAR% +COOKIE ASK http://a.b1.c/ Set-Cookie: some_value="quoted value"; expires=%NEXTYEAR% +# Without a = sign, the cookie gets interpreted as the value for a cookie with no name +# This is what IE and Netscape does +COOKIE ASK http://a.b1.c/ Set-Cookie: some_value +COOKIE ASK http://a.b1.c/ Set-Cookie: some_other_value; expires=%NEXTYEAR% +# This doesn't work with old-style netscape cookies, it should work with RFC2965 cookies +COOKIE ASK http://a.b2.c/ Set-Cookie: some_value="quoted value; and such"; expires=%NEXTYEAR% +# IE & Netscape does this: +## Check if deleting cookies works +COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=value1; Path="/"; expires=%LASTYEAR% +## Check if updating cookies works +COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=value2; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=value3; Path="/"; expires=%NEXTYEAR% +## Check if multiple cookies work +COOKIE ASK http://w.y3.z/ Set-Cookie: some_value2=foobar; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=; Path="/"; expires=%LASTYEAR% +## Check if path restrictions work +COOKIE ASK http://w.y4.z/ Set-Cookie: some_value=value1; Path="/Foo"; expires=%NEXTYEAR% +## Check if default path works +COOKIE ASK http://w.y5.z/Foo/ Set-Cookie: some_value=value1; expires=%NEXTYEAR% +## Check if cookies are correctly ordered based on path +COOKIE ASK http://w.y6.z/ Set-Cookie: some_value=value1; Path="/Foo"; expires=%NEXTYEAR% +COOKIE ASK http://w.y6.z/ Set-Cookie: some_value2=value2; Path="/Foo/Bar"; expires=%NEXTYEAR% +COOKIE ASK http://w.y6.z/ Set-Cookie: some_value3=value3; Path="/"; expires=%NEXTYEAR% +## Check cookies with same name but different paths +COOKIE ASK http://w.y7.z/Foo/ Set-Cookie: some_value=value1; expires=%NEXTYEAR% +COOKIE ASK http://w.y7.z/Bar/ Set-Cookie: some_value=value2; expires=%NEXTYEAR% +COOKIE ASK http://w.y7.z/ Set-Cookie: some_value=value3; expires=%NEXTYEAR% +## Check secure cookie handling +COOKIE ASK https://secure.y7.z/ Set-Cookie: some_value2=value2; Path="/"; expires=%NEXTYEAR%; secure +COOKIE ASK http://secure.y8.z/ Set-Cookie: some_value3=value3; Path="/"; expires=%NEXTYEAR%; secure +## Check domain restrictions #1 +COOKIE ASK http://www.acme9.com/ Set-Cookie: some_value=value1; Domain=".acme9.com"; expires=%NEXTYEAR% +## Check domain restrictions #2 +COOKIE ASK http://novell10.com/ Set-Cookie: some_value=value1; Domain=".novell10.com"; expires=%NEXTYEAR% +COOKIE ASK http://novell11.com/ Set-Cookie: some_value=value1; Domain="novell11.com"; expires=%NEXTYEAR% +## Check domain restrictions #3 +COOKIE ASK http://novell12.com/ Set-Cookie: some_value=value1; expires=%NEXTYEAR% +## Check domain restrictions #4 +COOKIE ASK http://novell13.com/ Set-Cookie: some_value=value1; Domain=".com"; expires=%NEXTYEAR% +# If the specified domain is too broad, we default to host only +## Check domain restrictions #5 +COOKIE ASK http://novell14.co.uk/ Set-Cookie: some_value=value1; Domain=".co.uk"; expires=%NEXTYEAR% +COOKIE ASK http://x.y.z.foobar14.com/ Set-Cookie: set_by=x.y.z.foobar14.com; Domain=".foobar14.com"; expires=%NEXTYEAR% +## Check domain restrictions #6 +COOKIE ASK http://x.y.z.frop15.com/ Set-Cookie: set_by=x.y.z.frop15.com; Domain=".foobar15.com"; expires=%NEXTYEAR% +COOKIE ASK http://x.y.z.frop15.com/ Set-Cookie: set_by2=x.y.z.frop15.com; Domain=".com"; expires=%NEXTYEAR% +## Check domain restrictions #7 +COOKIE ASK http://frop16.com/ Set-Cookie: set_by=x.y.z.frop16.com; Domain=".foobar16.com"; expires=%NEXTYEAR% +COOKIE ASK http://frop16.com/ Set-Cookie: set_by2=x.y.z.frop16.com; Domain=".com"; expires=%NEXTYEAR% +## RFC Cookies +## Check setting of cookies +COOKIE ASK http://w.y20.z/ Set-Cookie2: some_value="value1"; Version=1; Path="/"; Max-Age=3600 +# Although the examples in RFC2965 uses $Version="1" the syntax description suggests that +# such quotes are not allowed, KDE BR59990 reports that the Sun Java server fails to handle +# cookies that use $Version="1" +COOKIE ASK http://a.b20.c/ Set-Cookie2: some_value="value2"; Version=1; Path="/"; Max-Age=3600 +## Check cookie syntax +COOKIE ASK http://w.y21.z/ Set-Cookie2: some_value="value with spaces"; Version=1; Max-Age=3600 +COOKIE ASK http://w.y21.z/ Set-Cookie2: some_value ="extra space 1"; Version=1; Max-Age=3600 +COOKIE ASK http://w.y21.z/ Set-Cookie2: some_value= "extra space 2"; Version=1; Max-Age=3600 +COOKIE ASK http://a.b21.c/ Set-Cookie2: some_value=unquoted; Version=1; Max-Age=3600 +# Note that we parse this different for Netscape-style cookies! +COOKIE ASK http://a.b21.c/ Set-Cookie2: some_value="quoted value; and such"; Version=1; Max-Age=3600 +## Check if deleting cookies works #1 +COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value="value1"; Version=1; Path="/"; Max-Age=3600 +COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value=value1; Version=1; Path="/"; Max-Age=0 +## Check if updating cookies works +COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value=value2; Version=1; Path="/"; Max-Age=3600 +COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value=value3; Version=1; Path="/"; Max-Age=3600 +## Check if multiple cookies work +COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value2=foobar; Version=1; Path="/"; Max-Age=3600 +COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value=; Version=1; Path="/"; Max-Age=0 +## Check if path restrictions work +COOKIE ASK http://w.y23.z/ Set-Cookie2: some_value=value1; Version=1; Path="/Foo"; Max-Age=3600 +## Check if default path works +# RFC2965 says that we should default to the URL path +COOKIE ASK http://w.y24.z/Foo/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600 +## Check if cookies are correctly ordered based on path +COOKIE ASK http://w.y25.z/ Set-Cookie2: some_value=value1; Version=1; Path="/Foo"; Max-Age=3600 +COOKIE ASK http://w.y25.z/ Set-Cookie2: some_value2=value2; Version=1; Path="/Foo/Bar"; Max-Age=3600 +COOKIE ASK http://w.y25.z/ Set-Cookie2: some_value3=value3; Version=1; Path="/"; Max-Age=3600 +## Check cookies with same name but different paths +COOKIE ASK http://w.y26.z/Foo/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600 +COOKIE ASK http://w.y26.z/Bar/ Set-Cookie2: some_value=value2; Version=1; Max-Age=3600 +COOKIE ASK http://w.y26.z/ Set-Cookie2: some_value=value3; Version=1; Max-Age=3600 +## Check secure cookie handling +COOKIE ASK https://secure.y26.z/ Set-Cookie2: some_value2=value2; Version=1; Path="/"; Max-Age=3600; Secure +COOKIE ASK http://secure.y27.z/ Set-Cookie2: some_value3=value3; Version=1; Path="/"; Max-Age=3600; Secure +## Check domain restrictions #1 +COOKIE ASK http://www.acme28.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".acme28.com"; Max-Age=3600 +## Check domain restrictions #2 +COOKIE ASK http://novell29.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".novell29.com"; Max-Age=3600 +## Check domain restrictions #3 +COOKIE ASK http://novell30.com/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600 +## Check domain restrictions #4 +COOKIE ASK http://novell31.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".com"; Max-Age=3600 +# If the specified domain is too broad, we ignore the Domain +# FIXME: RFC2965 says we should ignore the cookie completely +## Check domain restrictions #5 +COOKIE ASK http://novell32.co.uk/ Set-Cookie2: some_value=value1; Version=1; Domain=".co.uk"; Max-Age=3600 +# If the specified domain is too broad, we default to host only +# FIXME: RFC2965 says we should ignore the cookie completely +COOKIE ASK http://x.y.z.foobar33.com/ Set-Cookie2: set_by=x.y.z.foobar.com; Version=1; Domain=".foobar33.com"; Max-Age=3600 +## Check domain restrictions #6 +COOKIE ASK http://x.y.z.frop34.com/ Set-Cookie2: set_by=x.y.z.frop.com; Version=1; Domain=".foobar.com"; Max-Age=3600 +COOKIE ASK http://x.y.z.frop34.com/ Set-Cookie2: set_by2=x.y.z.frop.com; Version=1; Domain=".com"; Max-Age=3600 +## Check domain restrictions #7 +COOKIE ASK http://frop35.com/ Set-Cookie2: set_by=x.y.z.frop.com; Version=1; Domain=".foobar.com"; Max-Age=3600 +COOKIE ASK http://frop35.com/ Set-Cookie2: set_by2=x.y.z.frop.com; Version=1; Domain=".com"; Max-Age=3600 + +## Check results +CHECK http://w.y.z/ +CHECK http://a.b.c/ +CHECK http://w.y1.z/ Cookie: some_value=value with spaces +CHECK http://a.b1.c/ Cookie: some_other_value; some_value="quoted value" +CHECK http://a.b2.c/ Cookie: some_value="quoted value +CHECK http://w.y3.z/ Cookie: some_value2=foobar +CHECK http://w.y4.z/ +CHECK http://w.y4.z/Foo Cookie: some_value=value1 +CHECK http://w.y4.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y4.z/Foo/bar Cookie: some_value=value1 +CHECK http://w.y5.z/ +CHECK http://w.y5.z/Foo Cookie: some_value=value1 +CHECK http://w.y5.z/FooBar +CHECK http://w.y5.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y5.z/Foo/bar Cookie: some_value=value1 +CHECK http://w.y6.z/Foo/Bar Cookie: some_value2=value2; some_value=value1; some_value3=value3 +CHECK http://w.y7.z/Bar/Foo Cookie: some_value=value2; some_value=value3 +CHECK http://w.y7.z/Foo/Bar Cookie: some_value=value1; some_value=value3 +CHECK https://secure.y7.z/Foo/bar Cookie: some_value2=value2 +CHECK http://secure.y7.z/Foo/bar +CHECK https://secure.y8.z/Foo/bar Cookie: some_value3=value3 +CHECK http://secure.y8.z/Foo/bar +CHECK http://www.acme9.com/ Cookie: some_value=value1 +CHECK http://www.abc9.com/ +CHECK http://frop.acme9.com/ Cookie: some_value=value1 +CHECK http://novell10.com/ Cookie: some_value=value1 +CHECK http://www.novell10.com/ Cookie: some_value=value1 +CHECK http://novell11.com/ Cookie: some_value=value1 +CHECK http://www.novell11.com/ Cookie: some_value=value1 +CHECK http://novell12.com/ Cookie: some_value=value1 +CHECK http://www.novell12.com/ +CHECK http://novell13.com/ Cookie: some_value=value1 +CHECK http://www.novell13.com/ +CHECK http://com/ +CHECK http://sun13.com/ +CHECK http://novell14.co.uk/ Cookie: some_value=value1 +CHECK http://www.novell14.co.uk/ +CHECK http://co.uk/ +CHECK http://sun14.co.uk/ +CHECK http://x.y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://www.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://x.y.z.foobar15.com/ +CHECK http://y.z.foobar15.com/ +CHECK http://z.foobar15.com/ +CHECK http://www.foobar15.com/ +CHECK http://foobar15.com/ +CHECK http://x.y.z.foobar16.com/ +CHECK http://y.z.foobar16.com/ +CHECK http://z.foobar16.com/ +CHECK http://www.foobar16.com/ +CHECK http://foobar16.com/ +## Check results for RFC cookies +CHECK http://w.y20.z/ Cookie: $Version=1; some_value="value1"; $Path="/" +CHECK http://a.b20.c/ Cookie: $Version=1; some_value="value2"; $Path="/" +CHECK http://w.y21.z/ Cookie: $Version=1; some_value="extra space 2" +CHECK http://a.b21.c/ Cookie: $Version=1; some_value="quoted value; and such" +CHECK http://w.y22.z/ Cookie: $Version=1; some_value2=foobar; $Path="/" +CHECK http://w.y23.z/ +CHECK http://w.y23.z/Foo Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y23.z/Foo/ Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y23.z/Foo/bar Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y24.z/ +CHECK http://w.y24.z/Foo Cookie: $Version=1; some_value=value1 +CHECK http://w.y24.z/FooBar +CHECK http://w.y24.z/Foo/ Cookie: $Version=1; some_value=value1 +CHECK http://w.y24.z/Foo/bar Cookie: $Version=1; some_value=value1 +CHECK http://w.y25.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo"; some_value3=value3; $Path="/" +CHECK http://w.y26.z/Bar/Foo Cookie: $Version=1; some_value=value2; some_value=value3 +CHECK http://w.y26.z/Foo/Bar Cookie: $Version=1; some_value=value1; some_value=value3 +CHECK https://secure.y26.z/Foo/bar Cookie: $Version=1; some_value2=value2; $Path="/" +CHECK http://secure.y26.z/Foo/bar +CHECK https://secure.y27.z/Foo/bar Cookie: $Version=1; some_value3=value3; $Path="/" +CHECK http://secure.y27.z/Foo/bar +CHECK http://www.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com" +CHECK http://www.abc28.com/ +CHECK http://frop.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com" +CHECK http://novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com" +CHECK http://www.novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com" +CHECK http://novell30.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell30.com/ +CHECK http://novell31.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell31.com/ +CHECK http://com/ +CHECK http://sun31.com/ +CHECK http://novell32.co.uk/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell32.co.uk/ +CHECK http://co.uk/ +CHECK http://sun32.co.uk/ +CHECK http://x.y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://www.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://x.y.z.foobar.com/ +CHECK http://y.z.foobar.com/ +CHECK http://z.foobar.com/ +CHECK http://www.foobar.com/ +CHECK http://foobar.com/ + + +SAVE +## Check result after saving +CHECK http://w.y.z/ +CHECK http://a.b.c/ +CHECK http://w.y1.z/ Cookie: some_value=value with spaces +CHECK http://a.b1.c/ Cookie: some_other_value; some_value="quoted value" +CHECK http://a.b2.c/ Cookie: some_value="quoted value +CHECK http://w.y3.z/ Cookie: some_value2=foobar +CHECK http://w.y4.z/ +CHECK http://w.y4.z/Foo Cookie: some_value=value1 +CHECK http://w.y4.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y4.z/Foo/bar Cookie: some_value=value1 +CHECK http://w.y5.z/ +CHECK http://w.y5.z/Foo Cookie: some_value=value1 +CHECK http://w.y5.z/FooBar +CHECK http://w.y5.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y5.z/Foo/bar Cookie: some_value=value1 +CHECK http://w.y6.z/Foo/Bar Cookie: some_value2=value2; some_value=value1; some_value3=value3 +CHECK http://w.y7.z/Bar/Foo Cookie: some_value=value2; some_value=value3 +CHECK http://w.y7.z/Foo/Bar Cookie: some_value=value1; some_value=value3 +CHECK https://secure.y7.z/Foo/bar Cookie: some_value2=value2 +CHECK http://secure.y7.z/Foo/bar +CHECK https://secure.y8.z/Foo/bar Cookie: some_value3=value3 +CHECK http://secure.y8.z/Foo/bar +CHECK http://www.acme9.com/ Cookie: some_value=value1 +CHECK http://www.abc9.com/ +CHECK http://frop.acme9.com/ Cookie: some_value=value1 +CHECK http://novell10.com/ Cookie: some_value=value1 +CHECK http://www.novell10.com/ Cookie: some_value=value1 +CHECK http://novell11.com/ Cookie: some_value=value1 +CHECK http://www.novell11.com/ Cookie: some_value=value1 +CHECK http://novell12.com/ Cookie: some_value=value1 +CHECK http://www.novell12.com/ +CHECK http://novell13.com/ Cookie: some_value=value1 +CHECK http://www.novell13.com/ +CHECK http://com/ +CHECK http://sun13.com/ +CHECK http://novell14.co.uk/ Cookie: some_value=value1 +CHECK http://www.novell14.co.uk/ +CHECK http://co.uk/ +CHECK http://sun14.co.uk/ +CHECK http://x.y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://www.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://x.y.z.foobar15.com/ +CHECK http://y.z.foobar15.com/ +CHECK http://z.foobar15.com/ +CHECK http://www.foobar15.com/ +CHECK http://foobar15.com/ +CHECK http://x.y.z.foobar16.com/ +CHECK http://y.z.foobar16.com/ +CHECK http://z.foobar16.com/ +CHECK http://www.foobar16.com/ +CHECK http://foobar16.com/ +## Check result for RFC cookies after saving +CHECK http://w.y20.z/ Cookie: $Version=1; some_value="value1"; $Path="/" +CHECK http://a.b20.c/ Cookie: $Version=1; some_value="value2"; $Path="/" +CHECK http://w.y21.z/ Cookie: $Version=1; some_value="extra space 2" +CHECK http://a.b21.c/ Cookie: $Version=1; some_value="quoted value; and such" +CHECK http://w.y22.z/ Cookie: $Version=1; some_value2=foobar; $Path="/" +CHECK http://w.y23.z/ +CHECK http://w.y23.z/Foo Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y23.z/Foo/ Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y23.z/Foo/bar Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y24.z/ +CHECK http://w.y24.z/Foo Cookie: $Version=1; some_value=value1 +CHECK http://w.y24.z/FooBar +CHECK http://w.y24.z/Foo/ Cookie: $Version=1; some_value=value1 +CHECK http://w.y24.z/Foo/bar Cookie: $Version=1; some_value=value1 +CHECK http://w.y25.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo"; some_value3=value3; $Path="/" +CHECK http://w.y26.z/Bar/Foo Cookie: $Version=1; some_value=value2; some_value=value3 +CHECK http://w.y26.z/Foo/Bar Cookie: $Version=1; some_value=value1; some_value=value3 +CHECK https://secure.y26.z/Foo/bar Cookie: $Version=1; some_value2=value2; $Path="/" +CHECK http://secure.y26.z/Foo/bar +CHECK https://secure.y27.z/Foo/bar Cookie: $Version=1; some_value3=value3; $Path="/" +CHECK http://secure.y27.z/Foo/bar +CHECK http://www.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com" +CHECK http://www.abc28.com/ +CHECK http://frop.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com" +CHECK http://novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com" +CHECK http://www.novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com" +CHECK http://novell30.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell30.com/ +CHECK http://novell31.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell31.com/ +CHECK http://com/ +CHECK http://sun31.com/ +CHECK http://novell32.co.uk/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell32.co.uk/ +CHECK http://co.uk/ +CHECK http://sun32.co.uk/ +CHECK http://x.y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://www.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://x.y.z.foobar.com/ +CHECK http://y.z.foobar.com/ +CHECK http://z.foobar.com/ +CHECK http://www.foobar.com/ +CHECK http://foobar.com/ + +SAVE +## Check result after saving a second time +CHECK http://w.y.z/ +CHECK http://a.b.c/ +CHECK http://w.y1.z/ Cookie: some_value=value with spaces +CHECK http://a.b1.c/ Cookie: some_other_value; some_value="quoted value" +CHECK http://a.b2.c/ Cookie: some_value="quoted value +CHECK http://w.y3.z/ Cookie: some_value2=foobar +CHECK http://w.y4.z/ +CHECK http://w.y4.z/Foo Cookie: some_value=value1 +CHECK http://w.y4.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y4.z/Foo/bar Cookie: some_value=value1 +CHECK http://w.y5.z/ +CHECK http://w.y5.z/Foo Cookie: some_value=value1 +CHECK http://w.y5.z/FooBar +CHECK http://w.y5.z/Foo/ Cookie: some_value=value1 +CHECK http://w.y5.z/Foo/bar Cookie: some_value=value1 +CHECK http://w.y6.z/Foo/Bar Cookie: some_value2=value2; some_value=value1; some_value3=value3 +CHECK http://w.y7.z/Bar/Foo Cookie: some_value=value2; some_value=value3 +CHECK http://w.y7.z/Foo/Bar Cookie: some_value=value1; some_value=value3 +CHECK https://secure.y7.z/Foo/bar Cookie: some_value2=value2 +CHECK http://secure.y7.z/Foo/bar +CHECK https://secure.y8.z/Foo/bar Cookie: some_value3=value3 +CHECK http://secure.y8.z/Foo/bar +CHECK http://www.acme9.com/ Cookie: some_value=value1 +CHECK http://www.abc9.com/ +CHECK http://frop.acme9.com/ Cookie: some_value=value1 +CHECK http://novell10.com/ Cookie: some_value=value1 +CHECK http://www.novell10.com/ Cookie: some_value=value1 +CHECK http://novell11.com/ Cookie: some_value=value1 +CHECK http://www.novell11.com/ Cookie: some_value=value1 +CHECK http://novell12.com/ Cookie: some_value=value1 +CHECK http://www.novell12.com/ +CHECK http://novell13.com/ Cookie: some_value=value1 +CHECK http://www.novell13.com/ +CHECK http://com/ +CHECK http://sun13.com/ +CHECK http://novell14.co.uk/ Cookie: some_value=value1 +CHECK http://www.novell14.co.uk/ +CHECK http://co.uk/ +CHECK http://sun14.co.uk/ +CHECK http://x.y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://www.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://foobar14.com/ Cookie: set_by=x.y.z.foobar14.com +CHECK http://x.y.z.foobar15.com/ +CHECK http://y.z.foobar15.com/ +CHECK http://z.foobar15.com/ +CHECK http://www.foobar15.com/ +CHECK http://foobar15.com/ +CHECK http://x.y.z.foobar16.com/ +CHECK http://y.z.foobar16.com/ +CHECK http://z.foobar16.com/ +CHECK http://www.foobar16.com/ +CHECK http://foobar16.com/ +## Check result for rfc cookies after saving a second time +CHECK http://w.y20.z/ Cookie: $Version=1; some_value="value1"; $Path="/" +CHECK http://a.b20.c/ Cookie: $Version=1; some_value="value2"; $Path="/" +CHECK http://w.y21.z/ Cookie: $Version=1; some_value="extra space 2" +CHECK http://a.b21.c/ Cookie: $Version=1; some_value="quoted value; and such" +CHECK http://w.y22.z/ Cookie: $Version=1; some_value2=foobar; $Path="/" +CHECK http://w.y23.z/ +CHECK http://w.y23.z/Foo Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y23.z/Foo/ Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y23.z/Foo/bar Cookie: $Version=1; some_value=value1; $Path="/Foo" +CHECK http://w.y24.z/ +CHECK http://w.y24.z/Foo Cookie: $Version=1; some_value=value1 +CHECK http://w.y24.z/FooBar +CHECK http://w.y24.z/Foo/ Cookie: $Version=1; some_value=value1 +CHECK http://w.y24.z/Foo/bar Cookie: $Version=1; some_value=value1 +CHECK http://w.y25.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo"; some_value3=value3; $Path="/" +CHECK http://w.y26.z/Bar/Foo Cookie: $Version=1; some_value=value2; some_value=value3 +CHECK http://w.y26.z/Foo/Bar Cookie: $Version=1; some_value=value1; some_value=value3 +CHECK https://secure.y26.z/Foo/bar Cookie: $Version=1; some_value2=value2; $Path="/" +CHECK http://secure.y26.z/Foo/bar +CHECK https://secure.y27.z/Foo/bar Cookie: $Version=1; some_value3=value3; $Path="/" +CHECK http://secure.y27.z/Foo/bar +CHECK http://www.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com" +CHECK http://www.abc28.com/ +CHECK http://frop.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com" +CHECK http://novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com" +CHECK http://www.novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com" +CHECK http://novell30.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell30.com/ +CHECK http://novell31.com/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell31.com/ +CHECK http://com/ +CHECK http://sun31.com/ +CHECK http://novell32.co.uk/ Cookie: $Version=1; some_value=value1 +CHECK http://www.novell32.co.uk/ +CHECK http://co.uk/ +CHECK http://sun32.co.uk/ +CHECK http://x.y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://www.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com" +CHECK http://x.y.z.foobar.com/ +CHECK http://y.z.foobar.com/ +CHECK http://z.foobar.com/ +CHECK http://www.foobar.com/ +CHECK http://foobar.com/ diff --git a/kioslave/http/kcookiejar/tests/cookie_settings.test b/kioslave/http/kcookiejar/tests/cookie_settings.test new file mode 100644 index 000000000..7fc1a03a7 --- /dev/null +++ b/kioslave/http/kcookiejar/tests/cookie_settings.test @@ -0,0 +1,116 @@ +## Check CookieGlobalAdvice setting +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value2; Path="/" +CONFIG CookieGlobalAdvice Reject +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value3; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value4; Path="/" +CONFIG CookieGlobalAdvice Accept +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value5; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value6; Path="/" +CONFIG CookieGlobalAdvice Ask +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value7; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value8; Path="/" +CONFIG AcceptSessionCookies true +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +# FIXME: Shouldn't this be considered a session cookie? +# COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="0" +# COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%LASTYEAR% +# FIXME: The 'Discard' attribute makes the cookie a session cookie +# COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +## Treat all cookies as session cookies +CONFIG IgnoreExpirationDate true +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check host-based domain policies +CONFIG IgnoreExpirationDate false +CONFIG AcceptSessionCookies false +CONFIG CookieDomainAdvice a.b.c:Reject +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check resetting of domain policies +CONFIG CookieDomainAdvice +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check domain policies +CONFIG CookieDomainAdvice .b.c:Reject +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check overriding of domain policies #1 +CONFIG CookieDomainAdvice .b.c:Reject,a.b.c:Accept +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check overriding of domain policies #2 +CONFIG CookieDomainAdvice a.b.c:Reject,.b.c:Accept +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE ACCEPT http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPT http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ACCEPT http://d.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ACCEPT http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check resetting of domain policies +CONFIG CookieDomainAdvice +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check overriding of domain policies #3 +CONFIG CookieDomainAdvice b.c:Reject,.b.c:Accept +COOKIE REJECT http://b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE REJECT http://b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE REJECT http://b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +## Check overriding of domain policies #4 +CONFIG CookieDomainAdvice .a.b.c.d:Reject,.b.c.d:Accept,.c.d:Ask +COOKIE REJECT http://www.a.b.c.d/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ACCEPT http://www.b.c.d/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE ASK http://www.c.d/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +## Check interaction with session policy +CONFIG AcceptSessionCookies true +CONFIG CookieDomainAdvice .b.c:Reject +COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" +COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR% +COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600" +COOKIE ACCEPT http://d.b.c/ Set-Cookie: some_value=value11; Path="/" +COOKIE ACCEPT http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/" diff --git a/kioslave/http/kcookiejar/tests/kcookiejartest.cpp b/kioslave/http/kcookiejar/tests/kcookiejartest.cpp new file mode 100644 index 000000000..f196f1820 --- /dev/null +++ b/kioslave/http/kcookiejar/tests/kcookiejartest.cpp @@ -0,0 +1,270 @@ +/* + This file is part of KDE + + Copyright (C) 2004 Waldo Bastian (bastian@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + version 2 as published by the Free Software Foundation. + + This software is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include <stdio.h> +#include <stdlib.h> + +#include <qdatetime.h> +#include <qstring.h> + +#include <kapplication.h> +#include <kaboutdata.h> +#include <kcmdlineargs.h> +#include <kstandarddirs.h> + +#include "../kcookiejar.cpp" + +static const char *description = "KCookiejar regression test"; + +static KCookieJar *jar; +static QCString *lastYear; +static QCString *nextYear; +static KConfig *config = 0; + + +static KCmdLineOptions options[] = +{ + { "+testfile", "Regression test to run", 0}, + KCmdLineLastOption +}; + +static void FAIL(const QString &msg) +{ + qWarning("%s", msg.local8Bit().data()); + exit(1); +} + +static void popArg(QCString &command, QCString & line) +{ + int i = line.find(' '); + if (i != -1) + { + command = line.left(i); + line = line.mid(i+1); + } + else + { + command = line; + line = 0; + } +} + + +static void popArg(QString &command, QCString & line) +{ + int i = line.find(' '); + if (i != -1) + { + command = QString::fromLatin1(line.left(i)); + line = line.mid(i+1); + } + else + { + command = QString::fromLatin1(line); + line = 0; + } +} + +static void clearConfig() +{ + delete config; + QString file = locateLocal("config", "kcookiejar-testconfig"); + QFile::remove(file); + config = new KConfig(file); + config->setGroup("Cookie Policy"); + config->writeEntry("RejectCrossDomainCookies", false); + config->writeEntry("AcceptSessionCookies", false); + config->writeEntry("IgnoreExpirationDate", false); + config->writeEntry("CookieGlobalAdvice", "Ask"); + jar->loadConfig(config, false); +} + +static void clearCookies() +{ + jar->eatAllCookies(); +} + +static void saveCookies() +{ + QString file = locateLocal("config", "kcookiejar-testcookies"); + QFile::remove(file); + jar->saveCookies(file); + delete jar; + jar = new KCookieJar(); + clearConfig(); + jar->loadCookies(file); +} + +static void processCookie(QCString &line) +{ + QString policy; + popArg(policy, line); + KCookieAdvice expectedAdvice = KCookieJar::strToAdvice(policy); + if (expectedAdvice == KCookieDunno) + FAIL(QString("Unknown accept policy '%1'").arg(policy)); + + QString urlStr; + popArg(urlStr, line); + KURL url(urlStr); + if (!url.isValid()) + FAIL(QString("Invalid URL '%1'").arg(urlStr)); + if (url.isEmpty()) + FAIL(QString("Missing URL")); + + line.replace("%LASTYEAR%", *lastYear); + line.replace("%NEXTYEAR%", *nextYear); + + KHttpCookieList list = jar->makeCookies(urlStr, line, 0); + + if (list.isEmpty()) + FAIL(QString("Failed to make cookies from: '%1'").arg(line)); + + for(KHttpCookie *cookie = list.first(); + cookie; cookie = list.next()) + { + KCookieAdvice cookieAdvice = jar->cookieAdvice(cookie); + if (cookieAdvice != expectedAdvice) + FAIL(urlStr+QString("\n'%2'\nGot advice '%3' expected '%4'").arg(line) + .arg(KCookieJar::adviceToStr(cookieAdvice)) + .arg(KCookieJar::adviceToStr(expectedAdvice))); + jar->addCookie(cookie); + } +} + +static void processCheck(QCString &line) +{ + QString urlStr; + popArg(urlStr, line); + KURL url(urlStr); + if (!url.isValid()) + FAIL(QString("Invalid URL '%1'").arg(urlStr)); + if (url.isEmpty()) + FAIL(QString("Missing URL")); + + QString expectedCookies = QString::fromLatin1(line); + + QString cookies = jar->findCookies(urlStr, false, 0, 0).stripWhiteSpace(); + if (cookies != expectedCookies) + FAIL(urlStr+QString("\nGot '%1' expected '%2'") + .arg(cookies, expectedCookies)); +} + +static void processClear(QCString &line) +{ + if (line == "CONFIG") + clearConfig(); + else if (line == "COOKIES") + clearCookies(); + else + FAIL(QString("Unknown command 'CLEAR %1'").arg(line)); +} + +static void processConfig(QCString &line) +{ + QCString key; + popArg(key, line); + + if (key.isEmpty()) + FAIL(QString("Missing Key")); + + config->setGroup("Cookie Policy"); + config->writeEntry(key.data(), line.data()); + jar->loadConfig(config, false); +} + +static void processLine(QCString line) +{ + if (line.isEmpty()) + return; + + if (line[0] == '#') + { + if (line[1] == '#') + qWarning("%s", line.data()); + return; + } + + QCString command; + popArg(command, line); + if (command.isEmpty()) + return; + + if (command == "COOKIE") + processCookie(line); + else if (command == "CHECK") + processCheck(line); + else if (command == "CLEAR") + processClear(line); + else if (command == "CONFIG") + processConfig(line); + else if (command == "SAVE") + saveCookies(); + else + FAIL(QString("Unknown command '%1'").arg(command)); +} + +static void runRegression(const QString &filename) +{ + FILE *file = fopen(filename.local8Bit(), "r"); + if (!file) + FAIL(QString("Can't open '%1'").arg(filename)); + + char buf[4096]; + while (fgets(buf, sizeof(buf), file)) + { + int l = strlen(buf); + if (l) + { + l--; + buf[l] = 0; + } + processLine(buf); + } + qWarning("%s OK", filename.local8Bit().data()); +} + +int main(int argc, char *argv[]) +{ + QString arg1; + QCString arg2; + QString result; + + lastYear = new QCString(QString("Fri, 04-May-%1 01:00:00 GMT").arg(QDate::currentDate().year()-1).utf8()); + nextYear = new QCString(QString(" expires=Fri, 04-May-%1 01:00:00 GMT").arg(QDate::currentDate().year()+1).utf8()); + + KAboutData about("kcookietest", "kcookietest", "1.0", description, KAboutData::License_GPL, "(C) 2004 Waldo Bastian"); + KCmdLineArgs::init( argc, argv, &about); + + KCmdLineArgs::addCmdLineOptions( options ); + + KInstance a("kcookietest"); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + if (args->count() != 1) + KCmdLineArgs::usage(); + + jar = new KCookieJar; + + clearConfig(); + + QString file = args->url(0).path(); + runRegression(file); + return 0; +} |