Adds support for multiple Cookie Jars.

Previously, there was a single global cookie jar shared between all web pages.
Now, one can have separate cookie jars for different web pages.

Makes CookieJar a normal class, not a singleton.
Moves many public CookieJar methods to public slots.
Adds default cookie jar to Phantom.
Adds the CookieJar module that provides access to cookie jars in javascript.
Adds cookie jar module tests.

Usage:
var jar = require('cookiejar').create();
var webpage = require('webpage').create();
webpage.cookieJar = jar;
...
webpage.close();
jar.close();

JS API changes:
Webpage:
    var jar = page.cookieJar; -- assigns 'jar' the given webpage's cookie jar.
    page.cookiejar = jar; -- sets 'jar' as the given webpage's cookie jar.
CookieJar:
    var jar = require('cookiejar').create(path)
        creates a cookie jar with persistent storage at the given file path
        (path not mandatory).
    var cookies = jar.cookies; -- assign's 'jar' the list of cookies in the
        cookie jar.
    jar.cookies = [c1, c2]; -- sets the cookie jar's cookies as the ones in the
        list.
    jar.addCookie(cookie) -- adds cookie 'cookie' to the cookie jar.

https://github.com/ariya/phantomjs/issues/11417
1.x
Joseph Rollinson 2013-06-18 14:09:36 -05:00 committed by Ariya Hidayat
parent 3ae9d38d40
commit 244cf251cd
14 changed files with 286 additions and 50 deletions

View File

@ -78,32 +78,22 @@ QDataStream &operator>>(QDataStream &stream, QList<QNetworkCookie> &list)
}
QT_END_NAMESPACE
// private:
// public:
CookieJar::CookieJar(QString cookiesFile, QObject *parent)
: QNetworkCookieJar(parent)
, m_cookieStorage(new QSettings(cookiesFile, QSettings::IniFormat, this))
, m_enabled(true)
{
load();
}
// public:
CookieJar *CookieJar::instance(QString cookiesFile)
{
static CookieJar *singleton = NULL;
if (!singleton) {
if (cookiesFile.isEmpty()) {
qDebug() << "CookieJar - Created but will not store cookies (use option '--cookies-file=<filename>' to enable persistent cookie storage)";
} else {
qDebug() << "CookieJar - Created and will store cookies in:" << cookiesFile;
}
// Create singleton and assign ownershipt to the Phantom singleton object
// NOTE: First time this is done is when we set "once and for all" the Cookies' File
singleton = new CookieJar(cookiesFile, Phantom::instance());
if (cookiesFile == "") {
m_cookieStorage = 0;
qDebug() << "CookieJar - Created but will not store cookies (use option '--cookies-file=<filename>' to enable persistent cookie storage)";
} else {
m_cookieStorage = new QSettings(cookiesFile, QSettings::IniFormat, this);
load();
qDebug() << "CookieJar - Created and will store cookies in:" << cookiesFile;
}
return singleton;
}
// private:
CookieJar::~CookieJar()
{
// On destruction, before saving, clear all the session cookies
@ -390,6 +380,11 @@ bool CookieJar::isEnabled() const
return m_enabled;
}
void CookieJar::close()
{
deleteLater();
}
// private:
bool CookieJar::purgeExpiredCookies()
{
@ -457,7 +452,9 @@ void CookieJar::save()
#endif
// Store cookies
m_cookieStorage->setValue(QLatin1String("cookies"), QVariant::fromValue<QList<QNetworkCookie> >(allCookies()));
if (m_cookieStorage) {
m_cookieStorage->setValue(QLatin1String("cookies"), QVariant::fromValue<QList<QNetworkCookie> >(allCookies()));
}
}
}
@ -468,7 +465,9 @@ void CookieJar::load()
qRegisterMetaTypeStreamOperators<QList<QNetworkCookie> >("QList<QNetworkCookie>");
// Load all the cookies
setAllCookies(qvariant_cast<QList<QNetworkCookie> >(m_cookieStorage->value(QLatin1String("cookies"))));
if (m_cookieStorage) {
setAllCookies(qvariant_cast<QList<QNetworkCookie> >(m_cookieStorage->value(QLatin1String("cookies"))));
}
// If any cookie has expired since last execution, purge and save before going any further
if (purgeExpiredCookies()) {

View File

@ -40,35 +40,36 @@ class CookieJar: public QNetworkCookieJar
{
Q_OBJECT
private:
CookieJar(QString cookiesFile, QObject *parent = NULL);
public:
static CookieJar *instance(QString cookiesFile = QString());
CookieJar(QString cookiesFile, QObject *parent = NULL);
virtual ~CookieJar();
bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl & url);
QList<QNetworkCookie> cookiesForUrl (const QUrl & url) const;
bool addCookie(const QNetworkCookie &cookie, const QString &url = QString());
bool addCookieFromMap(const QVariantMap &cookie, const QString &url = QString());
bool addCookies(const QList<QNetworkCookie> &cookiesList, const QString &url = QString());
bool addCookiesFromMap(const QVariantList &cookiesList, const QString &url = QString());
QList<QNetworkCookie> cookies(const QString &url = QString()) const;
QVariantList cookiesToMap(const QString &url = QString()) const;
QNetworkCookie cookie(const QString &name, const QString &url = QString()) const;
QVariantMap cookieToMap(const QString &name, const QString &url = QString()) const;
bool deleteCookie(const QString &name, const QString &url = QString());
bool deleteCookies(const QString &url = QString());
void clearCookies();
void enable();
void disable();
bool isEnabled() const;
public slots:
bool addCookieFromMap(const QVariantMap &cookie, const QString &url = QString());
bool addCookiesFromMap(const QVariantList &cookiesList, const QString &url = QString());
QVariantList cookiesToMap(const QString &url = QString()) const;
QVariantMap cookieToMap(const QString &name, const QString &url = QString()) const;
bool deleteCookie(const QString &name, const QString &url = QString());
void clearCookies();
void close();
private slots:
bool purgeExpiredCookies();
bool purgeSessionCookies();

68
src/modules/cookiejar.js Normal file
View File

@ -0,0 +1,68 @@
/*jslint sloppy: true, nomen: true */
/*global exports:true */
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2013 Joseph Rollinson, jtrollinson@gmail.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* Takes in a QtCookieJar and decorates it with useful functions. */
function decorateCookieJar(jar) {
/* Allows one to add a cookie to the cookie jar from inside JavaScript */
jar.addCookie = function(cookie) {
jar.addCookieFromMap(cookie);
};
/* Getting and Setting jar.cookies gets and sets all the cookies in the
* cookie jar.
*/
jar.__defineGetter__('cookies', function() {
return this.cookiesToMap();
});
jar.__defineSetter__('cookies', function(cookies) {
this.addCookiesFromMap(cookies);
});
return jar;
}
/* Creates and decorates a new cookie jar.
* path is the file path where Phantomjs will store the cookie jar persistently.
* path is not mandatory.
*/
exports.create = function (path) {
if (arguments.length < 1) {
path = "";
}
return decorateCookieJar(phantom.createCookieJar(path));
};
/* Exports the decorateCookieJar function */
exports.decorate = decorateCookieJar;

View File

@ -414,6 +414,20 @@ function decorateNewPage(opts, page) {
this.evaluate.apply(this, args);
};
/**
* get cookie jar for the page
*/
page.__defineGetter__("cookieJar", function() {
return require("cookiejar").decorate(this.cookieJar());
});
/**
* set cookie jar for the page
*/
page.__defineSetter__("cookieJar", function(cookieJar) {
this.setCookieJarFromQObject(cookieJar);
});
/**
* get cookies of the page
*/

View File

@ -120,8 +120,6 @@ NetworkAccessManager::NetworkAccessManager(QObject *parent, const Config *config
, m_networkDiskCache(0)
, m_sslConfiguration(QSslConfiguration::defaultConfiguration())
{
setCookieJar(CookieJar::instance());
if (config->diskCacheEnabled()) {
m_networkDiskCache = new QNetworkDiskCache(this);
m_networkDiskCache->setCacheDirectory(QDesktopServices::storageLocation(QDesktopServices::CacheLocation));
@ -195,8 +193,9 @@ void NetworkAccessManager::setCookieJar(QNetworkCookieJar *cookieJar)
QNetworkAccessManager::setCookieJar(cookieJar);
// Remove NetworkAccessManager's ownership of this CookieJar and
// pass it to the PhantomJS Singleton object.
// CookieJar is a SINGLETON, shouldn't be deleted when
// the NetworkAccessManager is deleted but only when we shutdown.
// CookieJar is shared between multiple instances of NetworkAccessManager.
// It shouldn't be deleted when the NetworkAccessManager is deleted, but
// only when close is called on the cookie jar.
cookieJar->setParent(Phantom::instance());
}

View File

@ -97,9 +97,10 @@ void Phantom::init()
}
// Initialize the CookieJar
CookieJar::instance(m_config.cookiesFile());
m_defaultCookieJar = new CookieJar(m_config.cookiesFile());
m_page = new WebPage(this, QUrl::fromLocalFile(m_config.scriptFile()));
m_page->setCookieJar(m_defaultCookieJar);
m_pages.append(m_page);
QString proxyType = m_config.proxyType();
@ -290,15 +291,15 @@ bool Phantom::printDebugMessages() const
bool Phantom::areCookiesEnabled() const
{
return CookieJar::instance()->isEnabled();
return m_defaultCookieJar->isEnabled();
}
void Phantom::setCookiesEnabled(const bool value)
{
if (value) {
CookieJar::instance()->enable();
m_defaultCookieJar->enable();
} else {
CookieJar::instance()->disable();
m_defaultCookieJar->disable();
}
}
@ -308,9 +309,14 @@ bool Phantom::webdriverMode() const
}
// public slots:
QObject *Phantom::createCookieJar(const QString& filePath) {
return new CookieJar(filePath, this);
}
QObject *Phantom::createWebPage()
{
WebPage *page = new WebPage(this);
page->setCookieJar(m_defaultCookieJar);
// Store pointer to the page for later cleanup
m_pages.append(page);
@ -436,33 +442,33 @@ void Phantom::onInitialized()
bool Phantom::setCookies(const QVariantList &cookies)
{
// Delete all the cookies from the CookieJar
CookieJar::instance()->clearCookies();
m_defaultCookieJar->clearCookies();
// Add a new set of cookies
return CookieJar::instance()->addCookiesFromMap(cookies);
return m_defaultCookieJar->addCookiesFromMap(cookies);
}
QVariantList Phantom::cookies() const
{
// Return all the Cookies in the CookieJar, as a list of Maps (aka JSON in JS space)
return CookieJar::instance()->cookiesToMap();
return m_defaultCookieJar->cookiesToMap();
}
bool Phantom::addCookie(const QVariantMap &cookie)
{
return CookieJar::instance()->addCookieFromMap(cookie);
return m_defaultCookieJar->addCookieFromMap(cookie);
}
bool Phantom::deleteCookie(const QString &cookieName)
{
if (!cookieName.isEmpty()) {
return CookieJar::instance()->deleteCookie(cookieName);
return m_defaultCookieJar->deleteCookie(cookieName);
}
return false;
}
void Phantom::clearCookies()
{
CookieJar::instance()->clearCookies();
m_defaultCookieJar->clearCookies();
}

View File

@ -39,6 +39,7 @@
#include "config.h"
#include "system.h"
#include "childprocess.h"
#include "cookiejar.h"
class WebPage;
class CustomPage;
@ -108,6 +109,7 @@ public:
Q_INVOKABLE QObject *_createChildProcess();
public slots:
QObject *createCookieJar(const QString &filePath);
QObject *createWebPage();
QObject *createWebServer();
QObject *createFilesystem();
@ -193,6 +195,7 @@ private:
QList<QPointer<WebPage> > m_pages;
QList<QPointer<WebServer> > m_servers;
Config m_config;
CookieJar *m_defaultCookieJar;
friend class CustomPage;
};

View File

@ -53,6 +53,7 @@ OTHER_FILES += \
modules/webpage.js \
modules/webserver.js \
modules/child_process.js \
modules/cookiejar.js \
repl.js
include(gif/gif.pri)

View File

@ -10,6 +10,7 @@
<file>modules/system.js</file>
<file>modules/child_process.js</file>
<file>modules/_coffee-script.js</file>
<file>modules/cookiejar.js</file>
<file>repl.js</file>
<file>coffee-script/package.json</file>

View File

@ -110,6 +110,10 @@ public:
}
}
void setCookieJar(CookieJar *cookieJar) {
m_cookieJar = cookieJar;
}
public slots:
bool shouldInterruptJavaScript() {
m_webPage->javascriptInterrupt();
@ -219,6 +223,7 @@ protected:
newPage = new WebPage(Phantom::instance());
Phantom::instance()->m_pages.append(newPage);
}
newPage->setCookieJar(m_cookieJar);
// Apply default settings
newPage->applySettings(Phantom::instance()->defaultPageSettings());
@ -235,6 +240,7 @@ private:
QString m_userAgent;
QStringList m_uploadFiles;
friend class WebPage;
CookieJar *m_cookieJar;
};
@ -775,36 +781,51 @@ QVariantMap WebPage::customHeaders() const
return m_networkAccessManager->customHeaders();
}
void WebPage::setCookieJar(CookieJar *cookieJar) {
m_cookieJar = cookieJar;
m_customWebPage->setCookieJar(m_cookieJar);
m_networkAccessManager->setCookieJar(m_cookieJar);
}
void WebPage::setCookieJarFromQObject(QObject *cookieJar) {
setCookieJar(qobject_cast<CookieJar *>(cookieJar));
}
CookieJar *WebPage::cookieJar()
{
return m_cookieJar;
}
bool WebPage::setCookies(const QVariantList &cookies)
{
// Delete all the cookies for this URL
CookieJar::instance()->deleteCookies(this->url());
m_cookieJar->deleteCookies(this->url());
// Add a new set of cookies foor this URL
return CookieJar::instance()->addCookiesFromMap(cookies, this->url());
return m_cookieJar->addCookiesFromMap(cookies, this->url());
}
QVariantList WebPage::cookies() const
{
// Return all the Cookies visible to this Page, as a list of Maps (aka JSON in JS space)
return CookieJar::instance()->cookiesToMap(this->url());
return m_cookieJar->cookiesToMap(this->url());
}
bool WebPage::addCookie(const QVariantMap &cookie)
{
return CookieJar::instance()->addCookieFromMap(cookie, this->url());
return m_cookieJar->addCookieFromMap(cookie, this->url());
}
bool WebPage::deleteCookie(const QString &cookieName)
{
if (!cookieName.isEmpty()) {
return CookieJar::instance()->deleteCookie(cookieName, this->url());
return m_cookieJar->deleteCookie(cookieName, this->url());
}
return false;
}
bool WebPage::clearCookies()
{
return CookieJar::instance()->deleteCookies(this->url());
return m_cookieJar->deleteCookies(this->url());
}
void WebPage::openUrl(const QString &address, const QVariant &op, const QVariantMap &settings)

View File

@ -36,6 +36,8 @@
#include <QWebPage>
#include <QWebFrame>
#include "cookiejar.h"
class Config;
class CustomPage;
class WebpageCallbacks;
@ -357,6 +359,21 @@ public slots:
*/
QString currentFrameName() const;
/**
* Allows to set cookie jar for this page.
*/
void setCookieJar(CookieJar *cookieJar);
/**
* Allows to set cookie jar in through QtWebKit Bridge
*/
void setCookieJarFromQObject(QObject *cookieJar);
/**
* Returns the CookieJar object
*/
CookieJar *cookieJar();
/**
* Allows to set cookies by this Page, at the current URL.
* This means that loading new URLs, causes the cookies to change dynamically
@ -520,6 +537,7 @@ private:
bool m_ownsPages;
int m_loadingProgress;
bool m_shouldInterruptJs;
CookieJar *m_cookieJar;
friend class Phantom;
friend class CustomPage;

100
test/cookiejar-spec.js Normal file
View File

@ -0,0 +1,100 @@
describe("CookieJar object", function() {
var jar = require('cookiejar').create();
it("should be creatable", function() {
expect(typeof jar).toEqual('object');
expect(jar).toNotEqual(null);
});
expectHasProperty(jar, 'cookies');
expectHasFunction(jar, 'addCookie');
expectHasFunction(jar, 'deleteCookie');
expectHasFunction(jar, 'clearCookies');
it("should add a cookie and then remove it", function() {
var cookie = {
'name' : 'Valid-Cookie-Name',
'value' : 'Valid-Cookie-Value',
'domain' : 'localhost',
'path' : '/foo',
'httponly' : true,
'secure' : false
};
jar.addCookie(cookie);
var cookies = jar.cookies;
expect(cookies.length).toEqual(1);
expect(jar.deleteCookie('Valid-Cookie-Name')).toBe(true);
expect(jar.cookies.length).toBe(0);
});
it("should set and get cookies with .cookies", function() {
var cookies = [{
'name' : 'Valid-Cookie-Name',
'value' : 'Valid-Cookie-Value',
'domain' : 'localhost',
'path' : '/foo',
'httponly' : true,
'secure' : false
},{
'name' : 'Valid-Cookie-Name-Sec',
'value' : 'Valid-Cookie-Value-Sec',
'domain' : 'localhost',
'path' : '/foo',
'httponly' : true,
'secure' : false,
'expires' : new Date().getTime() + 3600 //< expires in 1h
}];
jar.cookies = cookies;
expect(jar.cookies.length).toBe(2);
jar.clearCookies();
expect(jar.cookies.length).toEqual(0);
});
it("should be separate cookie jars", function() {
var jar1 = require('cookiejar').create();
var jar2 = require('cookiejar').create();
var cookie1 = {
'name' : 'Valid-Cookie-Name-1',
'value' : 'Valid-Cookie-Value',
'domain' : 'localhost',
'path' : '/foo',
'httponly' : true,
'secure' : false
};
var cookie2 = {
'name' : 'Valid-Cookie-Name-2',
'value' : 'Valid-Cookie-Value',
'domain' : 'localhost',
'path' : '/foo',
'httponly' : true,
'secure' : false
};
jar1.addCookie(cookie1);
expect(jar1.cookies.length).toBe(1);
expect(jar2.cookies.length).toBe(0);
jar2.addCookie(cookie2);
expect(jar1.deleteCookie('Valid-Cookie-Name-1')).toBe(true);
expect(jar1.cookies.length).toBe(0);
expect(jar2.cookies.length).toBe(1);
jar1.close();
jar2.close();
});
});

View File

@ -11,6 +11,10 @@ describe("require()", function() {
should.exist(require('webserver').create);
});
it("loads 'cookiejar' native module", function() {
should.exist(require('cookiejar').create);
});
it("loads 'system' native module", function() {
require('system').platform.should.equal('phantomjs');
});

View File

@ -70,6 +70,7 @@ phantom.injectJs("./fs-spec-03.js"); //< Filesystem Specs 03 (Paths)
phantom.injectJs("./fs-spec-04.js"); //< Filesystem Specs 04 (Tests)
phantom.injectJs("./system-spec.js");
phantom.injectJs("./webkit-spec.js");
phantom.injectJs("./cookiejar-spec.js");
require("./module_spec.js");
require("./require/require_spec.js");
require("./cjk-text-codecs.js");