Fix all remaining references to charade
[platform/upstream/python-requests.git] / requests / models.py
1 # -*- coding: utf-8 -*-
2
3 """
4 requests.models
5 ~~~~~~~~~~~~~~~
6
7 This module contains the primary objects that power Requests.
8 """
9
10 import collections
11 import logging
12 import datetime
13
14 from io import BytesIO, UnsupportedOperation
15 from .hooks import default_hooks
16 from .structures import CaseInsensitiveDict
17
18 from .auth import HTTPBasicAuth
19 from .cookies import cookiejar_from_dict, get_cookie_header
20 from .packages.urllib3.fields import RequestField
21 from .packages.urllib3.filepost import encode_multipart_formdata
22 from .packages.urllib3.util import parse_url
23 from .exceptions import (
24     HTTPError, RequestException, MissingSchema, InvalidURL,
25     ChunkedEncodingError)
26 from .utils import (
27     guess_filename, get_auth_from_url, requote_uri,
28     stream_decode_response_unicode, to_key_val_list, parse_header_links,
29     iter_slices, guess_json_utf, super_len, to_native_string)
30 from .compat import (
31     cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO,
32     is_py2, chardet, json, builtin_str, basestring, IncompleteRead)
33
34 CONTENT_CHUNK_SIZE = 10 * 1024
35 ITER_CHUNK_SIZE = 512
36
37 log = logging.getLogger(__name__)
38
39
40 class RequestEncodingMixin(object):
41     @property
42     def path_url(self):
43         """Build the path URL to use."""
44
45         url = []
46
47         p = urlsplit(self.url)
48
49         path = p.path
50         if not path:
51             path = '/'
52
53         url.append(path)
54
55         query = p.query
56         if query:
57             url.append('?')
58             url.append(query)
59
60         return ''.join(url)
61
62     @staticmethod
63     def _encode_params(data):
64         """Encode parameters in a piece of data.
65
66         Will successfully encode parameters when passed as a dict or a list of
67         2-tuples. Order is retained if data is a list of 2-tuples but arbitrary
68         if parameters are supplied as a dict.
69         """
70
71         if isinstance(data, (str, bytes)):
72             return data
73         elif hasattr(data, 'read'):
74             return data
75         elif hasattr(data, '__iter__'):
76             result = []
77             for k, vs in to_key_val_list(data):
78                 if isinstance(vs, basestring) or not hasattr(vs, '__iter__'):
79                     vs = [vs]
80                 for v in vs:
81                     if v is not None:
82                         result.append(
83                             (k.encode('utf-8') if isinstance(k, str) else k,
84                              v.encode('utf-8') if isinstance(v, str) else v))
85             return urlencode(result, doseq=True)
86         else:
87             return data
88
89     @staticmethod
90     def _encode_files(files, data):
91         """Build the body for a multipart/form-data request.
92
93         Will successfully encode files when passed as a dict or a list of
94         2-tuples. Order is retained if data is a list of 2-tuples but arbitrary
95         if parameters are supplied as a dict.
96
97         """
98         if (not files):
99             raise ValueError("Files must be provided.")
100         elif isinstance(data, basestring):
101             raise ValueError("Data must not be a string.")
102
103         new_fields = []
104         fields = to_key_val_list(data or {})
105         files = to_key_val_list(files or {})
106
107         for field, val in fields:
108             if isinstance(val, basestring) or not hasattr(val, '__iter__'):
109                 val = [val]
110             for v in val:
111                 if v is not None:
112                     # Don't call str() on bytestrings: in Py3 it all goes wrong.
113                     if not isinstance(v, bytes):
114                         v = str(v)
115
116                     new_fields.append(
117                         (field.decode('utf-8') if isinstance(field, bytes) else field,
118                          v.encode('utf-8') if isinstance(v, str) else v))
119
120         for (k, v) in files:
121             # support for explicit filename
122             ft = None
123             fh = None
124             if isinstance(v, (tuple, list)):
125                 if len(v) == 2:
126                     fn, fp = v
127                 elif len(v) == 3:
128                     fn, fp, ft = v
129                 else:
130                     fn, fp, ft, fh = v
131             else:
132                 fn = guess_filename(v) or k
133                 fp = v
134             if isinstance(fp, str):
135                 fp = StringIO(fp)
136             if isinstance(fp, bytes):
137                 fp = BytesIO(fp)
138
139             rf = RequestField(name=k, data=fp.read(),
140                               filename=fn, headers=fh)
141             rf.make_multipart(content_type=ft)
142             new_fields.append(rf)
143
144         body, content_type = encode_multipart_formdata(new_fields)
145
146         return body, content_type
147
148
149 class RequestHooksMixin(object):
150     def register_hook(self, event, hook):
151         """Properly register a hook."""
152
153         if event not in self.hooks:
154             raise ValueError('Unsupported event specified, with event name "%s"' % (event))
155
156         if isinstance(hook, collections.Callable):
157             self.hooks[event].append(hook)
158         elif hasattr(hook, '__iter__'):
159             self.hooks[event].extend(h for h in hook if isinstance(h, collections.Callable))
160
161     def deregister_hook(self, event, hook):
162         """Deregister a previously registered hook.
163         Returns True if the hook existed, False if not.
164         """
165
166         try:
167             self.hooks[event].remove(hook)
168             return True
169         except ValueError:
170             return False
171
172
173 class Request(RequestHooksMixin):
174     """A user-created :class:`Request <Request>` object.
175
176     Used to prepare a :class:`PreparedRequest <PreparedRequest>`, which is sent to the server.
177
178     :param method: HTTP method to use.
179     :param url: URL to send.
180     :param headers: dictionary of headers to send.
181     :param files: dictionary of {filename: fileobject} files to multipart upload.
182     :param data: the body to attach the request. If a dictionary is provided, form-encoding will take place.
183     :param params: dictionary of URL parameters to append to the URL.
184     :param auth: Auth handler or (user, pass) tuple.
185     :param cookies: dictionary or CookieJar of cookies to attach to this request.
186     :param hooks: dictionary of callback hooks, for internal usage.
187
188     Usage::
189
190       >>> import requests
191       >>> req = requests.Request('GET', 'http://httpbin.org/get')
192       >>> req.prepare()
193       <PreparedRequest [GET]>
194
195     """
196     def __init__(self,
197         method=None,
198         url=None,
199         headers=None,
200         files=None,
201         data=None,
202         params=None,
203         auth=None,
204         cookies=None,
205         hooks=None):
206
207         # Default empty dicts for dict params.
208         data = [] if data is None else data
209         files = [] if files is None else files
210         headers = {} if headers is None else headers
211         params = {} if params is None else params
212         hooks = {} if hooks is None else hooks
213
214         self.hooks = default_hooks()
215         for (k, v) in list(hooks.items()):
216             self.register_hook(event=k, hook=v)
217
218         self.method = method
219         self.url = url
220         self.headers = headers
221         self.files = files
222         self.data = data
223         self.params = params
224         self.auth = auth
225         self.cookies = cookies
226
227     def __repr__(self):
228         return '<Request [%s]>' % (self.method)
229
230     def prepare(self):
231         """Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it."""
232         p = PreparedRequest()
233         p.prepare(
234             method=self.method,
235             url=self.url,
236             headers=self.headers,
237             files=self.files,
238             data=self.data,
239             params=self.params,
240             auth=self.auth,
241             cookies=self.cookies,
242             hooks=self.hooks,
243         )
244         return p
245
246
247 class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
248     """The fully mutable :class:`PreparedRequest <PreparedRequest>` object,
249     containing the exact bytes that will be sent to the server.
250
251     Generated from either a :class:`Request <Request>` object or manually.
252
253     Usage::
254
255       >>> import requests
256       >>> req = requests.Request('GET', 'http://httpbin.org/get')
257       >>> r = req.prepare()
258       <PreparedRequest [GET]>
259
260       >>> s = requests.Session()
261       >>> s.send(r)
262       <Response [200]>
263
264     """
265
266     def __init__(self):
267         #: HTTP verb to send to the server.
268         self.method = None
269         #: HTTP URL to send the request to.
270         self.url = None
271         #: dictionary of HTTP headers.
272         self.headers = None
273         # The `CookieJar` used to create the Cookie header will be stored here
274         # after prepare_cookies is called
275         self._cookies = None
276         #: request body to send to the server.
277         self.body = None
278         #: dictionary of callback hooks, for internal usage.
279         self.hooks = default_hooks()
280
281     def prepare(self, method=None, url=None, headers=None, files=None,
282                 data=None, params=None, auth=None, cookies=None, hooks=None):
283         """Prepares the entire request with the given parameters."""
284
285         self.prepare_method(method)
286         self.prepare_url(url, params)
287         self.prepare_headers(headers)
288         self.prepare_cookies(cookies)
289         self.prepare_body(data, files)
290         self.prepare_auth(auth, url)
291         # Note that prepare_auth must be last to enable authentication schemes
292         # such as OAuth to work on a fully prepared request.
293
294         # This MUST go after prepare_auth. Authenticators could add a hook
295         self.prepare_hooks(hooks)
296
297     def __repr__(self):
298         return '<PreparedRequest [%s]>' % (self.method)
299
300     def copy(self):
301         p = PreparedRequest()
302         p.method = self.method
303         p.url = self.url
304         p.headers = self.headers.copy()
305         p._cookies = self._cookies.copy()
306         p.body = self.body
307         p.hooks = self.hooks
308         return p
309
310     def prepare_method(self, method):
311         """Prepares the given HTTP method."""
312         self.method = method
313         if self.method is not None:
314             self.method = self.method.upper()
315
316     def prepare_url(self, url, params):
317         """Prepares the given HTTP URL."""
318         #: Accept objects that have string representations.
319         try:
320             url = unicode(url)
321         except NameError:
322             # We're on Python 3.
323             url = str(url)
324         except UnicodeDecodeError:
325             pass
326
327         # Don't do any URL preparation for oddball schemes
328         if ':' in url and not url.lower().startswith('http'):
329             self.url = url
330             return
331
332         # Support for unicode domain names and paths.
333         scheme, auth, host, port, path, query, fragment = parse_url(url)
334
335         if not scheme:
336             raise MissingSchema("Invalid URL {0!r}: No schema supplied. "
337                                 "Perhaps you meant http://{0}?".format(url))
338
339         if not host:
340             raise InvalidURL("Invalid URL %r: No host supplied" % url)
341
342         # Only want to apply IDNA to the hostname
343         try:
344             host = host.encode('idna').decode('utf-8')
345         except UnicodeError:
346             raise InvalidURL('URL has an invalid label.')
347
348         # Carefully reconstruct the network location
349         netloc = auth or ''
350         if netloc:
351             netloc += '@'
352         netloc += host
353         if port:
354             netloc += ':' + str(port)
355
356         # Bare domains aren't valid URLs.
357         if not path:
358             path = '/'
359
360         if is_py2:
361             if isinstance(scheme, str):
362                 scheme = scheme.encode('utf-8')
363             if isinstance(netloc, str):
364                 netloc = netloc.encode('utf-8')
365             if isinstance(path, str):
366                 path = path.encode('utf-8')
367             if isinstance(query, str):
368                 query = query.encode('utf-8')
369             if isinstance(fragment, str):
370                 fragment = fragment.encode('utf-8')
371
372         enc_params = self._encode_params(params)
373         if enc_params:
374             if query:
375                 query = '%s&%s' % (query, enc_params)
376             else:
377                 query = enc_params
378
379         url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))
380         self.url = url
381
382     def prepare_headers(self, headers):
383         """Prepares the given HTTP headers."""
384
385         if headers:
386             self.headers = CaseInsensitiveDict((to_native_string(name), value) for name, value in headers.items())
387         else:
388             self.headers = CaseInsensitiveDict()
389
390     def prepare_body(self, data, files):
391         """Prepares the given HTTP body data."""
392
393         # Check if file, fo, generator, iterator.
394         # If not, run through normal process.
395
396         # Nottin' on you.
397         body = None
398         content_type = None
399         length = None
400
401         is_stream = all([
402             hasattr(data, '__iter__'),
403             not isinstance(data, basestring),
404             not isinstance(data, list),
405             not isinstance(data, dict)
406         ])
407
408         try:
409             length = super_len(data)
410         except (TypeError, AttributeError, UnsupportedOperation):
411             length = None
412
413         if is_stream:
414             body = data
415
416             if files:
417                 raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
418
419             if length is not None:
420                 self.headers['Content-Length'] = builtin_str(length)
421             else:
422                 self.headers['Transfer-Encoding'] = 'chunked'
423         else:
424             # Multi-part file uploads.
425             if files:
426                 (body, content_type) = self._encode_files(files, data)
427             else:
428                 if data:
429                     body = self._encode_params(data)
430                     if isinstance(data, str) or isinstance(data, builtin_str) or hasattr(data, 'read'):
431                         content_type = None
432                     else:
433                         content_type = 'application/x-www-form-urlencoded'
434
435             self.prepare_content_length(body)
436
437             # Add content-type if it wasn't explicitly provided.
438             if (content_type) and (not 'content-type' in self.headers):
439                 self.headers['Content-Type'] = content_type
440
441         self.body = body
442
443     def prepare_content_length(self, body):
444         if hasattr(body, 'seek') and hasattr(body, 'tell'):
445             body.seek(0, 2)
446             self.headers['Content-Length'] = builtin_str(body.tell())
447             body.seek(0, 0)
448         elif body is not None:
449             l = super_len(body)
450             if l:
451                 self.headers['Content-Length'] = builtin_str(l)
452         elif self.method not in ('GET', 'HEAD'):
453             self.headers['Content-Length'] = '0'
454
455     def prepare_auth(self, auth, url=''):
456         """Prepares the given HTTP auth data."""
457
458         # If no Auth is explicitly provided, extract it from the URL first.
459         if auth is None:
460             url_auth = get_auth_from_url(self.url)
461             auth = url_auth if any(url_auth) else None
462
463         if auth:
464             if isinstance(auth, tuple) and len(auth) == 2:
465                 # special-case basic HTTP auth
466                 auth = HTTPBasicAuth(*auth)
467
468             # Allow auth to make its changes.
469             r = auth(self)
470
471             # Update self to reflect the auth changes.
472             self.__dict__.update(r.__dict__)
473
474             # Recompute Content-Length
475             self.prepare_content_length(self.body)
476
477     def prepare_cookies(self, cookies):
478         """Prepares the given HTTP cookie data."""
479
480         if isinstance(cookies, cookielib.CookieJar):
481             self._cookies = cookies
482         else:
483             self._cookies = cookiejar_from_dict(cookies)
484
485         cookie_header = get_cookie_header(self._cookies, self)
486         if cookie_header is not None:
487             self.headers['Cookie'] = cookie_header
488
489     def prepare_hooks(self, hooks):
490         """Prepares the given hooks."""
491         for event in hooks:
492             self.register_hook(event, hooks[event])
493
494
495 class Response(object):
496     """The :class:`Response <Response>` object, which contains a
497     server's response to an HTTP request.
498     """
499
500     __attrs__ = [
501         '_content',
502         'status_code',
503         'headers',
504         'url',
505         'history',
506         'encoding',
507         'reason',
508         'cookies',
509         'elapsed',
510         'request',
511     ]
512
513     def __init__(self):
514         super(Response, self).__init__()
515
516         self._content = False
517         self._content_consumed = False
518
519         #: Integer Code of responded HTTP Status.
520         self.status_code = None
521
522         #: Case-insensitive Dictionary of Response Headers.
523         #: For example, ``headers['content-encoding']`` will return the
524         #: value of a ``'Content-Encoding'`` response header.
525         self.headers = CaseInsensitiveDict()
526
527         #: File-like object representation of response (for advanced usage).
528         #: Requires that ``stream=True` on the request.
529         # This requirement does not apply for use internally to Requests.
530         self.raw = None
531
532         #: Final URL location of Response.
533         self.url = None
534
535         #: Encoding to decode with when accessing r.text.
536         self.encoding = None
537
538         #: A list of :class:`Response <Response>` objects from
539         #: the history of the Request. Any redirect responses will end
540         #: up here. The list is sorted from the oldest to the most recent request.
541         self.history = []
542
543         self.reason = None
544
545         #: A CookieJar of Cookies the server sent back.
546         self.cookies = cookiejar_from_dict({})
547
548         #: The amount of time elapsed between sending the request
549         #: and the arrival of the response (as a timedelta)
550         self.elapsed = datetime.timedelta(0)
551
552     def __getstate__(self):
553         # Consume everything; accessing the content attribute makes
554         # sure the content has been fully read.
555         if not self._content_consumed:
556             self.content
557
558         return dict(
559             (attr, getattr(self, attr, None))
560             for attr in self.__attrs__
561         )
562
563     def __setstate__(self, state):
564         for name, value in state.items():
565             setattr(self, name, value)
566
567         # pickled objects do not have .raw
568         setattr(self, '_content_consumed', True)
569
570     def __repr__(self):
571         return '<Response [%s]>' % (self.status_code)
572
573     def __bool__(self):
574         """Returns true if :attr:`status_code` is 'OK'."""
575         return self.ok
576
577     def __nonzero__(self):
578         """Returns true if :attr:`status_code` is 'OK'."""
579         return self.ok
580
581     def __iter__(self):
582         """Allows you to use a response as an iterator."""
583         return self.iter_content(128)
584
585     @property
586     def ok(self):
587         try:
588             self.raise_for_status()
589         except RequestException:
590             return False
591         return True
592
593     @property
594     def apparent_encoding(self):
595         """The apparent encoding, provided by the lovely Charade library
596         (Thanks, Ian!)."""
597         return chardet.detect(self.content)['encoding']
598
599     def iter_content(self, chunk_size=1, decode_unicode=False):
600         """Iterates over the response data.  When stream=True is set on the
601         request, this avoids reading the content at once into memory for
602         large responses.  The chunk size is the number of bytes it should
603         read into memory.  This is not necessarily the length of each item
604         returned as decoding can take place.
605         """
606         if self._content_consumed:
607             # simulate reading small chunks of the content
608             return iter_slices(self._content, chunk_size)
609
610         def generate():
611             try:
612                 # Special case for urllib3.
613                 try:
614                     for chunk in self.raw.stream(chunk_size,
615                                                  decode_content=True):
616                         yield chunk
617                 except IncompleteRead as e:
618                     raise ChunkedEncodingError(e)
619             except AttributeError:
620                 # Standard file-like object.
621                 while True:
622                     chunk = self.raw.read(chunk_size)
623                     if not chunk:
624                         break
625                     yield chunk
626
627             self._content_consumed = True
628
629         gen = generate()
630
631         if decode_unicode:
632             gen = stream_decode_response_unicode(gen, self)
633
634         return gen
635
636     def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None):
637         """Iterates over the response data, one line at a time.  When
638         stream=True is set on the request, this avoids reading the
639         content at once into memory for large responses.
640         """
641
642         pending = None
643
644         for chunk in self.iter_content(chunk_size=chunk_size,
645                                        decode_unicode=decode_unicode):
646
647             if pending is not None:
648                 chunk = pending + chunk
649             lines = chunk.splitlines()
650
651             if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]:
652                 pending = lines.pop()
653             else:
654                 pending = None
655
656             for line in lines:
657                 yield line
658
659         if pending is not None:
660             yield pending
661
662     @property
663     def content(self):
664         """Content of the response, in bytes."""
665
666         if self._content is False:
667             # Read the contents.
668             try:
669                 if self._content_consumed:
670                     raise RuntimeError(
671                         'The content for this response was already consumed')
672
673                 if self.status_code == 0:
674                     self._content = None
675                 else:
676                     self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes()
677
678             except AttributeError:
679                 self._content = None
680
681         self._content_consumed = True
682         # don't need to release the connection; that's been handled by urllib3
683         # since we exhausted the data.
684         return self._content
685
686     @property
687     def text(self):
688         """Content of the response, in unicode.
689
690         If Response.encoding is None, encoding will be guessed using
691         ``chardet``.
692         """
693
694         # Try charset from content-type
695         content = None
696         encoding = self.encoding
697
698         if not self.content:
699             return str('')
700
701         # Fallback to auto-detected encoding.
702         if self.encoding is None:
703             encoding = self.apparent_encoding
704
705         # Decode unicode from given encoding.
706         try:
707             content = str(self.content, encoding, errors='replace')
708         except (LookupError, TypeError):
709             # A LookupError is raised if the encoding was not found which could
710             # indicate a misspelling or similar mistake.
711             #
712             # A TypeError can be raised if encoding is None
713             #
714             # So we try blindly encoding.
715             content = str(self.content, errors='replace')
716
717         return content
718
719     def json(self, **kwargs):
720         """Returns the json-encoded content of a response, if any.
721
722         :param \*\*kwargs: Optional arguments that ``json.loads`` takes.
723         """
724
725         if not self.encoding and len(self.content) > 3:
726             # No encoding set. JSON RFC 4627 section 3 states we should expect
727             # UTF-8, -16 or -32. Detect which one to use; If the detection or
728             # decoding fails, fall back to `self.text` (using chardet to make
729             # a best guess).
730             encoding = guess_json_utf(self.content)
731             if encoding is not None:
732                 return json.loads(self.content.decode(encoding), **kwargs)
733         return json.loads(self.text, **kwargs)
734
735     @property
736     def links(self):
737         """Returns the parsed header links of the response, if any."""
738
739         header = self.headers.get('link')
740
741         # l = MultiDict()
742         l = {}
743
744         if header:
745             links = parse_header_links(header)
746
747             for link in links:
748                 key = link.get('rel') or link.get('url')
749                 l[key] = link
750
751         return l
752
753     def raise_for_status(self):
754         """Raises stored :class:`HTTPError`, if one occurred."""
755
756         http_error_msg = ''
757
758         if 400 <= self.status_code < 500:
759             http_error_msg = '%s Client Error: %s' % (self.status_code, self.reason)
760
761         elif 500 <= self.status_code < 600:
762             http_error_msg = '%s Server Error: %s' % (self.status_code, self.reason)
763
764         if http_error_msg:
765             raise HTTPError(http_error_msg, response=self)
766
767     def close(self):
768         """Closes the underlying file descriptor and releases the connection
769         back to the pool.
770
771         *Note: Should not normally need to be called explicitly.*
772         """
773         return self.raw.release_conn()