Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Deprecated
Features added
--------------

* #13860: lickcheck: add :confval:`linkcheck_allow_forbidden` option to
let HTTP 403 responses be marked as "working".
* #13332: Add :confval:`doctest_fail_fast` option to exit after the first failed
test.
Patch by Till Hoffmann.
Expand Down
11 changes: 11 additions & 0 deletions doc/usage/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3812,6 +3812,17 @@ and the number of workers to use.

.. versionadded:: 7.3

.. confval:: linkcheck_allow_forbidden
:type: :code-py:`bool`
:default: :code-py:`False`

When a webserver responds with an HTTP 403 (forbidden) response,
the current default behaviour of the *linkcheck* builder is
to treat the link as "broken".
To change that behaviour, set this option to :code-py:`True`.

.. versionadded:: 8.3

.. confval:: linkcheck_rate_limit_timeout
:type: :code-py:`int`
:default: :code-py:`300`
Expand Down
11 changes: 11 additions & 0 deletions sphinx/builders/linkcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ def __init__(
self.retries: int = config.linkcheck_retries
self.rate_limit_timeout = config.linkcheck_rate_limit_timeout
self._allow_unauthorized = config.linkcheck_allow_unauthorized
self._allow_forbidden = config.linkcheck_allow_forbidden
self._timeout_status: Literal[_Status.BROKEN, _Status.TIMEOUT]
if config.linkcheck_report_timeouts_as_broken:
self._timeout_status = _Status.BROKEN
Expand Down Expand Up @@ -601,6 +602,13 @@ def _check_uri(self, uri: str, hyperlink: Hyperlink) -> _URIProperties:
else:
return _Status.BROKEN, 'unauthorized', 0

# Forbidden: the request was forbidden (access or refused)
if status_code == 403:
if self._allow_forbidden:
return _Status.WORKING, 'forbidden', 0
else:
return _Status.BROKEN, 'forbidden', 0

# Rate limiting; back-off if allowed, or report failure otherwise
if status_code == 429:
if next_check := self.limit_rate(response_url, retry_after):
Expand Down Expand Up @@ -813,6 +821,9 @@ def setup(app: Sphinx) -> ExtensionMetadata:
app.add_config_value(
'linkcheck_allow_unauthorized', False, '', types=frozenset({bool})
)
app.add_config_value(
'linkcheck_allow_forbidden', False, '', types=frozenset({bool})
)
app.add_config_value(
'linkcheck_report_timeouts_as_broken', False, '', types=frozenset({bool})
)
Expand Down
44 changes: 44 additions & 0 deletions tests/test_builders/test_build_linkcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,50 @@
assert content['status'] == 'broken'


@pytest.mark.sphinx(
'linkcheck',
testroot='linkcheck-localserver',
freshenv=True,
confoverrides={
'linkcheck_allow_forbidden': False,
'linkcheck_auth': [('.*', ('user1', 'bad'))],
},
)
def test_forbidden_broken(app: SphinxTestApp) -> None:
with serve_application(
app, 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'] == 'forbidden'
assert content['status'] == 'broken'


@pytest.mark.sphinx(
'linkcheck',
testroot='linkcheck-localserver',
freshenv=True,
confoverrides={
'linkcheck_allow_forbidden': True,
'linkcheck_auth': [('.*', ('user1', 'bad'))],
},
)
def test_forbidden_allowed(app: SphinxTestApp) -> None:
with serve_application(
app, 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'] == 'forbidden'
assert content['status'] == 'working'

Check failure on line 628 in tests/test_builders/test_build_linkcheck.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (W293)

tests/test_builders/test_build_linkcheck.py:628:1: W293 Blank line contains whitespace

@pytest.mark.sphinx(
'linkcheck',
testroot='linkcheck-localserver',
Expand Down
Loading