diff --git a/TODO b/TODO index a820a8fc5..f37da377e 100644 --- a/TODO +++ b/TODO @@ -46,7 +46,6 @@ catch import errors for PyQt and QtWebKit elem = frame.findFirstElement('*:focus') somehow unfocus elements (hide blinking cursor) when insert mode is left? tabs: some more padding? -exec command for shell custom stylesheet Really fix URL detection properly diff --git a/qutebrowser/browser/curcommand.py b/qutebrowser/browser/curcommand.py index ccbe08da4..e3e94fcb6 100644 --- a/qutebrowser/browser/curcommand.py +++ b/qutebrowser/browser/curcommand.py @@ -19,6 +19,7 @@ import os import logging +import subprocess from tempfile import mkstemp from functools import partial @@ -32,6 +33,7 @@ import qutebrowser.utils.message as message import qutebrowser.commands.utils as cmdutils import qutebrowser.utils.webelem as webelem import qutebrowser.config.config as config +from qutebrowser.utils.misc import shell_escape class CurCommandDispatcher(QObject): @@ -387,6 +389,23 @@ class CurCommandDispatcher(QObject): tab = self._tabs.currentWidget() tab.zoom(-count) + @cmdutils.register(instance='mainwindow.tabs.cur', split=False) + def spawn(self, cmd): + """Spawn a command in a shell. {} gets replaced by the current URL. + + The URL will already be quoted correctly, so there's no need to do + that. + + The command will be run in a shell, so you can use shell features like + redirections. + + Args: + cmd: The command to execute. + """ + url = urlutils.urlstring(self._tabs.currentWidget().url()) + cmd = cmd.replace('{}', shell_escape(url)) + subprocess.Popen(cmd, shell=True) + @cmdutils.register(instance='mainwindow.tabs.cur', modes=['insert'], name='open_editor', hide=True, needs_js=True) def editor(self): diff --git a/qutebrowser/utils/misc.py b/qutebrowser/utils/misc.py index 8affc9a5d..7975df56a 100644 --- a/qutebrowser/utils/misc.py +++ b/qutebrowser/utils/misc.py @@ -17,6 +17,7 @@ """Other utilities which don't fit anywhere else.""" +import re import shlex from functools import reduce from pkg_resources import resource_string @@ -76,3 +77,30 @@ def safe_shlex_split(s): else: raise return shlex.split(s) + + +def shell_escape(s): + """Escape a string so it's safe to pass to a shell. + + Backported from python's shlex because that's only available since 3.3 and + we might want to support 3.2. + + FIXME: Make this work correctly in Windows, but I'd probably rather kill + myself. [1] might help. + + [1] https://en.wikibooks.org/wiki/Windows_Batch_Scripting#How_a_command_line_is_interpreted + """ + + try: + return shlex.quote(s) + except AttributeError: + _find_unsafe = re.compile(r'[^\w@%+=:,./-]', re.ASCII).search + + if not s: + return "''" + if _find_unsafe(s) is None: + return s + + # use single quotes, and put single quotes into double quotes + # the string $'b is then quoted as '$'"'"'b' + return "'" + s.replace("'", "'\"'\"'") + "'"