Files
SingularityViewer/indra/llqtwebkit/qtwebkit_cookiejar/src/networkcookiejar.cpp
2012-09-08 02:03:07 -04:00

445 lines
13 KiB
C++

/* Copyright (c) 2006-2010, Linden Research, Inc.
*
* LLQtWebKit Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
* to you under the terms of the GNU General Public License, version 2.0
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in GPL-license.txt in this distribution, or online at
* http://secondlifegrid.net/technology-programs/license-virtual-world/viewerlicensing/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/technology-programs/license-virtual-world/viewerlicensing/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
* and agree to abide by those obligations.
*
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
*/
#include "networkcookiejar.h"
#include "networkcookiejar_p.h"
#include "twoleveldomains_p.h"
//#define NETWORKCOOKIEJAR_DEBUG
#ifndef QT_NO_DEBUG
// ^ Prevent being left on in a released product by accident
// qDebug any cookies that are rejected for further inspection
#define NETWORKCOOKIEJAR_LOGREJECTEDCOOKIES
#include <qdebug.h>
#endif
#include <qurl.h>
#include <qdatetime.h>
#if defined(NETWORKCOOKIEJAR_DEBUG)
#include <qdebug.h>
#endif
NetworkCookieJar::NetworkCookieJar(QObject *parent)
: QNetworkCookieJar(parent)
{
d = new NetworkCookieJarPrivate;
}
NetworkCookieJar::~NetworkCookieJar()
{
delete d;
}
static QStringList splitHost(const QString &host) {
QStringList parts = host.split(QLatin1Char('.'), QString::KeepEmptyParts);
// Remove empty components that are on the start and end
while (!parts.isEmpty() && parts.last().isEmpty())
parts.removeLast();
while (!parts.isEmpty() && parts.first().isEmpty())
parts.removeFirst();
return parts;
}
inline static bool shorterPaths(const QNetworkCookie &c1, const QNetworkCookie &c2)
{
return c2.path().length() < c1.path().length();
}
QList<QNetworkCookie> NetworkCookieJar::cookiesForUrl(const QUrl &url) const
{
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << "NetworkCookieJar::" << __FUNCTION__ << url;
#endif
// Generate split host
QString host = url.host();
if (url.scheme().toLower() == QLatin1String("file"))
host = QLatin1String("localhost");
QStringList urlHost = splitHost(host);
// Get all the cookies for url
QList<QNetworkCookie> cookies = d->tree.find(urlHost);
if (urlHost.count() > 2) {
int top = 2;
if (d->matchesBlacklist(urlHost.last()))
top = 3;
urlHost.removeFirst();
while (urlHost.count() >= top) {
cookies += d->tree.find(urlHost);
urlHost.removeFirst();
}
}
// Prevent doing anything expensive in the common case where
// there are no cookies to check
if (cookies.isEmpty())
return cookies;
QDateTime now = QDateTime::currentDateTime().toTimeSpec(Qt::UTC);
const QString urlPath = d->urlPath(url);
const bool isSecure = url.scheme().toLower() == QLatin1String("https");
QList<QNetworkCookie>::iterator i = cookies.begin();
for (; i != cookies.end();) {
if (!d->matchingPath(*i, urlPath)) {
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << __FUNCTION__ << "Ignoring cookie, path does not match" << *i << urlPath;
#endif
i = cookies.erase(i);
continue;
}
if (!isSecure && i->isSecure()) {
i = cookies.erase(i);
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << __FUNCTION__ << "Ignoring cookie, security mismatch"
<< *i << !isSecure;
#endif
continue;
}
if (!i->isSessionCookie() && now > i->expirationDate()) {
// remove now (expensive short term) because there will
// probably be many more cookiesForUrl calls for this host
d->tree.remove(splitHost(i->domain()), *i);
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << __FUNCTION__ << "Ignoring cookie, expiration issue"
<< *i << now;
#endif
i = cookies.erase(i);
continue;
}
++i;
}
// shorter paths should go first
qSort(cookies.begin(), cookies.end(), shorterPaths);
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << "NetworkCookieJar::" << __FUNCTION__ << "returning" << cookies.count();
qDebug() << cookies;
#endif
return cookies;
}
static const qint32 NetworkCookieJarMagic = 0xae;
QByteArray NetworkCookieJar::saveState () const
{
int version = 1;
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream << qint32(NetworkCookieJarMagic);
stream << qint32(version);
stream << d->tree;
return data;
}
bool NetworkCookieJar::restoreState(const QByteArray &state)
{
int version = 1;
QByteArray sd = state;
QDataStream stream(&sd, QIODevice::ReadOnly);
if (stream.atEnd())
return false;
qint32 marker;
qint32 v;
stream >> marker;
stream >> v;
if (marker != NetworkCookieJarMagic || v != version)
return false;
stream >> d->tree;
if (stream.status() != QDataStream::Ok) {
d->tree.clear();
return false;
}
return true;
}
/*!
Remove any session cookies or cookies that have expired.
*/
void NetworkCookieJar::endSession()
{
const QList<QNetworkCookie> cookies = d->tree.all();
QDateTime now = QDateTime::currentDateTime().toTimeSpec(Qt::UTC);
QList<QNetworkCookie>::const_iterator i = cookies.constBegin();
for (; i != cookies.constEnd();) {
if (i->isSessionCookie()
|| (!i->isSessionCookie() && now > i->expirationDate())) {
d->tree.remove(splitHost(i->domain()), *i);
}
++i;
}
}
static const int maxCookiePathLength = 1024;
bool NetworkCookieJar::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
{
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << "NetworkCookieJar::" << __FUNCTION__ << url;
qDebug() << cookieList;
#endif
QDateTime now = QDateTime::currentDateTime().toTimeSpec(Qt::UTC);
bool changed = false;
QString fullUrlPath = url.path();
QString defaultPath = fullUrlPath.mid(0, fullUrlPath.lastIndexOf(QLatin1Char('/')) + 1);
if (defaultPath.isEmpty())
defaultPath = QLatin1Char('/');
QString urlPath = d->urlPath(url);
foreach (QNetworkCookie cookie, cookieList) {
if (cookie.path().length() > maxCookiePathLength)
continue;
bool alreadyDead = !cookie.isSessionCookie() && cookie.expirationDate() < now;
if (cookie.path().isEmpty()) {
cookie.setPath(defaultPath);
}
// Matching the behavior of Firefox, no path checking is done when setting cookies
// Safari does something even odder, when that paths don't match it keeps
// the cookie, but changes the paths to the default path
#if 0
else if (!d->matchingPath(cookie, urlPath)) {
#ifdef NETWORKCOOKIEJAR_LOGREJECTEDCOOKIES
qDebug() << "NetworkCookieJar::" << __FUNCTION__
<< "Blocked cookie because: path doesn't match: " << cookie << url;
#endif
continue;
}
#endif
if (cookie.domain().isEmpty()) {
QString host = url.host().toLower();
if (host.isEmpty())
continue;
cookie.setDomain(host);
} else if (!d->matchingDomain(cookie, url)) {
#ifdef NETWORKCOOKIEJAR_LOGREJECTEDCOOKIES
qDebug() << "NetworkCookieJar::" << __FUNCTION__
<< "Blocked cookie because: domain doesn't match: " << cookie << url;
#endif
continue;
}
// replace/remove existing cookies
removeCookie(cookie);
// Notify derived class
onCookieSetFromURL(cookie, url, alreadyDead);
if (alreadyDead)
continue;
changed = true;
d->tree.insert(splitHost(cookie.domain()), cookie);
}
return changed;
}
QList<QNetworkCookie> NetworkCookieJar::allCookies() const
{
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << "NetworkCookieJar::" << __FUNCTION__;
#endif
return d->tree.all();
}
void NetworkCookieJar::clearCookies()
{
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << "NetworkCookieJar::" << __FUNCTION__;
#endif
d->tree.clear();
}
void NetworkCookieJar::setCookies(const QList<QNetworkCookie> &cookieList)
{
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << "NetworkCookieJar::" << __FUNCTION__ << cookieList.count();
#endif
QDateTime now = QDateTime::currentDateTime().toTimeSpec(Qt::UTC);
foreach (const QNetworkCookie &cookie, cookieList)
{
// If a matching cookie is already in the list, remove it.
removeCookie(cookie);
if(!cookie.isSessionCookie() && cookie.expirationDate() < now)
{
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << "NetworkCookieJar::" << __FUNCTION__ << "removing cookie: " << cookie;
#endif
// This cookie has expired -- don't re-add it
}
else
{
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << "NetworkCookieJar::" << __FUNCTION__ << "adding cookie: " << cookie;
#endif
// this cookie has not expired -- save it
d->tree.insert(splitHost(cookie.domain()), cookie);
}
}
}
void NetworkCookieJar::setAllCookies(const QList<QNetworkCookie> &cookieList)
{
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << "NetworkCookieJar::" << __FUNCTION__ << cookieList.count();
#endif
clearCookies();
setCookies(cookieList);
}
void NetworkCookieJar::removeCookie(const QNetworkCookie &cookie)
{
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << "NetworkCookieJar::" << __FUNCTION__ << "removing cookie: " << cookie;
#endif
// If a cookie with the matching domain, path, and name exists in the cookiejar, remove it.
QString domain = cookie.domain();
Q_ASSERT(!domain.isEmpty());
QStringList urlHost = splitHost(domain);
QList<QNetworkCookie> cookies = d->tree.find(urlHost);
QList<QNetworkCookie>::const_iterator it = cookies.constBegin();
for (; it != cookies.constEnd(); ++it)
{
if (cookie.name() == it->name() &&
domain == it->domain() &&
cookie.path() == it->path())
{
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << "NetworkCookieJar::" << __FUNCTION__ << "found matching cookie: " << *it;
#endif
d->tree.remove(urlHost, *it);
break;
}
}
}
void NetworkCookieJar::dump()
{
#if defined(NETWORKCOOKIEJAR_DEBUG)
qDebug() << "NetworkCookieJar::" << __FUNCTION__ << "dumping all cookies: ";
QList<QNetworkCookie> cookies = allCookies();
foreach (const QNetworkCookie &cookie, cookies)
{
qDebug() << " " << cookie;
}
#endif
}
QString NetworkCookieJarPrivate::urlPath(const QUrl &url) const
{
QString urlPath = url.path();
if (!urlPath.endsWith(QLatin1Char('/')))
urlPath += QLatin1Char('/');
return urlPath;
}
bool NetworkCookieJarPrivate::matchingPath(const QNetworkCookie &cookie, const QString &urlPath) const
{
QString cookiePath = cookie.path();
if (!cookiePath.endsWith(QLatin1Char('/')))
cookiePath += QLatin1Char('/');
return urlPath.startsWith(cookiePath);
}
bool NetworkCookieJarPrivate::matchesBlacklist(const QString &string) const
{
if (!setSecondLevelDomain) {
// Alternatively to save a little bit of ram we could just
// use bsearch on twoLevelDomains in place
for (int j = 0; twoLevelDomains[j]; ++j)
secondLevelDomains += QLatin1String(twoLevelDomains[j]);
setSecondLevelDomain = true;
}
QStringList::const_iterator i =
qBinaryFind(secondLevelDomains.constBegin(), secondLevelDomains.constEnd(), string);
return (i != secondLevelDomains.constEnd());
}
bool NetworkCookieJarPrivate::matchingDomain(const QNetworkCookie &cookie, const QUrl &url) const
{
QString domain = cookie.domain().simplified().toLower();
domain.remove(QLatin1Char(' '));
QStringList parts = splitHost(domain);
if (parts.isEmpty())
return false;
// When there is only one part only file://localhost/ is accepted
if (parts.count() == 1) {
QString s = parts.first();
if (parts.first() != QLatin1String("localhost"))
return false;
if (url.scheme().toLower() == QLatin1String("file"))
return true;
}
// Check for blacklist
if (parts.count() == 2 && matchesBlacklist(parts.last()))
return false;
QStringList urlParts = url.host().toLower().split(QLatin1Char('.'), QString::SkipEmptyParts);
if (urlParts.isEmpty())
return false;
while (urlParts.count() > parts.count())
urlParts.removeFirst();
for (int j = 0; j < urlParts.count(); ++j) {
if (urlParts.at(j) != parts.at(j)) {
return false;
}
}
return true;
}
void NetworkCookieJar::setSecondLevelDomains(const QStringList &secondLevelDomains)
{
d->setSecondLevelDomain = true;
d->secondLevelDomains = secondLevelDomains;
qSort(d->secondLevelDomains);
}
void NetworkCookieJar::onCookieSetFromURL(const QNetworkCookie &cookie, const QUrl &url, bool already_dead)
{
Q_UNUSED(cookie);
Q_UNUSED(url);
Q_UNUSED(already_dead);
// Derived classes can use this to track cookie changes.
}