Redesign the Cookies API

Addresses [Issue #761](http://code.google.com/p/phantomjs/issues/detail?id=761).

This is a squash of 5 commits.

1. Complete reimplementation of the CookieJar and the Cookie API - Part 1.

The save/load mechanism is inspired by the Qt Demo "browser".
More info: http://doc.qt.nokia.com/4.7-snapshot/demos-browser.html

2. Making the CookieJar a singleton.

We need to have multiple NetworkAccessManager to monitor the network activity of the page,
but we also need to maintain 1 CookieJar: in this way we now have a shared bucket of Cookies.

3. Exposing the new Cookies API to the JS space.

* Updated the completions.
* Ensured backward compatibility of the API.
* It's now possible to "phantom.cookieEnabled = false" to disable cookies completely.
* New methods: addCookie, deleteCookie, clearCookies

4. Provided some internal Doc for the new Cookies API

5. Ensuring the "page.deleteCookie(name)" method works only if a cookie name is given.
1.7
Ivan De Marino 2012-09-05 23:33:18 -07:00
parent 5cb68a1585
commit bd21373732
10 changed files with 586 additions and 148 deletions

View File

@ -2,6 +2,7 @@
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2012 Ivan De Marino <ivan.de.marino@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
@ -27,147 +28,372 @@
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "phantom.h"
#include "config.h"
#include "cookiejar.h"
#include <QDateTime>
#include <QSettings>
#include <QStringList>
#include <QTimer>
CookieJar::CookieJar(QString cookiesFile)
: QNetworkCookieJar()
#define COOKIE_JAR_VERSION 1
// Operators needed for Cookie Serialization
QT_BEGIN_NAMESPACE
QDataStream &operator<<(QDataStream &stream, const QList<QNetworkCookie> &list)
{
m_cookiesFile = cookiesFile;
stream << COOKIE_JAR_VERSION;
stream << quint32(list.size());
for (int i = 0; i < list.size(); ++i)
stream << list.at(i).toRawForm();
return stream;
}
bool CookieJar::setCookiesFromUrl(const QList<QNetworkCookie> & cookieList, const QUrl & url)
QDataStream &operator>>(QDataStream &stream, QList<QNetworkCookie> &list)
{
QSettings settings(m_cookiesFile, QSettings::IniFormat);
list.clear();
settings.beginGroup(url.host());
for (QList<QNetworkCookie>::const_iterator i = cookieList.begin(); i != cookieList.end(); i++) {
settings.setValue((*i).name(), QString((*i).value()));
quint32 version;
stream >> version;
if (version != COOKIE_JAR_VERSION)
return stream;
quint32 count;
stream >> count;
for(quint32 i = 0; i < count; ++i)
{
QByteArray value;
stream >> value;
QList<QNetworkCookie> newCookies = QNetworkCookie::parseCookies(value);
if (newCookies.count() == 0 && value.length() != 0) {
qWarning() << "CookieJar: Unable to parse saved cookie:" << value;
}
for (int j = 0; j < newCookies.count(); ++j)
list.append(newCookies.at(j));
if (stream.atEnd())
break;
}
return stream;
}
QT_END_NAMESPACE
settings.sync();
// updating cookies from the server.
QNetworkCookieJar::setCookiesFromUrl(cookieList, url);
return true;
// private:
CookieJar::CookieJar(QString cookiesFile, QObject *parent)
: QNetworkCookieJar(parent)
, m_cookieStorage(new QSettings(cookiesFile, QSettings::IniFormat, this))
, m_enabled(true)
{
QTimer::singleShot(0, this, SLOT(load()));
}
QList<QNetworkCookie> CookieJar::cookiesForUrl(const QUrl & url) const
// public:
CookieJar *CookieJar::instance(QString cookiesFile)
{
QSettings settings(m_cookiesFile, QSettings::IniFormat);
QList<QNetworkCookie> cookieList;
settings.beginGroup(url.host());
QStringList keys = settings.childKeys();
for (QStringList::iterator i = keys.begin(); i != keys.end(); i++) {
cookieList.push_back(QNetworkCookie((*i).toLocal8Bit(), settings.value(*i).toByteArray()));
static CookieJar *singleton = NULL;
if (!singleton) {
if (cookiesFile.isEmpty()) {
qDebug() << "Creating CookieJar, but no file to store cookies was provided (use '--cookies-file=<filename>')";
} else {
qDebug() << "Creating CookieJar, using file:" << 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());
}
// sending cookies to the server.
QList<QNetworkCookie> allCookies = QNetworkCookieJar::cookiesForUrl(url);
for (QList<QNetworkCookie>::const_iterator i = allCookies.begin(); i != allCookies.end(); i++) {
cookieList.push_back((*i));
}
return cookieList;
return singleton;
}
void CookieJar::setCookies(const QVariantList &cookies)
CookieJar::~CookieJar()
{
QList<QNetworkCookie> newCookies;
for (int i = 0; i < cookies.size(); ++i) {
QNetworkCookie nc;
QVariantMap cookie = cookies.at(i).toMap();
// On destruction, before saving, clear all the session cookies
purgeSessionCookies();
save();
}
//
// The field of domain and cookie name/value MUST be set, otherwise skip it.
//
if (cookie["domain"].isNull() || cookie["domain"].toString().isEmpty()
|| cookie["name"].isNull() || cookie["name"].toString().isEmpty()
|| cookie["value"].isNull()
) {
continue;
} else {
nc.setDomain(cookie["domain"].toString());
nc.setName(cookie["name"].toByteArray());
nc.setValue(cookie["value"].toByteArray());
}
bool CookieJar::setCookiesFromUrl(const QList<QNetworkCookie> & cookieList, const QUrl &url)
{
// Update cookies in memory
if (isEnabled() && QNetworkCookieJar::setCookiesFromUrl(cookieList, url)) {
// Update cookies in permanent storage, because at least 1 changed
save();
return true;
}
// No changes occurred
return false;
}
if (cookie["path"].isNull() || cookie["path"].toString().isEmpty()) {
nc.setPath("/");
} else {
nc.setPath(cookie["path"].toString());
}
QList<QNetworkCookie> CookieJar::cookiesForUrl(const QUrl &url) const
{
if (isEnabled()) {
return QNetworkCookieJar::cookiesForUrl(url);
}
// The CookieJar is disabled: don't return any cookie
return QList<QNetworkCookie>();
}
if (cookie["httponly"].isNull()) {
nc.setHttpOnly(false);
} else {
nc.setHttpOnly(cookie["httponly"].toBool());
}
void CookieJar::addCookie(const QNetworkCookie &cookie, const QString &url)
{
if (isEnabled()) {
// Save a single cookie
setCookiesFromUrl(
QList<QNetworkCookie>() << cookie, //< unfortunately, "setCookiesFromUrl" requires a list
!url.isEmpty() ?
url : //< use given URL
QUrl( //< mock-up a URL
(cookie.isSecure() ? "https://" : "http://") + //< URL protocol
QString(cookie.domain().startsWith('.') ? "www" : "") + cookie.domain() + //< URL domain
(cookie.path().isEmpty() ? "/" : cookie.path()))); //< URL path
}
}
if (cookie["secure"].isNull()) {
nc.setSecure(false);
} else {
nc.setSecure(cookie["secure"].toBool());
}
void CookieJar::addCookieFromMap(const QVariantMap &cookie, const QString &url)
{
QNetworkCookie newCookie;
// The cookie must have "domain", "name" and "value"
if (!cookie["domain"].isNull() && !cookie["domain"].toString().isEmpty() &&
!cookie["name"].isNull() && !cookie["name"].toString().isEmpty() &&
!cookie["value"].isNull()
) {
newCookie.setDomain(cookie["domain"].toString());
newCookie.setName(cookie["name"].toByteArray());
newCookie.setValue(cookie["value"].toByteArray());
newCookie.setPath((cookie["path"].isNull() || cookie["path"].toString().isEmpty()) ?
"/" :cookie["path"].toString());
newCookie.setHttpOnly(cookie["httponly"].isNull() ? false : cookie["httponly"].toBool());
newCookie.setSecure(cookie["secure"].isNull() ? false : cookie["secure"].toBool());
if (!cookie["expires"].isNull()) {
QString datetime = cookie["expires"].toString().replace(" GMT", "");
QDateTime expires = QDateTime::fromString(datetime, "ddd, dd MMM yyyy hh:mm:ss");
if (expires.isValid()) {
nc.setExpirationDate(expires);
newCookie.setExpirationDate(expires);
}
}
newCookies.append(nc);
addCookie(newCookie, url);
}
this->setAllCookies(newCookies);
}
QVariantList CookieJar::cookies() const
void CookieJar::addCookies(const QList<QNetworkCookie> &cookiesList, const QString &url)
{
QVariantList returnCookies;
QList<QNetworkCookie> allCookies = this->allCookies();
for (QList<QNetworkCookie>::const_iterator i = allCookies.begin(); i != allCookies.end(); i++) {
QVariantMap cookie;
for (int i = cookiesList.length() -1; i >=0; --i) {
addCookie(cookiesList.at(i), url);
}
}
cookie["domain"] = QVariant((*i).domain());
cookie["name"] = QVariant(QString((*i).name()));
cookie["value"] = QVariant(QString((*i).value()));
void CookieJar::addCookiesFromMap(const QVariantList &cookiesList, const QString &url)
{
for (int i = cookiesList.length() -1; i >= 0; --i) {
addCookieFromMap(cookiesList.at(i).toMap(), url);
}
}
if ((*i).path().isNull() || (*i).path().isEmpty()) {
cookie["path"] = QVariant("/");
} else {
cookie["path"] = QVariant((*i).path());
QList<QNetworkCookie> CookieJar::cookies(const QString &url) const
{
if (url.isEmpty()) {
// No url provided: return all the cookies in this CookieJar
return allCookies();
} else {
// Return ONLY the cookies that match this URL
return cookiesForUrl(url);
}
}
QVariantList CookieJar::cookiesToMap(const QString &url) const
{
QVariantList result;
QNetworkCookie c;
QVariantMap cookie;
QList<QNetworkCookie> cookiesList = cookies(url);
for (int i = cookiesList.length() -1; i >= 0; --i) {
c = cookiesList.at(i);
cookie["domain"] = QVariant(c.domain());
cookie["name"] = QVariant(QString(c.name()));
cookie["value"] = QVariant(QString(c.value()));
cookie["path"] = (c.path().isNull() || c.path().isEmpty()) ? QVariant("/") : QVariant(c.path());
cookie["httponly"] = QVariant(c.isHttpOnly());
cookie["secure"] = QVariant(c.isSecure());
if (c.expirationDate().isValid()) {
cookie["expires"] = QVariant(QString(c.expirationDate().toString("ddd, dd MMM yyyy hh:mm:ss")).append(" GMT"));
}
if ((*i).isHttpOnly()) {
cookie["httponly"] = QVariant(true);
} else {
cookie["httponly"] = QVariant(false);
}
if ((*i).isSecure()) {
cookie["secure"] = QVariant(true);
} else {
cookie["secure"] = QVariant(false);
}
if ((*i).expirationDate().isValid()) {
cookie["expires"] = QVariant(QString((*i).expirationDate().toString("ddd, dd MMM yyyy hh:mm:ss")).append(" GMT"));
}
returnCookies.append(cookie);
result.append(cookie);
}
return returnCookies;
return result;
}
QNetworkCookie CookieJar::cookie(const QString &name, const QString &url) const
{
QList<QNetworkCookie> cookiesList = cookies(url);
for (int i = cookiesList.length() -1; i >= 0; --i) {
if (cookiesList.at(i).name() == name) {
return cookiesList.at(i);
}
}
return QNetworkCookie();
}
QVariantMap CookieJar::cookieToMap(const QString &name, const QString &url) const
{
QVariantMap cookie;
QVariantList cookiesList = cookiesToMap(url);
for (int i = cookiesList.length() -1; i >= 0; --i) {
cookie = cookiesList.at(i).toMap();
if (cookie["name"].toString() == name) {
return cookie;
}
}
return QVariantMap();
}
void CookieJar::deleteCookie(const QString &name, const QString &url)
{
if (isEnabled()) {
QNetworkCookie cookie;
// For all the cookies that are visible to this URL
QList<QNetworkCookie> cookiesList = cookies(url);
for (int i = cookiesList.length() -1; i >= 0; --i) {
if (cookiesList.at(i).name() == name || name.isEmpty()) {
// Remove item from list
cookie = cookiesList.takeAt(i);
// If we found the right cookie, mark it expired so it gets purged
cookie.setExpirationDate(QDateTime::currentDateTime().addYears(-1));
// Add it back to the list
cookiesList.append(cookie);
// Set a new list of cookies for this URL
setCookiesFromUrl(cookiesList, url);
if (!name.isEmpty()) {
// Only one cookie was supposed to be deleted: we are done here!
return;
}
}
}
}
}
void CookieJar::deleteCookies(const QString &url)
{
if (isEnabled()) {
if (url.isEmpty()) {
// No URL provided: delete ALL the cookies in the CookieJar
clearCookies();
} else {
// No cookie name provided: delete all the cookies visible by this URL
deleteCookie("", url);
}
}
}
void CookieJar::clearCookies()
{
if (isEnabled()) {
setAllCookies(QList<QNetworkCookie>());
}
}
void CookieJar::enable()
{
m_enabled = true;
}
void CookieJar::disable()
{
m_enabled = false;
}
bool CookieJar::isEnabled() const
{
return m_enabled;
}
// private:
void CookieJar::save()
{
if (isEnabled()) {
// Get rid of all the Cookies that have expired
purgeExpiredCookies();
#ifndef QT_NO_DEBUG_OUTPUT
foreach (QNetworkCookie cookie, allCookies()) {
qDebug() << "CookieJar - Saved" << cookie.toRawForm() << "expires:" << cookie.expirationDate().toString();
}
#endif
// Store cookies
m_cookieStorage->setValue(QLatin1String("cookies"), QVariant::fromValue<QList<QNetworkCookie> >(allCookies()));
}
}
bool CookieJar::purgeExpiredCookies()
{
QList<QNetworkCookie> cookies = allCookies();
// If empty, there is nothing to purge
if (cookies.isEmpty()) {
return false;
}
// Check if any cookie has expired
int prePurgeCookiesCount = cookies.count();
QDateTime now = QDateTime::currentDateTime();
for (int i = cookies.count() - 1; i >= 0; --i) {
if (!cookies.at(i).isSessionCookie() && cookies.at(i).expirationDate() < now) {
cookies.removeAt(i);
}
}
// Returns "true" if at least 1 cookie expired and has been removed
return prePurgeCookiesCount != cookies.count();
}
bool CookieJar::purgeSessionCookies()
{
QList<QNetworkCookie> cookies = allCookies();
// If empty, there is nothing to purge
if (cookies.isEmpty()) {
return false;
}
// Check if any cookie has expired
int prePurgeCookiesCount = cookies.count();
for (int i = cookies.count() - 1; i >= 0; --i) {
if (cookies.at(i).isSessionCookie() || !cookies.at(i).expirationDate().isValid() || cookies.at(i).expirationDate().isNull()) {
cookies.removeAt(i);
}
}
// Returns "true" if at least 1 session cookie was found and removed
return prePurgeCookiesCount != cookies.count();
}
void CookieJar::load()
{
if (isEnabled()) {
// Register a "StreamOperator" for this Meta Type, so we can easily serialize/deserialize the cookies
qRegisterMetaTypeStreamOperators<QList<QNetworkCookie> >("QList<QNetworkCookie>");
// Load all the cookies
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()) {
save();
}
#ifndef QT_NO_DEBUG_OUTPUT
foreach (QNetworkCookie cookie, allCookies()) {
qDebug() << "CookieJar - Loaded" << cookie.toRawForm() << "expires:" << cookie.expirationDate().toString();
}
#endif
}
}

View File

@ -2,6 +2,7 @@
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2012 Ivan De Marino <ivan.de.marino@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
@ -30,20 +31,53 @@
#ifndef COOKIEJAR_H
#define COOKIEJAR_H
#include <QSettings>
#include <QNetworkCookieJar>
#include <QVariantList>
#include <QVariantMap>
class CookieJar: public QNetworkCookieJar
{
Q_OBJECT
private:
QString m_cookiesFile;
CookieJar(QString cookiesFile, QObject *parent = NULL);
public:
CookieJar(QString cookiesFile);
static CookieJar *instance(QString cookiesFile = QString());
virtual ~CookieJar();
bool setCookiesFromUrl(const QList<QNetworkCookie> & cookieList, const QUrl & url);
bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl & url);
QList<QNetworkCookie> cookiesForUrl (const QUrl & url) const;
void setCookies(const QVariantList &cookies);
QVariantList cookies() const;
void addCookie(const QNetworkCookie &cookie, const QString &url = QString());
void addCookieFromMap(const QVariantMap &cookie, const QString &url = QString());
void addCookies(const QList<QNetworkCookie> &cookiesList, const QString &url = QString());
void 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;
void deleteCookie(const QString &name, const QString &url = QString());
void deleteCookies(const QString &url = QString());
void clearCookies();
void enable();
void disable();
bool isEnabled() const;
private slots:
void save();
void load();
bool purgeExpiredCookies();
bool purgeSessionCookies();
private:
QSettings *m_cookieStorage;
bool m_enabled;
};
#endif // COOKIEJAR_H

View File

@ -36,6 +36,7 @@
#include <QNetworkRequest>
#include <QSslSocket>
#include "phantom.h"
#include "config.h"
#include "cookiejar.h"
#include "networkaccessmanager.h"
@ -73,7 +74,7 @@ NetworkAccessManager::NetworkAccessManager(QObject *parent, const Config *config
, m_idCounter(0)
, m_networkDiskCache(0)
{
setCookieJar(new CookieJar(config->cookiesFile()));
setCookieJar(CookieJar::instance());
if (config->diskCacheEnabled()) {
m_networkDiskCache = new QNetworkDiskCache(this);
@ -107,16 +108,14 @@ QVariantMap NetworkAccessManager::customHeaders() const
return m_customHeaders;
}
void NetworkAccessManager::setCookies(const QVariantList &cookies)
void NetworkAccessManager::setCookieJar(QNetworkCookieJar *cookieJar)
{
CookieJar* cookiejar = static_cast<CookieJar*>(cookieJar());
cookiejar->setCookies(cookies);
}
QVariantList NetworkAccessManager::cookies() const
{
CookieJar* cookiejar = static_cast<CookieJar*>(cookieJar());
return cookiejar->cookies();
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->setParent(Phantom::instance());
}
// protected:

View File

@ -49,8 +49,8 @@ public:
void setPassword(const QString &password);
void setCustomHeaders(const QVariantMap &headers);
QVariantMap customHeaders() const;
void setCookies(const QVariantList &cookies);
QVariantList cookies() const;
void setCookieJar(QNetworkCookieJar *cookieJar);
protected:
bool m_ignoreSslErrors;

View File

@ -46,6 +46,7 @@
#include "repl.h"
#include "system.h"
#include "callback.h"
#include "cookiejar.h"
static Phantom *phantomInstance = NULL;
@ -98,6 +99,9 @@ void Phantom::init()
return;
}
// Initialize the CookieJar
CookieJar::instance(m_config.cookiesFile());
m_page = new WebPage(this, QUrl::fromLocalFile(m_config.scriptFile()));
m_pages.append(m_page);
@ -253,6 +257,25 @@ bool Phantom::printDebugMessages() const
return m_config.printDebugMessages();
}
QVariantMap Phantom::keys() const
{
return m_keyMap;
}
bool Phantom::areCookiesEnabled() const
{
return CookieJar::instance()->isEnabled();
}
void Phantom::setCookiesEnabled(const bool value)
{
if (value) {
CookieJar::instance()->enable();
} else {
CookieJar::instance()->disable();
}
}
// public slots:
QObject *Phantom::createWebPage()
{
@ -382,12 +405,9 @@ void Phantom::initCompletions()
addCompletion("outputEncoding");
addCompletion("scriptName");
addCompletion("version");
addCompletion("keys");
addCompletion("cookiesEnabled");
// functions
addCompletion("exit");
addCompletion("injectJs");
}
QVariantMap Phantom::keys() const
{
return m_keyMap;
}

View File

@ -55,6 +55,7 @@ class Phantom: public REPLCompletable
Q_PROPERTY(QVariantMap version READ version)
Q_PROPERTY(QObject *page READ page)
Q_PROPERTY(QVariantMap keys READ keys)
Q_PROPERTY(bool cookiesEnabled READ areCookiesEnabled WRITE setCookiesEnabled)
private:
// Private constructor: the Phantom class is a singleton
@ -97,6 +98,9 @@ public:
QVariantMap keys() const;
bool areCookiesEnabled() const;
void setCookiesEnabled(const bool value);
public slots:
QObject *createWebPage();
QObject *createWebServer();

View File

@ -61,6 +61,7 @@
#include "config.h"
#include "consts.h"
#include "callback.h"
#include "cookiejar.h"
// Ensure we have at least head and body.
#define BLANK_HTML "<html><head></head><body></body></html>"
@ -563,12 +564,33 @@ QVariantMap WebPage::customHeaders() const
void WebPage::setCookies(const QVariantList &cookies)
{
m_networkAccessManager->setCookies(cookies);
// Delete all the cookies for this URL
CookieJar::instance()->deleteCookies(this->url());
// Add a new set of cookies foor this URL
CookieJar::instance()->addCookiesFromMap(cookies, this->url());
}
QVariantList WebPage::cookies() const
{
return m_networkAccessManager->cookies();
// Return all the Cookies visible to this Page, as a list of Maps (aka JSON in JS space)
return CookieJar::instance()->cookiesToMap(this->url());
}
void WebPage::addCookie(const QVariantMap &cookie)
{
CookieJar::instance()->addCookieFromMap(cookie, this->url());
}
void WebPage::deleteCookie(const QString &cookieName)
{
if (!cookieName.isEmpty()) {
CookieJar::instance()->deleteCookie(cookieName, this->url());
}
}
void WebPage::clearCookies()
{
CookieJar::instance()->deleteCookie(this->url());
}
void WebPage::openUrl(const QString &address, const QVariant &op, const QVariantMap &settings)
@ -1251,6 +1273,7 @@ void WebPage::initCompletions()
addCompletion("frameName");
addCompletion("framesName");
addCompletion("framesCount");
addCompletion("cookies");
// functions
addCompletion("evaluate");
addCompletion("includeJs");
@ -1265,6 +1288,9 @@ void WebPage::initCompletions()
addCompletion("switchToFrame");
addCompletion("switchToMainFrame");
addCompletion("switchToParentFrame");
addCompletion("addCookie");
addCompletion("deleteCookie");
addCompletion("clearCookies");
// callbacks
addCompletion("onAlert");
addCompletion("onCallback");

View File

@ -329,8 +329,53 @@ public slots:
*/
QString currentFrameName() const;
/**
* Allows to set cookies by this Page, at the current URL.
* This means that loading new URLs, causes the cookies to change dynamically
* as in a normal desktop browser.
*
* Cookies are expected in the format:
* <pre>
* {
* "name" : "cookie name (string)",
* "value" : "cookie value (string)",
* "domain" : "cookie domain (string)",
* "path" : "cookie path (string, optional)",
* "httponly" : "http only cookie (boolean, optional)",
* "secure" : "secure cookie (boolean, optional)",
* "expires" : "expiration date (string, GMT format, optional)"
* }
* </pre>
* @brief setCookies
* @param cookies Expects a QList of QVariantMaps
*/
void setCookies(const QVariantList &cookies);
/**
* Cookies visible by this Page, at the current URL.
*
* @see WebPage::setCookies for details on the format
* @brief cookies
* @return QList of QVariantMap cookies visible to this Page, at the current URL.
*/
QVariantList cookies() const;
/**
* Add a Cookie in QVariantMap format
* @see WebPage::setCookies for details on the format
* @brief addCookie
* @param cookie Cookie in QVariantMap format
*/
void addCookie(const QVariantMap &cookie);
/**
* Delete cookie by name from the ones visible by this Page, at the current URL
* @brief deleteCookie
* @param cookieName Name of the Cookie to delete
*/
void deleteCookie(const QString &cookieName);
/**
* Delete All Cookies visible by this Page, at the current URL
* @brief clearCookies
*/
void clearCookies();
signals:
void initialized();

View File

@ -70,4 +70,9 @@ describe("phantom global object", function() {
it("should have 'keys' property", function() {
expect(phantom.hasOwnProperty('keys')).toBeTruthy();
});
it("should have 'cookiesEnabled' property, and should be 'true' by default", function() {
expect(phantom.hasOwnProperty('cookiesEnabled')).toBeTruthy();
expect(phantom.cookiesEnabled).toBeTruthy();
});
});

View File

@ -169,6 +169,9 @@ describe("WebPage object", function() {
expectHasFunction(page, 'switchToMainFrame');
expectHasFunction(page, 'switchToParentFrame');
expectHasFunction(page, 'currentFrameName');
expectHasFunction(page, 'addCookie');
expectHasFunction(page, 'deleteCookie');
expectHasFunction(page, 'clearCookies');
it("should handle keydown event", function() {
runs(function() {
@ -595,7 +598,7 @@ describe("WebPage object", function() {
});
it("should set valid cookies properly", function() {
it("should set valid cookie properly, then remove it", function() {
var server = require('webserver').create();
server.listen(12345, function(request, response) {
// echo received request headers in response body
@ -606,8 +609,8 @@ describe("WebPage object", function() {
var url = "http://localhost:12345/foo/headers.txt?ab=cd";
page.cookies = [{
'name' : 'Cookie-Name',
'value' : 'Cookie-Value',
'name' : 'Valid-Cookie-Name',
'value' : 'Valid-Cookie-Value',
'domain' : 'localhost',
'path' : '/foo',
'httponly' : true,
@ -623,7 +626,8 @@ describe("WebPage object", function() {
var echoedHeaders = JSON.parse(page.plainText);
// console.log(JSON.stringify(echoedHeaders));
expect(echoedHeaders["Cookie"]).toContain("Cookie-Value");
expect(echoedHeaders["Cookie"]).toContain("Valid-Cookie-Name");
expect(echoedHeaders["Cookie"]).toContain("Valid-Cookie-Value");
});
});
@ -631,10 +635,11 @@ describe("WebPage object", function() {
runs(function() {
expect(handled).toEqual(true);
page.cookies = [];
expect(page.cookies.length).toNotBe(0);
page.cookies = []; //< delete all the cookies visible to this URL
expect(page.cookies.length).toBe(0);
server.close();
});
});
it("should not set invalid cookies", function() {
@ -648,28 +653,30 @@ describe("WebPage object", function() {
var url = "http://localhost:12345/foo/headers.txt?ab=cd";
page.cookies = [
{ // domain field missing.
'name' : 'Cookie-Name',
'value' : 'Cookie-Value',
{ // domain missing.
'name' : 'Invalid-Cookie-Name',
'value' : 'Invalid-Cookie-Value'
},{ // domain mismatch.
'name' : 'Cookie-Name',
'value' : 'Cookie-Value',
'name' : 'Invalid-Cookie-Name',
'value' : 'Invalid-Cookie-Value',
'domain' : 'foo.com'
},{ // path mismatch.
'name' : 'Cookie-Name',
'value' : 'Cookie-Value',
},{ // path mismatch: the cookie will be set,
// but won't be visible from the given URL (not same path).
'name' : 'Invalid-Cookie-Name',
'value' : 'Invalid-Cookie-Value',
'domain' : 'localhost',
'path' : '/bar',
'path' : '/bar'
},{ // cookie expired.
'name' : 'Cookie-Name',
'value' : 'Cookie-Value',
'name' : 'Invalid-Cookie-Name',
'value' : 'Invalid-Cookie-Value',
'domain' : 'localhost',
'expires' : 'Sat, 09 Jun 2012 00:00:00 GMT',
},{ // https only.
'name' : 'Cookie-Name',
'value' : 'Cookie-Value',
'expires' : 'Sat, 09 Jun 2012 00:00:00 GMT'
},{ // https only: the cookie will be set,
// but won't be visible from the given URL (not https).
'name' : 'Invalid-Cookie-Name',
'value' : 'Invalid-Cookie-Value',
'domain' : 'localhost',
'secure' : true,
'secure' : true
}];
var handled = false;
@ -689,10 +696,82 @@ describe("WebPage object", function() {
runs(function() {
expect(handled).toEqual(true);
page.cookies = [];
expect(page.cookies.length).toBe(0);
page.clearCookies(); //< delete all the cookies visible to this URL
expect(page.cookies.length).toBe(0);
server.close();
});
});
it("should add a cookie", function() {
var server = require('webserver').create();
server.listen(12345, function(request, response) {
// echo received request headers in response body
response.write(JSON.stringify(request.headers));
response.close();
});
var url = "http://localhost:12345/foo/headers.txt?ab=cd";
page.addCookie({
'name' : 'Added-Cookie-Name',
'value' : 'Added-Cookie-Value',
'domain' : 'localhost'
});
var handled = false;
runs(function() {
expect(handled).toEqual(false);
page.open(url, function (status) {
expect(status == 'success').toEqual(true);
handled = true;
var echoedHeaders = JSON.parse(page.plainText);
// console.log(JSON.stringify(echoedHeaders));
expect(echoedHeaders["Cookie"]).toContain("Added-Cookie-Name");
expect(echoedHeaders["Cookie"]).toContain("Added-Cookie-Value");
});
});
waits(50);
runs(function() {
expect(handled).toEqual(true);
server.close();
});
});
it("should delete a cookie", function() {
var server = require('webserver').create();
server.listen(12345, function(request, response) {
// echo received request headers in response body
response.write(JSON.stringify(request.headers));
response.close();
});
var url = "http://localhost:12345/foo/headers.txt?ab=cd";
page.deleteCookie("Added-Cookie-Name");
var handled = false;
runs(function() {
expect(handled).toEqual(false);
page.open(url, function (status) {
expect(status == 'success').toEqual(true);
handled = true;
var echoedHeaders = JSON.parse(page.plainText);
// console.log(JSON.stringify(echoedHeaders));
expect(echoedHeaders["Cookie"]).toBeUndefined();
});
});
waits(50);
runs(function() {
expect(handled).toEqual(true);
server.close();
});
});
it("should pass variables to functions properly", function() {