Exemplo n.º 1
0
    def _on_renderer_process_terminated(self, tab, status, code):
        """Show an error when a renderer process terminated."""
        if status == browsertab.TerminationStatus.normal:
            return

        messages = {
            browsertab.TerminationStatus.abnormal:
            "Renderer process exited with status {}".format(code),
            browsertab.TerminationStatus.crashed:
            "Renderer process crashed",
            browsertab.TerminationStatus.killed:
            "Renderer process was killed",
            browsertab.TerminationStatus.unknown:
            "Renderer process did not start",
        }
        msg = messages[status]

        def show_error_page(html):
            tab.set_html(html)
            log.webview.error(msg)

        if qtutils.version_check('5.9', compiled=False):
            url_string = tab.url(requested=True).toDisplayString()
            error_page = jinja.render(
                'error.html',
                title="Error loading {}".format(url_string),
                url=url_string,
                error=msg)
            QTimer.singleShot(100, lambda: show_error_page(error_page))
        else:
            # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58698
            message.error(msg)
            self._remove_tab(tab, crashed=True)
            if self.widget.count() == 0:
                self.tabopen(QUrl('about:blank'))
Exemplo n.º 2
0
def _move_data(old, new):
    """Migrate data from an old to a new directory.

    If the old directory does not exist, the migration is skipped.
    If the new directory already exists, an error is shown.

    Return: True if moving succeeded, False otherwise.
    """
    if not os.path.exists(old):
        return False

    log.init.debug("Migrating data from {} to {}".format(old, new))

    if os.path.exists(new):
        if not os.path.isdir(new) or os.listdir(new):
            message.error("Failed to move data from {} as {} is non-empty!"
                          .format(old, new))
            return False
        os.rmdir(new)

    try:
        shutil.move(old, new)
    except OSError as e:
        message.error("Failed to move data from {} to {}: {}".format(
            old, new, e))
        return False

    return True
Exemplo n.º 3
0
    def run(self, win_id, args=None, count=None):
        """Run the command.

        Note we don't catch CommandError here as it might happen async.

        Args:
            win_id: The window ID the command is run in.
            args: Arguments to the command.
            count: Command repetition count.
        """
        dbgout = ["command called:", self.name]
        if args:
            dbgout.append(str(args))
        elif args is None:
            args = []

        if count is not None:
            dbgout.append("(count={})".format(count))
        log.commands.debug(' '.join(dbgout))
        try:
            self.namespace = self.parser.parse_args(args)
        except argparser.ArgumentParserError as e:
            message.error('{}: {}'.format(self.name, e),
                          stack=traceback.format_exc())
            return
        except argparser.ArgumentParserExit as e:
            log.commands.debug("argparser exited with status {}: {}".format(
                e.status, e))
            return
        self._count = count
        self._check_prerequisites(win_id)
        posargs, kwargs = self._get_call_args(win_id)
        log.commands.debug('Calling {}'.format(
            debug_utils.format_call(self.handler, posargs, kwargs)))
        self.handler(*posargs, **kwargs)
Exemplo n.º 4
0
 def _parse_line(self, line):
     try:
         key, url = line.rsplit(maxsplit=1)
     except ValueError:
         message.error("Invalid quickmark '{}'".format(line))
     else:
         self.marks[key] = url
Exemplo n.º 5
0
    def quickmark_add(self, url, name):
        """Add a new quickmark.

        You can view all saved quickmarks on the
        link:glimpse://bookmarks[bookmarks page].

        Args:
            url: The url to add as quickmark.
            name: The name for the new quickmark.
        """
        # We don't raise cmdutils.CommandError here as this can be called async
        # via prompt_save.
        if not name:
            message.error("Can't set mark with empty name!")
            return
        if not url:
            message.error("Can't set mark with empty URL!")
            return

        def set_mark():
            """Really set the quickmark."""
            self.marks[name] = url
            self.changed.emit()
            log.misc.debug("Added quickmark {} for {}".format(name, url))

        if name in self.marks:
            message.confirm_async(title="Override existing quickmark?",
                                  yes_action=set_mark,
                                  default=True,
                                  url=url)
        else:
            set_mark()
Exemplo n.º 6
0
    def _hint_strings(self, elems):
        """Calculate the hint strings for elems.

        Inspired by Vimium.

        Args:
            elems: The elements to get hint strings for.

        Return:
            A list of hint strings, in the same order as the elements.
        """
        if not elems:
            return []
        hint_mode = self._context.hint_mode
        if hint_mode == 'word':
            try:
                return self._word_hinter.hint(elems)
            except HintingError as e:
                message.error(str(e))
                # falls back on letter hints
        if hint_mode == 'number':
            chars = '0123456789'
        else:
            chars = config.val.hints.chars
        min_chars = config.val.hints.min_chars
        if config.val.hints.scatter and hint_mode != 'number':
            return self._hint_scattered(min_chars, chars, elems)
        else:
            return self._hint_linear(min_chars, chars, elems)
Exemplo n.º 7
0
 def autosave(self):
     """Slot used when the configs are auto-saved."""
     for (key, saveable) in self.saveables.items():
         try:
             saveable.save(silent=True)
         except OSError as e:
             message.error("Failed to auto-save {}: {}".format(key, e))
Exemplo n.º 8
0
    def set_target(self, target):
        """Set the target for a given download.

        Args:
            target: The DownloadTarget for this download.
        """
        if isinstance(target, FileObjDownloadTarget):
            self._set_fileobj(target.fileobj, autoclose=False)
        elif isinstance(target, FileDownloadTarget):
            self._set_filename(target.filename,
                               force_overwrite=target.force_overwrite)
        elif isinstance(target, (OpenFileDownloadTarget, PDFJSDownloadTarget)):
            try:
                fobj = temp_download_manager.get_tmpfile(self.basename)
            except OSError as exc:
                msg = "Download error: {}".format(exc)
                message.error(msg)
                self.cancel()
                return

            if isinstance(target, OpenFileDownloadTarget):
                self.finished.connect(
                    functools.partial(self._open_if_successful,
                                      target.cmdline))
            elif isinstance(target, PDFJSDownloadTarget):
                self.finished.connect(self._pdfjs_if_successful)
            else:
                raise utils.Unreachable

            self._set_tempfile(fobj)
        else:  # pragma: no cover
            raise ValueError("Unsupported download target: {}".format(target))
Exemplo n.º 9
0
    def _prevnext_cb(elems):
        elem = _find_prevnext(prev, elems)
        word = 'prev' if prev else 'forward'

        if elem is None:
            message.error("No {} links found!".format(word))
            return
        url = elem.resolve_url(baseurl)
        if url is None:
            message.error("No {} links found!".format(word))
            return
        qtutils.ensure_valid(url)

        cur_tabbed_browser = objreg.get('tabbed-browser',
                                        scope='window',
                                        window=win_id)

        if window:
            new_window = mainwindow.MainWindow(
                private=cur_tabbed_browser.is_private)
            new_window.show()
            tabbed_browser = objreg.get('tabbed-browser',
                                        scope='window',
                                        window=new_window.win_id)
            tabbed_browser.tabopen(url, background=False)
        elif tab:
            cur_tabbed_browser.tabopen(url, background=background)
        else:
            browsertab.load_url(url)
Exemplo n.º 10
0
def glimpse_settings(url):
    """Handler for glimpse://settings. View/change glimpse configuration."""
    global csrf_token

    if url.path() == '/set':
        if url.password() != csrf_token:
            message.error("Invalid CSRF token for glimpse://settings!")
            raise RequestDeniedError("Invalid CSRF token!")
        return _glimpse_settings_set(url)

    # Requests to glimpse://settings/set should only be allowed from
    # glimpse://settings. As an additional security precaution, we generate a CSRF
    # token to use here.
    if secrets:
        csrf_token = secrets.token_urlsafe()
    else:
        # On Python < 3.6, from secrets.py
        token = base64.urlsafe_b64encode(os.urandom(32))
        csrf_token = token.rstrip(b'=').decode('ascii')

    src = jinja.render('settings.html',
                       title='settings',
                       configdata=configdata,
                       confget=config.instance.get_str,
                       csrf_token=csrf_token)
    return 'text/html', src
Exemplo n.º 11
0
    def _on_finished(self, code, status):
        """Show a message when the process finished."""
        self._started = False
        log.procs.debug("Process finished with code {}, status {}.".format(
            code, status))

        encoding = locale.getpreferredencoding(do_setlocale=False)
        stderr = bytes(self._proc.readAllStandardError()).decode(
            encoding, 'replace')
        stdout = bytes(self._proc.readAllStandardOutput()).decode(
            encoding, 'replace')

        if status == QProcess.CrashExit:
            exitinfo = "{} crashed!".format(self._what.capitalize())
            message.error(exitinfo)
        elif status == QProcess.NormalExit and code == 0:
            exitinfo = "{} exited successfully.".format(
                self._what.capitalize())
            if self.verbose:
                message.info(exitinfo)
        else:
            assert status == QProcess.NormalExit
            # We call this 'status' here as it makes more sense to the user -
            # it's actually 'code'.
            exitinfo = ("{} exited with status {}, see :messages for "
                        "details.").format(self._what.capitalize(), code)
            message.error(exitinfo)

            if stdout:
                log.procs.error("Process stdout:\n" + stdout.strip())
            if stderr:
                log.procs.error("Process stderr:\n" + stderr.strip())

        glimpsescheme.spawn_output = self._spawn_format(exitinfo, stdout, stderr)
Exemplo n.º 12
0
def _load_session(name):
    """Load the default session.

    Args:
        name: The name of the session to load, or None to read state file.
    """
    session_manager = objreg.get('session-manager')
    if name is None and session_manager.exists('_autosave'):
        name = '_autosave'
    elif name is None:
        try:
            name = configfiles.state['general']['session']
        except KeyError:
            # No session given as argument and none in the session file ->
            # start without loading a session
            return

    try:
        session_manager.load(name)
    except sessions.SessionNotFoundError:
        message.error("Session {} not found!".format(name))
    except sessions.SessionError as e:
        message.error("Failed to load session {}: {}".format(name, e))
    try:
        del configfiles.state['general']['session']
    except KeyError:
        pass
    # If this was a _restart session, delete it.
    if name == '_restart':
        session_manager.delete('_restart')
Exemplo n.º 13
0
        def on_file_updated() -> None:
            """Source the new config when editing finished.

            This can't use cmdutils.CommandError as it's run async.
            """
            try:
                configfiles.read_config_py(filename)
            except configexc.ConfigFileErrors as e:
                message.error(str(e))
Exemplo n.º 14
0
 def callback(text):
     """Set the commandline to the edited 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()
Exemplo n.º 15
0
 def accept(self, value=None, save=False):
     self._check_save_support(save)
     text = value if value is not None else self._lineedit.text()
     text = downloads.transform_path(text)
     if text is None:
         message.error("Invalid filename")
         return False
     self.question.answer = text
     return True
Exemplo n.º 16
0
 def _handle_error(self, safely) -> typing.Iterator[None]:
     """Show exceptions as errors if safely=True is given."""
     try:
         yield
     except cmdexc.Error as e:
         if safely:
             message.error(str(e), stack=traceback.format_exc())
         else:
             raise
Exemplo n.º 17
0
 def backup(self):
     """Create a backup if the content has changed from the original."""
     if not self._content:
         return
     try:
         fname = self._create_tempfile(self._content,
                                       'glimpsebrowser-editor-backup-')
         message.info('Editor backup at {}'.format(fname))
     except OSError as e:
         message.error('Failed to create editor backup: {}'.format(e))
Exemplo n.º 18
0
    def prepare_run(self, *args, **kwargs):
        self._args = args
        self._kwargs = kwargs

        try:
            handle = tempfile.NamedTemporaryFile(delete=False)
            handle.close()
            self._filepath = handle.name
        except OSError as e:
            message.error("Error while creating tempfile: {}".format(e))
            return
Exemplo n.º 19
0
    def event(self, e):
        """Handle macOS FileOpen events."""
        if e.type() == QEvent.FileOpen:
            url = e.url()
            if url.isValid():
                open_url(url, no_raise=True)
            else:
                message.error("Invalid URL: {}".format(url.errorString()))
        else:
            return super().event(e)

        return True
Exemplo n.º 20
0
def invalid_url_error(url, action):
    """Display an error message for a URL.

    Args:
        action: The action which was interrupted by the error.
    """
    if url.isValid():
        raise ValueError("Calling invalid_url_error with valid URL {}".format(
            url.toDisplayString()))
    errstring = get_errstring(
        url, "Trying to {} with invalid URL".format(action))
    message.error(errstring)
Exemplo n.º 21
0
    def start_detached(self, cmd, args):
        """Convenience wrapper around QProcess::startDetached."""
        log.procs.debug("Starting detached.")
        self._pre_start(cmd, args)
        ok, _pid = self._proc.startDetached(cmd, args, None)

        if not ok:
            message.error("Error while spawning {}".format(self._what))
            return False

        log.procs.debug("Process started.")
        self._started = True
        return True
Exemplo n.º 22
0
 def _on_file_changed(self, path):
     try:
         with open(path, 'r', encoding=config.val.editor.encoding) as f:
             text = f.read()
     except OSError as e:
         # NOTE: Do not replace this with "raise CommandError" as it's
         # executed async.
         message.error("Failed to read back edited file: {}".format(e))
         return
     log.procs.debug("Read back: {}".format(text))
     if self._content != text:
         self._content = text
         self.file_updated.emit(text)
Exemplo n.º 23
0
    def _set_filename(self,
                      filename,
                      *,
                      force_overwrite=False,
                      remember_directory=True):
        """Set the filename to save the download to.

        Args:
            filename: The full filename to save the download to.
                      None: special value to stop the download.
            force_overwrite: Force overwriting existing files.
            remember_directory: If True, remember the directory for future
                                downloads.
        """
        filename = os.path.expanduser(filename)
        self._ensure_can_set_filename(filename)

        self._filename = create_full_filename(self.basename, filename)
        if self._filename is None:
            # We only got a filename (without directory) or a relative path
            # from the user, so we append that to the default directory and
            # try again.
            self._filename = create_full_filename(
                self.basename, os.path.join(download_dir(), filename))

        # At this point, we have a misconfigured XDG_DOWNLOAD_DIR, as
        # download_dir() + filename is still no absolute path.
        # The config value is checked for "absoluteness", but
        # ~/.config/user-dirs.dirs may be misconfigured and a non-absolute path
        # may be set for XDG_DOWNLOAD_DIR
        if self._filename is None:
            message.error(
                "XDG_DOWNLOAD_DIR points to a relative path - please check"
                " your ~/.config/user-dirs.dirs. The download is saved in"
                " your home directory.", )
            # fall back to $HOME as download_dir
            self._filename = create_full_filename(self.basename,
                                                  os.path.expanduser('~'))

        dirname = os.path.dirname(self._filename)
        if not os.path.exists(dirname):
            txt = ("<b>{}</b> does not exist. Create it?".format(
                html.escape(os.path.join(dirname, ""))))
            self._ask_create_parent_question("Create directory?", txt,
                                             force_overwrite,
                                             remember_directory)
        else:
            self._after_create_parent_question(force_overwrite,
                                               remember_directory)
Exemplo n.º 24
0
def ignore_certificate_errors(url, errors, abort_on):
    """Display a certificate error question.

    Args:
        url: The URL the errors happened in
        errors: A list of QSslErrors or QWebEngineCertificateErrors

    Return:
        True if the error should be ignored, False otherwise.
    """
    ssl_strict = config.instance.get('content.ssl_strict', url=url)
    log.webview.debug("Certificate errors {!r}, strict {}".format(
        errors, ssl_strict))

    for error in errors:
        assert error.is_overridable(), repr(error)

    if ssl_strict == 'ask':
        err_template = jinja.environment.from_string("""
            Errors while loading <b>{{url.toDisplayString()}}</b>:<br/>
            <ul>
            {% for err in errors %}
                <li>{{err}}</li>
            {% endfor %}
            </ul>
        """.strip())
        msg = err_template.render(url=url, errors=errors)

        urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
        ignore = message.ask(title="Certificate errors - continue?", text=msg,
                             mode=usertypes.PromptMode.yesno, default=False,
                             abort_on=abort_on, url=urlstr)
        if ignore is None:
            # prompt aborted
            ignore = False
        return ignore
    elif ssl_strict is False:
        log.webview.debug("ssl_strict is False, only warning about errors")
        for err in errors:
            # FIXME we might want to use warn here (non-fatal error)
            # https://github.com/glimpsebrowser/glimpsebrowser/issues/114
            message.error('Certificate error: {}'.format(err))
        return True
    elif ssl_strict is True:
        return False
    else:
        raise ValueError("Invalid ssl_strict value {!r}".format(ssl_strict))
    raise utils.Unreachable
Exemplo n.º 25
0
def glimpse_help(url):
    """Handler for glimpse://help."""
    urlpath = url.path()
    if not urlpath or urlpath == '/':
        urlpath = 'index.html'
    else:
        urlpath = urlpath.lstrip('/')
    if not docutils.docs_up_to_date(urlpath):
        message.error("Your documentation is outdated! Please re-run "
                      "scripts/asciidoc2html.py.")

    path = 'html/doc/{}'.format(urlpath)
    if not urlpath.endswith('.html'):
        try:
            bdata = utils.read_file(path, binary=True)
        except OSError as e:
            raise SchemeOSError(e)
        mimetype = utils.guess_mimetype(urlpath)
        return mimetype, bdata

    try:
        data = utils.read_file(path)
    except OSError:
        asciidoc = _asciidoc_fallback_path(path)

        if asciidoc is None:
            raise

        preamble = textwrap.dedent("""
            There was an error loading the documentation!

            This most likely means the documentation was not generated
            properly. If you are running glimpsebrowser from the git repository,
            please (re)run scripts/asciidoc2html.py and reload this page.

            If you're running a released version this is a bug, please use
            :report to report it.

            Falling back to the plaintext version.

            ---------------------------------------------------------------


        """)
        return 'text/plain', (preamble + asciidoc).encode('utf-8')
    else:
        return 'text/html', data
Exemplo n.º 26
0
    def _start_cb(self, elems):
        """Initialize the elements and labels based on the context set."""
        if self._context is None:
            log.hints.debug("In _start_cb without context!")
            return

        if not elems:
            message.error("No elements found.")
            return

        # Because _start_cb is called asynchronously, it's possible that the
        # user switched to another tab or closed the tab/window. In that case
        # we should not start hinting.
        tabbed_browser = objreg.get('tabbed-browser', default=None,
                                    scope='window', window=self._win_id)
        tab = tabbed_browser.widget.currentWidget()
        if tab.tab_id != self._tab_id:
            log.hints.debug(
                "Current tab changed ({} -> {}) before _start_cb is run."
                .format(self._tab_id, tab.tab_id))
            return

        strings = self._hint_strings(elems)
        log.hints.debug("hints: {}".format(', '.join(strings)))

        for elem, string in zip(elems, strings):
            label = HintLabel(elem, self._context)
            label.update_text('', string)
            self._context.all_labels.append(label)
            self._context.labels[string] = label

        keyparsers = objreg.get('keyparsers', scope='window',
                                window=self._win_id)
        keyparser = keyparsers[usertypes.KeyMode.hint]
        keyparser.update_bindings(strings)

        message_bridge = objreg.get('message-bridge', scope='window',
                                    window=self._win_id)
        message_bridge.set_text(self._get_text())
        modeman.enter(self._win_id, usertypes.KeyMode.hint,
                      'HintManager.start')

        if self._context.first:
            self._fire(strings[0])
            return
        # to make auto_follow == 'always' work
        self._handle_auto_follow()
Exemplo n.º 27
0
def early_init(args: argparse.Namespace) -> None:
    """Initialize the part of the config which works without a QApplication."""
    configdata.init()

    yaml_config = configfiles.YamlConfig()

    config.instance = config.Config(yaml_config=yaml_config)
    config.val = config.ConfigContainer(config.instance)
    configapi.val = config.ConfigContainer(config.instance)
    config.key_instance = config.KeyConfig(config.instance)
    config.cache = configcache.ConfigCache()
    yaml_config.setParent(config.instance)

    for cf in config.change_filters:
        cf.validate()

    config_commands = configcommands.ConfigCommands(
        config.instance, config.key_instance)
    objreg.register('config-commands', config_commands)

    config_file = standarddir.config_py()

    try:
        if os.path.exists(config_file):
            configfiles.read_config_py(config_file)
        else:
            configfiles.read_autoconfig()
    except configexc.ConfigFileErrors as e:
        log.config.exception("Error while loading {}".format(e.basename))
        global _init_errors
        _init_errors = e

    configfiles.init()

    for opt, val in args.temp_settings:
        try:
            config.instance.set_str(opt, val)
        except configexc.Error as e:
            message.error("set: {} - {}".format(e.__class__.__name__, e))

    objects.backend = get_backend(args)

    configtypes.Font.monospace_fonts = config.val.fonts.monospace
    config.instance.changed.connect(_update_monospace_fonts)

    _init_envvars()
Exemplo n.º 28
0
    def prompt_yank(self, sel=False):
        """Yank URL to clipboard or primary selection.

        Args:
            sel: Use the primary selection instead of the clipboard.
        """
        question = self._prompt.question
        if question.url is None:
            message.error('No URL found.')
            return
        if sel and utils.supports_selection():
            target = 'primary selection'
        else:
            sel = False
            target = 'clipboard'
        utils.set_clipboard(question.url, sel)
        message.info("Yanked to {}: {}".format(target, question.url))
Exemplo n.º 29
0
    def _mousepress_backforward(self, e):
        """Handle back/forward mouse button presses.

        Args:
            e: The QMouseEvent.
        """
        if e.button() in [Qt.XButton1, Qt.LeftButton]:
            # Back button on mice which have it, or rocker gesture
            if self._tab.history.can_go_back():
                self._tab.history.back()
            else:
                message.error("At beginning of history.")
        elif e.button() in [Qt.XButton2, Qt.RightButton]:
            # Forward button on mice which have it, or rocker gesture
            if self._tab.history.can_go_forward():
                self._tab.history.forward()
            else:
                message.error("At end of history.")
Exemplo n.º 30
0
def download_dir():
    """Get the download directory to use."""
    directory = config.val.downloads.location.directory
    remember_dir = config.val.downloads.location.remember

    if remember_dir and last_used_directory is not None:
        ddir = last_used_directory
    elif directory is None:
        ddir = standarddir.download()
    else:
        ddir = directory

    try:
        os.makedirs(ddir, exist_ok=True)
    except OSError as e:
        message.error("Failed to create download directory: {}".format(e))

    return ddir