summaryrefslogtreecommitdiff
path: root/lib/python2.7/site-packages/pip/_vendor/cachecontrol/serialize.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/pip/_vendor/cachecontrol/serialize.py')
-rw-r--r--lib/python2.7/site-packages/pip/_vendor/cachecontrol/serialize.py196
1 files changed, 196 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/pip/_vendor/cachecontrol/serialize.py b/lib/python2.7/site-packages/pip/_vendor/cachecontrol/serialize.py
new file mode 100644
index 0000000..8f9c589
--- /dev/null
+++ b/lib/python2.7/site-packages/pip/_vendor/cachecontrol/serialize.py
@@ -0,0 +1,196 @@
+import base64
+import io
+import json
+import zlib
+
+from pip._vendor.requests.structures import CaseInsensitiveDict
+
+from .compat import HTTPResponse, pickle, text_type
+
+
+def _b64_encode_bytes(b):
+ return base64.b64encode(b).decode("ascii")
+
+
+def _b64_encode_str(s):
+ return _b64_encode_bytes(s.encode("utf8"))
+
+
+def _b64_encode(s):
+ if isinstance(s, text_type):
+ return _b64_encode_str(s)
+ return _b64_encode_bytes(s)
+
+
+def _b64_decode_bytes(b):
+ return base64.b64decode(b.encode("ascii"))
+
+
+def _b64_decode_str(s):
+ return _b64_decode_bytes(s).decode("utf8")
+
+
+class Serializer(object):
+
+ def dumps(self, request, response, body=None):
+ response_headers = CaseInsensitiveDict(response.headers)
+
+ if body is None:
+ body = response.read(decode_content=False)
+
+ # NOTE: 99% sure this is dead code. I'm only leaving it
+ # here b/c I don't have a test yet to prove
+ # it. Basically, before using
+ # `cachecontrol.filewrapper.CallbackFileWrapper`,
+ # this made an effort to reset the file handle. The
+ # `CallbackFileWrapper` short circuits this code by
+ # setting the body as the content is consumed, the
+ # result being a `body` argument is *always* passed
+ # into cache_response, and in turn,
+ # `Serializer.dump`.
+ response._fp = io.BytesIO(body)
+
+ data = {
+ "response": {
+ "body": _b64_encode_bytes(body),
+ "headers": dict(
+ (_b64_encode(k), _b64_encode(v))
+ for k, v in response.headers.items()
+ ),
+ "status": response.status,
+ "version": response.version,
+ "reason": _b64_encode_str(response.reason),
+ "strict": response.strict,
+ "decode_content": response.decode_content,
+ },
+ }
+
+ # Construct our vary headers
+ data["vary"] = {}
+ if "vary" in response_headers:
+ varied_headers = response_headers['vary'].split(',')
+ for header in varied_headers:
+ header = header.strip()
+ data["vary"][header] = request.headers.get(header, None)
+
+ # Encode our Vary headers to ensure they can be serialized as JSON
+ data["vary"] = dict(
+ (_b64_encode(k), _b64_encode(v) if v is not None else v)
+ for k, v in data["vary"].items()
+ )
+
+ return b",".join([
+ b"cc=2",
+ zlib.compress(
+ json.dumps(
+ data, separators=(",", ":"), sort_keys=True,
+ ).encode("utf8"),
+ ),
+ ])
+
+ def loads(self, request, data):
+ # Short circuit if we've been given an empty set of data
+ if not data:
+ return
+
+ # Determine what version of the serializer the data was serialized
+ # with
+ try:
+ ver, data = data.split(b",", 1)
+ except ValueError:
+ ver = b"cc=0"
+
+ # Make sure that our "ver" is actually a version and isn't a false
+ # positive from a , being in the data stream.
+ if ver[:3] != b"cc=":
+ data = ver + data
+ ver = b"cc=0"
+
+ # Get the version number out of the cc=N
+ ver = ver.split(b"=", 1)[-1].decode("ascii")
+
+ # Dispatch to the actual load method for the given version
+ try:
+ return getattr(self, "_loads_v{0}".format(ver))(request, data)
+ except AttributeError:
+ # This is a version we don't have a loads function for, so we'll
+ # just treat it as a miss and return None
+ return
+
+ def prepare_response(self, request, cached):
+ """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.
+ if "*" in cached.get("vary", {}):
+ return
+
+ # 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:
+ return
+
+ body_raw = cached["response"].pop("body")
+
+ headers = CaseInsensitiveDict(data=cached['response']['headers'])
+ if headers.get('transfer-encoding', '') == 'chunked':
+ headers.pop('transfer-encoding')
+
+ cached['response']['headers'] = headers
+
+ try:
+ body = io.BytesIO(body_raw)
+ 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'))
+
+ return HTTPResponse(
+ body=body,
+ preload_content=False,
+ **cached["response"]
+ )
+
+ def _loads_v0(self, request, data):
+ # The original legacy cache data. This doesn't contain enough
+ # information to construct everything we need, so we'll treat this as
+ # a miss.
+ return
+
+ def _loads_v1(self, request, data):
+ try:
+ cached = pickle.loads(data)
+ except ValueError:
+ return
+
+ return self.prepare_response(request, cached)
+
+ def _loads_v2(self, request, data):
+ try:
+ cached = json.loads(zlib.decompress(data).decode("utf8"))
+ except ValueError:
+ return
+
+ # We need to decode the items that we've base64 encoded
+ cached["response"]["body"] = _b64_decode_bytes(
+ cached["response"]["body"]
+ )
+ cached["response"]["headers"] = dict(
+ (_b64_decode_str(k), _b64_decode_str(v))
+ for k, v in cached["response"]["headers"].items()
+ )
+ cached["response"]["reason"] = _b64_decode_str(
+ cached["response"]["reason"],
+ )
+ cached["vary"] = dict(
+ (_b64_decode_str(k), _b64_decode_str(v) if v is not None else v)
+ for k, v in cached["vary"].items()
+ )
+
+ return self.prepare_response(request, cached)