comparison venv/lib/python2.7/site-packages/requests/cookies.py @ 0:d67268158946 draft

planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
author bcclaywell
date Mon, 12 Oct 2015 17:43:33 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:d67268158946
1 # -*- coding: utf-8 -*-
2
3 """
4 Compatibility code to be able to use `cookielib.CookieJar` with requests.
5
6 requests.utils imports from here, so be careful with imports.
7 """
8
9 import copy
10 import time
11 import collections
12 from .compat import cookielib, urlparse, urlunparse, Morsel
13
14 try:
15 import threading
16 # grr, pyflakes: this fixes "redefinition of unused 'threading'"
17 threading
18 except ImportError:
19 import dummy_threading as threading
20
21
22 class MockRequest(object):
23 """Wraps a `requests.Request` to mimic a `urllib2.Request`.
24
25 The code in `cookielib.CookieJar` expects this interface in order to correctly
26 manage cookie policies, i.e., determine whether a cookie can be set, given the
27 domains of the request and the cookie.
28
29 The original request object is read-only. The client is responsible for collecting
30 the new headers via `get_new_headers()` and interpreting them appropriately. You
31 probably want `get_cookie_header`, defined below.
32 """
33
34 def __init__(self, request):
35 self._r = request
36 self._new_headers = {}
37 self.type = urlparse(self._r.url).scheme
38
39 def get_type(self):
40 return self.type
41
42 def get_host(self):
43 return urlparse(self._r.url).netloc
44
45 def get_origin_req_host(self):
46 return self.get_host()
47
48 def get_full_url(self):
49 # Only return the response's URL if the user hadn't set the Host
50 # header
51 if not self._r.headers.get('Host'):
52 return self._r.url
53 # If they did set it, retrieve it and reconstruct the expected domain
54 host = self._r.headers['Host']
55 parsed = urlparse(self._r.url)
56 # Reconstruct the URL as we expect it
57 return urlunparse([
58 parsed.scheme, host, parsed.path, parsed.params, parsed.query,
59 parsed.fragment
60 ])
61
62 def is_unverifiable(self):
63 return True
64
65 def has_header(self, name):
66 return name in self._r.headers or name in self._new_headers
67
68 def get_header(self, name, default=None):
69 return self._r.headers.get(name, self._new_headers.get(name, default))
70
71 def add_header(self, key, val):
72 """cookielib has no legitimate use for this method; add it back if you find one."""
73 raise NotImplementedError("Cookie headers should be added with add_unredirected_header()")
74
75 def add_unredirected_header(self, name, value):
76 self._new_headers[name] = value
77
78 def get_new_headers(self):
79 return self._new_headers
80
81 @property
82 def unverifiable(self):
83 return self.is_unverifiable()
84
85 @property
86 def origin_req_host(self):
87 return self.get_origin_req_host()
88
89 @property
90 def host(self):
91 return self.get_host()
92
93
94 class MockResponse(object):
95 """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
96
97 ...what? Basically, expose the parsed HTTP headers from the server response
98 the way `cookielib` expects to see them.
99 """
100
101 def __init__(self, headers):
102 """Make a MockResponse for `cookielib` to read.
103
104 :param headers: a httplib.HTTPMessage or analogous carrying the headers
105 """
106 self._headers = headers
107
108 def info(self):
109 return self._headers
110
111 def getheaders(self, name):
112 self._headers.getheaders(name)
113
114
115 def extract_cookies_to_jar(jar, request, response):
116 """Extract the cookies from the response into a CookieJar.
117
118 :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar)
119 :param request: our own requests.Request object
120 :param response: urllib3.HTTPResponse object
121 """
122 if not (hasattr(response, '_original_response') and
123 response._original_response):
124 return
125 # the _original_response field is the wrapped httplib.HTTPResponse object,
126 req = MockRequest(request)
127 # pull out the HTTPMessage with the headers and put it in the mock:
128 res = MockResponse(response._original_response.msg)
129 jar.extract_cookies(res, req)
130
131
132 def get_cookie_header(jar, request):
133 """Produce an appropriate Cookie header string to be sent with `request`, or None."""
134 r = MockRequest(request)
135 jar.add_cookie_header(r)
136 return r.get_new_headers().get('Cookie')
137
138
139 def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
140 """Unsets a cookie by name, by default over all domains and paths.
141
142 Wraps CookieJar.clear(), is O(n).
143 """
144 clearables = []
145 for cookie in cookiejar:
146 if cookie.name == name:
147 if domain is None or domain == cookie.domain:
148 if path is None or path == cookie.path:
149 clearables.append((cookie.domain, cookie.path, cookie.name))
150
151 for domain, path, name in clearables:
152 cookiejar.clear(domain, path, name)
153
154
155 class CookieConflictError(RuntimeError):
156 """There are two cookies that meet the criteria specified in the cookie jar.
157 Use .get and .set and include domain and path args in order to be more specific."""
158
159
160 class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
161 """Compatibility class; is a cookielib.CookieJar, but exposes a dict
162 interface.
163
164 This is the CookieJar we create by default for requests and sessions that
165 don't specify one, since some clients may expect response.cookies and
166 session.cookies to support dict operations.
167
168 Requests does not use the dict interface internally; it's just for
169 compatibility with external client code. All requests code should work
170 out of the box with externally provided instances of ``CookieJar``, e.g.
171 ``LWPCookieJar`` and ``FileCookieJar``.
172
173 Unlike a regular CookieJar, this class is pickleable.
174
175 .. warning:: dictionary operations that are normally O(1) may be O(n).
176 """
177 def get(self, name, default=None, domain=None, path=None):
178 """Dict-like get() that also supports optional domain and path args in
179 order to resolve naming collisions from using one cookie jar over
180 multiple domains.
181
182 .. warning:: operation is O(n), not O(1)."""
183 try:
184 return self._find_no_duplicates(name, domain, path)
185 except KeyError:
186 return default
187
188 def set(self, name, value, **kwargs):
189 """Dict-like set() that also supports optional domain and path args in
190 order to resolve naming collisions from using one cookie jar over
191 multiple domains."""
192 # support client code that unsets cookies by assignment of a None value:
193 if value is None:
194 remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path'))
195 return
196
197 if isinstance(value, Morsel):
198 c = morsel_to_cookie(value)
199 else:
200 c = create_cookie(name, value, **kwargs)
201 self.set_cookie(c)
202 return c
203
204 def iterkeys(self):
205 """Dict-like iterkeys() that returns an iterator of names of cookies
206 from the jar. See itervalues() and iteritems()."""
207 for cookie in iter(self):
208 yield cookie.name
209
210 def keys(self):
211 """Dict-like keys() that returns a list of names of cookies from the
212 jar. See values() and items()."""
213 return list(self.iterkeys())
214
215 def itervalues(self):
216 """Dict-like itervalues() that returns an iterator of values of cookies
217 from the jar. See iterkeys() and iteritems()."""
218 for cookie in iter(self):
219 yield cookie.value
220
221 def values(self):
222 """Dict-like values() that returns a list of values of cookies from the
223 jar. See keys() and items()."""
224 return list(self.itervalues())
225
226 def iteritems(self):
227 """Dict-like iteritems() that returns an iterator of name-value tuples
228 from the jar. See iterkeys() and itervalues()."""
229 for cookie in iter(self):
230 yield cookie.name, cookie.value
231
232 def items(self):
233 """Dict-like items() that returns a list of name-value tuples from the
234 jar. See keys() and values(). Allows client-code to call
235 ``dict(RequestsCookieJar)`` and get a vanilla python dict of key value
236 pairs."""
237 return list(self.iteritems())
238
239 def list_domains(self):
240 """Utility method to list all the domains in the jar."""
241 domains = []
242 for cookie in iter(self):
243 if cookie.domain not in domains:
244 domains.append(cookie.domain)
245 return domains
246
247 def list_paths(self):
248 """Utility method to list all the paths in the jar."""
249 paths = []
250 for cookie in iter(self):
251 if cookie.path not in paths:
252 paths.append(cookie.path)
253 return paths
254
255 def multiple_domains(self):
256 """Returns True if there are multiple domains in the jar.
257 Returns False otherwise."""
258 domains = []
259 for cookie in iter(self):
260 if cookie.domain is not None and cookie.domain in domains:
261 return True
262 domains.append(cookie.domain)
263 return False # there is only one domain in jar
264
265 def get_dict(self, domain=None, path=None):
266 """Takes as an argument an optional domain and path and returns a plain
267 old Python dict of name-value pairs of cookies that meet the
268 requirements."""
269 dictionary = {}
270 for cookie in iter(self):
271 if (domain is None or cookie.domain == domain) and (path is None
272 or cookie.path == path):
273 dictionary[cookie.name] = cookie.value
274 return dictionary
275
276 def __getitem__(self, name):
277 """Dict-like __getitem__() for compatibility with client code. Throws
278 exception if there are more than one cookie with name. In that case,
279 use the more explicit get() method instead.
280
281 .. warning:: operation is O(n), not O(1)."""
282
283 return self._find_no_duplicates(name)
284
285 def __setitem__(self, name, value):
286 """Dict-like __setitem__ for compatibility with client code. Throws
287 exception if there is already a cookie of that name in the jar. In that
288 case, use the more explicit set() method instead."""
289
290 self.set(name, value)
291
292 def __delitem__(self, name):
293 """Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s
294 ``remove_cookie_by_name()``."""
295 remove_cookie_by_name(self, name)
296
297 def set_cookie(self, cookie, *args, **kwargs):
298 if hasattr(cookie.value, 'startswith') and cookie.value.startswith('"') and cookie.value.endswith('"'):
299 cookie.value = cookie.value.replace('\\"', '')
300 return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs)
301
302 def update(self, other):
303 """Updates this jar with cookies from another CookieJar or dict-like"""
304 if isinstance(other, cookielib.CookieJar):
305 for cookie in other:
306 self.set_cookie(copy.copy(cookie))
307 else:
308 super(RequestsCookieJar, self).update(other)
309
310 def _find(self, name, domain=None, path=None):
311 """Requests uses this method internally to get cookie values. Takes as
312 args name and optional domain and path. Returns a cookie.value. If
313 there are conflicting cookies, _find arbitrarily chooses one. See
314 _find_no_duplicates if you want an exception thrown if there are
315 conflicting cookies."""
316 for cookie in iter(self):
317 if cookie.name == name:
318 if domain is None or cookie.domain == domain:
319 if path is None or cookie.path == path:
320 return cookie.value
321
322 raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
323
324 def _find_no_duplicates(self, name, domain=None, path=None):
325 """Both ``__get_item__`` and ``get`` call this function: it's never
326 used elsewhere in Requests. Takes as args name and optional domain and
327 path. Returns a cookie.value. Throws KeyError if cookie is not found
328 and CookieConflictError if there are multiple cookies that match name
329 and optionally domain and path."""
330 toReturn = None
331 for cookie in iter(self):
332 if cookie.name == name:
333 if domain is None or cookie.domain == domain:
334 if path is None or cookie.path == path:
335 if toReturn is not None: # if there are multiple cookies that meet passed in criteria
336 raise CookieConflictError('There are multiple cookies with name, %r' % (name))
337 toReturn = cookie.value # we will eventually return this as long as no cookie conflict
338
339 if toReturn:
340 return toReturn
341 raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
342
343 def __getstate__(self):
344 """Unlike a normal CookieJar, this class is pickleable."""
345 state = self.__dict__.copy()
346 # remove the unpickleable RLock object
347 state.pop('_cookies_lock')
348 return state
349
350 def __setstate__(self, state):
351 """Unlike a normal CookieJar, this class is pickleable."""
352 self.__dict__.update(state)
353 if '_cookies_lock' not in self.__dict__:
354 self._cookies_lock = threading.RLock()
355
356 def copy(self):
357 """Return a copy of this RequestsCookieJar."""
358 new_cj = RequestsCookieJar()
359 new_cj.update(self)
360 return new_cj
361
362
363 def _copy_cookie_jar(jar):
364 if jar is None:
365 return None
366
367 if hasattr(jar, 'copy'):
368 # We're dealing with an instane of RequestsCookieJar
369 return jar.copy()
370 # We're dealing with a generic CookieJar instance
371 new_jar = copy.copy(jar)
372 new_jar.clear()
373 for cookie in jar:
374 new_jar.set_cookie(copy.copy(cookie))
375 return new_jar
376
377
378 def create_cookie(name, value, **kwargs):
379 """Make a cookie from underspecified parameters.
380
381 By default, the pair of `name` and `value` will be set for the domain ''
382 and sent on every request (this is sometimes called a "supercookie").
383 """
384 result = dict(
385 version=0,
386 name=name,
387 value=value,
388 port=None,
389 domain='',
390 path='/',
391 secure=False,
392 expires=None,
393 discard=True,
394 comment=None,
395 comment_url=None,
396 rest={'HttpOnly': None},
397 rfc2109=False,)
398
399 badargs = set(kwargs) - set(result)
400 if badargs:
401 err = 'create_cookie() got unexpected keyword arguments: %s'
402 raise TypeError(err % list(badargs))
403
404 result.update(kwargs)
405 result['port_specified'] = bool(result['port'])
406 result['domain_specified'] = bool(result['domain'])
407 result['domain_initial_dot'] = result['domain'].startswith('.')
408 result['path_specified'] = bool(result['path'])
409
410 return cookielib.Cookie(**result)
411
412
413 def morsel_to_cookie(morsel):
414 """Convert a Morsel object into a Cookie containing the one k/v pair."""
415
416 expires = None
417 if morsel['max-age']:
418 try:
419 expires = int(time.time() + int(morsel['max-age']))
420 except ValueError:
421 raise TypeError('max-age: %s must be integer' % morsel['max-age'])
422 elif morsel['expires']:
423 time_template = '%a, %d-%b-%Y %H:%M:%S GMT'
424 expires = int(time.mktime(
425 time.strptime(morsel['expires'], time_template)) - time.timezone)
426 return create_cookie(
427 comment=morsel['comment'],
428 comment_url=bool(morsel['comment']),
429 discard=False,
430 domain=morsel['domain'],
431 expires=expires,
432 name=morsel.key,
433 path=morsel['path'],
434 port=None,
435 rest={'HttpOnly': morsel['httponly']},
436 rfc2109=False,
437 secure=bool(morsel['secure']),
438 value=morsel.value,
439 version=morsel['version'] or 0,
440 )
441
442
443 def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
444 """Returns a CookieJar from a key/value dictionary.
445
446 :param cookie_dict: Dict of key/values to insert into CookieJar.
447 :param cookiejar: (optional) A cookiejar to add the cookies to.
448 :param overwrite: (optional) If False, will not replace cookies
449 already in the jar with new ones.
450 """
451 if cookiejar is None:
452 cookiejar = RequestsCookieJar()
453
454 if cookie_dict is not None:
455 names_from_jar = [cookie.name for cookie in cookiejar]
456 for name in cookie_dict:
457 if overwrite or (name not in names_from_jar):
458 cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))
459
460 return cookiejar
461
462
463 def merge_cookies(cookiejar, cookies):
464 """Add cookies to cookiejar and returns a merged CookieJar.
465
466 :param cookiejar: CookieJar object to add the cookies to.
467 :param cookies: Dictionary or CookieJar object to be added.
468 """
469 if not isinstance(cookiejar, cookielib.CookieJar):
470 raise ValueError('You can only merge into CookieJar')
471
472 if isinstance(cookies, dict):
473 cookiejar = cookiejar_from_dict(
474 cookies, cookiejar=cookiejar, overwrite=False)
475 elif isinstance(cookies, cookielib.CookieJar):
476 try:
477 cookiejar.update(cookies)
478 except AttributeError:
479 for cookie_in_jar in cookies:
480 cookiejar.set_cookie(cookie_in_jar)
481
482 return cookiejar