Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/urllib3/_collections.py: 31%

188 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +0000

1from __future__ import absolute_import 

2 

3try: 

4 from collections.abc import Mapping, MutableMapping 

5except ImportError: 

6 from collections import Mapping, MutableMapping 

7try: 

8 from threading import RLock 

9except ImportError: # Platform-specific: No threads available 

10 

11 class RLock: 

12 def __enter__(self): 

13 pass 

14 

15 def __exit__(self, exc_type, exc_value, traceback): 

16 pass 

17 

18 

19from collections import OrderedDict 

20 

21from .exceptions import InvalidHeader 

22from .packages import six 

23from .packages.six import iterkeys, itervalues 

24 

25__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"] 

26 

27 

28_Null = object() 

29 

30 

31class RecentlyUsedContainer(MutableMapping): 

32 """ 

33 Provides a thread-safe dict-like container which maintains up to 

34 ``maxsize`` keys while throwing away the least-recently-used keys beyond 

35 ``maxsize``. 

36 

37 :param maxsize: 

38 Maximum number of recent elements to retain. 

39 

40 :param dispose_func: 

41 Every time an item is evicted from the container, 

42 ``dispose_func(value)`` is called. Callback which will get called 

43 """ 

44 

45 ContainerCls = OrderedDict 

46 

47 def __init__(self, maxsize=10, dispose_func=None): 

48 self._maxsize = maxsize 

49 self.dispose_func = dispose_func 

50 

51 self._container = self.ContainerCls() 

52 self.lock = RLock() 

53 

54 def __getitem__(self, key): 

55 # Re-insert the item, moving it to the end of the eviction line. 

56 with self.lock: 

57 item = self._container.pop(key) 

58 self._container[key] = item 

59 return item 

60 

61 def __setitem__(self, key, value): 

62 evicted_value = _Null 

63 with self.lock: 

64 # Possibly evict the existing value of 'key' 

65 evicted_value = self._container.get(key, _Null) 

66 self._container[key] = value 

67 

68 # If we didn't evict an existing value, we might have to evict the 

69 # least recently used item from the beginning of the container. 

70 if len(self._container) > self._maxsize: 

71 _key, evicted_value = self._container.popitem(last=False) 

72 

73 if self.dispose_func and evicted_value is not _Null: 

74 self.dispose_func(evicted_value) 

75 

76 def __delitem__(self, key): 

77 with self.lock: 

78 value = self._container.pop(key) 

79 

80 if self.dispose_func: 

81 self.dispose_func(value) 

82 

83 def __len__(self): 

84 with self.lock: 

85 return len(self._container) 

86 

87 def __iter__(self): 

88 raise NotImplementedError( 

89 "Iteration over this class is unlikely to be threadsafe." 

90 ) 

91 

92 def clear(self): 

93 with self.lock: 

94 # Copy pointers to all values, then wipe the mapping 

95 values = list(itervalues(self._container)) 

96 self._container.clear() 

97 

98 if self.dispose_func: 

99 for value in values: 

100 self.dispose_func(value) 

101 

102 def keys(self): 

103 with self.lock: 

104 return list(iterkeys(self._container)) 

105 

106 

107class HTTPHeaderDict(MutableMapping): 

108 """ 

109 :param headers: 

110 An iterable of field-value pairs. Must not contain multiple field names 

111 when compared case-insensitively. 

112 

113 :param kwargs: 

114 Additional field-value pairs to pass in to ``dict.update``. 

115 

116 A ``dict`` like container for storing HTTP Headers. 

117 

118 Field names are stored and compared case-insensitively in compliance with 

119 RFC 7230. Iteration provides the first case-sensitive key seen for each 

120 case-insensitive pair. 

121 

122 Using ``__setitem__`` syntax overwrites fields that compare equal 

123 case-insensitively in order to maintain ``dict``'s api. For fields that 

124 compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add`` 

125 in a loop. 

126 

127 If multiple fields that are equal case-insensitively are passed to the 

128 constructor or ``.update``, the behavior is undefined and some will be 

129 lost. 

130 

131 >>> headers = HTTPHeaderDict() 

132 >>> headers.add('Set-Cookie', 'foo=bar') 

133 >>> headers.add('set-cookie', 'baz=quxx') 

134 >>> headers['content-length'] = '7' 

135 >>> headers['SET-cookie'] 

136 'foo=bar, baz=quxx' 

137 >>> headers['Content-Length'] 

138 '7' 

139 """ 

140 

141 def __init__(self, headers=None, **kwargs): 

142 super(HTTPHeaderDict, self).__init__() 

143 self._container = OrderedDict() 

144 if headers is not None: 

145 if isinstance(headers, HTTPHeaderDict): 

146 self._copy_from(headers) 

147 else: 

148 self.extend(headers) 

149 if kwargs: 

150 self.extend(kwargs) 

151 

152 def __setitem__(self, key, val): 

153 self._container[key.lower()] = [key, val] 

154 return self._container[key.lower()] 

155 

156 def __getitem__(self, key): 

157 val = self._container[key.lower()] 

158 return ", ".join(val[1:]) 

159 

160 def __delitem__(self, key): 

161 del self._container[key.lower()] 

162 

163 def __contains__(self, key): 

164 return key.lower() in self._container 

165 

166 def __eq__(self, other): 

167 if not isinstance(other, Mapping) and not hasattr(other, "keys"): 

168 return False 

169 if not isinstance(other, type(self)): 

170 other = type(self)(other) 

171 return dict((k.lower(), v) for k, v in self.itermerged()) == dict( 

172 (k.lower(), v) for k, v in other.itermerged() 

173 ) 

174 

175 def __ne__(self, other): 

176 return not self.__eq__(other) 

177 

178 if six.PY2: # Python 2 

179 iterkeys = MutableMapping.iterkeys 

180 itervalues = MutableMapping.itervalues 

181 

182 __marker = object() 

183 

184 def __len__(self): 

185 return len(self._container) 

186 

187 def __iter__(self): 

188 # Only provide the originally cased names 

189 for vals in self._container.values(): 

190 yield vals[0] 

191 

192 def pop(self, key, default=__marker): 

193 """D.pop(k[,d]) -> v, remove specified key and return the corresponding value. 

194 If key is not found, d is returned if given, otherwise KeyError is raised. 

195 """ 

196 # Using the MutableMapping function directly fails due to the private marker. 

197 # Using ordinary dict.pop would expose the internal structures. 

198 # So let's reinvent the wheel. 

199 try: 

200 value = self[key] 

201 except KeyError: 

202 if default is self.__marker: 

203 raise 

204 return default 

205 else: 

206 del self[key] 

207 return value 

208 

209 def discard(self, key): 

210 try: 

211 del self[key] 

212 except KeyError: 

213 pass 

214 

215 def add(self, key, val): 

216 """Adds a (name, value) pair, doesn't overwrite the value if it already 

217 exists. 

218 

219 >>> headers = HTTPHeaderDict(foo='bar') 

220 >>> headers.add('Foo', 'baz') 

221 >>> headers['foo'] 

222 'bar, baz' 

223 """ 

224 key_lower = key.lower() 

225 new_vals = [key, val] 

226 # Keep the common case aka no item present as fast as possible 

227 vals = self._container.setdefault(key_lower, new_vals) 

228 if new_vals is not vals: 

229 vals.append(val) 

230 

231 def extend(self, *args, **kwargs): 

232 """Generic import function for any type of header-like object. 

233 Adapted version of MutableMapping.update in order to insert items 

234 with self.add instead of self.__setitem__ 

235 """ 

236 if len(args) > 1: 

237 raise TypeError( 

238 "extend() takes at most 1 positional " 

239 "arguments ({0} given)".format(len(args)) 

240 ) 

241 other = args[0] if len(args) >= 1 else () 

242 

243 if isinstance(other, HTTPHeaderDict): 

244 for key, val in other.iteritems(): 

245 self.add(key, val) 

246 elif isinstance(other, Mapping): 

247 for key in other: 

248 self.add(key, other[key]) 

249 elif hasattr(other, "keys"): 

250 for key in other.keys(): 

251 self.add(key, other[key]) 

252 else: 

253 for key, value in other: 

254 self.add(key, value) 

255 

256 for key, value in kwargs.items(): 

257 self.add(key, value) 

258 

259 def getlist(self, key, default=__marker): 

260 """Returns a list of all the values for the named field. Returns an 

261 empty list if the key doesn't exist.""" 

262 try: 

263 vals = self._container[key.lower()] 

264 except KeyError: 

265 if default is self.__marker: 

266 return [] 

267 return default 

268 else: 

269 return vals[1:] 

270 

271 def _prepare_for_method_change(self): 

272 """ 

273 Remove content-specific header fields before changing the request 

274 method to GET or HEAD according to RFC 9110, Section 15.4. 

275 """ 

276 content_specific_headers = [ 

277 "Content-Encoding", 

278 "Content-Language", 

279 "Content-Location", 

280 "Content-Type", 

281 "Content-Length", 

282 "Digest", 

283 "Last-Modified", 

284 ] 

285 for header in content_specific_headers: 

286 self.discard(header) 

287 return self 

288 

289 # Backwards compatibility for httplib 

290 getheaders = getlist 

291 getallmatchingheaders = getlist 

292 iget = getlist 

293 

294 # Backwards compatibility for http.cookiejar 

295 get_all = getlist 

296 

297 def __repr__(self): 

298 return "%s(%s)" % (type(self).__name__, dict(self.itermerged())) 

299 

300 def _copy_from(self, other): 

301 for key in other: 

302 val = other.getlist(key) 

303 if isinstance(val, list): 

304 # Don't need to convert tuples 

305 val = list(val) 

306 self._container[key.lower()] = [key] + val 

307 

308 def copy(self): 

309 clone = type(self)() 

310 clone._copy_from(self) 

311 return clone 

312 

313 def iteritems(self): 

314 """Iterate over all header lines, including duplicate ones.""" 

315 for key in self: 

316 vals = self._container[key.lower()] 

317 for val in vals[1:]: 

318 yield vals[0], val 

319 

320 def itermerged(self): 

321 """Iterate over all headers, merging duplicate ones together.""" 

322 for key in self: 

323 val = self._container[key.lower()] 

324 yield val[0], ", ".join(val[1:]) 

325 

326 def items(self): 

327 return list(self.iteritems()) 

328 

329 @classmethod 

330 def from_httplib(cls, message): # Python 2 

331 """Read headers from a Python 2 httplib message object.""" 

332 # python2.7 does not expose a proper API for exporting multiheaders 

333 # efficiently. This function re-reads raw lines from the message 

334 # object and extracts the multiheaders properly. 

335 obs_fold_continued_leaders = (" ", "\t") 

336 headers = [] 

337 

338 for line in message.headers: 

339 if line.startswith(obs_fold_continued_leaders): 

340 if not headers: 

341 # We received a header line that starts with OWS as described 

342 # in RFC-7230 S3.2.4. This indicates a multiline header, but 

343 # there exists no previous header to which we can attach it. 

344 raise InvalidHeader( 

345 "Header continuation with no previous header: %s" % line 

346 ) 

347 else: 

348 key, value = headers[-1] 

349 headers[-1] = (key, value + " " + line.strip()) 

350 continue 

351 

352 key, value = line.split(":", 1) 

353 headers.append((key, value.strip())) 

354 

355 return cls(headers)