| 1 | # SPDX-License-Identifier: MIT |
| 2 | from __future__ import absolute_import |
| 3 | |
| 4 | from .filepost import encode_multipart_formdata |
| 5 | from .packages.six.moves.urllib.parse import urlencode |
| 6 | |
| 7 | |
| 8 | __all__ = ['RequestMethods'] |
| 9 | |
| 10 | |
| 11 | class RequestMethods(object): |
| 12 | """ |
| 13 | Convenience mixin for classes who implement a :meth:`urlopen` method, such |
| 14 | as :class:`~urllib3.connectionpool.HTTPConnectionPool` and |
| 15 | :class:`~urllib3.poolmanager.PoolManager`. |
| 16 | |
| 17 | Provides behavior for making common types of HTTP request methods and |
| 18 | decides which type of request field encoding to use. |
| 19 | |
| 20 | Specifically, |
| 21 | |
| 22 | :meth:`.request_encode_url` is for sending requests whose fields are |
| 23 | encoded in the URL (such as GET, HEAD, DELETE). |
| 24 | |
| 25 | :meth:`.request_encode_body` is for sending requests whose fields are |
| 26 | encoded in the *body* of the request using multipart or www-form-urlencoded |
| 27 | (such as for POST, PUT, PATCH). |
| 28 | |
| 29 | :meth:`.request` is for making any kind of request, it will look up the |
| 30 | appropriate encoding format and use one of the above two methods to make |
| 31 | the request. |
| 32 | |
| 33 | Initializer parameters: |
| 34 | |
| 35 | :param headers: |
| 36 | Headers to include with all requests, unless other headers are given |
| 37 | explicitly. |
| 38 | """ |
| 39 | |
| 40 | _encode_url_methods = set(['DELETE', 'GET', 'HEAD', 'OPTIONS']) |
| 41 | |
| 42 | def __init__(self, headers=None): |
| 43 | self.headers = headers or {} |
| 44 | |
| 45 | def urlopen(self, method, url, body=None, headers=None, |
| 46 | encode_multipart=True, multipart_boundary=None, |
| 47 | **kw): # Abstract |
| 48 | raise NotImplemented("Classes extending RequestMethods must implement " |
| 49 | "their own ``urlopen`` method.") |
| 50 | |
| 51 | def request(self, method, url, fields=None, headers=None, **urlopen_kw): |
| 52 | """ |
| 53 | Make a request using :meth:`urlopen` with the appropriate encoding of |
| 54 | ``fields`` based on the ``method`` used. |
| 55 | |
| 56 | This is a convenience method that requires the least amount of manual |
| 57 | effort. It can be used in most situations, while still having the |
| 58 | option to drop down to more specific methods when necessary, such as |
| 59 | :meth:`request_encode_url`, :meth:`request_encode_body`, |
| 60 | or even the lowest level :meth:`urlopen`. |
| 61 | """ |
| 62 | method = method.upper() |
| 63 | |
| 64 | if method in self._encode_url_methods: |
| 65 | return self.request_encode_url(method, url, fields=fields, |
| 66 | headers=headers, |
| 67 | **urlopen_kw) |
| 68 | else: |
| 69 | return self.request_encode_body(method, url, fields=fields, |
| 70 | headers=headers, |
| 71 | **urlopen_kw) |
| 72 | |
| 73 | def request_encode_url(self, method, url, fields=None, headers=None, |
| 74 | **urlopen_kw): |
| 75 | """ |
| 76 | Make a request using :meth:`urlopen` with the ``fields`` encoded in |
| 77 | the url. This is useful for request methods like GET, HEAD, DELETE, etc. |
| 78 | """ |
| 79 | if headers is None: |
| 80 | headers = self.headers |
| 81 | |
| 82 | extra_kw = {'headers': headers} |
| 83 | extra_kw.update(urlopen_kw) |
| 84 | |
| 85 | if fields: |
| 86 | url += '?' + urlencode(fields) |
| 87 | |
| 88 | return self.urlopen(method, url, **extra_kw) |
| 89 | |
| 90 | def request_encode_body(self, method, url, fields=None, headers=None, |
| 91 | encode_multipart=True, multipart_boundary=None, |
| 92 | **urlopen_kw): |
| 93 | """ |
| 94 | Make a request using :meth:`urlopen` with the ``fields`` encoded in |
| 95 | the body. This is useful for request methods like POST, PUT, PATCH, etc. |
| 96 | |
| 97 | When ``encode_multipart=True`` (default), then |
| 98 | :meth:`urllib3.filepost.encode_multipart_formdata` is used to encode |
| 99 | the payload with the appropriate content type. Otherwise |
| 100 | :meth:`urllib.urlencode` is used with the |
| 101 | 'application/x-www-form-urlencoded' content type. |
| 102 | |
| 103 | Multipart encoding must be used when posting files, and it's reasonably |
| 104 | safe to use it in other times too. However, it may break request |
| 105 | signing, such as with OAuth. |
| 106 | |
| 107 | Supports an optional ``fields`` parameter of key/value strings AND |
| 108 | key/filetuple. A filetuple is a (filename, data, MIME type) tuple where |
| 109 | the MIME type is optional. For example:: |
| 110 | |
| 111 | fields = { |
| 112 | 'foo': 'bar', |
| 113 | 'fakefile': ('foofile.txt', 'contents of foofile'), |
| 114 | 'realfile': ('barfile.txt', open('realfile').read()), |
| 115 | 'typedfile': ('bazfile.bin', open('bazfile').read(), |
| 116 | 'image/jpeg'), |
| 117 | 'nonamefile': 'contents of nonamefile field', |
| 118 | } |
| 119 | |
| 120 | When uploading a file, providing a filename (the first parameter of the |
| 121 | tuple) is optional but recommended to best mimick behavior of browsers. |
| 122 | |
| 123 | Note that if ``headers`` are supplied, the 'Content-Type' header will |
| 124 | be overwritten because it depends on the dynamic random boundary string |
| 125 | which is used to compose the body of the request. The random boundary |
| 126 | string can be explicitly set with the ``multipart_boundary`` parameter. |
| 127 | """ |
| 128 | if headers is None: |
| 129 | headers = self.headers |
| 130 | |
| 131 | extra_kw = {'headers': {}} |
| 132 | |
| 133 | if fields: |
| 134 | if 'body' in urlopen_kw: |
| 135 | raise TypeError( |
| 136 | "request got values for both 'fields' and 'body', can only specify one.") |
| 137 | |
| 138 | if encode_multipart: |
| 139 | body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary) |
| 140 | else: |
| 141 | body, content_type = urlencode(fields), 'application/x-www-form-urlencoded' |
| 142 | |
| 143 | extra_kw['body'] = body |
| 144 | extra_kw['headers'] = {'Content-Type': content_type} |
| 145 | |
| 146 | extra_kw['headers'].update(headers) |
| 147 | extra_kw.update(urlopen_kw) |
| 148 | |
| 149 | return self.urlopen(method, url, **extra_kw) |