diff options
Diffstat (limited to 'venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_vendor/cachecontrol/controller.py')
-rw-r--r-- | venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_vendor/cachecontrol/controller.py | 373 |
1 files changed, 0 insertions, 373 deletions
diff --git a/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_vendor/cachecontrol/controller.py b/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_vendor/cachecontrol/controller.py deleted file mode 100644 index bf4cc7f..0000000 --- a/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_vendor/cachecontrol/controller.py +++ /dev/null | |||
@@ -1,373 +0,0 @@ | |||
1 | """ | ||
2 | The httplib2 algorithms ported for use with requests. | ||
3 | """ | ||
4 | import logging | ||
5 | import re | ||
6 | import calendar | ||
7 | import time | ||
8 | from email.utils import parsedate_tz | ||
9 | |||
10 | from pip._vendor.requests.structures import CaseInsensitiveDict | ||
11 | |||
12 | from .cache import DictCache | ||
13 | from .serialize import Serializer | ||
14 | |||
15 | |||
16 | logger = logging.getLogger(__name__) | ||
17 | |||
18 | URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?") | ||
19 | |||
20 | |||
21 | def parse_uri(uri): | ||
22 | """Parses a URI using the regex given in Appendix B of RFC 3986. | ||
23 | |||
24 | (scheme, authority, path, query, fragment) = parse_uri(uri) | ||
25 | """ | ||
26 | groups = URI.match(uri).groups() | ||
27 | return (groups[1], groups[3], groups[4], groups[6], groups[8]) | ||
28 | |||
29 | |||
30 | class CacheController(object): | ||
31 | """An interface to see if request should cached or not. | ||
32 | """ | ||
33 | def __init__(self, cache=None, cache_etags=True, serializer=None, | ||
34 | status_codes=None): | ||
35 | self.cache = cache or DictCache() | ||
36 | self.cache_etags = cache_etags | ||
37 | self.serializer = serializer or Serializer() | ||
38 | self.cacheable_status_codes = status_codes or (200, 203, 300, 301) | ||
39 | |||
40 | @classmethod | ||
41 | def _urlnorm(cls, uri): | ||
42 | """Normalize the URL to create a safe key for the cache""" | ||
43 | (scheme, authority, path, query, fragment) = parse_uri(uri) | ||
44 | if not scheme or not authority: | ||
45 | raise Exception("Only absolute URIs are allowed. uri = %s" % uri) | ||
46 | |||
47 | scheme = scheme.lower() | ||
48 | authority = authority.lower() | ||
49 | |||
50 | if not path: | ||
51 | path = "/" | ||
52 | |||
53 | # Could do syntax based normalization of the URI before | ||
54 | # computing the digest. See Section 6.2.2 of Std 66. | ||
55 | request_uri = query and "?".join([path, query]) or path | ||
56 | defrag_uri = scheme + "://" + authority + request_uri | ||
57 | |||
58 | return defrag_uri | ||
59 | |||
60 | @classmethod | ||
61 | def cache_url(cls, uri): | ||
62 | return cls._urlnorm(uri) | ||
63 | |||
64 | def parse_cache_control(self, headers): | ||
65 | known_directives = { | ||
66 | # https://tools.ietf.org/html/rfc7234#section-5.2 | ||
67 | 'max-age': (int, True,), | ||
68 | 'max-stale': (int, False,), | ||
69 | 'min-fresh': (int, True,), | ||
70 | 'no-cache': (None, False,), | ||
71 | 'no-store': (None, False,), | ||
72 | 'no-transform': (None, False,), | ||
73 | 'only-if-cached' : (None, False,), | ||
74 | 'must-revalidate': (None, False,), | ||
75 | 'public': (None, False,), | ||
76 | 'private': (None, False,), | ||
77 | 'proxy-revalidate': (None, False,), | ||
78 | 's-maxage': (int, True,) | ||
79 | } | ||
80 | |||
81 | cc_headers = headers.get('cache-control', | ||
82 | headers.get('Cache-Control', '')) | ||
83 | |||
84 | retval = {} | ||
85 | |||
86 | for cc_directive in cc_headers.split(','): | ||
87 | parts = cc_directive.split('=', 1) | ||
88 | directive = parts[0].strip() | ||
89 | |||
90 | try: | ||
91 | typ, required = known_directives[directive] | ||
92 | except KeyError: | ||
93 | logger.debug('Ignoring unknown cache-control directive: %s', | ||
94 | directive) | ||
95 | continue | ||
96 | |||
97 | if not typ or not required: | ||
98 | retval[directive] = None | ||
99 | if typ: | ||
100 | try: | ||
101 | retval[directive] = typ(parts[1].strip()) | ||
102 | except IndexError: | ||
103 | if required: | ||
104 | logger.debug('Missing value for cache-control ' | ||
105 | 'directive: %s', directive) | ||
106 | except ValueError: | ||
107 | logger.debug('Invalid value for cache-control directive ' | ||
108 | '%s, must be %s', directive, typ.__name__) | ||
109 | |||
110 | return retval | ||
111 | |||
112 | def cached_request(self, request): | ||
113 | """ | ||
114 | Return a cached response if it exists in the cache, otherwise | ||
115 | return False. | ||
116 | """ | ||
117 | cache_url = self.cache_url(request.url) | ||
118 | logger.debug('Looking up "%s" in the cache', cache_url) | ||
119 | cc = self.parse_cache_control(request.headers) | ||
120 | |||
121 | # Bail out if the request insists on fresh data | ||
122 | if 'no-cache' in cc: | ||
123 | logger.debug('Request header has "no-cache", cache bypassed') | ||
124 | return False | ||
125 | |||
126 | if 'max-age' in cc and cc['max-age'] == 0: | ||
127 | logger.debug('Request header has "max_age" as 0, cache bypassed') | ||
128 | return False | ||
129 | |||
130 | # Request allows serving from the cache, let's see if we find something | ||
131 | cache_data = self.cache.get(cache_url) | ||
132 | if cache_data is None: | ||
133 | logger.debug('No cache entry available') | ||
134 | return False | ||
135 | |||
136 | # Check whether it can be deserialized | ||
137 | resp = self.serializer.loads(request, cache_data) | ||
138 | if not resp: | ||
139 | logger.warning('Cache entry deserialization failed, entry ignored') | ||
140 | return False | ||
141 | |||
142 | # If we have a cached 301, return it immediately. We don't | ||
143 | # need to test our response for other headers b/c it is | ||
144 | # intrinsically "cacheable" as it is Permanent. | ||
145 | # See: | ||
146 | # https://tools.ietf.org/html/rfc7231#section-6.4.2 | ||
147 | # | ||
148 | # Client can try to refresh the value by repeating the request | ||
149 | # with cache busting headers as usual (ie no-cache). | ||
150 | if resp.status == 301: | ||
151 | msg = ('Returning cached "301 Moved Permanently" response ' | ||
152 | '(ignoring date and etag information)') | ||
153 | logger.debug(msg) | ||
154 | return resp | ||
155 | |||
156 | headers = CaseInsensitiveDict(resp.headers) | ||
157 | if not headers or 'date' not in headers: | ||
158 | if 'etag' not in headers: | ||
159 | # Without date or etag, the cached response can never be used | ||
160 | # and should be deleted. | ||
161 | logger.debug('Purging cached response: no date or etag') | ||
162 | self.cache.delete(cache_url) | ||
163 | logger.debug('Ignoring cached response: no date') | ||
164 | return False | ||
165 | |||
166 | now = time.time() | ||
167 | date = calendar.timegm( | ||
168 | parsedate_tz(headers['date']) | ||
169 | ) | ||
170 | current_age = max(0, now - date) | ||
171 | logger.debug('Current age based on date: %i', current_age) | ||
172 | |||
173 | # TODO: There is an assumption that the result will be a | ||
174 | # urllib3 response object. This may not be best since we | ||
175 | # could probably avoid instantiating or constructing the | ||
176 | # response until we know we need it. | ||
177 | resp_cc = self.parse_cache_control(headers) | ||
178 | |||
179 | # determine freshness | ||
180 | freshness_lifetime = 0 | ||
181 | |||
182 | # Check the max-age pragma in the cache control header | ||
183 | if 'max-age' in resp_cc: | ||
184 | freshness_lifetime = resp_cc['max-age'] | ||
185 | logger.debug('Freshness lifetime from max-age: %i', | ||
186 | freshness_lifetime) | ||
187 | |||
188 | # If there isn't a max-age, check for an expires header | ||
189 | elif 'expires' in headers: | ||
190 | expires = parsedate_tz(headers['expires']) | ||
191 | if expires is not None: | ||
192 | expire_time = calendar.timegm(expires) - date | ||
193 | freshness_lifetime = max(0, expire_time) | ||
194 | logger.debug("Freshness lifetime from expires: %i", | ||
195 | freshness_lifetime) | ||
196 | |||
197 | # Determine if we are setting freshness limit in the | ||
198 | # request. Note, this overrides what was in the response. | ||
199 | if 'max-age' in cc: | ||
200 | freshness_lifetime = cc['max-age'] | ||
201 | logger.debug('Freshness lifetime from request max-age: %i', | ||
202 | freshness_lifetime) | ||
203 | |||
204 | if 'min-fresh' in cc: | ||
205 | min_fresh = cc['min-fresh'] | ||
206 | # adjust our current age by our min fresh | ||
207 | current_age += min_fresh | ||
208 | logger.debug('Adjusted current age from min-fresh: %i', | ||
209 | current_age) | ||
210 | |||
211 | # Return entry if it is fresh enough | ||
212 | if freshness_lifetime > current_age: | ||
213 | logger.debug('The response is "fresh", returning cached response') | ||
214 | logger.debug('%i > %i', freshness_lifetime, current_age) | ||
215 | return resp | ||
216 | |||
217 | # we're not fresh. If we don't have an Etag, clear it out | ||
218 | if 'etag' not in headers: | ||
219 | logger.debug( | ||
220 | 'The cached response is "stale" with no etag, purging' | ||
221 | ) | ||
222 | self.cache.delete(cache_url) | ||
223 | |||
224 | # return the original handler | ||
225 | return False | ||
226 | |||
227 | def conditional_headers(self, request): | ||
228 | cache_url = self.cache_url(request.url) | ||
229 | resp = self.serializer.loads(request, self.cache.get(cache_url)) | ||
230 | new_headers = {} | ||
231 | |||
232 | if resp: | ||
233 | headers = CaseInsensitiveDict(resp.headers) | ||
234 | |||
235 | if 'etag' in headers: | ||
236 | new_headers['If-None-Match'] = headers['ETag'] | ||
237 | |||
238 | if 'last-modified' in headers: | ||
239 | new_headers['If-Modified-Since'] = headers['Last-Modified'] | ||
240 | |||
241 | return new_headers | ||
242 | |||
243 | def cache_response(self, request, response, body=None, | ||
244 | status_codes=None): | ||
245 | """ | ||
246 | Algorithm for caching requests. | ||
247 | |||
248 | This assumes a requests Response object. | ||
249 | """ | ||
250 | # From httplib2: Don't cache 206's since we aren't going to | ||
251 | # handle byte range requests | ||
252 | cacheable_status_codes = status_codes or self.cacheable_status_codes | ||
253 | if response.status not in cacheable_status_codes: | ||
254 | logger.debug( | ||
255 | 'Status code %s not in %s', | ||
256 | response.status, | ||
257 | cacheable_status_codes | ||
258 | ) | ||
259 | return | ||
260 | |||
261 | response_headers = CaseInsensitiveDict(response.headers) | ||
262 | |||
263 | # If we've been given a body, our response has a Content-Length, that | ||
264 | # Content-Length is valid then we can check to see if the body we've | ||
265 | # been given matches the expected size, and if it doesn't we'll just | ||
266 | # skip trying to cache it. | ||
267 | if (body is not None and | ||
268 | "content-length" in response_headers and | ||
269 | response_headers["content-length"].isdigit() and | ||
270 | int(response_headers["content-length"]) != len(body)): | ||
271 | return | ||
272 | |||
273 | cc_req = self.parse_cache_control(request.headers) | ||
274 | cc = self.parse_cache_control(response_headers) | ||
275 | |||
276 | cache_url = self.cache_url(request.url) | ||
277 | logger.debug('Updating cache with response from "%s"', cache_url) | ||
278 | |||
279 | # Delete it from the cache if we happen to have it stored there | ||
280 | no_store = False | ||
281 | if 'no-store' in cc: | ||
282 | no_store = True | ||
283 | logger.debug('Response header has "no-store"') | ||
284 | if 'no-store' in cc_req: | ||
285 | no_store = True | ||
286 | logger.debug('Request header has "no-store"') | ||
287 | if no_store and self.cache.get(cache_url): | ||
288 | logger.debug('Purging existing cache entry to honor "no-store"') | ||
289 | self.cache.delete(cache_url) | ||
290 | |||
291 | # If we've been given an etag, then keep the response | ||
292 | if self.cache_etags and 'etag' in response_headers: | ||
293 | logger.debug('Caching due to etag') | ||
294 | self.cache.set( | ||
295 | cache_url, | ||
296 | self.serializer.dumps(request, response, body=body), | ||
297 | ) | ||
298 | |||
299 | # Add to the cache any 301s. We do this before looking that | ||
300 | # the Date headers. | ||
301 | elif response.status == 301: | ||
302 | logger.debug('Caching permanant redirect') | ||
303 | self.cache.set( | ||
304 | cache_url, | ||
305 | self.serializer.dumps(request, response) | ||
306 | ) | ||
307 | |||
308 | # Add to the cache if the response headers demand it. If there | ||
309 | # is no date header then we can't do anything about expiring | ||
310 | # the cache. | ||
311 | elif 'date' in response_headers: | ||
312 | # cache when there is a max-age > 0 | ||
313 | if 'max-age' in cc and cc['max-age'] > 0: | ||
314 | logger.debug('Caching b/c date exists and max-age > 0') | ||
315 | self.cache.set( | ||
316 | cache_url, | ||
317 | self.serializer.dumps(request, response, body=body), | ||
318 | ) | ||
319 | |||
320 | # If the request can expire, it means we should cache it | ||
321 | # in the meantime. | ||
322 | elif 'expires' in response_headers: | ||
323 | if response_headers['expires']: | ||
324 | logger.debug('Caching b/c of expires header') | ||
325 | self.cache.set( | ||
326 | cache_url, | ||
327 | self.serializer.dumps(request, response, body=body), | ||
328 | ) | ||
329 | |||
330 | def update_cached_response(self, request, response): | ||
331 | """On a 304 we will get a new set of headers that we want to | ||
332 | update our cached value with, assuming we have one. | ||
333 | |||
334 | This should only ever be called when we've sent an ETag and | ||
335 | gotten a 304 as the response. | ||
336 | """ | ||
337 | cache_url = self.cache_url(request.url) | ||
338 | |||
339 | cached_response = self.serializer.loads( | ||
340 | request, | ||
341 | self.cache.get(cache_url) | ||
342 | ) | ||
343 | |||
344 | if not cached_response: | ||
345 | # we didn't have a cached response | ||
346 | return response | ||
347 | |||
348 | # Lets update our headers with the headers from the new request: | ||
349 | # http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-26#section-4.1 | ||
350 | # | ||
351 | # The server isn't supposed to send headers that would make | ||
352 | # the cached body invalid. But... just in case, we'll be sure | ||
353 | # to strip out ones we know that might be problmatic due to | ||
354 | # typical assumptions. | ||
355 | excluded_headers = [ | ||
356 | "content-length", | ||
357 | ] | ||
358 | |||
359 | cached_response.headers.update( | ||
360 | dict((k, v) for k, v in response.headers.items() | ||
361 | if k.lower() not in excluded_headers) | ||
362 | ) | ||
363 | |||
364 | # we want a 200 b/c we have content via the cache | ||
365 | cached_response.status = 200 | ||
366 | |||
367 | # update our cache | ||
368 | self.cache.set( | ||
369 | cache_url, | ||
370 | self.serializer.dumps(request, cached_response), | ||
371 | ) | ||
372 | |||
373 | return cached_response | ||