Beispiel #1
0
def create_full_filename(basename, filename):
    """Create a full filename based on the given basename and filename.

    Args:
        basename: The basename to use if filename is a directory.
        filename: The path to a folder or file where you want to save.

    Return:
        The full absolute path, or None if filename creation was not possible.
    """
    # Remove chars which can't be encoded in the filename encoding.
    # See https://github.com/qutebrowser/qutebrowser/issues/427
    encoding = sys.getfilesystemencoding()
    filename = utils.force_encoding(filename, encoding)
    basename = utils.force_encoding(basename, encoding)
    if os.path.isabs(filename) and (os.path.isdir(filename)
                                    or filename.endswith(os.sep)):
        # We got an absolute directory from the user, so we save it under
        # the default filename in that directory.
        return os.path.join(filename, basename)
    elif os.path.isabs(filename):
        # We got an absolute filename from the user, so we save it under
        # that filename.
        return filename
    return None
Beispiel #2
0
def create_full_filename(basename, filename):
    """Create a full filename based on the given basename and filename.

    Args:
        basename: The basename to use if filename is a directory.
        filename: The path to a folder or file where you want to save.

    Return:
        The full absolute path, or None if filename creation was not possible.
    """
    # Remove chars which can't be encoded in the filename encoding.
    # See https://github.com/qutebrowser/qutebrowser/issues/427
    encoding = sys.getfilesystemencoding()
    filename = utils.force_encoding(filename, encoding)
    basename = utils.force_encoding(basename, encoding)
    if os.path.isabs(filename) and (os.path.isdir(filename) or
                                    filename.endswith(os.sep)):
        # We got an absolute directory from the user, so we save it under
        # the default filename in that directory.
        return os.path.join(filename, basename)
    elif os.path.isabs(filename):
        # We got an absolute filename from the user, so we save it under
        # that filename.
        return filename
    return None
Beispiel #3
0
def start_download_checked(target, tab):
    """First check if dest is already a file, then start the download.

    Args:
        target: The DownloadTarget where the resulting file should be saved.
        tab: Specify the tab whose page should be loaded.
    """
    if not isinstance(target, downloads.FileDownloadTarget):
        _start_download(target, tab)
        return
    # The default name is 'page title.mhtml'
    title = tab.title()
    default_name = utils.sanitize_filename(title + '.mhtml')

    # Remove characters which cannot be expressed in the file system encoding
    encoding = sys.getfilesystemencoding()
    default_name = utils.force_encoding(default_name, encoding)
    dest = utils.force_encoding(target.filename, encoding)

    dest = os.path.expanduser(dest)

    # See if we already have an absolute path
    path = downloads.create_full_filename(default_name, dest)
    if path is None:
        # We still only have a relative path, prepend download_dir and
        # try again.
        path = downloads.create_full_filename(
            default_name, os.path.join(downloads.download_dir(), dest))
    downloads.last_used_directory = os.path.dirname(path)

    # Avoid downloading files if we can't save the output anyway...
    # Yes, this is prone to race conditions, but we're checking again before
    # saving the file anyway.
    if not os.path.isdir(os.path.dirname(path)):
        folder = os.path.dirname(path)
        message.error("Directory {} does not exist.".format(folder))
        return

    target = downloads.FileDownloadTarget(path)
    if not os.path.isfile(path):
        _start_download(target, tab=tab)
        return

    q = usertypes.Question()
    q.mode = usertypes.PromptMode.yesno
    q.title = "Overwrite existing file?"
    q.text = "<b>{}</b> already exists. Overwrite?".format(
        html.escape(path))
    q.completed.connect(q.deleteLater)
    q.answered_yes.connect(functools.partial(
        _start_download, target, tab=tab))
    message.global_bridge.ask(q, blocking=False)
Beispiel #4
0
def start_download_checked(target, tab):
    """First check if dest is already a file, then start the download.

    Args:
        target: The DownloadTarget where the resulting file should be saved.
        tab: Specify the tab whose page should be loaded.
    """
    if not isinstance(target, downloads.FileDownloadTarget):
        _start_download(target, tab)
        return
    # The default name is 'page title.mhtml'
    title = tab.title()
    default_name = utils.sanitize_filename(title + '.mhtml')

    # Remove characters which cannot be expressed in the file system encoding
    encoding = sys.getfilesystemencoding()
    default_name = utils.force_encoding(default_name, encoding)
    dest = utils.force_encoding(target.filename, encoding)

    dest = os.path.expanduser(dest)

    # See if we already have an absolute path
    path = downloads.create_full_filename(default_name, dest)
    if path is None:
        # We still only have a relative path, prepend download_dir and
        # try again.
        path = downloads.create_full_filename(
            default_name, os.path.join(downloads.download_dir(), dest))
    downloads.last_used_directory = os.path.dirname(path)

    # Avoid downloading files if we can't save the output anyway...
    # Yes, this is prone to race conditions, but we're checking again before
    # saving the file anyway.
    if not os.path.isdir(os.path.dirname(path)):
        folder = os.path.dirname(path)
        message.error("Directory {} does not exist.".format(folder))
        return

    target = downloads.FileDownloadTarget(path)
    if not os.path.isfile(path):
        _start_download(target, tab=tab)
        return

    q = usertypes.Question()
    q.mode = usertypes.PromptMode.yesno
    q.title = "Overwrite existing file?"
    q.text = "<b>{}</b> already exists. Overwrite?".format(
        html.escape(path))
    q.completed.connect(q.deleteLater)
    q.answered_yes.connect(functools.partial(
        _start_download, target, tab=tab))
    message.global_bridge.ask(q, blocking=False)
Beispiel #5
0
def start_download_checked(dest, web_view):
    """First check if dest is already a file, then start the download.

    Args:
        dest: The filename where the resulting file should be saved.
        web_view: Specify the webview whose page should be loaded.
    """
    # The default name is 'page title.mht'
    title = web_view.title()
    default_name = utils.sanitize_filename(title + '.mht')

    # Remove characters which cannot be expressed in the file system encoding
    encoding = sys.getfilesystemencoding()
    default_name = utils.force_encoding(default_name, encoding)
    dest = utils.force_encoding(dest, encoding)

    dest = os.path.expanduser(dest)

    # See if we already have an absolute path
    path = downloads.create_full_filename(default_name, dest)
    if path is None:
        # We still only have a relative path, prepend download_dir and
        # try again.
        path = downloads.create_full_filename(
            default_name, os.path.join(downloads.download_dir(), dest))
    downloads.last_used_directory = os.path.dirname(path)

    # Avoid downloading files if we can't save the output anyway...
    # Yes, this is prone to race conditions, but we're checking again before
    # saving the file anyway.
    if not os.path.isdir(os.path.dirname(path)):
        folder = os.path.dirname(path)
        message.error(web_view.win_id,
                      "Directory {} does not exist.".format(folder))
        return

    if not os.path.isfile(path):
        _start_download(path, web_view=web_view)
        return

    q = usertypes.Question()
    q.mode = usertypes.PromptMode.yesno
    q.text = "{} exists. Overwrite?".format(path)
    q.completed.connect(q.deleteLater)
    q.answered_yes.connect(
        functools.partial(_start_download, path, web_view=web_view))
    message_bridge = objreg.get('message-bridge',
                                scope='window',
                                window=web_view.win_id)
    message_bridge.ask(q, blocking=False)
Beispiel #6
0
def start_download_checked(dest, web_view):
    """First check if dest is already a file, then start the download.

    Args:
        dest: The filename where the resulting file should be saved.
        web_view: Specify the webview whose page should be loaded.
    """
    # The default name is 'page title.mht'
    title = web_view.title()
    default_name = utils.sanitize_filename(title + '.mht')

    # Remove characters which cannot be expressed in the file system encoding
    encoding = sys.getfilesystemencoding()
    default_name = utils.force_encoding(default_name, encoding)
    dest = utils.force_encoding(dest, encoding)

    dest = os.path.expanduser(dest)

    # See if we already have an absolute path
    path = downloads.create_full_filename(default_name, dest)
    if path is None:
        # We still only have a relative path, prepend download_dir and
        # try again.
        path = downloads.create_full_filename(
            default_name, os.path.join(downloads.download_dir(), dest))
    downloads.last_used_directory = os.path.dirname(path)

    # Avoid downloading files if we can't save the output anyway...
    # Yes, this is prone to race conditions, but we're checking again before
    # saving the file anyway.
    if not os.path.isdir(os.path.dirname(path)):
        folder = os.path.dirname(path)
        message.error(web_view.win_id,
                      "Directory {} does not exist.".format(folder))
        return

    if not os.path.isfile(path):
        _start_download(path, web_view=web_view)
        return

    q = usertypes.Question()
    q.mode = usertypes.PromptMode.yesno
    q.text = "{} exists. Overwrite?".format(path)
    q.completed.connect(q.deleteLater)
    q.answered_yes.connect(functools.partial(
        _start_download, path, web_view=web_view))
    message_bridge = objreg.get('message-bridge', scope='window',
                                window=web_view.win_id)
    message_bridge.ask(q, blocking=False)
Beispiel #7
0
    def set_filename(self, filename):
        """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.
        """
        if self.fileobj is not None:
            raise ValueError("fileobj was already set! filename: {}, "
                             "existing: {}, fileobj {}".format(
                                 filename, self._filename, self.fileobj))
        filename = os.path.expanduser(filename)
        # Remove chars which can't be encoded in the filename encoding.
        # See https://github.com/The-Compiler/qutebrowser/issues/427
        encoding = sys.getfilesystemencoding()
        filename = utils.force_encoding(filename, encoding)
        if not self._create_full_filename(filename):
            # 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._create_full_filename(os.path.join(_download_dir(), filename))

        log.downloads.debug("Setting filename to {}".format(filename))
        if os.path.isfile(self._filename):
            # The file already exists, so ask the user if it should be
            # overwritten.
            self._ask_overwrite_question()
        else:
            self._create_fileobj()
Beispiel #8
0
    def set_filename(self, filename):
        """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.
        """
        if self.fileobj is not None:
            raise ValueError("fileobj was already set! filename: {}, "
                             "existing: {}, fileobj {}".format(
                                 filename, self._filename, self.fileobj))
        filename = os.path.expanduser(filename)
        # Remove chars which can't be encoded in the filename encoding.
        # See https://github.com/The-Compiler/qutebrowser/issues/427
        encoding = sys.getfilesystemencoding()
        filename = utils.force_encoding(filename, encoding)
        if not self._create_full_filename(filename):
            # 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._create_full_filename(os.path.join(_download_dir(), filename))

        log.downloads.debug("Setting filename to {}".format(filename))
        if os.path.isfile(self._filename):
            # The file already exists, so ask the user if it should be
            # overwritten.
            self._ask_overwrite_question()
        else:
            self._create_fileobj()
Beispiel #9
0
    def set_filename(self, filename):
        """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.
        """
        global last_used_directory
        if self.fileobj is not None:
            raise ValueError("fileobj was already set! filename: {}, "
                             "existing: {}, fileobj {}".format(
                                 filename, self._filename, self.fileobj))
        filename = os.path.expanduser(filename)
        # Remove chars which can't be encoded in the filename encoding.
        # See https://github.com/The-Compiler/qutebrowser/issues/427
        encoding = sys.getfilesystemencoding()
        filename = utils.force_encoding(filename, encoding)
        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(
                self._win_id,
                "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(os.path.join('~', filename)))

        self.basename = os.path.basename(self._filename)
        last_used_directory = os.path.dirname(self._filename)

        log.downloads.debug("Setting filename to {}".format(filename))
        if os.path.isfile(self._filename):
            # The file already exists, so ask the user if it should be
            # overwritten.
            txt = self._filename + " already exists. Overwrite?"
            self._ask_confirm_question(txt)
        # FIFO, device node, etc. Make sure we want to do this
        elif (os.path.exists(self._filename) and not
                os.path.isdir(self._filename)):
            txt = (self._filename + " already exists and is a special file. "
                   "Write to this?")
            self._ask_confirm_question(txt)
        else:
            self._create_fileobj()
Beispiel #10
0
    def get_request(self,
                    request,
                    *,
                    fileobj=None,
                    filename=None,
                    prompt_download_directory=None,
                    **kwargs):
        """Start a download with a QNetworkRequest.

        Args:
            request: The QNetworkRequest to download.
            fileobj: The file object to write the answer to.
            filename: A path to write the data to.
            prompt_download_directory: Whether to prompt for the download dir
                                       or automatically download. If None, the
                                       config is used.
            **kwargs: Passed to fetch_request.

        Return:
            If the download could start immediately, (fileobj/filename given),
            the created DownloadItem.

            If not, None.
        """
        if fileobj is not None and filename is not None:
            raise TypeError("Only one of fileobj/filename may be given!")
        # WORKAROUND for Qt corrupting data loaded from cache:
        # https://bugreports.qt.io/browse/QTBUG-42757
        request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
                             QNetworkRequest.AlwaysNetwork)
        suggested_fn = urlutils.filename_from_url(request.url())

        if prompt_download_directory is None:
            prompt_download_directory = config.get(
                'storage', 'prompt-download-directory')
        if not prompt_download_directory and not fileobj:
            filename = config.get('storage', 'download-directory')

        if fileobj is not None or filename is not None:
            return self.fetch_request(request,
                                      fileobj=fileobj,
                                      filename=filename,
                                      suggested_filename=suggested_fn,
                                      **kwargs)
        if suggested_fn is None:
            suggested_fn = 'qutebrowser-download'
        else:
            encoding = sys.getfilesystemencoding()
            suggested_fn = utils.force_encoding(suggested_fn, encoding)
        q = self._prepare_question()
        q.default = _path_suggestion(suggested_fn)
        message_bridge = objreg.get('message-bridge',
                                    scope='window',
                                    window=self._win_id)
        q.answered.connect(lambda fn: self.fetch_request(
            request, filename=fn, suggested_filename=suggested_fn, **kwargs))
        message_bridge.ask(q, blocking=False)
        return None
Beispiel #11
0
    def set_filename(self, filename):
        """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.
        """
        global last_used_directory
        if self.fileobj is not None:
            raise ValueError("fileobj was already set! filename: {}, "
                             "existing: {}, fileobj {}".format(
                                 filename, self._filename, self.fileobj))
        filename = os.path.expanduser(filename)
        # Remove chars which can't be encoded in the filename encoding.
        # See https://github.com/The-Compiler/qutebrowser/issues/427
        encoding = sys.getfilesystemencoding()
        filename = utils.force_encoding(filename, encoding)
        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(
                self._win_id,
                "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(os.path.join('~', filename)))

        self.basename = os.path.basename(self._filename)
        last_used_directory = os.path.dirname(self._filename)

        log.downloads.debug("Setting filename to {}".format(filename))
        if os.path.isfile(self._filename):
            # The file already exists, so ask the user if it should be
            # overwritten.
            txt = self._filename + " already exists. Overwrite?"
            self._ask_confirm_question(txt)
        # FIFO, device node, etc. Make sure we want to do this
        elif (os.path.exists(self._filename) and not
                os.path.isdir(self._filename)):
            txt = (self._filename + " already exists and is a special file. "
                   "Write to this?")
            self._ask_confirm_question(txt)
        else:
            self._create_fileobj()
    def get_request(self, request, *, fileobj=None, filename=None,
                    prompt_download_directory=None, **kwargs):
        """Start a download with a QNetworkRequest.

        Args:
            request: The QNetworkRequest to download.
            fileobj: The file object to write the answer to.
            filename: A path to write the data to.
            prompt_download_directory: Whether to prompt for the download dir
                                       or automatically download. If None, the
                                       config is used.
            **kwargs: Passed to fetch_request.

        Return:
            If the download could start immediately, (fileobj/filename given),
            the created DownloadItem.

            If not, None.
        """
        if fileobj is not None and filename is not None:
            raise TypeError("Only one of fileobj/filename may be given!")
        # WORKAROUND for Qt corrupting data loaded from cache:
        # https://bugreports.qt.io/browse/QTBUG-42757
        request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
                             QNetworkRequest.AlwaysNetwork)
        suggested_fn = urlutils.filename_from_url(request.url())

        if prompt_download_directory is None:
            prompt_download_directory = config.get(
                'storage', 'prompt-download-directory')
        if not prompt_download_directory and not fileobj:
            filename = config.get('storage', 'download-directory')

        if fileobj is not None or filename is not None:
            return self.fetch_request(request,
                                      fileobj=fileobj,
                                      filename=filename,
                                      suggested_filename=suggested_fn,
                                      **kwargs)
        if suggested_fn is None:
            suggested_fn = 'qutebrowser-download'
        else:
            encoding = sys.getfilesystemencoding()
            suggested_fn = utils.force_encoding(suggested_fn, encoding)
        q = self._prepare_question()
        q.default = _path_suggestion(suggested_fn)
        message_bridge = objreg.get('message-bridge', scope='window',
                                    window=self._win_id)
        q.answered.connect(
            lambda fn: self.fetch_request(request,
                                          filename=fn,
                                          suggested_filename=suggested_fn,
                                          **kwargs))
        message_bridge.ask(q, blocking=False)
        return None
Beispiel #13
0
    def _finished(self, url, item):
        """Callback when a single asset is downloaded.

        Args:
            url: The original url of the asset as QUrl.
            item: The DownloadItem given by the DownloadManager
        """
        if self.writer is None:
            raise AssertionError

        self.pending_downloads.remove((url, item))
        mime = item.raw_headers.get(b'Content-Type', b'')

        # Note that this decoding always works and doesn't produce errors
        # RFC 7230 (https://tools.ietf.org/html/rfc7230) states:
        # Historically, HTTP has allowed field content with text in the
        # ISO-8859-1 charset [ISO-8859-1], supporting other charsets only
        # through use of [RFC2047] encoding.  In practice, most HTTP header
        # field values use only a subset of the US-ASCII charset [USASCII].
        # Newly defined header fields SHOULD limit their field values to
        # US-ASCII octets.  A recipient SHOULD treat other octets in field
        # content (obs-text) as opaque data.
        mime = mime.decode('iso-8859-1')

        if mime.lower() == 'text/css' or url.fileName().endswith('.css'):
            # We can't always assume that CSS files are UTF-8, but CSS files
            # shouldn't contain many non-ASCII characters anyway (in most
            # cases). Using "ignore" lets us decode the file even if it's
            # invalid UTF-8 data.
            # The file written to the MHTML file won't be modified by this
            # decoding, since there we're taking the original bytestream.
            try:
                css_string = item.fileobj.getvalue().decode('utf-8')
            except UnicodeDecodeError:
                log.downloads.warning("Invalid UTF-8 data in {}".format(url))
                css_string = item.fileobj.getvalue().decode('utf-8', 'ignore')
            import_urls = _get_css_imports(css_string)
            for import_url in import_urls:
                absolute_url = url.resolved(QUrl(import_url))
                self._fetch_url(absolute_url)

        encode = E_QUOPRI if mime.startswith('text/') else E_BASE64
        # Our MHTML handler refuses non-ASCII headers. This will replace every
        # non-ASCII char with '?'. This is probably okay, as official Content-
        # Type headers contain ASCII only anyway. Anything else is madness.
        mime = utils.force_encoding(mime, 'ascii')
        self.writer.add_file(urlutils.encoded_url(url),
                             item.fileobj.getvalue(), mime, encode)
        item.fileobj.actual_close()
        if self.pending_downloads:
            return
        self._finish_file()
Beispiel #14
0
    def _finished(self, url, item):
        """Callback when a single asset is downloaded.

        Args:
            url: The original url of the asset as QUrl.
            item: The DownloadItem given by the DownloadManager
        """
        self.pending_downloads.remove((url, item))
        mime = item.raw_headers.get(b'Content-Type', b'')

        # Note that this decoding always works and doesn't produce errors
        # RFC 7230 (https://tools.ietf.org/html/rfc7230) states:
        # Historically, HTTP has allowed field content with text in the
        # ISO-8859-1 charset [ISO-8859-1], supporting other charsets only
        # through use of [RFC2047] encoding.  In practice, most HTTP header
        # field values use only a subset of the US-ASCII charset [USASCII].
        # Newly defined header fields SHOULD limit their field values to
        # US-ASCII octets.  A recipient SHOULD treat other octets in field
        # content (obs-text) as opaque data.
        mime = mime.decode('iso-8859-1')

        if mime.lower() == 'text/css' or url.fileName().endswith('.css'):
            # We can't always assume that CSS files are UTF-8, but CSS files
            # shouldn't contain many non-ASCII characters anyway (in most
            # cases). Using "ignore" lets us decode the file even if it's
            # invalid UTF-8 data.
            # The file written to the MHTML file won't be modified by this
            # decoding, since there we're taking the original bytestream.
            try:
                css_string = item.fileobj.getvalue().decode('utf-8')
            except UnicodeDecodeError:
                log.downloads.warning("Invalid UTF-8 data in {}".format(url))
                css_string = item.fileobj.getvalue().decode('utf-8', 'ignore')
            import_urls = _get_css_imports(css_string)
            for import_url in import_urls:
                absolute_url = url.resolved(QUrl(import_url))
                self._fetch_url(absolute_url)

        encode = E_QUOPRI if mime.startswith('text/') else E_BASE64
        # Our MHTML handler refuses non-ASCII headers. This will replace every
        # non-ASCII char with '?'. This is probably okay, as official Content-
        # Type headers contain ASCII only anyway. Anything else is madness.
        mime = utils.force_encoding(mime, 'ascii')
        self.writer.add_file(urlutils.encoded_url(url),
                             item.fileobj.getvalue(), mime, encode)
        item.fileobj.actual_close()
        if self.pending_downloads:
            return
        self._finish_file()
Beispiel #15
0
    def get_request(self,
                    request,
                    page=None,
                    fileobj=None,
                    filename=None,
                    auto_remove=False):
        """Start a download with a QNetworkRequest.

        Args:
            request: The QNetworkRequest to download.
            page: The QWebPage to use.
            fileobj: The file object to write the answer to.
            filename: A path to write the data to.
            auto_remove: Whether to remove the download even if
                         ui -> remove-finished-downloads is set to false.

        Return:
            If the download could start immediately, (fileobj/filename given),
            the created DownloadItem.

            If not, None.
        """
        if fileobj is not None and filename is not None:
            raise TypeError("Only one of fileobj/filename may be given!")
        # WORKAROUND for Qt corrupting data loaded from cache:
        # https://bugreports.qt-project.org/browse/QTBUG-42757
        request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
                             QNetworkRequest.AlwaysNetwork)
        suggested_fn = urlutils.filename_from_url(request.url())
        if fileobj is not None or filename is not None:
            return self.fetch_request(request, page, fileobj, filename,
                                      auto_remove, suggested_fn)
        encoding = sys.getfilesystemencoding()
        suggested_fn = utils.force_encoding(suggested_fn, encoding)
        q = self._prepare_question()
        q.default = _path_suggestion(suggested_fn)
        message_bridge = objreg.get('message-bridge',
                                    scope='window',
                                    window=self._win_id)
        q.answered.connect(
            lambda fn: self.fetch_request(request,
                                          page,
                                          filename=fn,
                                          auto_remove=auto_remove,
                                          suggested_filename=suggested_fn))
        message_bridge.ask(q, blocking=False)
        return None
Beispiel #16
0
def ask_for_filename(suggested_filename,
                     win_id,
                     *,
                     parent=None,
                     prompt_download_directory=None):
    """Prepare a question for a download-path.

    If a filename can be determined directly, it is returned instead.

    Returns a (filename, question)-namedtuple, in which one component is
    None. filename is a string, question is a usertypes.Question. The
    question has a special .ask() method that takes no arguments for
    convenience, as this function does not yet ask the question, it
    only prepares it.

    Args:
        suggested_filename: The "default"-name that is pre-entered as path.
        win_id: The window where the question will be asked.
        parent: The parent of the question (a QObject).
        prompt_download_directory: If this is something else than None, it
                                   will overwrite the
                                   storage->prompt-download-directory setting.
    """
    if prompt_download_directory is None:
        prompt_download_directory = config.get('storage',
                                               'prompt-download-directory')

    if not prompt_download_directory:
        return DownloadPath(filename=download_dir(), question=None)

    encoding = sys.getfilesystemencoding()
    suggested_filename = utils.force_encoding(suggested_filename, encoding)

    q = usertypes.Question(parent)
    q.text = "Save file to:"
    q.mode = usertypes.PromptMode.text
    q.completed.connect(q.deleteLater)
    q.default = path_suggestion(suggested_filename)

    message_bridge = objreg.get('message-bridge',
                                scope='window',
                                window=win_id)
    q.ask = lambda: message_bridge.ask(q, blocking=False)
    return DownloadPath(filename=None, question=q)
Beispiel #17
0
    def get_request(self, request, page=None, fileobj=None, filename=None,
                    auto_remove=False):
        """Start a download with a QNetworkRequest.

        Args:
            request: The QNetworkRequest to download.
            page: The QWebPage to use.
            fileobj: The file object to write the answer to.
            filename: A path to write the data to.
            auto_remove: Whether to remove the download even if
                         ui -> remove-finished-downloads is set to false.

        Return:
            If the download could start immediately, (fileobj/filename given),
            the created DownloadItem.

            If not, None.
        """
        if fileobj is not None and filename is not None:
            raise TypeError("Only one of fileobj/filename may be given!")
        # WORKAROUND for Qt corrupting data loaded from cache:
        # https://bugreports.qt-project.org/browse/QTBUG-42757
        request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
                             QNetworkRequest.AlwaysNetwork)
        suggested_fn = urlutils.filename_from_url(request.url())
        if fileobj is not None or filename is not None:
            return self.fetch_request(request, page, fileobj, filename,
                                      auto_remove, suggested_fn)
        if suggested_fn is None:
            suggested_fn = 'qutebrowser-download'
        else:
            encoding = sys.getfilesystemencoding()
            suggested_fn = utils.force_encoding(suggested_fn, encoding)
        q = self._prepare_question()
        q.default = _path_suggestion(suggested_fn)
        message_bridge = objreg.get('message-bridge', scope='window',
                                    window=self._win_id)
        q.answered.connect(
            lambda fn: self.fetch_request(request, page, filename=fn,
                                          auto_remove=auto_remove,
                                          suggested_filename=suggested_fn))
        message_bridge.ask(q, blocking=False)
        return None
Beispiel #18
0
def get_filename_question(*, suggested_filename, url, parent=None):
    """Get a Question object for a download-path.

    Args:
        suggested_filename: The "default"-name that is pre-entered as path.
        url: The URL the download originated from.
        parent: The parent of the question (a QObject).
    """
    encoding = sys.getfilesystemencoding()
    suggested_filename = utils.force_encoding(suggested_filename, encoding)

    q = usertypes.Question(parent)
    q.title = "Save file to:"
    q.text = "Please enter a location for <b>{}</b>".format(
        html.escape(url.toDisplayString()))
    q.mode = usertypes.PromptMode.download
    q.completed.connect(q.deleteLater)
    q.default = _path_suggestion(suggested_filename)
    return q
Beispiel #19
0
def get_filename_question(*, suggested_filename, url, parent=None):
    """Get a Question object for a download-path.

    Args:
        suggested_filename: The "default"-name that is pre-entered as path.
        url: The URL the download originated from.
        parent: The parent of the question (a QObject).
    """
    encoding = sys.getfilesystemencoding()
    suggested_filename = utils.force_encoding(suggested_filename, encoding)

    q = usertypes.Question(parent)
    q.title = "Save file to:"
    q.text = "Please enter a location for <b>{}</b>".format(
        html.escape(url.toDisplayString()))
    q.mode = usertypes.PromptMode.text
    q.completed.connect(q.deleteLater)
    q.default = _path_suggestion(suggested_filename)
    return q
Beispiel #20
0
    def set_filename(self, filename):
        """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.
        """
        global last_used_directory
        if self.fileobj is not None:
            raise ValueError("fileobj was already set! filename: {}, "
                             "existing: {}, fileobj {}".format(
                                 filename, self._filename, self.fileobj))
        filename = os.path.expanduser(filename)
        # Remove chars which can't be encoded in the filename encoding.
        # See https://github.com/The-Compiler/qutebrowser/issues/427
        encoding = sys.getfilesystemencoding()
        filename = utils.force_encoding(filename, encoding)
        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))

        self.basename = os.path.basename(self._filename)
        last_used_directory = os.path.dirname(self._filename)

        log.downloads.debug("Setting filename to {}".format(filename))
        if os.path.isfile(self._filename):
            # The file already exists, so ask the user if it should be
            # overwritten.
            txt = self._filename + " already exists. Overwrite?"
            self._ask_confirm_question(txt)
        # FIFO, device node, etc. Make sure we want to do this
        elif (os.path.exists(self._filename)
              and not os.path.isdir(self._filename)):
            txt = (self._filename + " already exists and is a special file. "
                   "Write to this?")
            self._ask_confirm_question(txt)
        else:
            self._create_fileobj()
Beispiel #21
0
    def set_filename(self, filename):
        """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.
        """
        global last_used_directory
        if self.fileobj is not None:
            raise ValueError("fileobj was already set! filename: {}, "
                             "existing: {}, fileobj {}".format(
                                 filename, self._filename, self.fileobj))
        filename = os.path.expanduser(filename)
        # Remove chars which can't be encoded in the filename encoding.
        # See https://github.com/The-Compiler/qutebrowser/issues/427
        encoding = sys.getfilesystemencoding()
        filename = utils.force_encoding(filename, encoding)
        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))

        self.basename = os.path.basename(self._filename)
        last_used_directory = os.path.dirname(self._filename)

        log.downloads.debug("Setting filename to {}".format(filename))
        if os.path.isfile(self._filename):
            # The file already exists, so ask the user if it should be
            # overwritten.
            txt = self._filename + " already exists. Overwrite?"
            self._ask_confirm_question(txt)
        # FIFO, device node, etc. Make sure we want to do this
        elif (os.path.exists(self._filename) and not
                os.path.isdir(self._filename)):
            txt = (self._filename + " already exists and is a special file. "
                   "Write to this?")
            self._ask_confirm_question(txt)
        else:
            self._create_fileobj()
Beispiel #22
0
def ask_for_filename(suggested_filename, win_id, *, parent=None,
                     prompt_download_directory=None):
    """Prepare a question for a download-path.

    If a filename can be determined directly, it is returned instead.

    Returns a (filename, question)-namedtuple, in which one component is
    None. filename is a string, question is a usertypes.Question. The
    question has a special .ask() method that takes no arguments for
    convenience, as this function does not yet ask the question, it
    only prepares it.

    Args:
        suggested_filename: The "default"-name that is pre-entered as path.
        win_id: The window where the question will be asked.
        parent: The parent of the question (a QObject).
        prompt_download_directory: If this is something else than None, it
                                   will overwrite the
                                   storage->prompt-download-directory setting.
    """
    if prompt_download_directory is None:
        prompt_download_directory = config.get('storage',
                                               'prompt-download-directory')

    if not prompt_download_directory:
        return DownloadPath(filename=download_dir(), question=None)

    encoding = sys.getfilesystemencoding()
    suggested_filename = utils.force_encoding(suggested_filename,
                                              encoding)

    q = usertypes.Question(parent)
    q.text = "Save file to:"
    q.mode = usertypes.PromptMode.text
    q.completed.connect(q.deleteLater)
    q.default = path_suggestion(suggested_filename)

    message_bridge = objreg.get('message-bridge', scope='window',
                                window=win_id)
    q.ask = lambda: message_bridge.ask(q, blocking=False)
    return DownloadPath(filename=None, question=q)
Beispiel #23
0
    def set_filename(self, filename):
        """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.
        """
        if self.fileobj is not None:
            raise ValueError("fileobj was already set! filename: {}, "
                             "existing: {}, fileobj {}".format(
                                 filename, self._filename, self.fileobj))
        filename = os.path.expanduser(filename)
        # Remove chars which can't be encoded in the filename encoding.
        # See https://github.com/The-Compiler/qutebrowser/issues/427
        encoding = sys.getfilesystemencoding()
        filename = utils.force_encoding(filename, encoding)
        if os.path.isabs(filename) and os.path.isdir(filename):
            # We got an absolute directory from the user, so we save it under
            # the default filename in that directory.
            self._filename = os.path.join(filename, self.basename)
        elif os.path.isabs(filename):
            # We got an absolute filename from the user, so we save it under
            # that filename.
            self._filename = filename
            self.basename = os.path.basename(self._filename)
        else:
            # We only got a filename (without directory) from the user, so we
            # save it under that filename in the default directory.
            download_dir = config.get('storage', 'download-directory')
            if download_dir is None:
                download_dir = standarddir.get(
                    QStandardPaths.DownloadLocation)
            self._filename = os.path.join(download_dir, filename)
            self.basename = filename
        log.downloads.debug("Setting filename to {}".format(filename))
        if os.path.isfile(self._filename):
            # The file already exists, so ask the user if it should be
            # overwritten.
            self._ask_overwrite_question()
        else:
            self._create_fileobj()
Beispiel #24
0
    def get_tmpfile(self, suggested_name):
        """Return a temporary file in the temporary downloads directory.

        The files are kept as long as qutebrowser is running and automatically
        cleaned up at program exit.

        Args:
            suggested_name: str of the "suggested"/original filename. Used as a
                            suffix, so any file extenions are preserved.

        Return:
            A tempfile.NamedTemporaryFile that should be used to save the file.
        """
        tmpdir = self._get_tmpdir()
        encoding = sys.getfilesystemencoding()
        suggested_name = utils.force_encoding(suggested_name, encoding)
        # Make sure that the filename is not too long
        suggested_name = utils.elide_filename(suggested_name, 50)
        fobj = tempfile.NamedTemporaryFile(dir=tmpdir.name, delete=False,
                                           suffix=suggested_name)
        self.files.append(fobj)
        return fobj
Beispiel #25
0
    def get_tmpfile(self, suggested_name):
        """Return a temporary file in the temporary downloads directory.

        The files are kept as long as qutebrowser is running and automatically
        cleaned up at program exit.

        Args:
            suggested_name: str of the "suggested"/original filename. Used as a
                            suffix, so any file extenions are preserved.

        Return:
            A tempfile.NamedTemporaryFile that should be used to save the file.
        """
        tmpdir = self._get_tmpdir()
        encoding = sys.getfilesystemencoding()
        suggested_name = utils.force_encoding(suggested_name, encoding)
        # Make sure that the filename is not too long
        suggested_name = utils.elide_filename(suggested_name, 50)
        fobj = tempfile.NamedTemporaryFile(dir=tmpdir.name, delete=False,
                                           suffix=suggested_name)
        self.files.append(fobj)
        return fobj
Beispiel #26
0
 def test_fitting_ascii(self, inp, enc, expected):
     """Test force_encoding will yield expected text."""
     assert utils.force_encoding(inp, enc) == expected
Beispiel #27
0
    def fetch(self, reply, fileobj=None, filename=None, auto_remove=False):
        """Download a QNetworkReply to disk.

        Args:
            reply: The QNetworkReply to download.
            fileobj: The file object to write the answer to.
            filename: A path to write the data to.
            auto_remove: Whether to remove the download even if
                         ui -> remove-finished-downloads is set to false.

        Return:
            The created DownloadItem.
        """
        if fileobj is not None and filename is not None:
            raise TypeError("Only one of fileobj/filename may be given!")
        if filename is not None:
            suggested_filename = os.path.basename(filename)
        elif fileobj is not None and getattr(fileobj, 'name', None):
            suggested_filename = fileobj.name
        else:
            _inline, suggested_filename = http.parse_content_disposition(reply)
        log.downloads.debug("fetch: {} -> {}".format(reply.url(),
                                                     suggested_filename))
        download = DownloadItem(reply, self._win_id, self)
        download.cancelled.connect(
            functools.partial(self.remove_item, download))
        if config.get('ui', 'remove-finished-downloads') or auto_remove:
            download.finished.connect(
                functools.partial(self.remove_item, download))
        download.data_changed.connect(
            functools.partial(self.on_data_changed, download))
        download.error.connect(self.on_error)
        download.redirected.connect(
            functools.partial(self.on_redirect, download))
        download.do_retry.connect(self.fetch)
        download.basename = suggested_filename
        idx = len(self.downloads) + 1
        self.beginInsertRows(QModelIndex(), idx, idx)
        self.downloads.append(download)
        self.endInsertRows()

        if filename is not None:
            download.set_filename(filename)
        elif fileobj is not None:
            download.set_fileobj(fileobj)
            download.autoclose = False
        else:
            q = self._prepare_question()
            encoding = sys.getfilesystemencoding()
            suggested_filename = utils.force_encoding(suggested_filename,
                                                      encoding)
            q.default = suggested_filename
            q.answered.connect(download.set_filename)
            q.cancelled.connect(download.cancel)
            download.cancelled.connect(q.abort)
            download.error.connect(q.abort)
            message_bridge = objreg.get('message-bridge', scope='window',
                                        window=self._win_id)
            message_bridge.ask(q, blocking=False)

        return download
Beispiel #28
0
 def test_fitting_ascii(self):
     """Test with a text fitting into ascii."""
     text = 'hello world'
     self.assertEqual(utils.force_encoding(text, 'ascii'), text)
Beispiel #29
0
 def test_fitting_utf8(self):
     """Test with a text fitting into utf-8."""
     text = 'hellö wörld'
     self.assertEqual(utils.force_encoding(text, 'utf-8'), text)
Beispiel #30
0
 def test_fitting_ascii(self):
     """Test with a text fitting into ascii."""
     text = 'hello world'
     self.assertEqual(utils.force_encoding(text, 'ascii'), text)
Beispiel #31
0
 def test_fitting_utf8(self):
     """Test with a text fitting into utf-8."""
     text = 'hellö wörld'
     self.assertEqual(utils.force_encoding(text, 'utf-8'), text)
Beispiel #32
0
 def test_not_fitting_ascii(self):
     """Test with a text not fitting into ascii."""
     text = 'hellö wörld'
     self.assertEqual(utils.force_encoding(text, 'ascii'), 'hell? w?rld')
Beispiel #33
0
 def test_fitting_ascii(self, inp, enc, expected):
     """Test force_encoding will yield expected text."""
     assert utils.force_encoding(inp, enc) == expected
Beispiel #34
0
 def test_not_fitting_ascii(self):
     """Test with a text not fitting into ascii."""
     text = 'hellö wörld'
     self.assertEqual(utils.force_encoding(text, 'ascii'), 'hell? w?rld')