diff --git a/src/cookiejar.cpp b/src/cookiejar.cpp index c36be0c5..16c2fbaf 100644 --- a/src/cookiejar.cpp +++ b/src/cookiejar.cpp @@ -2,6 +2,7 @@ This file is part of the PhantomJS project from Ofi Labs. Copyright (C) 2011 Ariya Hidayat + Copyright (C) 2012 Ivan De Marino 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 #include -#include +#include -CookieJar::CookieJar(QString cookiesFile) - : QNetworkCookieJar() +#define COOKIE_JAR_VERSION 1 + +// Operators needed for Cookie Serialization +QT_BEGIN_NAMESPACE +QDataStream &operator<<(QDataStream &stream, const QList &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 & cookieList, const QUrl & url) +QDataStream &operator>>(QDataStream &stream, QList &list) { - QSettings settings(m_cookiesFile, QSettings::IniFormat); + list.clear(); - settings.beginGroup(url.host()); - - for (QList::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 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 CookieJar::cookiesForUrl(const QUrl & url) const +// public: +CookieJar *CookieJar::instance(QString cookiesFile) { - QSettings settings(m_cookiesFile, QSettings::IniFormat); - QList 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=')"; + } 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 allCookies = QNetworkCookieJar::cookiesForUrl(url); - for (QList::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 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 & 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 CookieJar::cookiesForUrl(const QUrl &url) const +{ + if (isEnabled()) { + return QNetworkCookieJar::cookiesForUrl(url); + } + // The CookieJar is disabled: don't return any cookie + return QList(); +} - 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() << 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 &cookiesList, const QString &url) { - QVariantList returnCookies; - QList allCookies = this->allCookies(); - for (QList::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 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 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 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 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()); + } +} + +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 >(allCookies())); + } +} + +bool CookieJar::purgeExpiredCookies() +{ + QList 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 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"); + + // Load all the cookies + setAllCookies(qvariant_cast >(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 + } } diff --git a/src/cookiejar.h b/src/cookiejar.h index 15390f42..cd75f6f9 100644 --- a/src/cookiejar.h +++ b/src/cookiejar.h @@ -2,6 +2,7 @@ This file is part of the PhantomJS project from Ofi Labs. Copyright (C) 2011 Ariya Hidayat + Copyright (C) 2012 Ivan De Marino 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 #include +#include +#include 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 & cookieList, const QUrl & url); + bool setCookiesFromUrl(const QList &cookieList, const QUrl & url); QList 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 &cookiesList, const QString &url = QString()); + void addCookiesFromMap(const QVariantList &cookiesList, const QString &url = QString()); + + QList 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 diff --git a/src/networkaccessmanager.cpp b/src/networkaccessmanager.cpp index ae1103ca..72906254 100644 --- a/src/networkaccessmanager.cpp +++ b/src/networkaccessmanager.cpp @@ -36,6 +36,7 @@ #include #include +#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->setCookies(cookies); -} - -QVariantList NetworkAccessManager::cookies() const -{ - CookieJar* cookiejar = static_cast(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: diff --git a/src/networkaccessmanager.h b/src/networkaccessmanager.h index 056e4633..9511da78 100644 --- a/src/networkaccessmanager.h +++ b/src/networkaccessmanager.h @@ -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; diff --git a/src/phantom.cpp b/src/phantom.cpp index 4879d941..c3ca32f7 100644 --- a/src/phantom.cpp +++ b/src/phantom.cpp @@ -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; -} diff --git a/src/phantom.h b/src/phantom.h index a764fb76..bae7dd0a 100644 --- a/src/phantom.h +++ b/src/phantom.h @@ -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(); diff --git a/src/webpage.cpp b/src/webpage.cpp index 080c82f4..123b4e03 100644 --- a/src/webpage.cpp +++ b/src/webpage.cpp @@ -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 "" @@ -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"); diff --git a/src/webpage.h b/src/webpage.h index 26100721..ab75e845 100644 --- a/src/webpage.h +++ b/src/webpage.h @@ -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: + *
+     * {
+     *   "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)"
+     * }
+     * 
+ * @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(); diff --git a/test/phantom-spec.js b/test/phantom-spec.js index 9280a9fa..973b04ce 100644 --- a/test/phantom-spec.js +++ b/test/phantom-spec.js @@ -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(); + }); }); diff --git a/test/webpage-spec.js b/test/webpage-spec.js index 658abee9..4d67279c 100644 --- a/test/webpage-spec.js +++ b/test/webpage-spec.js @@ -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() {