2023-01-12 01:04:47 +00:00
|
|
|
# SPDX-FileCopyrightText: 2015 Eric Larson
|
|
|
|
#
|
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
2023-09-17 19:22:54 +00:00
|
|
|
from __future__ import annotations
|
2023-01-12 01:04:47 +00:00
|
|
|
|
|
|
|
import io
|
2023-09-17 19:22:54 +00:00
|
|
|
from typing import IO, TYPE_CHECKING, Any, Mapping, cast
|
2023-01-12 01:04:47 +00:00
|
|
|
|
|
|
|
import msgpack
|
|
|
|
from requests.structures import CaseInsensitiveDict
|
2023-09-17 19:22:54 +00:00
|
|
|
from urllib3 import HTTPResponse
|
2023-01-12 01:04:47 +00:00
|
|
|
|
2023-09-17 19:22:54 +00:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
from requests import PreparedRequest
|
2023-01-12 01:04:47 +00:00
|
|
|
|
|
|
|
|
2023-09-17 19:22:54 +00:00
|
|
|
class Serializer:
|
|
|
|
serde_version = "4"
|
2023-01-12 01:04:47 +00:00
|
|
|
|
2023-09-17 19:22:54 +00:00
|
|
|
def dumps(
|
|
|
|
self,
|
|
|
|
request: PreparedRequest,
|
|
|
|
response: HTTPResponse,
|
|
|
|
body: bytes | None = None,
|
|
|
|
) -> bytes:
|
|
|
|
response_headers: CaseInsensitiveDict[str] = CaseInsensitiveDict(
|
|
|
|
response.headers
|
|
|
|
)
|
2023-01-12 01:04:47 +00:00
|
|
|
|
2023-01-06 11:47:44 +00:00
|
|
|
if body is None:
|
|
|
|
# When a body isn't passed in, we'll read the response. We
|
|
|
|
# also update the response with a new file handler to be
|
|
|
|
# sure it acts as though it was never read.
|
|
|
|
body = response.read(decode_content=False)
|
2024-06-05 08:19:49 +00:00
|
|
|
response._fp = io.BytesIO(body) # type: ignore[assignment]
|
2023-09-17 19:22:54 +00:00
|
|
|
response.length_remaining = len(body)
|
|
|
|
|
2023-01-12 01:04:47 +00:00
|
|
|
data = {
|
2023-09-17 19:22:54 +00:00
|
|
|
"response": {
|
|
|
|
"body": body, # Empty bytestring if body is stored separately
|
2024-06-05 08:19:49 +00:00
|
|
|
"headers": {str(k): str(v) for k, v in response.headers.items()},
|
2023-09-17 19:22:54 +00:00
|
|
|
"status": response.status,
|
|
|
|
"version": response.version,
|
|
|
|
"reason": str(response.reason),
|
|
|
|
"decode_content": response.decode_content,
|
2023-01-12 01:04:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Construct our vary headers
|
2023-09-17 19:22:54 +00:00
|
|
|
data["vary"] = {}
|
|
|
|
if "vary" in response_headers:
|
|
|
|
varied_headers = response_headers["vary"].split(",")
|
2023-01-12 01:04:47 +00:00
|
|
|
for header in varied_headers:
|
2023-09-17 19:22:54 +00:00
|
|
|
header = str(header).strip()
|
2023-01-12 01:04:47 +00:00
|
|
|
header_value = request.headers.get(header, None)
|
|
|
|
if header_value is not None:
|
2023-09-17 19:22:54 +00:00
|
|
|
header_value = str(header_value)
|
|
|
|
data["vary"][header] = header_value
|
|
|
|
|
|
|
|
return b",".join([f"cc={self.serde_version}".encode(), self.serialize(data)])
|
2023-01-12 01:04:47 +00:00
|
|
|
|
2023-09-17 19:22:54 +00:00
|
|
|
def serialize(self, data: dict[str, Any]) -> bytes:
|
|
|
|
return cast(bytes, msgpack.dumps(data, use_bin_type=True))
|
2023-01-12 01:04:47 +00:00
|
|
|
|
2023-09-17 19:22:54 +00:00
|
|
|
def loads(
|
|
|
|
self,
|
|
|
|
request: PreparedRequest,
|
|
|
|
data: bytes,
|
|
|
|
body_file: IO[bytes] | None = None,
|
|
|
|
) -> HTTPResponse | None:
|
2023-01-12 01:04:47 +00:00
|
|
|
# Short circuit if we've been given an empty set of data
|
|
|
|
if not data:
|
2023-09-17 19:22:54 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
# Previous versions of this library supported other serialization
|
|
|
|
# formats, but these have all been removed.
|
|
|
|
if not data.startswith(f"cc={self.serde_version},".encode()):
|
|
|
|
return None
|
|
|
|
|
|
|
|
data = data[5:]
|
|
|
|
return self._loads_v4(request, data, body_file)
|
|
|
|
|
|
|
|
def prepare_response(
|
|
|
|
self,
|
|
|
|
request: PreparedRequest,
|
|
|
|
cached: Mapping[str, Any],
|
|
|
|
body_file: IO[bytes] | None = None,
|
|
|
|
) -> HTTPResponse | None:
|
2023-01-12 01:04:47 +00:00
|
|
|
"""Verify our vary headers match and construct a real urllib3
|
|
|
|
HTTPResponse object.
|
|
|
|
"""
|
|
|
|
# Special case the '*' Vary value as it means we cannot actually
|
|
|
|
# determine if the cached response is suitable for this request.
|
|
|
|
# This case is also handled in the controller code when creating
|
|
|
|
# a cache entry, but is left here for backwards compatibility.
|
|
|
|
if "*" in cached.get("vary", {}):
|
2023-09-17 19:22:54 +00:00
|
|
|
return None
|
2023-01-12 01:04:47 +00:00
|
|
|
|
|
|
|
# Ensure that the Vary headers for the cached response match our
|
|
|
|
# request
|
|
|
|
for header, value in cached.get("vary", {}).items():
|
|
|
|
if request.headers.get(header, None) != value:
|
2023-09-17 19:22:54 +00:00
|
|
|
return None
|
2023-01-12 01:04:47 +00:00
|
|
|
|
|
|
|
body_raw = cached["response"].pop("body")
|
|
|
|
|
2023-09-17 19:22:54 +00:00
|
|
|
headers: CaseInsensitiveDict[str] = CaseInsensitiveDict(
|
|
|
|
data=cached["response"]["headers"]
|
|
|
|
)
|
2023-01-12 01:04:47 +00:00
|
|
|
if headers.get("transfer-encoding", "") == "chunked":
|
|
|
|
headers.pop("transfer-encoding")
|
|
|
|
|
|
|
|
cached["response"]["headers"] = headers
|
|
|
|
|
|
|
|
try:
|
2023-09-17 19:22:54 +00:00
|
|
|
body: IO[bytes]
|
2023-01-06 11:47:44 +00:00
|
|
|
if body_file is None:
|
|
|
|
body = io.BytesIO(body_raw)
|
|
|
|
else:
|
|
|
|
body = body_file
|
2023-01-12 01:04:47 +00:00
|
|
|
except TypeError:
|
|
|
|
# This can happen if cachecontrol serialized to v1 format (pickle)
|
|
|
|
# using Python 2. A Python 2 str(byte string) will be unpickled as
|
|
|
|
# a Python 3 str (unicode string), which will cause the above to
|
|
|
|
# fail with:
|
|
|
|
#
|
|
|
|
# TypeError: 'str' does not support the buffer interface
|
|
|
|
body = io.BytesIO(body_raw.encode("utf8"))
|
|
|
|
|
2023-09-17 19:22:54 +00:00
|
|
|
# Discard any `strict` parameter serialized by older version of cachecontrol.
|
|
|
|
cached["response"].pop("strict", None)
|
2023-01-12 01:04:47 +00:00
|
|
|
|
2023-09-17 19:22:54 +00:00
|
|
|
return HTTPResponse(body=body, preload_content=False, **cached["response"])
|
2023-01-12 01:04:47 +00:00
|
|
|
|
2023-09-17 19:22:54 +00:00
|
|
|
def _loads_v4(
|
|
|
|
self,
|
|
|
|
request: PreparedRequest,
|
|
|
|
data: bytes,
|
|
|
|
body_file: IO[bytes] | None = None,
|
|
|
|
) -> HTTPResponse | None:
|
2023-01-12 01:04:47 +00:00
|
|
|
try:
|
|
|
|
cached = msgpack.loads(data, raw=False)
|
|
|
|
except ValueError:
|
2023-09-17 19:22:54 +00:00
|
|
|
return None
|
2023-01-12 01:04:47 +00:00
|
|
|
|
2023-01-06 11:47:44 +00:00
|
|
|
return self.prepare_response(request, cached, body_file)
|