Add a command-line option --local-urls={true,false}

The default is 'true'.  When set 'false', file: and qrc: URLs are
treated as invalid (unknown scheme) rather than opening local files,
as requested in issue #12752.

In order to test this, I added a mechanism to test/run-tests.py
allowing individual tests to be annotated with command-line
options to pass to phantomjs or the script.
2.0
Zack Weinberg 2014-11-20 21:00:45 -05:00
parent a65a487d99
commit 50ae50e871
7 changed files with 141 additions and 21 deletions

View File

@ -56,6 +56,7 @@ static const struct QCommandLineConfigEntry flags[] =
{ QCommandLine::Option, '\0', "load-images", "Loads all inlined images: 'true' (default) or 'false'", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "local-storage-path", "Specifies the location for offline local storage", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "local-storage-quota", "Sets the maximum size of the offline local storage (in KB)", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "local-url-access", "Allows use of 'file:///' URLs: 'true' (default) or 'false'", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "local-to-remote-url-access", "Allows local content to access remote URL: 'true' or 'false' (default)", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "max-disk-cache-size", "Limits the size of the disk cache (in KB)", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "output-encoding", "Sets the encoding for the terminal output, default is 'utf8'", QCommandLine::Optional },
@ -249,6 +250,16 @@ void Config::setIgnoreSslErrors(const bool value)
m_ignoreSslErrors = value;
}
bool Config::localUrlAccessEnabled() const
{
return m_localUrlAccessEnabled;
}
void Config::setLocalUrlAccessEnabled(const bool value)
{
m_localUrlAccessEnabled = value;
}
bool Config::localToRemoteUrlAccessEnabled() const
{
return m_localToRemoteUrlAccessEnabled;
@ -535,6 +546,7 @@ void Config::resetToDefaults()
m_diskCacheEnabled = false;
m_maxDiskCacheSize = -1;
m_ignoreSslErrors = false;
m_localUrlAccessEnabled = true;
m_localToRemoteUrlAccessEnabled = false;
m_outputEncoding = "UTF-8";
m_proxyType = "http";
@ -643,6 +655,7 @@ void Config::handleOption(const QString &option, const QVariant &value)
booleanFlags << "disk-cache";
booleanFlags << "ignore-ssl-errors";
booleanFlags << "load-images";
booleanFlags << "local-url-access";
booleanFlags << "local-to-remote-url-access";
booleanFlags << "remote-debugger-autorun";
booleanFlags << "web-security";
@ -686,6 +699,10 @@ void Config::handleOption(const QString &option, const QVariant &value)
setOfflineStorageDefaultQuota(value.toInt());
}
if (option == "local-url-access") {
setLocalUrlAccessEnabled(boolValue);
}
if (option == "local-to-remote-url-access") {
setLocalToRemoteUrlAccessEnabled(boolValue);
}

View File

@ -45,6 +45,7 @@ class Config: public QObject
Q_PROPERTY(bool diskCacheEnabled READ diskCacheEnabled WRITE setDiskCacheEnabled)
Q_PROPERTY(int maxDiskCacheSize READ maxDiskCacheSize WRITE setMaxDiskCacheSize)
Q_PROPERTY(bool ignoreSslErrors READ ignoreSslErrors WRITE setIgnoreSslErrors)
Q_PROPERTY(bool localUrlAccessEnabled READ localUrlAccessEnabled WRITE setLocalUrlAccessEnabled)
Q_PROPERTY(bool localToRemoteUrlAccessEnabled READ localToRemoteUrlAccessEnabled WRITE setLocalToRemoteUrlAccessEnabled)
Q_PROPERTY(QString outputEncoding READ outputEncoding WRITE setOutputEncoding)
Q_PROPERTY(QString proxyType READ proxyType WRITE setProxyType)
@ -95,6 +96,9 @@ public:
bool ignoreSslErrors() const;
void setIgnoreSslErrors(const bool value);
bool localUrlAccessEnabled() const;
void setLocalUrlAccessEnabled(const bool value);
bool localToRemoteUrlAccessEnabled() const;
void setLocalToRemoteUrlAccessEnabled(const bool value);
@ -201,6 +205,7 @@ private:
bool m_diskCacheEnabled;
int m_maxDiskCacheSize;
bool m_ignoreSslErrors;
bool m_localUrlAccessEnabled;
bool m_localToRemoteUrlAccessEnabled;
QString m_outputEncoding;
QString m_proxyType;

View File

@ -73,6 +73,30 @@ static const char *toString(QNetworkAccessManager::Operation op)
return str;
}
// Stub QNetworkReply used when file:/// URLs are disabled.
// Somewhat cargo-culted from QDisabledNetworkReply.
NoFileAccessReply::NoFileAccessReply(QObject *parent, const QNetworkRequest &req, const QNetworkAccessManager::Operation op)
: QNetworkReply(parent)
{
setRequest(req);
setUrl(req.url());
setOperation(op);
qRegisterMetaType<QNetworkReply::NetworkError>();
QString msg = (QCoreApplication::translate("QNetworkReply", "Protocol \"%1\" is unknown")
.arg(req.url().scheme()));
setError(ProtocolUnknownError, msg);
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QNetworkReply::NetworkError, ProtocolUnknownError));
QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
}
// The destructor must be out-of-line in order to trigger generation of the vtable.
NoFileAccessReply::~NoFileAccessReply() {}
TimeoutTimer::TimeoutTimer(QObject* parent)
: QTimer(parent)
{
@ -129,6 +153,7 @@ const ssl_protocol_option ssl_protocol_options[] = {
NetworkAccessManager::NetworkAccessManager(QObject *parent, const Config *config)
: QNetworkAccessManager(parent)
, m_ignoreSslErrors(config->ignoreSslErrors())
, m_localUrlAccessEnabled(config->localUrlAccessEnabled())
, m_authAttempts(0)
, m_maxAuthAttempts(3)
, m_resourceTimeout(0)
@ -238,10 +263,12 @@ void NetworkAccessManager::setCookieJar(QNetworkCookieJar *cookieJar)
QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkRequest & request, QIODevice * outgoingData)
{
QNetworkRequest req(request);
QString scheme = req.url().scheme().toLower();
bool isLocalFile = req.url().isLocalFile();
if (!QSslSocket::supportsSsl()) {
if (req.url().scheme().toLower() == QLatin1String("https"))
qWarning() << "Request using https scheme without SSL support";
if (scheme == QLatin1String("https"))
qWarning() << "Request using https scheme without SSL support";
} else {
req.setSslConfiguration(m_sslConfiguration);
}
@ -288,8 +315,15 @@ QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkR
JsNetworkRequest jsNetworkRequest(&req, this);
emit resourceRequested(data, &jsNetworkRequest);
// Pass duty to the superclass - Nothing special to do here (yet?)
QNetworkReply *reply = QNetworkAccessManager::createRequest(op, req, outgoingData);
// Pass duty to the superclass - special case: file:/// may be disabled.
// This conditional must match QNetworkAccessManager's own idea of what a
// local file URL is.
QNetworkReply *reply;
if (!m_localUrlAccessEnabled && (isLocalFile || scheme == QLatin1String("qrc"))) {
reply = new NoFileAccessReply(this, req, op);
} else {
reply = QNetworkAccessManager::createRequest(op, req, outgoingData);
}
// reparent jsNetworkRequest to make sure that it will be destroyed with QNetworkReply
jsNetworkRequest.setParent(reply);

View File

@ -67,6 +67,18 @@ private:
QNetworkRequest* m_networkRequest;
};
class NoFileAccessReply : public QNetworkReply
{
Q_OBJECT
public:
NoFileAccessReply(QObject *parent, const QNetworkRequest &req, const QNetworkAccessManager::Operation op);
~NoFileAccessReply();
void abort() {}
protected:
qint64 readData(char *, qint64) { return -1; }
};
class NetworkAccessManager : public QNetworkAccessManager
{
Q_OBJECT
@ -83,6 +95,7 @@ public:
protected:
bool m_ignoreSslErrors;
bool m_localUrlAccessEnabled;
int m_authAttempts;
int m_maxAuthAttempts;
int m_resourceTimeout;

View File

@ -0,0 +1,18 @@
// phantomjs: --local-url-access=no
var assert = require("../../assert");
var p = require("webpage").create();
var url = "file:///nonexistent";
var rsErrorCalled = false;
p.onResourceError = function (error) {
rsErrorCalled = true;
assert.strictEqual(error.url, url);
assert.strictEqual(error.errorCode, 301);
assert.strictEqual(error.errorString, 'Protocol "file" is unknown');
};
p.open(url, function () {
assert(rsErrorCalled);
});

View File

@ -0,0 +1,19 @@
// phantomjs: --local-url-access=yes
var assert = require("../../assert");
var p = require("webpage").create();
var url = "file:///nonexistent";
var rsErrorCalled = false;
p.onResourceError = function (error) {
rsErrorCalled = true;
assert.strictEqual(error.url, url);
assert.strictEqual(error.errorCode, 203);
assert.strictEqual(/^Error opening\b.*?\bnonexistent:/.test(error.errorString),
true);
};
p.open(url, function () {
assert(rsErrorCalled);
});

View File

@ -6,6 +6,7 @@ import imp
import optparse
import os
import posixpath
import shlex
import SimpleHTTPServer
import SocketServer
import socket
@ -216,10 +217,12 @@ def init():
print 'Checking PhantomJS version %s' % version
def run_phantomjs(script, args=[]):
def run_phantomjs(script, script_args=[], pjs_args=[]):
output = []
command = [phantomjs_exe, script]
command.extend(args)
command = [phantomjs_exe]
command.extend(pjs_args)
command.append(script)
command.extend(script_args)
process = subprocess.Popen(command, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
def runner():
@ -244,23 +247,34 @@ def run_phantomjs(script, args=[]):
def run_test(script, name):
args = []
script_args = []
pjs_args = []
if options.verbose:
args.append('--verbose')
script_args.append('--verbose')
result = 0
if not os.path.isfile(script):
print 'Could not locate %s' % name
result = 1
else:
print '%s:' % name
returncode, output = run_phantomjs(script, args)
if returncode != 0:
if not options.verbose:
print '%s' % output
result = 1
try:
with open(script, "rt") as s:
p_prefix = "// phantomjs: "
s_prefix = "// script: "
for line in s:
if line.startswith(p_prefix):
pjs_args.extend(shlex.split(line[len(p_prefix):]))
if line.startswith(s_prefix):
script_args.extend(shlex.split(line[len(s_prefix):]))
if not line.startswith("//"):
break
except OSError as e:
print '%s: %s: %s' % (name, e.filename, e.strerror)
return 1
return result
print '%s:' % name
returncode, output = run_phantomjs(script, script_args, pjs_args)
if returncode != 0:
if not options.verbose:
print '%s' % output
return 1
return 0
def run_tests():