390 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			390 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8 -*-
 | |
| 
 | |
| """
 | |
| Compatibility code to be able to use `cookielib.CookieJar` with requests.
 | |
| 
 | |
| requests.utils imports from here, so be careful with imports.
 | |
| """
 | |
| 
 | |
| import collections
 | |
| from .compat import cookielib, urlparse, Morsel
 | |
| 
 | |
| try:
 | |
|     import threading
 | |
|     # grr, pyflakes: this fixes "redefinition of unused 'threading'"
 | |
|     threading
 | |
| except ImportError:
 | |
|     import dummy_threading as threading
 | |
| 
 | |
| 
 | |
| class MockRequest(object):
 | |
|     """Wraps a `requests.Request` to mimic a `urllib2.Request`.
 | |
| 
 | |
|     The code in `cookielib.CookieJar` expects this interface in order to correctly
 | |
|     manage cookie policies, i.e., determine whether a cookie can be set, given the
 | |
|     domains of the request and the cookie.
 | |
| 
 | |
|     The original request object is read-only. The client is responsible for collecting
 | |
|     the new headers via `get_new_headers()` and interpreting them appropriately. You
 | |
|     probably want `get_cookie_header`, defined below.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, request):
 | |
|         self._r = request
 | |
|         self._new_headers = {}
 | |
|         self.type = urlparse(self._r.url).scheme
 | |
| 
 | |
|     def get_type(self):
 | |
|         return self.type
 | |
| 
 | |
|     def get_host(self):
 | |
|         return urlparse(self._r.url).netloc
 | |
| 
 | |
|     def get_origin_req_host(self):
 | |
|         return self.get_host()
 | |
| 
 | |
|     def get_full_url(self):
 | |
|         return self._r.url
 | |
| 
 | |
|     def is_unverifiable(self):
 | |
|         return True
 | |
| 
 | |
|     def has_header(self, name):
 | |
|         return name in self._r.headers or name in self._new_headers
 | |
| 
 | |
|     def get_header(self, name, default=None):
 | |
|         return self._r.headers.get(name, self._new_headers.get(name, default))
 | |
| 
 | |
|     def add_header(self, key, val):
 | |
|         """cookielib has no legitimate use for this method; add it back if you find one."""
 | |
|         raise NotImplementedError("Cookie headers should be added with add_unredirected_header()")
 | |
| 
 | |
|     def add_unredirected_header(self, name, value):
 | |
|         self._new_headers[name] = value
 | |
| 
 | |
|     def get_new_headers(self):
 | |
|         return self._new_headers
 | |
| 
 | |
|     @property
 | |
|     def unverifiable(self):
 | |
|         return self.is_unverifiable()
 | |
| 
 | |
|     @property
 | |
|     def origin_req_host(self):
 | |
|         return self.get_origin_req_host()
 | |
| 
 | |
| 
 | |
| class MockResponse(object):
 | |
|     """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
 | |
| 
 | |
|     ...what? Basically, expose the parsed HTTP headers from the server response
 | |
|     the way `cookielib` expects to see them.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, headers):
 | |
|         """Make a MockResponse for `cookielib` to read.
 | |
| 
 | |
|         :param headers: a httplib.HTTPMessage or analogous carrying the headers
 | |
|         """
 | |
|         self._headers = headers
 | |
| 
 | |
|     def info(self):
 | |
|         return self._headers
 | |
| 
 | |
|     def getheaders(self, name):
 | |
|         self._headers.getheaders(name)
 | |
| 
 | |
| 
 | |
| def extract_cookies_to_jar(jar, request, response):
 | |
|     """Extract the cookies from the response into a CookieJar.
 | |
| 
 | |
|     :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar)
 | |
|     :param request: our own requests.Request object
 | |
|     :param response: urllib3.HTTPResponse object
 | |
|     """
 | |
|     # the _original_response field is the wrapped httplib.HTTPResponse object,
 | |
|     req = MockRequest(request)
 | |
|     # pull out the HTTPMessage with the headers and put it in the mock:
 | |
|     res = MockResponse(response._original_response.msg)
 | |
|     jar.extract_cookies(res, req)
 | |
| 
 | |
| 
 | |
| def get_cookie_header(jar, request):
 | |
|     """Produce an appropriate Cookie header string to be sent with `request`, or None."""
 | |
|     r = MockRequest(request)
 | |
|     jar.add_cookie_header(r)
 | |
|     return r.get_new_headers().get('Cookie')
 | |
| 
 | |
| 
 | |
| def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
 | |
|     """Unsets a cookie by name, by default over all domains and paths.
 | |
| 
 | |
|     Wraps CookieJar.clear(), is O(n).
 | |
|     """
 | |
|     clearables = []
 | |
|     for cookie in cookiejar:
 | |
|         if cookie.name == name:
 | |
|             if domain is None or domain == cookie.domain:
 | |
|                 if path is None or path == cookie.path:
 | |
|                     clearables.append((cookie.domain, cookie.path, cookie.name))
 | |
| 
 | |
|     for domain, path, name in clearables:
 | |
|         cookiejar.clear(domain, path, name)
 | |
| 
 | |
| 
 | |
| class CookieConflictError(RuntimeError):
 | |
|     """There are two cookies that meet the criteria specified in the cookie jar.
 | |
|     Use .get and .set and include domain and path args in order to be more specific."""
 | |
| 
 | |
| 
 | |
| class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
 | |
|     """Compatibility class; is a cookielib.CookieJar, but exposes a dict interface.
 | |
| 
 | |
|     This is the CookieJar we create by default for requests and sessions that
 | |
|     don't specify one, since some clients may expect response.cookies and
 | |
|     session.cookies to support dict operations.
 | |
| 
 | |
|     Don't use the dict interface internally; it's just for compatibility with
 | |
|     with external client code. All `requests` code should work out of the box
 | |
|     with externally provided instances of CookieJar, e.g., LWPCookieJar and
 | |
|     FileCookieJar.
 | |
| 
 | |
|     Caution: dictionary operations that are normally O(1) may be O(n).
 | |
| 
 | |
|     Unlike a regular CookieJar, this class is pickleable.
 | |
|     """
 | |
| 
 | |
|     def get(self, name, default=None, domain=None, path=None):
 | |
|         """Dict-like get() that also supports optional domain and path args in
 | |
|         order to resolve naming collisions from using one cookie jar over
 | |
|         multiple domains. Caution: operation is O(n), not O(1)."""
 | |
|         try:
 | |
|             return self._find_no_duplicates(name, domain, path)
 | |
|         except KeyError:
 | |
|             return default
 | |
| 
 | |
|     def set(self, name, value, **kwargs):
 | |
|         """Dict-like set() that also supports optional domain and path args in
 | |
|         order to resolve naming collisions from using one cookie jar over
 | |
|         multiple domains."""
 | |
|         # support client code that unsets cookies by assignment of a None value:
 | |
|         if value is None:
 | |
|             remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path'))
 | |
|             return
 | |
| 
 | |
|         if isinstance(value, Morsel):
 | |
|             c = morsel_to_cookie(value)
 | |
|         else:
 | |
|             c = create_cookie(name, value, **kwargs)
 | |
|         self.set_cookie(c)
 | |
|         return c
 | |
| 
 | |
|     def keys(self):
 | |
|         """Dict-like keys() that returns a list of names of cookies from the jar.
 | |
|         See values() and items()."""
 | |
|         keys = []
 | |
|         for cookie in iter(self):
 | |
|             keys.append(cookie.name)
 | |
|         return keys
 | |
| 
 | |
|     def values(self):
 | |
|         """Dict-like values() that returns a list of values of cookies from the jar.
 | |
|         See keys() and items()."""
 | |
|         values = []
 | |
|         for cookie in iter(self):
 | |
|             values.append(cookie.value)
 | |
|         return values
 | |
| 
 | |
|     def items(self):
 | |
|         """Dict-like items() that returns a list of name-value tuples from the jar.
 | |
|         See keys() and values(). Allows client-code to call "dict(RequestsCookieJar)
 | |
|         and get a vanilla python dict of key value pairs."""
 | |
|         items = []
 | |
|         for cookie in iter(self):
 | |
|             items.append((cookie.name, cookie.value))
 | |
|         return items
 | |
| 
 | |
|     def list_domains(self):
 | |
|         """Utility method to list all the domains in the jar."""
 | |
|         domains = []
 | |
|         for cookie in iter(self):
 | |
|             if cookie.domain not in domains:
 | |
|                 domains.append(cookie.domain)
 | |
|         return domains
 | |
| 
 | |
|     def list_paths(self):
 | |
|         """Utility method to list all the paths in the jar."""
 | |
|         paths = []
 | |
|         for cookie in iter(self):
 | |
|             if cookie.path not in paths:
 | |
|                 paths.append(cookie.path)
 | |
|         return paths
 | |
| 
 | |
|     def multiple_domains(self):
 | |
|         """Returns True if there are multiple domains in the jar.
 | |
|         Returns False otherwise."""
 | |
|         domains = []
 | |
|         for cookie in iter(self):
 | |
|             if cookie.domain is not None and cookie.domain in domains:
 | |
|                 return True
 | |
|             domains.append(cookie.domain)
 | |
|         return False  # there is only one domain in jar
 | |
| 
 | |
|     def get_dict(self, domain=None, path=None):
 | |
|         """Takes as an argument an optional domain and path and returns a plain old
 | |
|         Python dict of name-value pairs of cookies that meet the requirements."""
 | |
|         dictionary = {}
 | |
|         for cookie in iter(self):
 | |
|             if (domain is None or cookie.domain == domain) and (path is None
 | |
|                                                 or cookie.path == path):
 | |
|                 dictionary[cookie.name] = cookie.value
 | |
|         return dictionary
 | |
| 
 | |
|     def __getitem__(self, name):
 | |
|         """Dict-like __getitem__() for compatibility with client code. Throws exception
 | |
|         if there are more than one cookie with name. In that case, use the more
 | |
|         explicit get() method instead. Caution: operation is O(n), not O(1)."""
 | |
| 
 | |
|         return self._find_no_duplicates(name)
 | |
| 
 | |
|     def __setitem__(self, name, value):
 | |
|         """Dict-like __setitem__ for compatibility with client code. Throws exception
 | |
|         if there is already a cookie of that name in the jar. In that case, use the more
 | |
|         explicit set() method instead."""
 | |
| 
 | |
|         self.set(name, value)
 | |
| 
 | |
|     def __delitem__(self, name):
 | |
|         """Deletes a cookie given a name. Wraps cookielib.CookieJar's remove_cookie_by_name()."""
 | |
|         remove_cookie_by_name(self, name)
 | |
| 
 | |
|     def update(self, other):
 | |
|         """Updates this jar with cookies from another CookieJar or dict-like"""
 | |
|         if isinstance(other, cookielib.CookieJar):
 | |
|             for cookie in other:
 | |
|                 self.set_cookie(cookie)
 | |
|         else:
 | |
|             super(RequestsCookieJar, self).update(other)
 | |
| 
 | |
|     def _find(self, name, domain=None, path=None):
 | |
|         """Requests uses this method internally to get cookie values. Takes as args name
 | |
|         and optional domain and path. Returns a cookie.value. If there are conflicting cookies,
 | |
|         _find arbitrarily chooses one. See _find_no_duplicates if you want an exception thrown
 | |
|         if there are conflicting cookies."""
 | |
|         for cookie in iter(self):
 | |
|             if cookie.name == name:
 | |
|                 if domain is None or cookie.domain == domain:
 | |
|                     if path is None or cookie.path == path:
 | |
|                         return cookie.value
 | |
| 
 | |
|         raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
 | |
| 
 | |
|     def _find_no_duplicates(self, name, domain=None, path=None):
 | |
|         """__get_item__ and get call _find_no_duplicates -- never used in Requests internally.
 | |
|         Takes as args name and optional domain and path. Returns a cookie.value.
 | |
|         Throws KeyError if cookie is not found and CookieConflictError if there are
 | |
|         multiple cookies that match name and optionally domain and path."""
 | |
|         toReturn = None
 | |
|         for cookie in iter(self):
 | |
|             if cookie.name == name:
 | |
|                 if domain is None or cookie.domain == domain:
 | |
|                     if path is None or cookie.path == path:
 | |
|                         if toReturn is not None:  # if there are multiple cookies that meet passed in criteria
 | |
|                             raise CookieConflictError('There are multiple cookies with name, %r' % (name))
 | |
|                         toReturn = cookie.value  # we will eventually return this as long as no cookie conflict
 | |
| 
 | |
|         if toReturn:
 | |
|             return toReturn
 | |
|         raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
 | |
| 
 | |
|     def __getstate__(self):
 | |
|         """Unlike a normal CookieJar, this class is pickleable."""
 | |
|         state = self.__dict__.copy()
 | |
|         # remove the unpickleable RLock object
 | |
|         state.pop('_cookies_lock')
 | |
|         return state
 | |
| 
 | |
|     def __setstate__(self, state):
 | |
|         """Unlike a normal CookieJar, this class is pickleable."""
 | |
|         self.__dict__.update(state)
 | |
|         if '_cookies_lock' not in self.__dict__:
 | |
|             self._cookies_lock = threading.RLock()
 | |
| 
 | |
|     def copy(self):
 | |
|         """Return a copy of this RequestsCookieJar."""
 | |
|         new_cj = RequestsCookieJar()
 | |
|         new_cj.update(self)
 | |
|         return new_cj
 | |
| 
 | |
| 
 | |
| def create_cookie(name, value, **kwargs):
 | |
|     """Make a cookie from underspecified parameters.
 | |
| 
 | |
|     By default, the pair of `name` and `value` will be set for the domain ''
 | |
|     and sent on every request (this is sometimes called a "supercookie").
 | |
|     """
 | |
|     result = dict(
 | |
|         version=0,
 | |
|         name=name,
 | |
|         value=value,
 | |
|         port=None,
 | |
|         domain='',
 | |
|         path='/',
 | |
|         secure=False,
 | |
|         expires=None,
 | |
|         discard=True,
 | |
|         comment=None,
 | |
|         comment_url=None,
 | |
|         rest={'HttpOnly': None},
 | |
|         rfc2109=False,)
 | |
| 
 | |
|     badargs = set(kwargs) - set(result)
 | |
|     if badargs:
 | |
|         err = 'create_cookie() got unexpected keyword arguments: %s'
 | |
|         raise TypeError(err % list(badargs))
 | |
| 
 | |
|     result.update(kwargs)
 | |
|     result['port_specified'] = bool(result['port'])
 | |
|     result['domain_specified'] = bool(result['domain'])
 | |
|     result['domain_initial_dot'] = result['domain'].startswith('.')
 | |
|     result['path_specified'] = bool(result['path'])
 | |
| 
 | |
|     return cookielib.Cookie(**result)
 | |
| 
 | |
| 
 | |
| def morsel_to_cookie(morsel):
 | |
|     """Convert a Morsel object into a Cookie containing the one k/v pair."""
 | |
|     c = create_cookie(
 | |
|         name=morsel.key,
 | |
|         value=morsel.value,
 | |
|         version=morsel['version'] or 0,
 | |
|         port=None,
 | |
|         port_specified=False,
 | |
|         domain=morsel['domain'],
 | |
|         domain_specified=bool(morsel['domain']),
 | |
|         domain_initial_dot=morsel['domain'].startswith('.'),
 | |
|         path=morsel['path'],
 | |
|         path_specified=bool(morsel['path']),
 | |
|         secure=bool(morsel['secure']),
 | |
|         expires=morsel['max-age'] or morsel['expires'],
 | |
|         discard=False,
 | |
|         comment=morsel['comment'],
 | |
|         comment_url=bool(morsel['comment']),
 | |
|         rest={'HttpOnly': morsel['httponly']},
 | |
|         rfc2109=False,)
 | |
|     return c
 | |
| 
 | |
| 
 | |
| def cookiejar_from_dict(cookie_dict, cookiejar=None):
 | |
|     """Returns a CookieJar from a key/value dictionary.
 | |
| 
 | |
|     :param cookie_dict: Dict of key/values to insert into CookieJar.
 | |
|     """
 | |
|     if cookiejar is None:
 | |
|         cookiejar = RequestsCookieJar()
 | |
| 
 | |
|     if cookie_dict is not None:
 | |
|         for name in cookie_dict:
 | |
|             cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))
 | |
|     return cookiejar
 |