diff --git a/lib/capybara/driver/webkit/browser.rb b/lib/capybara/driver/webkit/browser.rb index de603ef..e4b7ea5 100644 --- a/lib/capybara/driver/webkit/browser.rb +++ b/lib/capybara/driver/webkit/browser.rb @@ -86,6 +86,20 @@ class Capybara::Driver::Webkit command "Render", path, width, height end + def set_cookie(cookie) + command "SetCookie", cookie + end + + def clear_cookies + command "ClearCookies" + end + + def get_cookies + command("GetCookies").lines + .map { |line| line.strip } + .select { |line| !line.empty? } + end + private def start_server diff --git a/spec/driver_spec.rb b/spec/driver_spec.rb index 1e3d379..107041f 100644 --- a/spec/driver_spec.rb +++ b/spec/driver_spec.rb @@ -884,6 +884,61 @@ describe Capybara::Driver::Webkit do end end + context "cookie-based app" do + before(:all) do + @cookie = 'cookie=abc; domain=127.0.0.1; path=/' + @app = lambda do |env| + request = ::Rack::Request.new(env) + + body = <<-HTML + + + + HTML + [200, + { 'Content-Type' => 'text/html; charset=UTF-8', + 'Content-Length' => body.length.to_s, + 'Set-Cookie' => @cookie, + }, + [body]] + end + end + + def echoed_cookie + subject.find('id("cookie")').first.text + end + + it "remembers the cookie on second visit" do + echoed_cookie.should == "" + subject.visit "/" + echoed_cookie.should == "abc" + end + + it "uses a custom cookie" do + subject.browser.set_cookie @cookie + subject.visit "/" + echoed_cookie.should == "abc" + end + + it "clears cookies" do + subject.browser.clear_cookies + subject.visit "/" + echoed_cookie.should == "" + end + + it "allows enumeration of cookies" do + cookies = subject.browser.get_cookies + + cookies.size.should == 1 + + cookie = Hash[cookies[0].split(/\s*;\s*/) + .map { |x| x.split("=", 2) }] + cookie["cookie"].should == "abc" + cookie["domain"].should include "127.0.0.1" + cookie["path"].should == "/" + end + end + context "with socket debugger" do let(:socket_debugger_class){ Capybara::Driver::Webkit::SocketDebugger } let(:browser_with_debugger){ diff --git a/src/ClearCookies.cpp b/src/ClearCookies.cpp new file mode 100644 index 0000000..048e877 --- /dev/null +++ b/src/ClearCookies.cpp @@ -0,0 +1,18 @@ +#include "ClearCookies.h" +#include "WebPage.h" +#include "NetworkCookieJar.h" +#include + +ClearCookies::ClearCookies(WebPage *page, QObject *parent) + : Command(page, parent) +{ } + +void ClearCookies::start(QStringList &arguments) +{ + Q_UNUSED(arguments); + NetworkCookieJar *jar = qobject_cast(page() + ->networkAccessManager() + ->cookieJar()); + jar->clearCookies(); + emit finished(new Response(true)); +} diff --git a/src/ClearCookies.h b/src/ClearCookies.h new file mode 100644 index 0000000..0a1841a --- /dev/null +++ b/src/ClearCookies.h @@ -0,0 +1,11 @@ +#include "Command.h" + +class WebPage; + +class ClearCookies : public Command { + Q_OBJECT; + + public: + ClearCookies(WebPage *page, QObject *parent = 0); + virtual void start(QStringList &arguments); +}; diff --git a/src/Connection.cpp b/src/Connection.cpp index a79cd54..ce4ed7a 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -16,6 +16,9 @@ #include "Body.h" #include "Status.h" #include "Headers.h" +#include "SetCookie.h" +#include "ClearCookies.h" +#include "GetCookies.h" #include #include diff --git a/src/GetCookies.cpp b/src/GetCookies.cpp new file mode 100644 index 0000000..ecf3097 --- /dev/null +++ b/src/GetCookies.cpp @@ -0,0 +1,22 @@ +#include "GetCookies.h" +#include "WebPage.h" +#include "NetworkCookieJar.h" + +GetCookies::GetCookies(WebPage *page, QObject *parent) + : Command(page, parent) +{ + m_buffer = ""; +} + +void GetCookies::start(QStringList &arguments) +{ + Q_UNUSED(arguments); + NetworkCookieJar *jar = qobject_cast(page() + ->networkAccessManager() + ->cookieJar()); + foreach (QNetworkCookie cookie, jar->getAllCookies()) { + m_buffer.append(cookie.toRawForm()); + m_buffer.append("\n"); + } + emit finished(new Response(true, m_buffer)); +} diff --git a/src/GetCookies.h b/src/GetCookies.h new file mode 100644 index 0000000..d98f844 --- /dev/null +++ b/src/GetCookies.h @@ -0,0 +1,14 @@ +#include "Command.h" + +class WebPage; + +class GetCookies : public Command { + Q_OBJECT; + + public: + GetCookies(WebPage *page, QObject *parent = 0); + virtual void start(QStringList &arguments); + + private: + QString m_buffer; +}; diff --git a/src/NetworkCookieJar.cpp b/src/NetworkCookieJar.cpp new file mode 100644 index 0000000..4ecd718 --- /dev/null +++ b/src/NetworkCookieJar.cpp @@ -0,0 +1,101 @@ +#include "NetworkCookieJar.h" +#include "QtCore/qdatetime.h" + +NetworkCookieJar::NetworkCookieJar(QObject *parent) + : QNetworkCookieJar(parent) +{ } + +QList NetworkCookieJar::getAllCookies() const +{ + return allCookies(); +} + +void NetworkCookieJar::clearCookies() +{ + setAllCookies(QList()); +} + +static inline bool isParentDomain(QString domain, QString reference) +{ + if (!reference.startsWith(QLatin1Char('.'))) + return domain == reference; + + return domain.endsWith(reference) || domain == reference.mid(1); +} + +void NetworkCookieJar::overwriteCookies(const QList& cookieList) +{ + /* this function is basically a copy-and-paste of the original + QNetworkCookieJar::setCookiesFromUrl with the domain and + path validations removed */ + + QString defaultPath(QLatin1Char('/')); + QDateTime now = QDateTime::currentDateTime(); + QList newCookies = allCookies(); + + foreach (QNetworkCookie cookie, cookieList) { + bool isDeletion = (!cookie.isSessionCookie() && + cookie.expirationDate() < now); + + // validate the cookie & set the defaults if unset + if (cookie.path().isEmpty()) + cookie.setPath(defaultPath); + + // don't do path checking. See http://bugreports.qt.nokia.com/browse/QTBUG-5815 + // else if (!isParentPath(pathAndFileName, cookie.path())) { + // continue; // not accepted + // } + + if (cookie.domain().isEmpty()) { + continue; + } else { + // Ensure the domain starts with a dot if its field was not empty + // in the HTTP header. There are some servers that forget the + // leading dot and this is actually forbidden according to RFC 2109, + // but all browsers accept it anyway so we do that as well. + if (!cookie.domain().startsWith(QLatin1Char('.'))) + cookie.setDomain(QLatin1Char('.') + cookie.domain()); + + QString domain = cookie.domain(); + + // the check for effective TLDs makes the "embedded dot" rule from RFC 2109 section 4.3.2 + // redundant; the "leading dot" rule has been relaxed anyway, see above + // we remove the leading dot for this check + /* + if (QNetworkCookieJarPrivate::isEffectiveTLD(domain.remove(0, 1))) + continue; // not accepted + */ + } + + for (int i = 0; i < newCookies.size(); ++i) { + // does this cookie already exist? + const QNetworkCookie ¤t = newCookies.at(i); + if (cookie.name() == current.name() && + cookie.domain() == current.domain() && + cookie.path() == current.path()) { + // found a match + newCookies.removeAt(i); + break; + } + } + + // did not find a match + if (!isDeletion) { + int countForDomain = 0; + for (int i = newCookies.size() - 1; i >= 0; --i) { + // Start from the end and delete the oldest cookies to keep a maximum count of 50. + const QNetworkCookie ¤t = newCookies.at(i); + if (isParentDomain(cookie.domain(), current.domain()) + || isParentDomain(current.domain(), cookie.domain())) { + if (countForDomain >= 49) + newCookies.removeAt(i); + else + ++countForDomain; + } + } + + newCookies += cookie; + } + } + setAllCookies(newCookies); +} diff --git a/src/NetworkCookieJar.h b/src/NetworkCookieJar.h new file mode 100644 index 0000000..8796532 --- /dev/null +++ b/src/NetworkCookieJar.h @@ -0,0 +1,15 @@ +#include +#include + +class NetworkCookieJar : public QNetworkCookieJar { + + Q_OBJECT; + + public: + + NetworkCookieJar(QObject *parent = 0); + + QList getAllCookies() const; + void clearCookies(); + void overwriteCookies(const QList& cookieList); +}; diff --git a/src/Reset.cpp b/src/Reset.cpp index e5663ea..c4de016 100644 --- a/src/Reset.cpp +++ b/src/Reset.cpp @@ -1,6 +1,7 @@ #include "Reset.h" #include "WebPage.h" #include "NetworkAccessManager.h" +#include "NetworkCookieJar.h" Reset::Reset(WebPage *page, QObject *parent) : Command(page, parent) { } @@ -10,7 +11,7 @@ void Reset::start(QStringList &arguments) { page()->triggerAction(QWebPage::Stop); page()->currentFrame()->setHtml(""); - page()->networkAccessManager()->setCookieJar(new QNetworkCookieJar()); + page()->networkAccessManager()->setCookieJar(new NetworkCookieJar()); page()->setCustomNetworkAccessManager(); page()->setUserAgent(NULL); page()->resetResponseHeaders(); diff --git a/src/SetCookie.cpp b/src/SetCookie.cpp new file mode 100644 index 0000000..3b2fd9d --- /dev/null +++ b/src/SetCookie.cpp @@ -0,0 +1,18 @@ +#include "SetCookie.h" +#include "WebPage.h" +#include "NetworkCookieJar.h" +#include + +SetCookie::SetCookie(WebPage *page, QObject *parent) + : Command(page, parent) +{ } + +void SetCookie::start(QStringList &arguments) +{ + QList cookies = QNetworkCookie::parseCookies(arguments[0].toAscii()); + NetworkCookieJar *jar = qobject_cast(page() + ->networkAccessManager() + ->cookieJar()); + jar->overwriteCookies(cookies); + emit finished(new Response(true)); +} diff --git a/src/SetCookie.h b/src/SetCookie.h new file mode 100644 index 0000000..8959c79 --- /dev/null +++ b/src/SetCookie.h @@ -0,0 +1,11 @@ +#include "Command.h" + +class WebPage; + +class SetCookie : public Command { + Q_OBJECT; + + public: + SetCookie(WebPage *page, QObject *parent = 0); + virtual void start(QStringList &arguments); +}; diff --git a/src/WebPage.cpp b/src/WebPage.cpp index 753ed11..53e0b14 100644 --- a/src/WebPage.cpp +++ b/src/WebPage.cpp @@ -1,6 +1,7 @@ #include "WebPage.h" #include "JavascriptInvocation.h" #include "NetworkAccessManager.h" +#include "NetworkCookieJar.h" #include "UnsupportedContentHandler.h" #include #include @@ -23,6 +24,7 @@ WebPage::WebPage(QObject *parent) : QWebPage(parent) { void WebPage::setCustomNetworkAccessManager() { NetworkAccessManager *manager = new NetworkAccessManager(); + manager->setCookieJar(new NetworkCookieJar()); this->setNetworkAccessManager(manager); connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinished(QNetworkReply *))); } diff --git a/src/find_command.h b/src/find_command.h index a480c7d..4965719 100644 --- a/src/find_command.h +++ b/src/find_command.h @@ -16,4 +16,7 @@ CHECK_COMMAND(Header) CHECK_COMMAND(Render) CHECK_COMMAND(Body) CHECK_COMMAND(Status) -CHECK_COMMAND(Headers) \ No newline at end of file +CHECK_COMMAND(Headers) +CHECK_COMMAND(SetCookie) +CHECK_COMMAND(ClearCookies) +CHECK_COMMAND(GetCookies) diff --git a/src/webkit_server.pro b/src/webkit_server.pro index 1e89330..25acabd 100644 --- a/src/webkit_server.pro +++ b/src/webkit_server.pro @@ -18,12 +18,16 @@ HEADERS = \ FrameFocus.h \ Response.h \ NetworkAccessManager.h \ + NetworkCookieJar.h \ Header.h \ Render.h \ body.h \ Status.h \ Headers.h \ UnsupportedContentHandler.h \ + SetCookie.h \ + ClearCookies.h \ + GetCookies.h \ SOURCES = \ main.cpp \ @@ -43,12 +47,16 @@ SOURCES = \ FrameFocus.cpp \ Response.cpp \ NetworkAccessManager.cpp \ + NetworkCookieJar.cpp \ Header.cpp \ Render.cpp \ body.cpp \ Status.cpp \ Headers.cpp \ UnsupportedContentHandler.cpp \ + SetCookie.cpp \ + ClearCookies.cpp \ + GetCookies.cpp \ RESOURCES = webkit_server.qrc QT += network webkit