Repository: qutebrowser/qutebrowser

Issue:
:edit-command accepts invalid commands
When using `:edit-command` and changing the command to `foo` (without an initial `:`):

```
10:33:50 DEBUG    procs      guiprocess:on_finished:98 Process finished with code 0, status 0.
10:33:50 DEBUG    procs      editor:on_proc_closed:73 Editor closed
10:33:50 DEBUG    procs      editor:on_proc_closed:90 Read back: foo

10:33:50 ERROR    misc       crashsignal:exception_hook:216 Uncaught exception
Traceback (most recent call last):
  File "/home/florian/proj/qutebrowser/git/qutebrowser/mainwindow/statusbar/command.py", line 179, in callback
    self.set_cmd_text(text)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/mainwindow/statusbar/command.py", line 86, in set_cmd_text
    self.setText(text)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/mainwindow/statusbar/command.py", line 212, in setText
    "'{}'!".format(text))
AssertionError: setText got called with invalid text 'foo
'!
```

When changing it to an empty file and pressing enter:

```
10:34:38 DEBUG    commands   command:run:484 command called: command-accept
[...]
10:34:38 ERROR    misc       crashsignal:exception_hook:216 Uncaught exception
Traceback (most recent call last):
  File "/home/florian/proj/qutebrowser/git/qutebrowser/app.py", line 935, in eventFilter
    return handler(event)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/app.py", line 895, in _handle_key_event
    return man.eventFilter(event)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/keyinput/modeman.py", line 326, in eventFilter
    return self._eventFilter_keypress(event)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/keyinput/modeman.py", line 162, in _eventFilter_keypress
    handled = parser.handle(event)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/keyinput/basekeyparser.py", line 266, in handle
    handled = self._handle_special_key(e)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/keyinput/basekeyparser.py", line 139, in _handle_special_key
    self.execute(cmdstr, self.Type.special, count)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/keyinput/keyparser.py", line 44, in execute
    self._commandrunner.run(cmdstr, count)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/commands/runners.py", line 301, in run
    result.cmd.run(self._win_id, args, count=count)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/commands/command.py", line 500, in run
    self.handler(*posargs, **kwargs)
  File "/home/florian/proj/qutebrowser/git/qutebrowser/mainwindow/statusbar/command.py", line 167, in command_accept
    self.got_cmd[str].emit(prefixes[text[0]] + text[1:])
IndexError: string index out of range
```

Report: https://crashes.qutebrowser.org/view/97044b65

cc @rcorre



Discussion:

Resolving patch:
diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py
--- a/qutebrowser/mainwindow/statusbar/command.py
+++ b/qutebrowser/mainwindow/statusbar/command.py
@@ -26,7 +26,7 @@
 from qutebrowser.commands import cmdexc, cmdutils
 from qutebrowser.misc import cmdhistory, editor
 from qutebrowser.misc import miscwidgets as misc
-from qutebrowser.utils import usertypes, log, objreg
+from qutebrowser.utils import usertypes, log, objreg, message
 
 
 class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
@@ -176,6 +176,10 @@ def edit_command(self, run=False):
         ed = editor.ExternalEditor(parent=self)
 
         def callback(text):
+            if not text or text[0] not in modeparsers.STARTCHARS:
+                message.error('command must start with one of {}'
+                              .format(modeparsers.STARTCHARS))
+                return
             self.set_cmd_text(text)
             if run:
                 self.command_accept()


Test patch:
diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature
--- a/tests/end2end/features/editor.feature
+++ b/tests/end2end/features/editor.feature
@@ -150,3 +150,16 @@ Feature: Opening external editors
         And I run :edit-command --run
         Then the message "bar" should be shown
         And "Leaving mode KeyMode.command (reason: cmd accept)" should be logged
+
+    Scenario: Edit a command and omit the start char
+        When I set up a fake editor returning "message-info foo"
+        And I run :edit-command
+        Then the error "command must start with one of :/?" should be shown
+        And "Leaving mode KeyMode.command *" should not be logged
+
+    Scenario: Edit a command to be empty
+        When I run :set-cmd-text :
+        When I set up a fake editor returning empty text
+        And I run :edit-command
+        Then the error "command must start with one of :/?" should be shown
+        And "Leaving mode KeyMode.command *" should not be logged
diff --git a/tests/end2end/features/test_editor_bdd.py b/tests/end2end/features/test_editor_bdd.py
--- a/tests/end2end/features/test_editor_bdd.py
+++ b/tests/end2end/features/test_editor_bdd.py
@@ -58,3 +58,8 @@ def set_up_editor(quteproc, server, tmpdir, text):
     """.format(text=text)))
     editor = json.dumps([sys.executable, str(script), '{}'])
     quteproc.set_setting('editor.command', editor)
+
+@bdd.when(bdd.parsers.parse('I set up a fake editor returning empty text'))
+def set_up_editor_empty(quteproc, server, tmpdir):
+    """Set up editor.command to a small python script inserting empty text."""
+    set_up_editor(quteproc, server, tmpdir, "")