Redirect qute:foo to qute://foo

Before, we just returned the same data for both, but then we'll run into
same-origin restrictions as qute:history and qute:history/data are not the same
host.
This commit is contained in:
Florian Bruhin 2017-04-06 20:38:15 +02:00
parent 3cc9f9f073
commit 4ec5700cbf
20 changed files with 118 additions and 63 deletions

View File

@ -9,7 +9,7 @@ directly ask me via IRC (nickname thorsten\`) in #qutebrowser on freenode.
$blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
WARNING: the passwords are stored in qutebrowser's
debug log reachable via the url qute:log
debug log reachable via the url qute://log
$blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
Usage: run as a userscript form qutebrowser, e.g.:

View File

@ -273,7 +273,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
if self.fileobj is None or self._reply is None:
# No filename has been set yet (so we don't empty the buffer) or we
# got a readyRead after the reply was finished (which happens on
# qute:log for example).
# qute://log for example).
return
if not self._reply.isOpen():
raise OSError("Reply is closed!")

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Backend-independent qute:* code.
"""Backend-independent qute://* code.
Module attributes:
pyeval_output: The output of the last :pyeval command.
@ -31,7 +31,7 @@ import time
import urllib.parse
import datetime
from PyQt5.QtCore import QUrlQuery
from PyQt5.QtCore import QUrlQuery, QUrl
import qutebrowser
from qutebrowser.config import config
@ -78,12 +78,25 @@ class QuteSchemeError(Exception):
super().__init__(errorstring)
class add_handler: # pylint: disable=invalid-name
class Redirect(Exception):
"""Decorator to register a qute:* URL handler.
"""Exception to signal a redirect should happen.
Attributes:
_name: The 'foo' part of qute:foo
url: The URL to redirect to, as a QUrl.
"""
def __init__(self, url):
super().__init__(url.toDisplayString())
self.url = url
class add_handler: # pylint: disable=invalid-name
"""Decorator to register a qute://* URL handler.
Attributes:
_name: The 'foo' part of qute://foo
backend: Limit which backends the handler can run with.
"""
@ -106,7 +119,7 @@ class add_handler: # pylint: disable=invalid-name
def wrong_backend_handler(self, url):
"""Show an error page about using the invalid backend."""
html = jinja.render('error.html',
title="Error while opening qute:url",
title="Error while opening qute://url",
url=url.toDisplayString(),
error='{} is not available with this '
'backend'.format(url.toDisplayString()),
@ -128,13 +141,17 @@ def data_for_url(url):
# A url like "qute:foo" is split as "scheme:path", not "scheme:host".
log.misc.debug("url: {}, path: {}, host {}".format(
url.toDisplayString(), path, host))
if path and not host:
new_url = QUrl()
new_url.setScheme('qute')
new_url.setHost(path)
raise Redirect(new_url)
try:
handler = _HANDLERS[path]
handler = _HANDLERS[host]
except KeyError:
try:
handler = _HANDLERS[host]
except KeyError:
raise NoHandlerFound(url)
raise NoHandlerFound(url)
try:
mimetype, data = handler(url)
except OSError as e:
@ -153,7 +170,7 @@ def data_for_url(url):
@add_handler('bookmarks')
def qute_bookmarks(_url):
"""Handler for qute:bookmarks. Display all quickmarks / bookmarks."""
"""Handler for qute://bookmarks. Display all quickmarks / bookmarks."""
bookmarks = sorted(objreg.get('bookmark-manager').marks.items(),
key=lambda x: x[1]) # Sort by title
quickmarks = sorted(objreg.get('quickmark-manager').marks.items(),
@ -246,7 +263,7 @@ def history_data(start_time): # noqa
@add_handler('history')
def qute_history(url):
"""Handler for qute:history. Display and serve history."""
"""Handler for qute://history. Display and serve history."""
if url.path() == '/data':
# Use start_time in query or current time.
try:
@ -309,7 +326,7 @@ def qute_history(url):
@add_handler('javascript')
def qute_javascript(url):
"""Handler for qute:javascript.
"""Handler for qute://javascript.
Return content of file given as query parameter.
"""
@ -323,7 +340,7 @@ def qute_javascript(url):
@add_handler('pyeval')
def qute_pyeval(_url):
"""Handler for qute:pyeval."""
"""Handler for qute://pyeval."""
html = jinja.render('pre.html', title='pyeval', content=pyeval_output)
return 'text/html', html
@ -331,7 +348,7 @@ def qute_pyeval(_url):
@add_handler('version')
@add_handler('verizon')
def qute_version(_url):
"""Handler for qute:version."""
"""Handler for qute://version."""
html = jinja.render('version.html', title='Version info',
version=version.version(),
copyright=qutebrowser.__copyright__)
@ -340,7 +357,7 @@ def qute_version(_url):
@add_handler('plainlog')
def qute_plainlog(url):
"""Handler for qute:plainlog.
"""Handler for qute://plainlog.
An optional query parameter specifies the minimum log level to print.
For example, qute://log?level=warning prints warnings and errors.
@ -360,7 +377,7 @@ def qute_plainlog(url):
@add_handler('log')
def qute_log(url):
"""Handler for qute:log.
"""Handler for qute://log.
An optional query parameter specifies the minimum log level to print.
For example, qute://log?level=warning prints warnings and errors.
@ -381,13 +398,13 @@ def qute_log(url):
@add_handler('gpl')
def qute_gpl(_url):
"""Handler for qute:gpl. Return HTML content as string."""
"""Handler for qute://gpl. Return HTML content as string."""
return 'text/html', utils.read_file('html/COPYING.html')
@add_handler('help')
def qute_help(url):
"""Handler for qute:help."""
"""Handler for qute://help."""
try:
utils.read_file('html/doc/index.html')
except OSError:

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""QtWebEngine specific qute:* handlers and glue code."""
"""QtWebEngine specific qute://* handlers and glue code."""
from PyQt5.QtCore import QBuffer, QIODevice
# pylint: disable=no-name-in-module,import-error,useless-suppression
@ -26,15 +26,15 @@ from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler,
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.browser import qutescheme
from qutebrowser.utils import log
from qutebrowser.utils import log, qtutils
class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
"""Handle qute:* requests on QtWebEngine."""
"""Handle qute://* requests on QtWebEngine."""
def install(self, profile):
"""Install the handler for qute: URLs on the given profile."""
"""Install the handler for qute:// URLs on the given profile."""
profile.installUrlSchemeHandler(b'qute', self)
def requestStarted(self, job):
@ -58,12 +58,15 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
job.fail(QWebEngineUrlRequestJob.UrlNotFound)
except qutescheme.QuteSchemeOSError:
# FIXME:qtwebengine how do we show a better error here?
log.misc.exception("OSError while handling qute:* URL")
log.misc.exception("OSError while handling qute://* URL")
job.fail(QWebEngineUrlRequestJob.UrlNotFound)
except qutescheme.QuteSchemeError:
# FIXME:qtwebengine how do we show a better error here?
log.misc.exception("Error while handling qute:* URL")
log.misc.exception("Error while handling qute://* URL")
job.fail(QWebEngineUrlRequestJob.RequestFailed)
except qutescheme.Redirect as e:
qtutils.ensure_valid(e.url)
job.redirect(e.url)
else:
log.misc.debug("Returning {} data".format(mimetype))

View File

@ -55,7 +55,7 @@ def init():
app = QApplication.instance()
profile = QWebEngineProfile.defaultProfile()
log.init.debug("Initializing qute:* handler...")
log.init.debug("Initializing qute://* handler...")
_qute_scheme_handler = webenginequtescheme.QuteSchemeHandler(parent=app)
_qute_scheme_handler.install(profile)

View File

@ -19,11 +19,15 @@
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
#
# For some reason, a segfault will be triggered if the unnecessary lambdas in
# this file aren't there.
# pylint: disable=unnecessary-lambda
"""Special network replies.."""
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
from PyQt5.QtCore import pyqtSlot, QIODevice, QByteArray, QTimer
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest, QNetworkAccessManager
from PyQt5.QtCore import pyqtSlot, QIODevice, QByteArray, QTimer, QUrl
class FixedDataNetworkReply(QNetworkReply):
@ -114,9 +118,6 @@ class ErrorNetworkReply(QNetworkReply):
# the device to avoid getting a warning.
self.setOpenMode(QIODevice.ReadOnly)
self.setError(error, errorstring)
# For some reason, a segfault will be triggered if these lambdas aren't
# there.
# pylint: disable=unnecessary-lambda
QTimer.singleShot(0, lambda: self.error.emit(error))
QTimer.singleShot(0, lambda: self.finished.emit())
@ -137,3 +138,16 @@ class ErrorNetworkReply(QNetworkReply):
def isRunning(self):
return False
class RedirectNetworkReply(QNetworkReply):
"""A reply which redirects to the given URL."""
def __init__(self, new_url, parent=None):
super().__init__(parent)
self.setAttribute(QNetworkRequest.RedirectionTargetAttribute, new_url)
QTimer.singleShot(0, lambda: self.finished.emit())
def readData(self, _maxlen):
return bytes()

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""QtWebKit specific qute:* handlers and glue code."""
"""QtWebKit specific qute://* handlers and glue code."""
import mimetypes
import functools
@ -28,13 +28,13 @@ from PyQt5.QtNetwork import QNetworkReply
from qutebrowser.browser import pdfjs, qutescheme
from qutebrowser.browser.webkit.network import schemehandler, networkreply
from qutebrowser.utils import jinja, log, message, objreg, usertypes
from qutebrowser.utils import jinja, log, message, objreg, usertypes, qtutils
from qutebrowser.config import configexc, configdata
class QuteSchemeHandler(schemehandler.SchemeHandler):
"""Scheme handler for qute: URLs."""
"""Scheme handler for qute:// URLs."""
def createRequest(self, _op, request, _outgoing_data):
"""Create a new request.
@ -62,6 +62,9 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
except qutescheme.QuteSchemeError as e:
return networkreply.ErrorNetworkReply(request, e.errorstring,
e.error, self.parent())
except qutescheme.Redirect as e:
qtutils.ensure_valid(e.url)
return networkreply.RedirectNetworkReply(e.url, self.parent())
return networkreply.FixedDataNetworkReply(request, data, mimetype,
self.parent())
@ -69,15 +72,15 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
class JSBridge(QObject):
"""Javascript-bridge for special qute:... pages."""
"""Javascript-bridge for special qute://... pages."""
@pyqtSlot(str, str, str)
def set(self, sectname, optname, value):
"""Slot to set a setting from qute:settings."""
"""Slot to set a setting from qute://settings."""
# https://github.com/qutebrowser/qutebrowser/issues/727
if ((sectname, optname) == ('content', 'allow-javascript') and
value == 'false'):
message.error("Refusing to disable javascript via qute:settings "
message.error("Refusing to disable javascript via qute://settings "
"as it needs javascript support.")
return
try:
@ -88,7 +91,7 @@ class JSBridge(QObject):
@qutescheme.add_handler('settings', backend=usertypes.Backend.QtWebKit)
def qute_settings(_url):
"""Handler for qute:settings. View/change qute configuration."""
"""Handler for qute://settings. View/change qute configuration."""
config_getter = functools.partial(objreg.get('config').get, raw=True)
html = jinja.render('settings.html', title='settings', config=configdata,
confget=config_getter)

View File

@ -140,7 +140,7 @@ class WebView(QWebView):
@pyqtSlot()
def add_js_bridge(self):
"""Add the javascript bridge for qute:... pages."""
"""Add the javascript bridge for qute://... pages."""
frame = self.sender()
if not isinstance(frame, QWebFrame):
log.webview.error("Got non-QWebFrame {!r} in "

View File

@ -805,7 +805,7 @@ class ConfigManager(QObject):
if section_ is None and option is None:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
tabbed_browser.openurl(QUrl('qute:settings'), newtab=False)
tabbed_browser.openurl(QUrl('qute://settings'), newtab=False)
return
if option.endswith('?') and option != '?':

View File

@ -1689,7 +1689,7 @@ KEY_DATA = collections.OrderedDict([
('home', ['<Ctrl-h>']),
('stop', ['<Ctrl-s>']),
('print', ['<Ctrl-Alt-p>']),
('open qute:settings', ['Ss']),
('open qute://settings', ['Ss']),
('follow-selected', RETURN_KEYS),
('follow-selected -t', ['<Ctrl-Return>', '<Ctrl-Enter>']),
('repeat-command', ['.']),

View File

@ -228,7 +228,7 @@ def debug_pyeval(s, quiet=False):
else:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='last-focused')
tabbed_browser.openurl(QUrl('qute:pyeval'), newtab=True)
tabbed_browser.openurl(QUrl('qute://pyeval'), newtab=True)
@cmdutils.register(debug=True)

View File

@ -74,7 +74,7 @@ def whitelist_generator():
yield 'PyQt5.QtWebKit.QWebPage.ErrorPageExtensionReturn().fileNames'
yield 'PyQt5.QtWidgets.QStyleOptionViewItem.backgroundColor'
## qute:... handlers
## qute://... handlers
for name in qutescheme._HANDLERS: # pylint: disable=protected-access
yield 'qutebrowser.browser.qutescheme.qute_' + name

View File

@ -83,6 +83,14 @@ Feature: Page history
Then the page should contain the plaintext "3.txt"
Then the page should contain the plaintext "4.txt"
Scenario: Listing history with qute:history redirect
When I open data/numbers/3.txt
And I open data/numbers/4.txt
And I open qute:history without waiting
And I wait until qute://history is loaded
Then the page should contain the plaintext "3.txt"
Then the page should contain the plaintext "4.txt"
## Bugs
@qtwebengine_skip @qtwebkit_ng_skip

View File

@ -405,12 +405,12 @@ Feature: Various utility commands.
# :pyeval
Scenario: Running :pyeval
When I run :debug-pyeval 1+1
And I wait until qute:pyeval is loaded
And I wait until qute://pyeval is loaded
Then the page should contain the plaintext "2"
Scenario: Causing exception in :pyeval
When I run :debug-pyeval 1/0
And I wait until qute:pyeval is loaded
And I wait until qute://pyeval is loaded
Then the page should contain the plaintext "ZeroDivisionError"
Scenario: Running :pyeval with --quiet
@ -512,12 +512,12 @@ Feature: Various utility commands.
When I run :messages cataclysmic
Then the error "Invalid log level cataclysmic!" should be shown
Scenario: Using qute:log directly
When I open qute:log
Scenario: Using qute://log directly
When I open qute://log
Then no crash should happen
Scenario: Using qute:plainlog directly
When I open qute:plainlog
Scenario: Using qute://plainlog directly
When I open qute://plainlog
Then no crash should happen
Scenario: Using :messages without messages

View File

@ -78,15 +78,15 @@ Feature: Setting settings.
When I run :set -t colors statusbar.bg green
Then colors -> statusbar.bg should be green
# qute:settings isn't actually implemented on QtWebEngine, but this works
# qute://settings isn't actually implemented on QtWebEngine, but this works
# (and displays a page saying it's not available)
Scenario: Opening qute:settings
Scenario: Opening qute://settings
When I run :set
And I wait until qute:settings is loaded
And I wait until qute://settings is loaded
Then the following tabs should be open:
- qute:settings (active)
- qute://settings (active)
@qtwebengine_todo: qute:settings is not implemented yet
@qtwebengine_todo: qute://settings is not implemented yet
Scenario: Focusing input fields in qute://settings and entering valid value
When I set general -> ignore-case to false
And I open qute://settings
@ -101,7 +101,7 @@ Feature: Setting settings.
And I press the key "<Tab>"
Then general -> ignore-case should be true
@qtwebengine_todo: qute:settings is not implemented yet
@qtwebengine_todo: qute://settings is not implemented yet
Scenario: Focusing input fields in qute://settings and entering invalid value
When I open qute://settings
# scroll to the right - the table does not fit in the default screen

View File

@ -225,12 +225,12 @@ Feature: quickmarks and bookmarks
Scenario: Listing quickmarks
When I run :quickmark-add http://localhost:(port)/data/numbers/20.txt twenty
And I run :quickmark-add http://localhost:(port)/data/numbers/21.txt twentyone
And I open qute:bookmarks
And I open qute://bookmarks
Then the page should contain the plaintext "twenty"
And the page should contain the plaintext "twentyone"
Scenario: Listing bookmarks
When I open data/title.html in a new tab
And I run :bookmark-add
And I open qute:bookmarks
And I open qute://bookmarks
Then the page should contain the plaintext "Test title"

View File

@ -145,7 +145,7 @@ Feature: Miscellaneous utility commands exposed to the user.
And I run :message-info oldstuff
And I run :repeat 20 message-info otherstuff
And I run :message-info newstuff
And I open qute:log
And I open qute://log
Then the page should contain the plaintext "newstuff"
And the page should not contain the plaintext "oldstuff"

View File

@ -138,10 +138,10 @@ def test_misconfigured_user_dirs(request, httpbin, temp_basedir_env,
def test_no_loglines(request, quteproc_new):
"""Test qute:log with --loglines=0."""
"""Test qute://log with --loglines=0."""
quteproc_new.start(args=['--temp-basedir', '--loglines=0'] +
_base_args(request.config))
quteproc_new.open_path('qute:log')
quteproc_new.open_path('qute://log')
assert quteproc_new.get_content() == 'Log output was disabled.'

View File

@ -91,3 +91,10 @@ def test_error_network_reply(qtbot, req):
assert reply.readData(1) == b''
assert reply.error() == QNetworkReply.UnknownNetworkError
assert reply.errorString() == "This is an error"
def test_redirect_network_reply():
url = QUrl('https://www.example.com/')
reply = networkreply.RedirectNetworkReply(url)
assert reply.readData(1) == b''
assert reply.attribute(QNetworkRequest.RedirectionTargetAttribute) == url

View File

@ -265,6 +265,7 @@ class TestFuzzyUrl:
('file:///tmp/foo', True),
('about:blank', True),
('qute:version', True),
('qute://version', True),
('http://www.qutebrowser.org/', False),
('www.qutebrowser.org', False),
])
@ -317,9 +318,11 @@ def test_get_search_url_invalid(urlutils_config_stub, url):
(True, True, False, 'file:///tmp/foo'),
(True, True, False, 'about:blank'),
(True, True, False, 'qute:version'),
(True, True, False, 'qute://version'),
(True, True, False, 'localhost'),
# _has_explicit_scheme False, special_url True
(True, True, False, 'qute::foo'),
(True, True, False, 'qute:://foo'),
# Invalid URLs
(False, False, False, ''),
(False, True, False, 'onlyscheme:'),