fix(evidence): bind HTTP validators to final URL
This commit is contained in:
@@ -57,7 +57,8 @@ class HttpManifestEvidenceSource:
|
||||
self._session = requests.Session()
|
||||
self._session.trust_env = False
|
||||
self._cache: OrderedDict[str, AcquiredDocument] = OrderedDict()
|
||||
self._validators: dict[str, tuple[str | None, str | None]] = {}
|
||||
# provenance -> (exact final effective URL, ETag, Last-Modified)
|
||||
self._validators: dict[str, tuple[str, str | None, str | None]] = {}
|
||||
self._cache_bytes = 0
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -137,12 +138,14 @@ class HttpManifestEvidenceSource:
|
||||
return EvidenceSourceErrorCategory.TRANSIENT
|
||||
return EvidenceSourceErrorCategory.PERMANENT
|
||||
|
||||
def _conditional_headers(self, provenance: str) -> dict[str, str]:
|
||||
def _conditional_headers(self, provenance: str, request_url: str) -> dict[str, str]:
|
||||
cached = self._cache.get(self._source_id(provenance))
|
||||
validators = self._validators.get(provenance)
|
||||
if cached is None or validators is None:
|
||||
return {}
|
||||
etag, last_modified = validators
|
||||
final_url, etag, last_modified = validators
|
||||
if request_url != final_url:
|
||||
return {}
|
||||
headers = {}
|
||||
if etag:
|
||||
headers["If-None-Match"] = etag
|
||||
@@ -153,6 +156,7 @@ class HttpManifestEvidenceSource:
|
||||
def _remember(
|
||||
self,
|
||||
provenance: str,
|
||||
final_url: str,
|
||||
document: AcquiredDocument,
|
||||
validators: tuple[str | None, str | None],
|
||||
) -> None:
|
||||
@@ -162,7 +166,7 @@ class HttpManifestEvidenceSource:
|
||||
self._cache_bytes -= len(old.content)
|
||||
self._cache[source_id] = document
|
||||
self._cache_bytes += len(document.content)
|
||||
self._validators[provenance] = validators
|
||||
self._validators[provenance] = (final_url, *validators)
|
||||
while self._cache and self._cache_bytes > self.max_cache_bytes:
|
||||
evicted_id, evicted = self._cache.popitem(last=False)
|
||||
self._cache_bytes -= len(evicted.content)
|
||||
@@ -172,9 +176,9 @@ class HttpManifestEvidenceSource:
|
||||
|
||||
def _download(self, transport_url: str, provenance: str) -> AcquiredDocument:
|
||||
current = transport_url
|
||||
headers = self._conditional_headers(provenance)
|
||||
try:
|
||||
for redirect_count in range(self.max_redirects + 1):
|
||||
headers = self._conditional_headers(provenance, current)
|
||||
allowed = self._resolve_allowed(current)
|
||||
response = None
|
||||
try:
|
||||
@@ -194,25 +198,18 @@ class HttpManifestEvidenceSource:
|
||||
self._validate_url_shape(destination)
|
||||
except ValueError as error:
|
||||
raise self._safe_error("redirect") from error
|
||||
current_origin = urlsplit(current)
|
||||
destination_origin = urlsplit(destination)
|
||||
if (
|
||||
current_origin.scheme,
|
||||
current_origin.hostname,
|
||||
current_origin.port,
|
||||
) != (
|
||||
destination_origin.scheme,
|
||||
destination_origin.hostname,
|
||||
destination_origin.port,
|
||||
):
|
||||
headers = {}
|
||||
# Resolve every hop independently. Validators are never forwarded across
|
||||
# origins, where even an opaque ETag would become cross-origin state.
|
||||
# The next iteration binds validators to the exact destination URL.
|
||||
current = destination
|
||||
continue
|
||||
if response.status_code == 304:
|
||||
cached = self._cache.get(self._source_id(provenance))
|
||||
if cached is None:
|
||||
binding = self._validators.get(provenance)
|
||||
if (
|
||||
cached is None
|
||||
or not headers
|
||||
or binding is None
|
||||
or binding[0] != current
|
||||
):
|
||||
raise self._safe_error("conditional_response")
|
||||
self._cache.move_to_end(cached.source.source_id)
|
||||
return cached
|
||||
@@ -273,7 +270,7 @@ class HttpManifestEvidenceSource:
|
||||
media_type=media_type,
|
||||
acquired_at=datetime.now(UTC),
|
||||
)
|
||||
self._remember(provenance, document, (etag, last_modified))
|
||||
self._remember(provenance, current, document, (etag, last_modified))
|
||||
return document
|
||||
|
||||
def discover(self):
|
||||
|
||||
Reference in New Issue
Block a user