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