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
14 changes: 14 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@ Output:
</item>
```

<!-- markdownlint-disable MD046 -->
!!! note "Remote images served over HTTPS"
Since version 1.20, TLS certificates are always verified when retrieving a remote image length. If verification fails, the build goes on but the image length cannot be determined, just as for an unreachable image.

To trust a custom certificate authority (corporate proxy, self-signed internal server...), point `REQUESTS_CA_BUNDLE` to a bundle that includes it. This variable **replaces** the default bundle, so append your certificate to it rather than using it alone:

```sh
cat "$(python -c 'import certifi; print(certifi.where())')" my-ca.pem > ca-bundle.pem
REQUESTS_CA_BUNDLE="$PWD/ca-bundle.pem" mkdocs build
```

Responses are cached in [`cache_dir`](#cache_dir): clear it to force new requests.
<!-- markdownlint-enable MD046 -->

----

## Plugin options
Expand Down
27 changes: 15 additions & 12 deletions mkdocs_rss_plugin/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

# 3rd party
import markdown
import urllib3
from cachecontrol import CacheControl
from cachecontrol.caches.file_cache import SeparateBodyFileCache
from git import (
Expand All @@ -30,7 +29,8 @@
from mkdocs.structure.pages import Page
from mkdocs.utils import get_build_datetime
from requests import Session
from requests.exceptions import ConnectionError, HTTPError
from requests.exceptions import ConnectionError as RequestsConnectionError
from requests.exceptions import HTTPError, SSLError

# package
from mkdocs_rss_plugin.constants import (
Expand All @@ -53,7 +53,6 @@
# ################################

logger = get_plugin_logger(MKDOCS_LOGGER_NAME)
urllib3.disable_warnings() # disable warnings for unverified requests

# ############################################################################
# ########## Classes #############
Expand Down Expand Up @@ -690,12 +689,12 @@ def get_remote_image_length(
http_method: str = "HEAD",
attempt: int = 0,
req_timeout: tuple[float, float] = (5, 30),
ssl_verify: bool = True,
) -> Optional[int]:
"""Retrieve length for remote images (starting with 'http').

Firstly, it tries to perform a HEAD request and get the length from the headers. \
If it fails, it tries again with a GET and disabling SSL verification.
If it fails, it tries again with a GET. TLS certificate verification is always \
enforced: a TLS error is never retried.

Args:
image_url (str): image URL
Expand All @@ -704,8 +703,6 @@ def get_remote_image_length(
attempt (int, optional): request tries counter. Defaults to 0.
req_timeout (tuple[float, float], optional): (connect, read) timeout in \
secondes. Defaults to (5, 30).
ssl_verify (bool, optional): option to perform SSL verification or not.
Defaults to True.

Returns:
int | None: image length as int or None
Expand All @@ -724,19 +721,25 @@ def get_remote_image_length(
method=http_method,
timeout=req_timeout,
url=image_url,
verify=ssl_verify,
)
req_response.raise_for_status()
img_length = req_response.headers.get("content-length")
except (ConnectionError, HTTPError) as err:
except SSLError as err:
# SSLError subclasses RequestsConnectionError: catch it first and never
# retry, a failed certificate check must not be worked around.
logger.info(
f"Remote image skipped: TLS verification failed for {image_url}. "
f"Trace: {err}"
)
return None
except (RequestsConnectionError, HTTPError) as err:
logger.debug(
f"Remote image could not been reached: {image_url}. "
f"Trying again with {http_method} and disabling SSL verification. "
f"Attempt: {attempt}/2. Trace: {err}"
f"Trying again with GET. Attempt: {attempt}/2. Trace: {err}"
)
if attempt < 2:
return self.get_remote_image_length(
image_url, http_method="GET", attempt=attempt, ssl_verify=False
image_url, http_method="GET", attempt=attempt
)
else:
logger.info(
Expand Down
15 changes: 15 additions & 0 deletions tests/test_rss_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
# Standard library
import unittest
from pathlib import Path
from unittest.mock import patch

# 3rd party
from requests.exceptions import SSLError
from validator_collection import checkers

# plugin target
Expand Down Expand Up @@ -95,6 +97,19 @@ def test_remote_image_none(self):
)
self.assertIsNone(img_length)

def test_remote_image_ssl_error_not_retried(self):
"""TLS failures must not be retried, especially without verification."""
with patch.object(
self.plg_utils.req_session, "request", side_effect=SSLError("bad cert")
) as mock_request:
img_length = self.plg_utils.get_remote_image_length(
image_url="https://self-signed.example.org/image.png"
)

self.assertIsNone(img_length)
mock_request.assert_called_once()
self.assertNotIn("verify", mock_request.call_args.kwargs)

def test_get_value_from_dot_key(self):
param_list = [
{
Expand Down
Loading