Exemple #1
0
def test_invalid_url_error(message_mock, caplog, url, valid, has_err_string):
    """Test invalid_url_error().

    Args:
        url: The URL to check.
        valid: Whether the QUrl is valid (isValid() == True).
        has_err_string: Whether the QUrl is expected to have errorString set.
    """
    qurl = QUrl(url)
    assert qurl.isValid() == valid
    if valid:
        with pytest.raises(ValueError):
            urlutils.invalid_url_error(qurl, '')
        assert not message_mock.messages
    else:
        assert bool(qurl.errorString()) == has_err_string
        with caplog.at_level(logging.ERROR):
            urlutils.invalid_url_error(qurl, 'frozzle')

        msg = message_mock.getmsg(usertypes.MessageLevel.error)
        if has_err_string:
            expected_text = ("Trying to frozzle with invalid URL - " +
                             qurl.errorString())
        else:
            expected_text = "Trying to frozzle with invalid URL"
        assert msg.text == expected_text
def test_invalid_url_error(urlutils_message_mock, url, valid, has_err_string):
    """Test invalid_url_error().

    Args:
        url: The URL to check.
        valid: Whether the QUrl is valid (isValid() == True).
        has_err_string: Whether the QUrl is expected to have errorString set.
    """
    qurl = QUrl(url)
    assert qurl.isValid() == valid
    if valid:
        with pytest.raises(ValueError):
            urlutils.invalid_url_error(0, qurl, '')
        assert not urlutils_message_mock.messages
    else:
        assert bool(qurl.errorString()) == has_err_string
        urlutils.invalid_url_error(0, qurl, 'frozzle')

        msg = urlutils_message_mock.getmsg()
        assert msg.win_id == 0
        assert not msg.immediate
        if has_err_string:
            expected_text = ("Trying to frozzle with invalid URL - " +
                             qurl.errorString())
        else:
            expected_text = "Trying to frozzle with invalid URL"
        assert msg.text == expected_text
def test_invalid_url_error(urlutils_message_mock, url, valid, has_err_string):
    """Test invalid_url_error().

    Args:
        url: The URL to check.
        valid: Whether the QUrl is valid (isValid() == True).
        has_err_string: Whether the QUrl is expected to have errorString set.
    """
    qurl = QUrl(url)
    assert qurl.isValid() == valid
    if valid:
        with pytest.raises(ValueError):
            urlutils.invalid_url_error(0, qurl, '')
        assert not urlutils_message_mock.messages
    else:
        assert bool(qurl.errorString()) == has_err_string
        urlutils.invalid_url_error(0, qurl, 'frozzle')

        msg = urlutils_message_mock.getmsg(urlutils_message_mock.Level.error)
        if has_err_string:
            expected_text = ("Trying to frozzle with invalid URL - " +
                             qurl.errorString())
        else:
            expected_text = "Trying to frozzle with invalid URL"
        assert msg.text == expected_text
Exemple #4
0
    def from_str(cls, line):
        """Parse a history line like '12345 http://example.com title'."""
        data = line.split(maxsplit=2)
        if len(data) == 2:
            atime, url = data
            title = ""
        elif len(data) == 3:
            atime, url, title = data
        else:
            raise ValueError("2 or 3 fields expected")

        url = QUrl(url)
        if not url.isValid():
            raise ValueError("Invalid URL: {}".format(url.errorString()))

        if atime.startswith('\0'):
            log.init.debug(
                "Removing NUL bytes from entry {!r} - see "
                "https://github.com/The-Compiler/qutebrowser/issues/"
                "670".format(data))
            atime = atime.lstrip('\0')

        if '-' in atime:
            atime, flags = atime.split('-')
        else:
            flags = ''

        if not set(flags).issubset('r'):
            raise ValueError("Invalid flags {!r}".format(flags))

        redirect = 'r' in flags

        return cls(atime, url, title, redirect=redirect)
def blocklist_to_url(path):
    """Get an example.com-URL with the given filename as path."""
    assert not path.is_absolute(), path
    url = QUrl("http://example.com/")
    url.setPath("/" + str(path))
    assert url.isValid(), url.errorString()
    return url
Exemple #6
0
def blocklist_to_url(filename):
    """Get an example.com-URL with the given filename as path."""
    assert not os.path.isabs(filename), filename
    url = QUrl("http://example.com/")
    url.setPath("/" + filename)
    assert url.isValid(), url.errorString()
    return url
Exemple #7
0
    def wait_for_load_finished_url(self, url, *, timeout=None,
                                   load_status='success'):
        """Wait until a URL has finished loading."""
        __tracebackhide__ = (lambda e: e.errisinstance(
            testprocess.WaitForTimeout))

        if timeout is None:
            if 'CI' in os.environ:
                timeout = 15000
            else:
                timeout = 5000

        # We really need the same representation that the webview uses in its
        # __repr__
        qurl = QUrl(url)
        if not qurl.isValid():
            raise ValueError("Invalid URL {}: {}".format(url,
                                                         qurl.errorString()))
        url = utils.elide(qurl.toDisplayString(QUrl.EncodeUnicode), 100)
        assert url

        pattern = re.compile(
            r"(load status for <qutebrowser\.browser\..* "
            r"tab_id=\d+ url='{url}/?'>: LoadStatus\.{load_status}|fetch: "
            r"PyQt5\.QtCore\.QUrl\('{url}'\) -> .*)".format(
                load_status=re.escape(load_status), url=re.escape(url)))

        try:
            self.wait_for(message=pattern, timeout=timeout)
        except testprocess.WaitForTimeout:
            raise testprocess.WaitForTimeout("Timed out while waiting for {} "
                                             "to be loaded".format(url))
Exemple #8
0
    def from_str(cls, line):
        """Parse a history line like '12345 http://example.com title'."""
        data = line.split(maxsplit=2)
        if len(data) == 2:
            atime, url = data
            title = ""
        elif len(data) == 3:
            atime, url, title = data
        else:
            raise ValueError("2 or 3 fields expected")

        url = QUrl(url)
        if not url.isValid():
            raise ValueError("Invalid URL: {}".format(url.errorString()))

        # https://github.com/The-Compiler/qutebrowser/issues/670
        atime = atime.lstrip('\0')

        if '-' in atime:
            atime, flags = atime.split('-')
        else:
            flags = ''

        if not set(flags).issubset('r'):
            raise ValueError("Invalid flags {!r}".format(flags))

        redirect = 'r' in flags

        return cls(atime, url, title, redirect=redirect)
def blocklist_to_url(filename):
    """Get an example.com-URL with the given filename as path."""
    assert not os.path.isabs(filename), filename
    url = QUrl('http://example.com/')
    url.setPath('/' + filename)
    assert url.isValid(), url.errorString()
    return url
Exemple #10
0
def main():
    import sys

    app = QApplication(sys.argv)

    QQuickWindow.setDefaultAlphaBuffer(True)

    QCoreApplication.setApplicationName("Photosurface")
    QCoreApplication.setOrganizationName("QtProject")
    QCoreApplication.setApplicationVersion(QT_VERSION_STR)
    parser = QCommandLineParser()
    parser.setApplicationDescription("Qt Quick Demo - Photo Surface")
    parser.addHelpOption()
    parser.addVersionOption()
    parser.addPositionalArgument("directory",
                                 "The image directory or URL to show.")
    parser.process(app)

    initialUrl = QUrl()
    if parser.positionalArguments():
        initialUrl = QUrl.fromUserInput(parser.positionalArguments()[0],
                                        QDir.currentPath(),
                                        QUrl.AssumeLocalFile)
        if not initialUrl.isValid():
            print(
                'Invalid argument: "',
                parser.positionalArguments()[0],
                '": ',
                initialUrl.errorString(),
            )
            sys.exit(1)

    nameFilters = imageNameFilters()

    engine = QQmlApplicationEngine()
    context: QQmlContext = engine.rootContext()

    picturesLocationUrl = QUrl.fromLocalFile(QDir.homePath())
    picturesLocations = QStandardPaths.standardLocations(
        QStandardPaths.PicturesLocation)
    if picturesLocations:
        picturesLocationUrl = QUrl.fromLocalFile(picturesLocations[0])
        if not initialUrl and QDir(picturesLocations[0]).entryInfoList(
                nameFilters, QDir.Files):
            initialUrl = picturesLocationUrl

    context.setContextProperty("contextPicturesLocation", picturesLocationUrl)
    context.setContextProperty("contextInitialUrl", initialUrl)
    context.setContextProperty("contextImageNameFilters", nameFilters)

    engine.load(QUrl("qrc:///photosurface.qml"))
    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())
Exemple #11
0
 def validate(self, value):
     if not value:
         if self._none_ok:
             return
         else:
             raise configexc.ValidationError(value, "may not be empty!")
     if "{}" not in value:
         raise configexc.ValidationError(value, 'must contain "{}"')
     url = QUrl(value.replace("{}", "foobar"))
     if not url.isValid():
         raise configexc.ValidationError(value, "invalid url, {}".format(url.errorString()))
Exemple #12
0
def test_raise_cmdexc_if_invalid(url, valid, has_err_string):
    """Test raise_cmdexc_if_invalid.

    Args:
        url: The URL to check.
        valid: Whether the QUrl is valid (isValid() == True).
        has_err_string: Whether the QUrl is expected to have errorString set.
    """
    qurl = QUrl(url)
    assert qurl.isValid() == valid
    if valid:
        urlutils.raise_cmdexc_if_invalid(qurl)
    else:
        assert bool(qurl.errorString()) == has_err_string
        if has_err_string:
            expected_text = "Invalid URL - " + qurl.errorString()
        else:
            expected_text = "Invalid URL"
        with pytest.raises(cmdutils.CommandError, match=expected_text):
            urlutils.raise_cmdexc_if_invalid(qurl)
def test_raise_cmdexc_if_invalid(url, valid, has_err_string):
    """Test raise_cmdexc_if_invalid.

    Args:
        url: The URL to check.
        valid: Whether the QUrl is valid (isValid() == True).
        has_err_string: Whether the QUrl is expected to have errorString set.
    """
    qurl = QUrl(url)
    assert qurl.isValid() == valid
    if valid:
        urlutils.raise_cmdexc_if_invalid(qurl)
    else:
        assert bool(qurl.errorString()) == has_err_string
        if has_err_string:
            expected_text = "Invalid URL - " + qurl.errorString()
        else:
            expected_text = "Invalid URL"
        with pytest.raises(cmdexc.CommandError, match=expected_text):
            urlutils.raise_cmdexc_if_invalid(qurl)
Exemple #14
0
def get(name):
    """Get the URL of the quickmark named name as a QUrl."""
    if name not in marks:
        raise cmdexc.CommandError(
            "Quickmark '{}' does not exist!".format(name))
    urlstr = marks[name]
    url = QUrl(urlstr)
    if not url.isValid():
        raise cmdexc.CommandError(
            "Invalid URL for quickmark {}: {} ({})".format(name, urlstr,
                                                           url.errorString()))
    return url
Exemple #15
0
 def validate(self, value):
     if not value:
         if self._none_ok:
             return
         else:
             raise ValidationError(value, "may not be empty!")
     if '{}' not in value:
         raise ValidationError(value, "must contain \"{}\"")
     url = QUrl(value.replace('{}', 'foobar'))
     if not url.isValid():
         raise ValidationError(value,
                               "invalid url, {}".format(url.errorString()))
Exemple #16
0
 def validate(self, value):
     if not value:
         if self._none_ok:
             return
         else:
             raise configexc.ValidationError(value, "may not be empty!")
     if '{}' not in value:
         raise configexc.ValidationError(value, "must contain \"{}\"")
     url = QUrl(value.replace('{}', 'foobar'))
     if not url.isValid():
         raise configexc.ValidationError(value, "invalid url, {}".format(
             url.errorString()))
Exemple #17
0
 def validate(self, value):
     self._basic_validation(value)
     if not value:
         return
     elif value in self.valid_values:
         return
     url = QUrl(value)
     if not url.isValid():
         raise configexc.ValidationError(value, "invalid url, {}".format(
             url.errorString()))
     elif url.scheme() not in self.PROXY_TYPES:
         raise configexc.ValidationError(value, "must be a proxy URL "
                                         "(http://... or socks://...) or "
                                         "system/none!")
Exemple #18
0
    def quickmark_load(self, name, tab=False, bg=False):
        """Load a quickmark.

        Args:
            name: The name of the quickmark to load.
            tab: Load the quickmark in a new tab.
            bg: Load the quickmark in a new background tab.
        """
        urlstr = quickmarks.get(name)
        url = QUrl(urlstr)
        if not url.isValid():
            raise cmdexc.CommandError("Invalid URL {} ({})".format(
                urlstr, url.errorString()))
        self._open(url, tab, bg)
Exemple #19
0
def get_errstring(url: QUrl, base: str = "Invalid URL") -> str:
    """Get an error string for a URL.

    Args:
        url: The URL as a QUrl.
        base: The base error string.

    Return:
        A new string with url.errorString() is appended if available.
    """
    url_error = url.errorString()
    if url_error:
        return base + " - {}".format(url_error)
    else:
        return base
Exemple #20
0
 def validate(self, value):
     if not value:
         if self._none_ok:
             return
         else:
             raise ValidationError(value, "may not be empty!")
     if value in self.valid_values:
         return
     url = QUrl(value)
     if not url.isValid():
         raise ValidationError(value, "invalid url, {}".format(
             url.errorString()))
     elif url.scheme() not in self.PROXY_TYPES:
         raise ValidationError(value, "must be a proxy URL (http://... or "
                                      "socks://...) or system/none!")
 def validate(self, value):
     if not value:
         if self._none_ok:
             return
         else:
             raise ValidationError(value, "may not be empty!")
     if value in self.valid_values:
         return
     url = QUrl(value)
     if not url.isValid():
         raise ValidationError(value, "invalid url, {}".format(
             url.errorString()))
     elif url.scheme() not in self.PROXY_TYPES:
         raise ValidationError(value, "must be a proxy URL (http://... or "
                                      "socks://...) or system/none!")
Exemple #22
0
    def validate(self, value):
        self._basic_validation(value)
        if not value:
            return
        elif '{}' not in value:
            raise configexc.ValidationError(value, "must contain \"{}\"")
        try:
            value.format("")
        except KeyError:
            raise configexc.ValidationError(
                value, "may not contain {...} (use {{ and }} for literal {/})")

        url = QUrl(value.replace('{}', 'foobar'))
        if not url.isValid():
            raise configexc.ValidationError(value, "invalid url, {}".format(
                url.errorString()))
Exemple #23
0
    def wait_for_load_finished_url(self,
                                   url,
                                   *,
                                   timeout=None,
                                   load_status='success',
                                   after=None):
        """Wait until a URL has finished loading."""
        __tracebackhide__ = (
            lambda e: e.errisinstance(testprocess.WaitForTimeout))

        if timeout is None:
            if 'CI' in os.environ:
                timeout = 15000
            else:
                timeout = 5000

        qurl = QUrl(url)
        if not qurl.isValid():
            raise ValueError("Invalid URL {}: {}".format(
                url, qurl.errorString()))

        if (qurl == QUrl('about:blank')
                and not qtutils.version_check('5.10', compiled=False)):
            # For some reason, we don't get a LoadStatus.success for
            # about:blank sometimes.
            # However, if we do this for Qt 5.10, we get general testsuite
            # instability as site loads get reported with about:blank...
            pattern = "Changing title for idx * to 'about:blank'"
        else:
            # We really need the same representation that the webview uses in
            # its __repr__
            url = utils.elide(qurl.toDisplayString(QUrl.EncodeUnicode), 100)
            assert url

            pattern = re.compile(
                r"(load status for <qutebrowser\.browser\..* "
                r"tab_id=\d+ url='{url}/?'>: LoadStatus\.{load_status}|fetch: "
                r"PyQt5\.QtCore\.QUrl\('{url}'\) -> .*)".format(
                    load_status=re.escape(load_status), url=re.escape(url)))

        try:
            self.wait_for(message=pattern, timeout=timeout, after=after)
        except testprocess.WaitForTimeout:
            raise testprocess.WaitForTimeout("Timed out while waiting for {} "
                                             "to be loaded".format(url))
Exemple #24
0
    def validate(self, value):
        self._basic_validation(value)
        if not value:
            return
        elif '{}' not in value:
            raise configexc.ValidationError(value, "must contain \"{}\"")
        try:
            value.format("")
        except (KeyError, IndexError) as e:
            raise configexc.ValidationError(
                value, "may not contain {...} (use {{ and }} for literal {/})")
        except ValueError as e:
            raise configexc.ValidationError(value, str(e))

        url = QUrl(value.replace('{}', 'foobar'))
        if not url.isValid():
            raise configexc.ValidationError(value, "invalid url, {}".format(
                url.errorString()))
Exemple #25
0
    def _init_host(self, parsed: urllib.parse.ParseResult) -> None:
        """Parse the host from the given URL.

        Deviation from Chromium:
        - http://:1234/ is not a valid URL because it has no host.
        - We don't allow patterns for dot/space hosts which QUrl considers
          invalid.
        """
        if parsed.hostname is None or not parsed.hostname.strip():
            if self._scheme not in self._SCHEMES_WITHOUT_HOST:
                raise ParseError("Pattern without host")
            assert self.host is None
            return

        if parsed.netloc.startswith('['):
            # Using QUrl parsing to minimize ipv6 addresses
            url = QUrl()
            url.setHost(parsed.hostname)
            if not url.isValid():
                raise ParseError(url.errorString())
            self.host = url.host()
            return

        if parsed.hostname == '*':
            self._match_subdomains = True
            hostname = None
        elif parsed.hostname.startswith('*.'):
            if len(parsed.hostname) == 2:
                # We don't allow just '*.' as a host.
                raise ParseError("Pattern without host")
            self._match_subdomains = True
            hostname = parsed.hostname[2:]
        elif set(parsed.hostname) in {frozenset('.'), frozenset('. ')}:
            raise ParseError("Invalid host")
        else:
            hostname = parsed.hostname

        if hostname is None:
            self.host = None
        elif '*' in hostname:
            # Only * or *.foo is allowed as host.
            raise ParseError("Invalid host wildcard")
        else:
            self.host = hostname.rstrip('.')
    def _parse_entry(self, line):
        """Parse a history line like '12345 http://example.com title'."""
        if not line or line.startswith('#'):
            return None
        data = line.split(maxsplit=2)
        if len(data) == 2:
            atime, url = data
            title = ""
        elif len(data) == 3:
            atime, url, title = data
        else:
            raise ValueError("2 or 3 fields expected")

        # http://xn--pple-43d.com/ with
        # https://bugreports.qt.io/browse/QTBUG-60364
        if url in [
                'http://.com/', 'https://.com/', 'http://www..com/',
                'https://www..com/'
        ]:
            return None

        url = QUrl(url)
        if not url.isValid():
            raise ValueError("Invalid URL: {}".format(url.errorString()))

        # https://github.com/qutebrowser/qutebrowser/issues/2646
        if url.scheme() == 'data':
            return None

        # https://github.com/qutebrowser/qutebrowser/issues/670
        atime = atime.lstrip('\0')

        if '-' in atime:
            atime, flags = atime.split('-')
        else:
            flags = ''

        if not set(flags).issubset('r'):
            raise ValueError("Invalid flags {!r}".format(flags))

        redirect = 'r' in flags
        return (url, title, int(atime), redirect)
Exemple #27
0
    def _init_host(self, parsed: urllib.parse.ParseResult) -> None:
        """Parse the host from the given URL.

        Deviation from Chromium:
        - http://:1234/ is not a valid URL because it has no host.
        """
        # https://github.com/python/typeshed/commit/f0ccb325aa787ca0a539ef9914276b2c3148327a
        if (parsed.hostname is None or  # type: ignore
                not parsed.hostname.strip()):
            if self._scheme not in self._SCHEMES_WITHOUT_HOST:
                raise ParseError("Pattern without host")
            assert self._host is None
            return

        if parsed.netloc.startswith('['):
            # Using QUrl parsing to minimize ipv6 addresses
            url = QUrl()
            url.setHost(parsed.hostname)
            if not url.isValid():
                raise ParseError(url.errorString())
            self._host = url.host()
            return

        # FIXME what about multiple dots?
        host_parts = parsed.hostname.rstrip('.').split('.')
        if host_parts[0] == '*':
            host_parts = host_parts[1:]
            self._match_subdomains = True

        if not host_parts:
            self._host = None
            return

        self._host = '.'.join(host_parts)

        if self._host.endswith('.*'):
            # Special case to have a nicer error
            raise ParseError("TLD wildcards are not implemented yet")
        if '*' in self._host:
            # Only * or *.foo is allowed as host.
            raise ParseError("Invalid host wildcard")
    def wait_for_load_finished_url(self, url, *, timeout=None,
                                   load_status='success', after=None):
        """Wait until a URL has finished loading."""
        __tracebackhide__ = (lambda e: e.errisinstance(
            testprocess.WaitForTimeout))

        if timeout is None:
            if 'CI' in os.environ:
                timeout = 15000
            else:
                timeout = 5000

        qurl = QUrl(url)
        if not qurl.isValid():
            raise ValueError("Invalid URL {}: {}".format(url,
                                                         qurl.errorString()))

        if (qurl == QUrl('about:blank') and
                not qtutils.version_check('5.10', compiled=False)):
            # For some reason, we don't get a LoadStatus.success for
            # about:blank sometimes.
            # However, if we do this for Qt 5.10, we get general testsuite
            # instability as site loads get reported with about:blank...
            pattern = "Changing title for idx * to 'about:blank'"
        else:
            # We really need the same representation that the webview uses in
            # its __repr__
            url = utils.elide(qurl.toDisplayString(QUrl.EncodeUnicode), 100)
            assert url

            pattern = re.compile(
                r"(load status for <qutebrowser\.browser\..* "
                r"tab_id=\d+ url='{url}/?'>: LoadStatus\.{load_status}|fetch: "
                r"PyQt5\.QtCore\.QUrl\('{url}'\) -> .*)".format(
                    load_status=re.escape(load_status), url=re.escape(url)))

        try:
            self.wait_for(message=pattern, timeout=timeout, after=after)
        except testprocess.WaitForTimeout:
            raise testprocess.WaitForTimeout("Timed out while waiting for {} "
                                             "to be loaded".format(url))
Exemple #29
0
    def _parse_entry(self, line):
        """Parse a history line like '12345 http://example.com title'."""
        if not line or line.startswith('#'):
            return None
        data = line.split(maxsplit=2)
        if len(data) == 2:
            atime, url = data
            title = ""
        elif len(data) == 3:
            atime, url, title = data
        else:
            raise ValueError("2 or 3 fields expected")

        # http://xn--pple-43d.com/ with
        # https://bugreports.qt.io/browse/QTBUG-60364
        if url in ['http://.com/', 'https://.com/',
                   'http://www..com/', 'https://www..com/']:
            return None

        url = QUrl(url)
        if not url.isValid():
            raise ValueError("Invalid URL: {}".format(url.errorString()))

        # https://github.com/qutebrowser/qutebrowser/issues/2646
        if url.scheme() == 'data':
            return None

        # https://github.com/qutebrowser/qutebrowser/issues/670
        atime = atime.lstrip('\0')

        if '-' in atime:
            atime, flags = atime.split('-')
        else:
            flags = ''

        if not set(flags).issubset('r'):
            raise ValueError("Invalid flags {!r}".format(flags))

        redirect = 'r' in flags
        return (url, title, int(atime), redirect)
Exemple #30
0
    def _init_host(self, parsed):
        """Parse the host from the given URL.

        Deviation from Chromium:
        - http://:1234/ is not a valid URL because it has no host.
        """
        if parsed.hostname is None or not parsed.hostname.strip():
            if self._scheme not in self._SCHEMES_WITHOUT_HOST:
                raise ParseError("Pattern without host")
            assert self._host is None
            return

        if parsed.netloc.startswith('['):
            # Using QUrl parsing to minimize ipv6 addresses
            url = QUrl()
            url.setHost(parsed.hostname)
            if not url.isValid():
                raise ParseError(url.errorString())
            self._host = url.host()
            return

        # FIXME what about multiple dots?
        host_parts = parsed.hostname.rstrip('.').split('.')
        if host_parts[0] == '*':
            host_parts = host_parts[1:]
            self._match_subdomains = True

        if not host_parts:
            self._host = None
            return

        self._host = '.'.join(host_parts)

        if self._host.endswith('.*'):
            # Special case to have a nicer error
            raise ParseError("TLD wildcards are not implemented yet")
        if '*' in self._host:
            # Only * or *.foo is allowed as host.
            raise ParseError("Invalid host wildcard")
Exemple #31
0
    def to_py(self, value):
        self._basic_py_validation(value, str)
        if not value:
            return None

        if not ('{}' in value or '{0}' in value):
            raise configexc.ValidationError(value, "must contain \"{}\"")

        try:
            value.format("")
        except (KeyError, IndexError) as e:
            raise configexc.ValidationError(
                value, "may not contain {...} (use {{ and }} for literal {/})")
        except ValueError as e:
            raise configexc.ValidationError(value, str(e))

        url = QUrl(value.replace('{}', 'foobar'))
        if not url.isValid():
            raise configexc.ValidationError(
                value, "invalid url, {}".format(url.errorString()))

        return value