Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6aee5b8
linkcheck builder: update handling of HTTP 401 status code to conside…
jayaddison May 21, 2023
7f68206
linkcheck builder: introduce a 'linkcheck_allow_unauthorized' config …
jayaddison Sep 21, 2023
2d4f4bc
Update CHANGES.rst
jayaddison Sep 21, 2023
a958e86
Clarify phrasing in CHANGES.rst: the setting has to be configured to …
jayaddison Sep 21, 2023
712f060
CHANGES.rst: fixup: relocate the changelog entry to the correct locat…
jayaddison Sep 21, 2023
ee7348f
docs: add documentation for the 'linkcheck_allow_unauthorized' config…
jayaddison Sep 21, 2023
c19f7c3
CHANGES.rst: nitpick: undo accidental empty-line removal
jayaddison Sep 21, 2023
a290e3c
CHANGES.rst: nitpick: phrasing: 'handle...as broken' -> 'report...as …
jayaddison Sep 22, 2023
46d8206
CHANGES.rst: nitpick: brevity
jayaddison Sep 22, 2023
c895ca2
linkcheck builder: add a deprecation warning indicating that the 'lin…
jayaddison Sep 22, 2023
37b50ae
CHANGES.rst: fixup: add self-attribution
jayaddison Sep 22, 2023
bc3c390
Merge branch 'master' into issue-11433/adjust-linkcheck-http-401-hand…
jayaddison Sep 23, 2023
37634b1
ruff: linting fixups
jayaddison Sep 23, 2023
a81f283
Apply code review suggestion: filter warnings in test case using more…
jayaddison Sep 26, 2023
de6ac23
Apply code review suggestion: make the 'allow_unauthorized' worker va…
jayaddison Sep 26, 2023
9f87c41
docs: add removal note to 'linkcheck_allow_unauthorized' config optio…
jayaddison Sep 26, 2023
2b90b76
linkcheck builder: relocate 'linkcheck_allow_unauthorized' warning to…
jayaddison Sep 26, 2023
d9144cc
fixup: use pytest.mark.filterwarnings to filter warning _before_ app …
jayaddison Sep 26, 2023
fa9be8a
cleanup: remove unused imports
jayaddison Sep 26, 2023
4750345
Apply code review suggestion: set default value for 'allow_unauthoriz…
jayaddison Sep 27, 2023
6d450c2
Updated plan: instead of removing the setting entirely in Sphinx 8.0,…
jayaddison Sep 27, 2023
cdfa342
Apply code review suggestion: when an HTTP 401 response is encountere…
jayaddison Sep 27, 2023
daf4efb
Code behaviour consensus: the URI of unauthorized HTTP responses shou…
jayaddison Sep 30, 2023
e17581a
CHANGES.rst: prefer Pythonic representation of false value
jayaddison Oct 1, 2023
2070c88
Merge branch 'master' into issue-11433/adjust-linkcheck-http-401-hand…
jayaddison Oct 3, 2023
bc97be3
Merge branch 'master' into issue-11433/adjust-linkcheck-http-401-hand…
jayaddison Oct 11, 2023
a75abcf
Merge branch 'master' into issue-11433/adjust-linkcheck-http-401-hand…
jayaddison Dec 11, 2023
5d16bb3
Merge branch 'master' into issue-11433/adjust-linkcheck-http-401-hand…
jayaddison Dec 26, 2023
000af8b
Merge branch 'master' into issue-11433/adjust-linkcheck-http-401-hand…
jayaddison Dec 30, 2023
bfaf179
Merge branch 'master' into issue-11433/adjust-linkcheck-http-401-hand…
jayaddison Jan 9, 2024
16a3695
Update sphinx/builders/linkcheck.py
AA-Turner Jan 9, 2024
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
4 changes: 3 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Bugs fixed

* #11668: Raise a useful error when ``theme.conf`` is missing.
Patch by Vinay Sajip.
* #11433: The ``linkcheck_allow_unauthorized`` configuration option (default
value ``true``) has been added. Set this option to ``false`` to handle
HTTP 401 (unauthorized) server responses as broken hyperlinks.

Testing
-------
Expand Down Expand Up @@ -304,7 +307,6 @@ Features added

Bugs fixed
----------

* Restored the ``footnote-reference`` class that has been removed in
the latest (unreleased) version of Docutils.
* #11486: Use :rfc:`8081` font file MIME types in the EPUB builder.
Expand Down
8 changes: 8 additions & 0 deletions doc/usage/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2915,6 +2915,14 @@ Options for the linkcheck builder

.. versionadded:: 4.4

.. confval:: linkcheck_allow_unauthorized

When a webserver responds with an HTTP 401 (unauthorized) response, the
default behaviour is to treat the link as "working". To change that
behaviour, set this option to ``False``.

.. versionadded:: 7.3


Options for the XML builder
---------------------------
Expand Down
7 changes: 5 additions & 2 deletions sphinx/builders/linkcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ def __init__(self, config: Config,
self.allowed_redirects = config.linkcheck_allowed_redirects
self.retries: int = config.linkcheck_retries
self.rate_limit_timeout = config.linkcheck_rate_limit_timeout
self.allow_unauthorized = config.linkcheck_allow_unauthorized

self.user_agent = config.user_agent
self.tls_verify = config.tls_verify
Expand Down Expand Up @@ -437,9 +438,10 @@ def _check_uri(self, uri: str, hyperlink: Hyperlink) -> tuple[str, str, int]:
except HTTPError as err:
error_message = str(err)

# Unauthorised: the reference probably exists
# Unauthorized: the client did not provide required credentials
if status_code == 401:
return 'working', 'unauthorized', 0
status = 'working' if self.allow_unauthorized else 'broken'
return status, 'unauthorized', 0

# Rate limiting; back-off if allowed, or report failure otherwise
if status_code == 429:
Expand Down Expand Up @@ -625,6 +627,7 @@ def setup(app: Sphinx) -> dict[str, Any]:
app.add_config_value('linkcheck_anchors_ignore', ['^!'], False)
app.add_config_value('linkcheck_anchors_ignore_for_url', (), False, (tuple, list))
app.add_config_value('linkcheck_rate_limit_timeout', 300.0, False)
app.add_config_value('linkcheck_allow_unauthorized', True, False)

app.add_event('linkcheck-process-uri')

Expand Down
29 changes: 23 additions & 6 deletions tests/test_build_linkcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,12 @@ class CustomHandler(http.server.BaseHTTPRequestHandler):

def authenticated(method):
def method_if_authenticated(self):
if (expected_token is None
or self.headers["Authorization"] == f"Basic {expected_token}"):
if expected_token is None:
return method(self)
elif not self.headers["Authorization"]:
self.send_response(401, "Unauthorized")
self.end_headers()
elif self.headers["Authorization"] == f"Basic {expected_token}":
return method(self)
else:
self.send_response(403, "Forbidden")
Expand Down Expand Up @@ -387,6 +391,20 @@ def test_auth_header_uses_first_match(app):
assert content["status"] == "working"


@pytest.mark.sphinx(
'linkcheck', testroot='linkcheck-localserver', freshenv=True,
confoverrides={'linkcheck_allow_unauthorized': False})
def test_unauthorized_broken(app):
with http_server(custom_handler(valid_credentials=("user1", "password"))):
app.build()

with open(app.outdir / "output.json", encoding="utf-8") as fp:
content = json.load(fp)

assert content["info"] == "unauthorized"
assert content["status"] == "broken"


@pytest.mark.sphinx(
'linkcheck', testroot='linkcheck-localserver', freshenv=True,
confoverrides={'linkcheck_auth': [(r'^$', ('user1', 'password'))]})
Expand All @@ -397,10 +415,9 @@ def test_auth_header_no_match(app):
with open(app.outdir / "output.json", encoding="utf-8") as fp:
content = json.load(fp)

# TODO: should this test's webserver return HTTP 401 here?
# https://github.com/sphinx-doc/sphinx/issues/11433
assert content["info"] == "403 Client Error: Forbidden for url: http://localhost:7777/"
assert content["status"] == "broken"
# This link is considered working based on the default linkcheck_allow_unauthorized=true
assert content["info"] == "unauthorized"
assert content["status"] == "working"


@pytest.mark.sphinx(
Expand Down