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