adds cookie management functions to Webkit::Browser

This commit is contained in:
Niklas Baumstark 2011-09-24 23:25:15 +02:00 committed by Matthew Mongeau
parent dc5130ce6b
commit fb119f6ff8
15 changed files with 298 additions and 2 deletions

View File

@ -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

View File

@ -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><body>
<p id="cookie">#{request.cookies["cookie"] || ""}</p>
</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){

18
src/ClearCookies.cpp Normal file
View File

@ -0,0 +1,18 @@
#include "ClearCookies.h"
#include "WebPage.h"
#include "NetworkCookieJar.h"
#include <QNetworkCookie>
ClearCookies::ClearCookies(WebPage *page, QObject *parent)
: Command(page, parent)
{ }
void ClearCookies::start(QStringList &arguments)
{
Q_UNUSED(arguments);
NetworkCookieJar *jar = qobject_cast<NetworkCookieJar*>(page()
->networkAccessManager()
->cookieJar());
jar->clearCookies();
emit finished(new Response(true));
}

11
src/ClearCookies.h Normal file
View File

@ -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);
};

View File

@ -16,6 +16,9 @@
#include "Body.h"
#include "Status.h"
#include "Headers.h"
#include "SetCookie.h"
#include "ClearCookies.h"
#include "GetCookies.h"
#include <QTcpSocket>
#include <iostream>

22
src/GetCookies.cpp Normal file
View File

@ -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<NetworkCookieJar*>(page()
->networkAccessManager()
->cookieJar());
foreach (QNetworkCookie cookie, jar->getAllCookies()) {
m_buffer.append(cookie.toRawForm());
m_buffer.append("\n");
}
emit finished(new Response(true, m_buffer));
}

14
src/GetCookies.h Normal file
View File

@ -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;
};

101
src/NetworkCookieJar.cpp Normal file
View File

@ -0,0 +1,101 @@
#include "NetworkCookieJar.h"
#include "QtCore/qdatetime.h"
NetworkCookieJar::NetworkCookieJar(QObject *parent)
: QNetworkCookieJar(parent)
{ }
QList<QNetworkCookie> NetworkCookieJar::getAllCookies() const
{
return allCookies();
}
void NetworkCookieJar::clearCookies()
{
setAllCookies(QList<QNetworkCookie>());
}
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<QNetworkCookie>& 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<QNetworkCookie> 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 &current = 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 &current = 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);
}

15
src/NetworkCookieJar.h Normal file
View File

@ -0,0 +1,15 @@
#include <QtNetwork/QNetworkCookieJar>
#include <QtNetwork/QNetworkCookie>
class NetworkCookieJar : public QNetworkCookieJar {
Q_OBJECT;
public:
NetworkCookieJar(QObject *parent = 0);
QList<QNetworkCookie> getAllCookies() const;
void clearCookies();
void overwriteCookies(const QList<QNetworkCookie>& cookieList);
};

View File

@ -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("<html><body></body></html>");
page()->networkAccessManager()->setCookieJar(new QNetworkCookieJar());
page()->networkAccessManager()->setCookieJar(new NetworkCookieJar());
page()->setCustomNetworkAccessManager();
page()->setUserAgent(NULL);
page()->resetResponseHeaders();

18
src/SetCookie.cpp Normal file
View File

@ -0,0 +1,18 @@
#include "SetCookie.h"
#include "WebPage.h"
#include "NetworkCookieJar.h"
#include <QNetworkCookie>
SetCookie::SetCookie(WebPage *page, QObject *parent)
: Command(page, parent)
{ }
void SetCookie::start(QStringList &arguments)
{
QList<QNetworkCookie> cookies = QNetworkCookie::parseCookies(arguments[0].toAscii());
NetworkCookieJar *jar = qobject_cast<NetworkCookieJar*>(page()
->networkAccessManager()
->cookieJar());
jar->overwriteCookies(cookies);
emit finished(new Response(true));
}

11
src/SetCookie.h Normal file
View File

@ -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);
};

View File

@ -1,6 +1,7 @@
#include "WebPage.h"
#include "JavascriptInvocation.h"
#include "NetworkAccessManager.h"
#include "NetworkCookieJar.h"
#include "UnsupportedContentHandler.h"
#include <QResource>
#include <iostream>
@ -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 *)));
}

View File

@ -16,4 +16,7 @@ CHECK_COMMAND(Header)
CHECK_COMMAND(Render)
CHECK_COMMAND(Body)
CHECK_COMMAND(Status)
CHECK_COMMAND(Headers)
CHECK_COMMAND(Headers)
CHECK_COMMAND(SetCookie)
CHECK_COMMAND(ClearCookies)
CHECK_COMMAND(GetCookies)

View File

@ -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