1"""Private module full of compatibility hacks.
2
3Primarily this is for downstream redistributions of requests that unvendor
4urllib3 without providing a shim.
5
6.. warning::
7
8 This module is private. If you use it, and something breaks, you were
9 warned
10"""
11import sys
12
13import requests
14
15try:
16 from requests.packages.urllib3 import fields
17 from requests.packages.urllib3 import filepost
18 from requests.packages.urllib3 import poolmanager
19except ImportError:
20 from urllib3 import fields
21 from urllib3 import filepost
22 from urllib3 import poolmanager
23
24try:
25 from requests.packages.urllib3.connection import HTTPConnection
26 from requests.packages.urllib3 import connection
27except ImportError:
28 try:
29 from urllib3.connection import HTTPConnection
30 from urllib3 import connection
31 except ImportError:
32 HTTPConnection = None
33 connection = None
34
35
36if requests.__build__ < 0x020300:
37 timeout = None
38else:
39 try:
40 from requests.packages.urllib3.util import timeout
41 except ImportError:
42 from urllib3.util import timeout
43
44PY3 = sys.version_info > (3, 0)
45
46if PY3:
47 from collections.abc import Mapping, MutableMapping
48 import queue
49 from urllib.parse import urlencode, urljoin
50else:
51 from collections import Mapping, MutableMapping
52 import Queue as queue
53 from urllib import urlencode
54 from urlparse import urljoin
55
56try:
57 basestring = basestring
58except NameError:
59 basestring = (str, bytes)
60
61
62class HTTPHeaderDict(MutableMapping):
63 """
64 :param headers:
65 An iterable of field-value pairs. Must not contain multiple field names
66 when compared case-insensitively.
67
68 :param kwargs:
69 Additional field-value pairs to pass in to ``dict.update``.
70
71 A ``dict`` like container for storing HTTP Headers.
72
73 Field names are stored and compared case-insensitively in compliance with
74 RFC 7230. Iteration provides the first case-sensitive key seen for each
75 case-insensitive pair.
76
77 Using ``__setitem__`` syntax overwrites fields that compare equal
78 case-insensitively in order to maintain ``dict``'s api. For fields that
79 compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add``
80 in a loop.
81
82 If multiple fields that are equal case-insensitively are passed to the
83 constructor or ``.update``, the behavior is undefined and some will be
84 lost.
85
86 >>> headers = HTTPHeaderDict()
87 >>> headers.add('Set-Cookie', 'foo=bar')
88 >>> headers.add('set-cookie', 'baz=quxx')
89 >>> headers['content-length'] = '7'
90 >>> headers['SET-cookie']
91 'foo=bar, baz=quxx'
92 >>> headers['Content-Length']
93 '7'
94 """
95
96 def __init__(self, headers=None, **kwargs):
97 super(HTTPHeaderDict, self).__init__()
98 self._container = {}
99 if headers is not None:
100 if isinstance(headers, HTTPHeaderDict):
101 self._copy_from(headers)
102 else:
103 self.extend(headers)
104 if kwargs:
105 self.extend(kwargs)
106
107 def __setitem__(self, key, val):
108 self._container[key.lower()] = (key, val)
109 return self._container[key.lower()]
110
111 def __getitem__(self, key):
112 val = self._container[key.lower()]
113 return ', '.join(val[1:])
114
115 def __delitem__(self, key):
116 del self._container[key.lower()]
117
118 def __contains__(self, key):
119 return key.lower() in self._container
120
121 def __eq__(self, other):
122 if not isinstance(other, Mapping) and not hasattr(other, 'keys'):
123 return False
124 if not isinstance(other, type(self)):
125 other = type(self)(other)
126 return ({k.lower(): v for k, v in self.itermerged()} ==
127 {k.lower(): v for k, v in other.itermerged()})
128
129 def __ne__(self, other):
130 return not self.__eq__(other)
131
132 if not PY3: # Python 2
133 iterkeys = MutableMapping.iterkeys
134 itervalues = MutableMapping.itervalues
135
136 __marker = object()
137
138 def __len__(self):
139 return len(self._container)
140
141 def __iter__(self):
142 # Only provide the originally cased names
143 for vals in self._container.values():
144 yield vals[0]
145
146 def pop(self, key, default=__marker):
147 """D.pop(k[,d]) -> v, remove specified key and return its value.
148
149 If key is not found, d is returned if given, otherwise KeyError is
150 raised.
151 """
152 # Using the MutableMapping function directly fails due to the private
153 # marker.
154 # Using ordinary dict.pop would expose the internal structures.
155 # So let's reinvent the wheel.
156 try:
157 value = self[key]
158 except KeyError:
159 if default is self.__marker:
160 raise
161 return default
162 else:
163 del self[key]
164 return value
165
166 def discard(self, key):
167 try:
168 del self[key]
169 except KeyError:
170 pass
171
172 def add(self, key, val):
173 """Adds a (name, value) pair, doesn't overwrite the value if it already
174 exists.
175
176 >>> headers = HTTPHeaderDict(foo='bar')
177 >>> headers.add('Foo', 'baz')
178 >>> headers['foo']
179 'bar, baz'
180 """
181 key_lower = key.lower()
182 new_vals = key, val
183 # Keep the common case aka no item present as fast as possible
184 vals = self._container.setdefault(key_lower, new_vals)
185 if new_vals is not vals:
186 # new_vals was not inserted, as there was a previous one
187 if isinstance(vals, list):
188 # If already several items got inserted, we have a list
189 vals.append(val)
190 else:
191 # vals should be a tuple then, i.e. only one item so far
192 # Need to convert the tuple to list for further extension
193 self._container[key_lower] = [vals[0], vals[1], val]
194
195 def extend(self, *args, **kwargs):
196 """Generic import function for any type of header-like object.
197 Adapted version of MutableMapping.update in order to insert items
198 with self.add instead of self.__setitem__
199 """
200 if len(args) > 1:
201 raise TypeError("extend() takes at most 1 positional "
202 "arguments ({} given)".format(len(args)))
203 other = args[0] if len(args) >= 1 else ()
204
205 if isinstance(other, HTTPHeaderDict):
206 for key, val in other.iteritems():
207 self.add(key, val)
208 elif isinstance(other, Mapping):
209 for key in other:
210 self.add(key, other[key])
211 elif hasattr(other, "keys"):
212 for key in other.keys():
213 self.add(key, other[key])
214 else:
215 for key, value in other:
216 self.add(key, value)
217
218 for key, value in kwargs.items():
219 self.add(key, value)
220
221 def getlist(self, key):
222 """Returns a list of all the values for the named field. Returns an
223 empty list if the key doesn't exist."""
224 try:
225 vals = self._container[key.lower()]
226 except KeyError:
227 return []
228 else:
229 if isinstance(vals, tuple):
230 return [vals[1]]
231 else:
232 return vals[1:]
233
234 # Backwards compatibility for httplib
235 getheaders = getlist
236 getallmatchingheaders = getlist
237 iget = getlist
238
239 def __repr__(self):
240 return "%s(%s)" % (type(self).__name__, dict(self.itermerged()))
241
242 def _copy_from(self, other):
243 for key in other:
244 val = other.getlist(key)
245 if isinstance(val, list):
246 # Don't need to convert tuples
247 val = list(val)
248 self._container[key.lower()] = [key] + val
249
250 def copy(self):
251 clone = type(self)()
252 clone._copy_from(self)
253 return clone
254
255 def iteritems(self):
256 """Iterate over all header lines, including duplicate ones."""
257 for key in self:
258 vals = self._container[key.lower()]
259 for val in vals[1:]:
260 yield vals[0], val
261
262 def itermerged(self):
263 """Iterate over all headers, merging duplicate ones together."""
264 for key in self:
265 val = self._container[key.lower()]
266 yield val[0], ', '.join(val[1:])
267
268 def items(self):
269 return list(self.iteritems())
270
271 @classmethod
272 def from_httplib(cls, message): # Python 2
273 """Read headers from a Python 2 httplib message object."""
274 # python2.7 does not expose a proper API for exporting multiheaders
275 # efficiently. This function re-reads raw lines from the message
276 # object and extracts the multiheaders properly.
277 headers = []
278
279 for line in message.headers:
280 if line.startswith((' ', '\t')):
281 key, value = headers[-1]
282 headers[-1] = (key, value + '\r\n' + line.rstrip())
283 continue
284
285 key, value = line.split(':', 1)
286 headers.append((key, value.strip()))
287
288 return cls(headers)
289
290
291__all__ = (
292 'basestring',
293 'connection',
294 'fields',
295 'filepost',
296 'poolmanager',
297 'timeout',
298 'HTTPHeaderDict',
299 'queue',
300 'urlencode',
301 'urljoin',
302)