Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions properdocs/config/config_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from urllib.parse import urlsplit, urlunsplit

import markdown
import pathspec
import pathspec.gitignore

from properdocs import plugins, theme, utils
Expand Down Expand Up @@ -497,8 +496,8 @@ def run_validation(self, value: object) -> str:
return value
try:
parsed_url = urlsplit(value)
except (AttributeError, TypeError):
raise ValidationError("Unable to parse the URL.")
except Exception:
raise ValidationError("The URL is invalid")

if parsed_url.scheme and parsed_url.netloc:
if self.is_dir and not parsed_url.path.endswith('/'):
Expand Down
13 changes: 10 additions & 3 deletions properdocs/structure/nav.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,16 @@ def get_navigation(files: Files, config: ProperDocsConfig) -> Navigation:

links = _get_by_type(items, Link)
for link in links:
scheme, netloc, path, query, fragment = urlsplit(link.url)
if scheme or netloc:
log.debug(f"An external link to '{link.url}' is included in the 'nav' configuration.")
invalid: str | None = None
try:
scheme, netloc, path, query, fragment = urlsplit(link.url)
except Exception as e:
invalid = 'invalid'
else:
if scheme or netloc:
invalid = 'external'
if invalid:
log.debug(f"An {invalid} link to '{link.url}' is included in the 'nav' configuration.")
elif (
link.url.startswith('/')
and config.validation.nav.absolute_links
Expand Down
10 changes: 9 additions & 1 deletion properdocs/structure/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,15 @@ def _possible_target_uris(
tried.add(guess)

def path_to_url(self, url: str) -> str:
scheme, netloc, path, query, anchor = urlsplit(url)
try:
scheme, netloc, path, query, anchor = urlsplit(url)
except ValueError: # Invalid URL, e.g. invalid IPv6.
log.log(
self.config.validation.links.unrecognized_links,
f"Doc file '{self.file.src_uri}' contains an invalid link '{url}', "
f"it was left as is.",
)
return url

absolute_link = None
warning_level, warning = 0, ''
Expand Down
12 changes: 11 additions & 1 deletion properdocs/tests/config/config_options_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,14 +424,24 @@ class Schema(Config):
conf = self.get_config(Schema, {'option': None})
self.assertEqual(conf.option, None)

@unittest.skipUnless(sys.version_info >= (3, 11), "new error kind in Python 3.11")
def test_invalid_url(self) -> None:
class Schema(Config):
option = c.URL()

with self.expect_error(option="Required configuration not provided."):
self.get_config(Schema, {'option': None})

for url in "properdocs.org", "//properdocs.org/test", "http:/properdocs.org/", "/hello/":
for url in ["http://[a]/"]:
with self.subTest(url=url):
with self.expect_error(option="The URL is invalid"):
self.get_config(Schema, {'option': url})

def test_invalid_url_no_http(self) -> None:
class Schema(Config):
option = c.URL()

for url in ["properdocs.org", "//properdocs.org/test", "http:/properdocs.org/", "/hello/"]:
with self.subTest(url=url):
with self.expect_error(
option="The URL isn't valid, it should include the http:// (scheme)"
Expand Down
13 changes: 13 additions & 0 deletions properdocs/tests/structure/page_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,19 @@ def test_absolute_win_local_path(self):
'<a href="\\image.png">absolute local path</a>',
)

@unittest.skipUnless(sys.version_info >= (3, 11), "new error kind in Python 3.11")
def test_invalid_link_ipv6(self):
for use_directory_urls in True, False:
self.assertEqual(
self.get_rendered_result(
use_directory_urls=use_directory_urls,
content='[invalid link](http://[a]/)',
files=['index.md'],
logs="INFO:Doc file 'index.md' contains an invalid link 'http://[a]/', it was left as is.",
),
'<a href="http://[a]/">invalid link</a>',
)

def test_email_link(self):
self.assertEqual(
self.get_rendered_result(content='<mail@example.com>', files=['index.md']),
Expand Down
5 changes: 4 additions & 1 deletion properdocs/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,11 @@ def _get_norm_url(path: str) -> tuple[str, int]:
f"That will be unsupported in a future release. Please change it to '/'."
)
path = path.replace('\\', '/')
try:
parsed = urlsplit(path)
except ValueError:
return path, -1
# Allow links to be fully qualified URLs
parsed = urlsplit(path)
if parsed.scheme or parsed.netloc or path.startswith(('/', '#')):
return path, -1

Expand Down
Loading