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

92 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1from __future__ import annotations 

2 

3import email.utils 

4import mimetypes 

5import typing 

6 

7_TYPE_FIELD_VALUE = typing.Union[str, bytes] 

8_TYPE_FIELD_VALUE_TUPLE = typing.Union[ 

9 _TYPE_FIELD_VALUE, 

10 typing.Tuple[str, _TYPE_FIELD_VALUE], 

11 typing.Tuple[str, _TYPE_FIELD_VALUE, str], 

12] 

13 

14 

15def guess_content_type( 

16 filename: str | None, default: str = "application/octet-stream" 

17) -> str: 

18 """ 

19 Guess the "Content-Type" of a file. 

20 

21 :param filename: 

22 The filename to guess the "Content-Type" of using :mod:`mimetypes`. 

23 :param default: 

24 If no "Content-Type" can be guessed, default to `default`. 

25 """ 

26 if filename: 

27 return mimetypes.guess_type(filename)[0] or default 

28 return default 

29 

30 

31def format_header_param_rfc2231(name: str, value: _TYPE_FIELD_VALUE) -> str: 

32 """ 

33 Helper function to format and quote a single header parameter using the 

34 strategy defined in RFC 2231. 

35 

36 Particularly useful for header parameters which might contain 

37 non-ASCII values, like file names. This follows 

38 `RFC 2388 Section 4.4 <https://tools.ietf.org/html/rfc2388#section-4.4>`_. 

39 

40 :param name: 

41 The name of the parameter, a string expected to be ASCII only. 

42 :param value: 

43 The value of the parameter, provided as ``bytes`` or `str``. 

44 :returns: 

45 An RFC-2231-formatted unicode string. 

46 

47 .. deprecated:: 2.0.0 

48 Will be removed in urllib3 v2.1.0. This is not valid for 

49 ``multipart/form-data`` header parameters. 

50 """ 

51 import warnings 

52 

53 warnings.warn( 

54 "'format_header_param_rfc2231' is deprecated and will be " 

55 "removed in urllib3 v2.1.0. This is not valid for " 

56 "multipart/form-data header parameters.", 

57 DeprecationWarning, 

58 stacklevel=2, 

59 ) 

60 

61 if isinstance(value, bytes): 

62 value = value.decode("utf-8") 

63 

64 if not any(ch in value for ch in '"\\\r\n'): 

65 result = f'{name}="{value}"' 

66 try: 

67 result.encode("ascii") 

68 except (UnicodeEncodeError, UnicodeDecodeError): 

69 pass 

70 else: 

71 return result 

72 

73 value = email.utils.encode_rfc2231(value, "utf-8") 

74 value = f"{name}*={value}" 

75 

76 return value 

77 

78 

79def format_multipart_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str: 

80 """ 

81 Format and quote a single multipart header parameter. 

82 

83 This follows the `WHATWG HTML Standard`_ as of 2021/06/10, matching 

84 the behavior of current browser and curl versions. Values are 

85 assumed to be UTF-8. The ``\\n``, ``\\r``, and ``"`` characters are 

86 percent encoded. 

87 

88 .. _WHATWG HTML Standard: 

89 https://html.spec.whatwg.org/multipage/ 

90 form-control-infrastructure.html#multipart-form-data 

91 

92 :param name: 

93 The name of the parameter, an ASCII-only ``str``. 

94 :param value: 

95 The value of the parameter, a ``str`` or UTF-8 encoded 

96 ``bytes``. 

97 :returns: 

98 A string ``name="value"`` with the escaped value. 

99 

100 .. versionchanged:: 2.0.0 

101 Matches the WHATWG HTML Standard as of 2021/06/10. Control 

102 characters are no longer percent encoded. 

103 

104 .. versionchanged:: 2.0.0 

105 Renamed from ``format_header_param_html5`` and 

106 ``format_header_param``. The old names will be removed in 

107 urllib3 v2.1.0. 

108 """ 

109 if isinstance(value, bytes): 

110 value = value.decode("utf-8") 

111 

112 # percent encode \n \r " 

113 value = value.translate({10: "%0A", 13: "%0D", 34: "%22"}) 

114 return f'{name}="{value}"' 

115 

116 

117def format_header_param_html5(name: str, value: _TYPE_FIELD_VALUE) -> str: 

118 """ 

119 .. deprecated:: 2.0.0 

120 Renamed to :func:`format_multipart_header_param`. Will be 

121 removed in urllib3 v2.1.0. 

122 """ 

123 import warnings 

124 

125 warnings.warn( 

126 "'format_header_param_html5' has been renamed to " 

127 "'format_multipart_header_param'. The old name will be " 

128 "removed in urllib3 v2.1.0.", 

129 DeprecationWarning, 

130 stacklevel=2, 

131 ) 

132 return format_multipart_header_param(name, value) 

133 

134 

135def format_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str: 

136 """ 

137 .. deprecated:: 2.0.0 

138 Renamed to :func:`format_multipart_header_param`. Will be 

139 removed in urllib3 v2.1.0. 

140 """ 

141 import warnings 

142 

143 warnings.warn( 

144 "'format_header_param' has been renamed to " 

145 "'format_multipart_header_param'. The old name will be " 

146 "removed in urllib3 v2.1.0.", 

147 DeprecationWarning, 

148 stacklevel=2, 

149 ) 

150 return format_multipart_header_param(name, value) 

151 

152 

153class RequestField: 

154 """ 

155 A data container for request body parameters. 

156 

157 :param name: 

158 The name of this request field. Must be unicode. 

159 :param data: 

160 The data/value body. 

161 :param filename: 

162 An optional filename of the request field. Must be unicode. 

163 :param headers: 

164 An optional dict-like object of headers to initially use for the field. 

165 

166 .. versionchanged:: 2.0.0 

167 The ``header_formatter`` parameter is deprecated and will 

168 be removed in urllib3 v2.1.0. 

169 """ 

170 

171 def __init__( 

172 self, 

173 name: str, 

174 data: _TYPE_FIELD_VALUE, 

175 filename: str | None = None, 

176 headers: typing.Mapping[str, str] | None = None, 

177 header_formatter: typing.Callable[[str, _TYPE_FIELD_VALUE], str] | None = None, 

178 ): 

179 self._name = name 

180 self._filename = filename 

181 self.data = data 

182 self.headers: dict[str, str | None] = {} 

183 if headers: 

184 self.headers = dict(headers) 

185 

186 if header_formatter is not None: 

187 import warnings 

188 

189 warnings.warn( 

190 "The 'header_formatter' parameter is deprecated and " 

191 "will be removed in urllib3 v2.1.0.", 

192 DeprecationWarning, 

193 stacklevel=2, 

194 ) 

195 self.header_formatter = header_formatter 

196 else: 

197 self.header_formatter = format_multipart_header_param 

198 

199 @classmethod 

200 def from_tuples( 

201 cls, 

202 fieldname: str, 

203 value: _TYPE_FIELD_VALUE_TUPLE, 

204 header_formatter: typing.Callable[[str, _TYPE_FIELD_VALUE], str] | None = None, 

205 ) -> RequestField: 

206 """ 

207 A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. 

208 

209 Supports constructing :class:`~urllib3.fields.RequestField` from 

210 parameter of key/value strings AND key/filetuple. A filetuple is a 

211 (filename, data, MIME type) tuple where the MIME type is optional. 

212 For example:: 

213 

214 'foo': 'bar', 

215 'fakefile': ('foofile.txt', 'contents of foofile'), 

216 'realfile': ('barfile.txt', open('realfile').read()), 

217 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'), 

218 'nonamefile': 'contents of nonamefile field', 

219 

220 Field names and filenames must be unicode. 

221 """ 

222 filename: str | None 

223 content_type: str | None 

224 data: _TYPE_FIELD_VALUE 

225 

226 if isinstance(value, tuple): 

227 if len(value) == 3: 

228 filename, data, content_type = value 

229 else: 

230 filename, data = value 

231 content_type = guess_content_type(filename) 

232 else: 

233 filename = None 

234 content_type = None 

235 data = value 

236 

237 request_param = cls( 

238 fieldname, data, filename=filename, header_formatter=header_formatter 

239 ) 

240 request_param.make_multipart(content_type=content_type) 

241 

242 return request_param 

243 

244 def _render_part(self, name: str, value: _TYPE_FIELD_VALUE) -> str: 

245 """ 

246 Override this method to change how each multipart header 

247 parameter is formatted. By default, this calls 

248 :func:`format_multipart_header_param`. 

249 

250 :param name: 

251 The name of the parameter, an ASCII-only ``str``. 

252 :param value: 

253 The value of the parameter, a ``str`` or UTF-8 encoded 

254 ``bytes``. 

255 

256 :meta public: 

257 """ 

258 return self.header_formatter(name, value) 

259 

260 def _render_parts( 

261 self, 

262 header_parts: ( 

263 dict[str, _TYPE_FIELD_VALUE | None] 

264 | typing.Sequence[tuple[str, _TYPE_FIELD_VALUE | None]] 

265 ), 

266 ) -> str: 

267 """ 

268 Helper function to format and quote a single header. 

269 

270 Useful for single headers that are composed of multiple items. E.g., 

271 'Content-Disposition' fields. 

272 

273 :param header_parts: 

274 A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format 

275 as `k1="v1"; k2="v2"; ...`. 

276 """ 

277 iterable: typing.Iterable[tuple[str, _TYPE_FIELD_VALUE | None]] 

278 

279 parts = [] 

280 if isinstance(header_parts, dict): 

281 iterable = header_parts.items() 

282 else: 

283 iterable = header_parts 

284 

285 for name, value in iterable: 

286 if value is not None: 

287 parts.append(self._render_part(name, value)) 

288 

289 return "; ".join(parts) 

290 

291 def render_headers(self) -> str: 

292 """ 

293 Renders the headers for this request field. 

294 """ 

295 lines = [] 

296 

297 sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"] 

298 for sort_key in sort_keys: 

299 if self.headers.get(sort_key, False): 

300 lines.append(f"{sort_key}: {self.headers[sort_key]}") 

301 

302 for header_name, header_value in self.headers.items(): 

303 if header_name not in sort_keys: 

304 if header_value: 

305 lines.append(f"{header_name}: {header_value}") 

306 

307 lines.append("\r\n") 

308 return "\r\n".join(lines) 

309 

310 def make_multipart( 

311 self, 

312 content_disposition: str | None = None, 

313 content_type: str | None = None, 

314 content_location: str | None = None, 

315 ) -> None: 

316 """ 

317 Makes this request field into a multipart request field. 

318 

319 This method overrides "Content-Disposition", "Content-Type" and 

320 "Content-Location" headers to the request parameter. 

321 

322 :param content_disposition: 

323 The 'Content-Disposition' of the request body. Defaults to 'form-data' 

324 :param content_type: 

325 The 'Content-Type' of the request body. 

326 :param content_location: 

327 The 'Content-Location' of the request body. 

328 

329 """ 

330 content_disposition = (content_disposition or "form-data") + "; ".join( 

331 [ 

332 "", 

333 self._render_parts( 

334 (("name", self._name), ("filename", self._filename)) 

335 ), 

336 ] 

337 ) 

338 

339 self.headers["Content-Disposition"] = content_disposition 

340 self.headers["Content-Type"] = content_type 

341 self.headers["Content-Location"] = content_location