1 | n/a | import datetime |
---|
2 | n/a | import textwrap |
---|
3 | n/a | import unittest |
---|
4 | n/a | from email import errors |
---|
5 | n/a | from email import policy |
---|
6 | n/a | from email.message import Message |
---|
7 | n/a | from test.test_email import TestEmailBase, parameterize |
---|
8 | n/a | from email import headerregistry |
---|
9 | n/a | from email.headerregistry import Address, Group |
---|
10 | n/a | |
---|
11 | n/a | |
---|
12 | n/a | DITTO = object() |
---|
13 | n/a | |
---|
14 | n/a | |
---|
15 | n/a | class TestHeaderRegistry(TestEmailBase): |
---|
16 | n/a | |
---|
17 | n/a | def test_arbitrary_name_unstructured(self): |
---|
18 | n/a | factory = headerregistry.HeaderRegistry() |
---|
19 | n/a | h = factory('foobar', 'test') |
---|
20 | n/a | self.assertIsInstance(h, headerregistry.BaseHeader) |
---|
21 | n/a | self.assertIsInstance(h, headerregistry.UnstructuredHeader) |
---|
22 | n/a | |
---|
23 | n/a | def test_name_case_ignored(self): |
---|
24 | n/a | factory = headerregistry.HeaderRegistry() |
---|
25 | n/a | # Whitebox check that test is valid |
---|
26 | n/a | self.assertNotIn('Subject', factory.registry) |
---|
27 | n/a | h = factory('Subject', 'test') |
---|
28 | n/a | self.assertIsInstance(h, headerregistry.BaseHeader) |
---|
29 | n/a | self.assertIsInstance(h, headerregistry.UniqueUnstructuredHeader) |
---|
30 | n/a | |
---|
31 | n/a | class FooBase: |
---|
32 | n/a | def __init__(self, *args, **kw): |
---|
33 | n/a | pass |
---|
34 | n/a | |
---|
35 | n/a | def test_override_default_base_class(self): |
---|
36 | n/a | factory = headerregistry.HeaderRegistry(base_class=self.FooBase) |
---|
37 | n/a | h = factory('foobar', 'test') |
---|
38 | n/a | self.assertIsInstance(h, self.FooBase) |
---|
39 | n/a | self.assertIsInstance(h, headerregistry.UnstructuredHeader) |
---|
40 | n/a | |
---|
41 | n/a | class FooDefault: |
---|
42 | n/a | parse = headerregistry.UnstructuredHeader.parse |
---|
43 | n/a | |
---|
44 | n/a | def test_override_default_class(self): |
---|
45 | n/a | factory = headerregistry.HeaderRegistry(default_class=self.FooDefault) |
---|
46 | n/a | h = factory('foobar', 'test') |
---|
47 | n/a | self.assertIsInstance(h, headerregistry.BaseHeader) |
---|
48 | n/a | self.assertIsInstance(h, self.FooDefault) |
---|
49 | n/a | |
---|
50 | n/a | def test_override_default_class_only_overrides_default(self): |
---|
51 | n/a | factory = headerregistry.HeaderRegistry(default_class=self.FooDefault) |
---|
52 | n/a | h = factory('subject', 'test') |
---|
53 | n/a | self.assertIsInstance(h, headerregistry.BaseHeader) |
---|
54 | n/a | self.assertIsInstance(h, headerregistry.UniqueUnstructuredHeader) |
---|
55 | n/a | |
---|
56 | n/a | def test_dont_use_default_map(self): |
---|
57 | n/a | factory = headerregistry.HeaderRegistry(use_default_map=False) |
---|
58 | n/a | h = factory('subject', 'test') |
---|
59 | n/a | self.assertIsInstance(h, headerregistry.BaseHeader) |
---|
60 | n/a | self.assertIsInstance(h, headerregistry.UnstructuredHeader) |
---|
61 | n/a | |
---|
62 | n/a | def test_map_to_type(self): |
---|
63 | n/a | factory = headerregistry.HeaderRegistry() |
---|
64 | n/a | h1 = factory('foobar', 'test') |
---|
65 | n/a | factory.map_to_type('foobar', headerregistry.UniqueUnstructuredHeader) |
---|
66 | n/a | h2 = factory('foobar', 'test') |
---|
67 | n/a | self.assertIsInstance(h1, headerregistry.BaseHeader) |
---|
68 | n/a | self.assertIsInstance(h1, headerregistry.UnstructuredHeader) |
---|
69 | n/a | self.assertIsInstance(h2, headerregistry.BaseHeader) |
---|
70 | n/a | self.assertIsInstance(h2, headerregistry.UniqueUnstructuredHeader) |
---|
71 | n/a | |
---|
72 | n/a | |
---|
73 | n/a | class TestHeaderBase(TestEmailBase): |
---|
74 | n/a | |
---|
75 | n/a | factory = headerregistry.HeaderRegistry() |
---|
76 | n/a | |
---|
77 | n/a | def make_header(self, name, value): |
---|
78 | n/a | return self.factory(name, value) |
---|
79 | n/a | |
---|
80 | n/a | |
---|
81 | n/a | class TestBaseHeaderFeatures(TestHeaderBase): |
---|
82 | n/a | |
---|
83 | n/a | def test_str(self): |
---|
84 | n/a | h = self.make_header('subject', 'this is a test') |
---|
85 | n/a | self.assertIsInstance(h, str) |
---|
86 | n/a | self.assertEqual(h, 'this is a test') |
---|
87 | n/a | self.assertEqual(str(h), 'this is a test') |
---|
88 | n/a | |
---|
89 | n/a | def test_substr(self): |
---|
90 | n/a | h = self.make_header('subject', 'this is a test') |
---|
91 | n/a | self.assertEqual(h[5:7], 'is') |
---|
92 | n/a | |
---|
93 | n/a | def test_has_name(self): |
---|
94 | n/a | h = self.make_header('subject', 'this is a test') |
---|
95 | n/a | self.assertEqual(h.name, 'subject') |
---|
96 | n/a | |
---|
97 | n/a | def _test_attr_ro(self, attr): |
---|
98 | n/a | h = self.make_header('subject', 'this is a test') |
---|
99 | n/a | with self.assertRaises(AttributeError): |
---|
100 | n/a | setattr(h, attr, 'foo') |
---|
101 | n/a | |
---|
102 | n/a | def test_name_read_only(self): |
---|
103 | n/a | self._test_attr_ro('name') |
---|
104 | n/a | |
---|
105 | n/a | def test_defects_read_only(self): |
---|
106 | n/a | self._test_attr_ro('defects') |
---|
107 | n/a | |
---|
108 | n/a | def test_defects_is_tuple(self): |
---|
109 | n/a | h = self.make_header('subject', 'this is a test') |
---|
110 | n/a | self.assertEqual(len(h.defects), 0) |
---|
111 | n/a | self.assertIsInstance(h.defects, tuple) |
---|
112 | n/a | # Make sure it is still true when there are defects. |
---|
113 | n/a | h = self.make_header('date', '') |
---|
114 | n/a | self.assertEqual(len(h.defects), 1) |
---|
115 | n/a | self.assertIsInstance(h.defects, tuple) |
---|
116 | n/a | |
---|
117 | n/a | # XXX: FIXME |
---|
118 | n/a | #def test_CR_in_value(self): |
---|
119 | n/a | # # XXX: this also re-raises the issue of embedded headers, |
---|
120 | n/a | # # need test and solution for that. |
---|
121 | n/a | # value = '\r'.join(['this is', ' a test']) |
---|
122 | n/a | # h = self.make_header('subject', value) |
---|
123 | n/a | # self.assertEqual(h, value) |
---|
124 | n/a | # self.assertDefectsEqual(h.defects, [errors.ObsoleteHeaderDefect]) |
---|
125 | n/a | |
---|
126 | n/a | |
---|
127 | n/a | @parameterize |
---|
128 | n/a | class TestUnstructuredHeader(TestHeaderBase): |
---|
129 | n/a | |
---|
130 | n/a | def string_as_value(self, |
---|
131 | n/a | source, |
---|
132 | n/a | decoded, |
---|
133 | n/a | *args): |
---|
134 | n/a | l = len(args) |
---|
135 | n/a | defects = args[0] if l>0 else [] |
---|
136 | n/a | header = 'Subject:' + (' ' if source else '') |
---|
137 | n/a | folded = header + (args[1] if l>1 else source) + '\n' |
---|
138 | n/a | h = self.make_header('Subject', source) |
---|
139 | n/a | self.assertEqual(h, decoded) |
---|
140 | n/a | self.assertDefectsEqual(h.defects, defects) |
---|
141 | n/a | self.assertEqual(h.fold(policy=policy.default), folded) |
---|
142 | n/a | |
---|
143 | n/a | string_params = { |
---|
144 | n/a | |
---|
145 | n/a | 'rfc2047_simple_quopri': ( |
---|
146 | n/a | '=?utf-8?q?this_is_a_test?=', |
---|
147 | n/a | 'this is a test', |
---|
148 | n/a | [], |
---|
149 | n/a | 'this is a test'), |
---|
150 | n/a | |
---|
151 | n/a | 'rfc2047_gb2312_base64': ( |
---|
152 | n/a | '=?gb2312?b?1eLKx9bQzsSy4srUo6E=?=', |
---|
153 | n/a | '\u8fd9\u662f\u4e2d\u6587\u6d4b\u8bd5\uff01', |
---|
154 | n/a | [], |
---|
155 | n/a | '=?utf-8?b?6L+Z5piv5Lit5paH5rWL6K+V77yB?='), |
---|
156 | n/a | |
---|
157 | n/a | 'rfc2047_simple_nonascii_quopri': ( |
---|
158 | n/a | '=?utf-8?q?=C3=89ric?=', |
---|
159 | n/a | 'Ãric'), |
---|
160 | n/a | |
---|
161 | n/a | 'rfc2047_quopri_with_regular_text': ( |
---|
162 | n/a | 'The =?utf-8?q?=C3=89ric=2C?= Himself', |
---|
163 | n/a | 'The Ãric, Himself'), |
---|
164 | n/a | |
---|
165 | n/a | } |
---|
166 | n/a | |
---|
167 | n/a | |
---|
168 | n/a | @parameterize |
---|
169 | n/a | class TestDateHeader(TestHeaderBase): |
---|
170 | n/a | |
---|
171 | n/a | datestring = 'Sun, 23 Sep 2001 20:10:55 -0700' |
---|
172 | n/a | utcoffset = datetime.timedelta(hours=-7) |
---|
173 | n/a | tz = datetime.timezone(utcoffset) |
---|
174 | n/a | dt = datetime.datetime(2001, 9, 23, 20, 10, 55, tzinfo=tz) |
---|
175 | n/a | |
---|
176 | n/a | def test_parse_date(self): |
---|
177 | n/a | h = self.make_header('date', self.datestring) |
---|
178 | n/a | self.assertEqual(h, self.datestring) |
---|
179 | n/a | self.assertEqual(h.datetime, self.dt) |
---|
180 | n/a | self.assertEqual(h.datetime.utcoffset(), self.utcoffset) |
---|
181 | n/a | self.assertEqual(h.defects, ()) |
---|
182 | n/a | |
---|
183 | n/a | def test_set_from_datetime(self): |
---|
184 | n/a | h = self.make_header('date', self.dt) |
---|
185 | n/a | self.assertEqual(h, self.datestring) |
---|
186 | n/a | self.assertEqual(h.datetime, self.dt) |
---|
187 | n/a | self.assertEqual(h.defects, ()) |
---|
188 | n/a | |
---|
189 | n/a | def test_date_header_properties(self): |
---|
190 | n/a | h = self.make_header('date', self.datestring) |
---|
191 | n/a | self.assertIsInstance(h, headerregistry.UniqueDateHeader) |
---|
192 | n/a | self.assertEqual(h.max_count, 1) |
---|
193 | n/a | self.assertEqual(h.defects, ()) |
---|
194 | n/a | |
---|
195 | n/a | def test_resent_date_header_properties(self): |
---|
196 | n/a | h = self.make_header('resent-date', self.datestring) |
---|
197 | n/a | self.assertIsInstance(h, headerregistry.DateHeader) |
---|
198 | n/a | self.assertEqual(h.max_count, None) |
---|
199 | n/a | self.assertEqual(h.defects, ()) |
---|
200 | n/a | |
---|
201 | n/a | def test_no_value_is_defect(self): |
---|
202 | n/a | h = self.make_header('date', '') |
---|
203 | n/a | self.assertEqual(len(h.defects), 1) |
---|
204 | n/a | self.assertIsInstance(h.defects[0], errors.HeaderMissingRequiredValue) |
---|
205 | n/a | |
---|
206 | n/a | def test_datetime_read_only(self): |
---|
207 | n/a | h = self.make_header('date', self.datestring) |
---|
208 | n/a | with self.assertRaises(AttributeError): |
---|
209 | n/a | h.datetime = 'foo' |
---|
210 | n/a | |
---|
211 | n/a | def test_set_date_header_from_datetime(self): |
---|
212 | n/a | m = Message(policy=policy.default) |
---|
213 | n/a | m['Date'] = self.dt |
---|
214 | n/a | self.assertEqual(m['Date'], self.datestring) |
---|
215 | n/a | self.assertEqual(m['Date'].datetime, self.dt) |
---|
216 | n/a | |
---|
217 | n/a | |
---|
218 | n/a | @parameterize |
---|
219 | n/a | class TestContentTypeHeader(TestHeaderBase): |
---|
220 | n/a | |
---|
221 | n/a | def content_type_as_value(self, |
---|
222 | n/a | source, |
---|
223 | n/a | content_type, |
---|
224 | n/a | maintype, |
---|
225 | n/a | subtype, |
---|
226 | n/a | *args): |
---|
227 | n/a | l = len(args) |
---|
228 | n/a | parmdict = args[0] if l>0 else {} |
---|
229 | n/a | defects = args[1] if l>1 else [] |
---|
230 | n/a | decoded = args[2] if l>2 and args[2] is not DITTO else source |
---|
231 | n/a | header = 'Content-Type:' + ' ' if source else '' |
---|
232 | n/a | folded = args[3] if l>3 else header + source + '\n' |
---|
233 | n/a | h = self.make_header('Content-Type', source) |
---|
234 | n/a | self.assertEqual(h.content_type, content_type) |
---|
235 | n/a | self.assertEqual(h.maintype, maintype) |
---|
236 | n/a | self.assertEqual(h.subtype, subtype) |
---|
237 | n/a | self.assertEqual(h.params, parmdict) |
---|
238 | n/a | with self.assertRaises(TypeError): |
---|
239 | n/a | h.params['abc'] = 'xyz' # params is read-only. |
---|
240 | n/a | self.assertDefectsEqual(h.defects, defects) |
---|
241 | n/a | self.assertEqual(h, decoded) |
---|
242 | n/a | self.assertEqual(h.fold(policy=policy.default), folded) |
---|
243 | n/a | |
---|
244 | n/a | content_type_params = { |
---|
245 | n/a | |
---|
246 | n/a | # Examples from RFC 2045. |
---|
247 | n/a | |
---|
248 | n/a | 'RFC_2045_1': ( |
---|
249 | n/a | 'text/plain; charset=us-ascii (Plain text)', |
---|
250 | n/a | 'text/plain', |
---|
251 | n/a | 'text', |
---|
252 | n/a | 'plain', |
---|
253 | n/a | {'charset': 'us-ascii'}, |
---|
254 | n/a | [], |
---|
255 | n/a | 'text/plain; charset="us-ascii"'), |
---|
256 | n/a | |
---|
257 | n/a | 'RFC_2045_2': ( |
---|
258 | n/a | 'text/plain; charset=us-ascii', |
---|
259 | n/a | 'text/plain', |
---|
260 | n/a | 'text', |
---|
261 | n/a | 'plain', |
---|
262 | n/a | {'charset': 'us-ascii'}, |
---|
263 | n/a | [], |
---|
264 | n/a | 'text/plain; charset="us-ascii"'), |
---|
265 | n/a | |
---|
266 | n/a | 'RFC_2045_3': ( |
---|
267 | n/a | 'text/plain; charset="us-ascii"', |
---|
268 | n/a | 'text/plain', |
---|
269 | n/a | 'text', |
---|
270 | n/a | 'plain', |
---|
271 | n/a | {'charset': 'us-ascii'}), |
---|
272 | n/a | |
---|
273 | n/a | # RFC 2045 5.2 says syntactically invalid values are to be treated as |
---|
274 | n/a | # text/plain. |
---|
275 | n/a | |
---|
276 | n/a | 'no_subtype_in_content_type': ( |
---|
277 | n/a | 'text/', |
---|
278 | n/a | 'text/plain', |
---|
279 | n/a | 'text', |
---|
280 | n/a | 'plain', |
---|
281 | n/a | {}, |
---|
282 | n/a | [errors.InvalidHeaderDefect]), |
---|
283 | n/a | |
---|
284 | n/a | 'no_slash_in_content_type': ( |
---|
285 | n/a | 'foo', |
---|
286 | n/a | 'text/plain', |
---|
287 | n/a | 'text', |
---|
288 | n/a | 'plain', |
---|
289 | n/a | {}, |
---|
290 | n/a | [errors.InvalidHeaderDefect]), |
---|
291 | n/a | |
---|
292 | n/a | 'junk_text_in_content_type': ( |
---|
293 | n/a | '<crazy "stuff">', |
---|
294 | n/a | 'text/plain', |
---|
295 | n/a | 'text', |
---|
296 | n/a | 'plain', |
---|
297 | n/a | {}, |
---|
298 | n/a | [errors.InvalidHeaderDefect]), |
---|
299 | n/a | |
---|
300 | n/a | 'too_many_slashes_in_content_type': ( |
---|
301 | n/a | 'image/jpeg/foo', |
---|
302 | n/a | 'text/plain', |
---|
303 | n/a | 'text', |
---|
304 | n/a | 'plain', |
---|
305 | n/a | {}, |
---|
306 | n/a | [errors.InvalidHeaderDefect]), |
---|
307 | n/a | |
---|
308 | n/a | # But unknown names are OK. We could make non-IANA names a defect, but |
---|
309 | n/a | # by not doing so we make ourselves future proof. The fact that they |
---|
310 | n/a | # are unknown will be detectable by the fact that they don't appear in |
---|
311 | n/a | # the mime_registry...and the application is free to extend that list |
---|
312 | n/a | # to handle them even if the core library doesn't. |
---|
313 | n/a | |
---|
314 | n/a | 'unknown_content_type': ( |
---|
315 | n/a | 'bad/names', |
---|
316 | n/a | 'bad/names', |
---|
317 | n/a | 'bad', |
---|
318 | n/a | 'names'), |
---|
319 | n/a | |
---|
320 | n/a | # The content type is case insensitive, and CFWS is ignored. |
---|
321 | n/a | |
---|
322 | n/a | 'mixed_case_content_type': ( |
---|
323 | n/a | 'ImAge/JPeg', |
---|
324 | n/a | 'image/jpeg', |
---|
325 | n/a | 'image', |
---|
326 | n/a | 'jpeg'), |
---|
327 | n/a | |
---|
328 | n/a | 'spaces_in_content_type': ( |
---|
329 | n/a | ' text / plain ', |
---|
330 | n/a | 'text/plain', |
---|
331 | n/a | 'text', |
---|
332 | n/a | 'plain'), |
---|
333 | n/a | |
---|
334 | n/a | 'cfws_in_content_type': ( |
---|
335 | n/a | '(foo) text (bar)/(baz)plain(stuff)', |
---|
336 | n/a | 'text/plain', |
---|
337 | n/a | 'text', |
---|
338 | n/a | 'plain'), |
---|
339 | n/a | |
---|
340 | n/a | # test some parameters (more tests could be added for parameters |
---|
341 | n/a | # associated with other content types, but since parameter parsing is |
---|
342 | n/a | # generic they would be redundant for the current implementation). |
---|
343 | n/a | |
---|
344 | n/a | 'charset_param': ( |
---|
345 | n/a | 'text/plain; charset="utf-8"', |
---|
346 | n/a | 'text/plain', |
---|
347 | n/a | 'text', |
---|
348 | n/a | 'plain', |
---|
349 | n/a | {'charset': 'utf-8'}), |
---|
350 | n/a | |
---|
351 | n/a | 'capitalized_charset': ( |
---|
352 | n/a | 'text/plain; charset="US-ASCII"', |
---|
353 | n/a | 'text/plain', |
---|
354 | n/a | 'text', |
---|
355 | n/a | 'plain', |
---|
356 | n/a | {'charset': 'US-ASCII'}), |
---|
357 | n/a | |
---|
358 | n/a | 'unknown_charset': ( |
---|
359 | n/a | 'text/plain; charset="fOo"', |
---|
360 | n/a | 'text/plain', |
---|
361 | n/a | 'text', |
---|
362 | n/a | 'plain', |
---|
363 | n/a | {'charset': 'fOo'}), |
---|
364 | n/a | |
---|
365 | n/a | 'capitalized_charset_param_name_and_comment': ( |
---|
366 | n/a | 'text/plain; (interjection) Charset="utf-8"', |
---|
367 | n/a | 'text/plain', |
---|
368 | n/a | 'text', |
---|
369 | n/a | 'plain', |
---|
370 | n/a | {'charset': 'utf-8'}, |
---|
371 | n/a | [], |
---|
372 | n/a | # Should the parameter name be lowercased here? |
---|
373 | n/a | 'text/plain; Charset="utf-8"'), |
---|
374 | n/a | |
---|
375 | n/a | # Since this is pretty much the ur-mimeheader, we'll put all the tests |
---|
376 | n/a | # that exercise the parameter parsing and formatting here. |
---|
377 | n/a | # |
---|
378 | n/a | # XXX: question: is minimal quoting preferred? |
---|
379 | n/a | |
---|
380 | n/a | 'unquoted_param_value': ( |
---|
381 | n/a | 'text/plain; title=foo', |
---|
382 | n/a | 'text/plain', |
---|
383 | n/a | 'text', |
---|
384 | n/a | 'plain', |
---|
385 | n/a | {'title': 'foo'}, |
---|
386 | n/a | [], |
---|
387 | n/a | 'text/plain; title="foo"'), |
---|
388 | n/a | |
---|
389 | n/a | 'param_value_with_tspecials': ( |
---|
390 | n/a | 'text/plain; title="(bar)foo blue"', |
---|
391 | n/a | 'text/plain', |
---|
392 | n/a | 'text', |
---|
393 | n/a | 'plain', |
---|
394 | n/a | {'title': '(bar)foo blue'}), |
---|
395 | n/a | |
---|
396 | n/a | 'param_with_extra_quoted_whitespace': ( |
---|
397 | n/a | 'text/plain; title=" a loong way \t home "', |
---|
398 | n/a | 'text/plain', |
---|
399 | n/a | 'text', |
---|
400 | n/a | 'plain', |
---|
401 | n/a | {'title': ' a loong way \t home '}), |
---|
402 | n/a | |
---|
403 | n/a | 'bad_params': ( |
---|
404 | n/a | 'blarg; baz; boo', |
---|
405 | n/a | 'text/plain', |
---|
406 | n/a | 'text', |
---|
407 | n/a | 'plain', |
---|
408 | n/a | {'baz': '', 'boo': ''}, |
---|
409 | n/a | [errors.InvalidHeaderDefect]*3), |
---|
410 | n/a | |
---|
411 | n/a | 'spaces_around_param_equals': ( |
---|
412 | n/a | 'Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"', |
---|
413 | n/a | 'multipart/mixed', |
---|
414 | n/a | 'multipart', |
---|
415 | n/a | 'mixed', |
---|
416 | n/a | {'boundary': 'CPIMSSMTPC06p5f3tG'}, |
---|
417 | n/a | [], |
---|
418 | n/a | 'Multipart/mixed; boundary="CPIMSSMTPC06p5f3tG"'), |
---|
419 | n/a | |
---|
420 | n/a | 'spaces_around_semis': ( |
---|
421 | n/a | ('image/jpeg; name="wibble.JPG" ; x-mac-type="4A504547" ; ' |
---|
422 | n/a | 'x-mac-creator="474B4F4E"'), |
---|
423 | n/a | 'image/jpeg', |
---|
424 | n/a | 'image', |
---|
425 | n/a | 'jpeg', |
---|
426 | n/a | {'name': 'wibble.JPG', |
---|
427 | n/a | 'x-mac-type': '4A504547', |
---|
428 | n/a | 'x-mac-creator': '474B4F4E'}, |
---|
429 | n/a | [], |
---|
430 | n/a | ('image/jpeg; name="wibble.JPG"; x-mac-type="4A504547"; ' |
---|
431 | n/a | 'x-mac-creator="474B4F4E"'), |
---|
432 | n/a | # XXX: it could be that we will eventually prefer to fold starting |
---|
433 | n/a | # from the decoded value, in which case these spaces and similar |
---|
434 | n/a | # spaces in other tests will be wrong. |
---|
435 | n/a | ('Content-Type: image/jpeg; name="wibble.JPG" ; ' |
---|
436 | n/a | 'x-mac-type="4A504547" ;\n' |
---|
437 | n/a | ' x-mac-creator="474B4F4E"\n'), |
---|
438 | n/a | ), |
---|
439 | n/a | |
---|
440 | n/a | 'semis_inside_quotes': ( |
---|
441 | n/a | 'image/jpeg; name="Jim&&Jill"', |
---|
442 | n/a | 'image/jpeg', |
---|
443 | n/a | 'image', |
---|
444 | n/a | 'jpeg', |
---|
445 | n/a | {'name': 'Jim&&Jill'}), |
---|
446 | n/a | |
---|
447 | n/a | 'single_quotes_inside_quotes': ( |
---|
448 | n/a | 'image/jpeg; name="Jim \'Bob\' Jill"', |
---|
449 | n/a | 'image/jpeg', |
---|
450 | n/a | 'image', |
---|
451 | n/a | 'jpeg', |
---|
452 | n/a | {'name': "Jim 'Bob' Jill"}), |
---|
453 | n/a | |
---|
454 | n/a | 'double_quotes_inside_quotes': ( |
---|
455 | n/a | r'image/jpeg; name="Jim \"Bob\" Jill"', |
---|
456 | n/a | 'image/jpeg', |
---|
457 | n/a | 'image', |
---|
458 | n/a | 'jpeg', |
---|
459 | n/a | {'name': 'Jim "Bob" Jill'}, |
---|
460 | n/a | [], |
---|
461 | n/a | r'image/jpeg; name="Jim \"Bob\" Jill"'), |
---|
462 | n/a | |
---|
463 | n/a | # XXX: This test works except for the refolding of the header. I'll |
---|
464 | n/a | # deal with that bug when I deal with the other folding bugs. |
---|
465 | n/a | #'non_ascii_in_params': ( |
---|
466 | n/a | # ('foo\xa7/bar; b\xa7r=two; ' |
---|
467 | n/a | # 'baz=thr\xa7e'.encode('latin-1').decode('us-ascii', |
---|
468 | n/a | # 'surrogateescape')), |
---|
469 | n/a | # 'foo\uFFFD/bar', |
---|
470 | n/a | # 'foo\uFFFD', |
---|
471 | n/a | # 'bar', |
---|
472 | n/a | # {'b\uFFFDr': 'two', 'baz': 'thr\uFFFDe'}, |
---|
473 | n/a | # [errors.UndecodableBytesDefect]*3, |
---|
474 | n/a | # 'foo�/bar; b�r="two"; baz="thr�e"', |
---|
475 | n/a | # ), |
---|
476 | n/a | |
---|
477 | n/a | # RFC 2231 parameter tests. |
---|
478 | n/a | |
---|
479 | n/a | 'rfc2231_segmented_normal_values': ( |
---|
480 | n/a | 'image/jpeg; name*0="abc"; name*1=".html"', |
---|
481 | n/a | 'image/jpeg', |
---|
482 | n/a | 'image', |
---|
483 | n/a | 'jpeg', |
---|
484 | n/a | {'name': "abc.html"}, |
---|
485 | n/a | [], |
---|
486 | n/a | 'image/jpeg; name="abc.html"'), |
---|
487 | n/a | |
---|
488 | n/a | 'quotes_inside_rfc2231_value': ( |
---|
489 | n/a | r'image/jpeg; bar*0="baz\"foobar"; bar*1="\"baz"', |
---|
490 | n/a | 'image/jpeg', |
---|
491 | n/a | 'image', |
---|
492 | n/a | 'jpeg', |
---|
493 | n/a | {'bar': 'baz"foobar"baz'}, |
---|
494 | n/a | [], |
---|
495 | n/a | r'image/jpeg; bar="baz\"foobar\"baz"'), |
---|
496 | n/a | |
---|
497 | n/a | # XXX: This test works except for the refolding of the header. I'll |
---|
498 | n/a | # deal with that bug when I deal with the other folding bugs. |
---|
499 | n/a | #'non_ascii_rfc2231_value': ( |
---|
500 | n/a | # ('text/plain; charset=us-ascii; ' |
---|
501 | n/a | # "title*=us-ascii'en'This%20is%20" |
---|
502 | n/a | # 'not%20f\xa7n').encode('latin-1').decode('us-ascii', |
---|
503 | n/a | # 'surrogateescape'), |
---|
504 | n/a | # 'text/plain', |
---|
505 | n/a | # 'text', |
---|
506 | n/a | # 'plain', |
---|
507 | n/a | # {'charset': 'us-ascii', 'title': 'This is not f\uFFFDn'}, |
---|
508 | n/a | # [errors.UndecodableBytesDefect], |
---|
509 | n/a | # 'text/plain; charset="us-ascii"; title="This is not f�n"'), |
---|
510 | n/a | |
---|
511 | n/a | 'rfc2231_encoded_charset': ( |
---|
512 | n/a | 'text/plain; charset*=ansi-x3.4-1968\'\'us-ascii', |
---|
513 | n/a | 'text/plain', |
---|
514 | n/a | 'text', |
---|
515 | n/a | 'plain', |
---|
516 | n/a | {'charset': 'us-ascii'}, |
---|
517 | n/a | [], |
---|
518 | n/a | 'text/plain; charset="us-ascii"'), |
---|
519 | n/a | |
---|
520 | n/a | # This follows the RFC: no double quotes around encoded values. |
---|
521 | n/a | 'rfc2231_encoded_no_double_quotes': ( |
---|
522 | n/a | ("text/plain;" |
---|
523 | n/a | "\tname*0*=''This%20is%20;" |
---|
524 | n/a | "\tname*1*=%2A%2A%2Afun%2A%2A%2A%20;" |
---|
525 | n/a | '\tname*2="is it not.pdf"'), |
---|
526 | n/a | 'text/plain', |
---|
527 | n/a | 'text', |
---|
528 | n/a | 'plain', |
---|
529 | n/a | {'name': 'This is ***fun*** is it not.pdf'}, |
---|
530 | n/a | [], |
---|
531 | n/a | 'text/plain; name="This is ***fun*** is it not.pdf"', |
---|
532 | n/a | ('Content-Type: text/plain;\tname*0*=\'\'This%20is%20;\n' |
---|
533 | n/a | '\tname*1*=%2A%2A%2Afun%2A%2A%2A%20;\tname*2="is it not.pdf"\n'), |
---|
534 | n/a | ), |
---|
535 | n/a | |
---|
536 | n/a | # Make sure we also handle it if there are spurious double quotes. |
---|
537 | n/a | 'rfc2231_encoded_with_double_quotes': ( |
---|
538 | n/a | ("text/plain;" |
---|
539 | n/a | '\tname*0*="us-ascii\'\'This%20is%20even%20more%20";' |
---|
540 | n/a | '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";' |
---|
541 | n/a | '\tname*2="is it not.pdf"'), |
---|
542 | n/a | 'text/plain', |
---|
543 | n/a | 'text', |
---|
544 | n/a | 'plain', |
---|
545 | n/a | {'name': 'This is even more ***fun*** is it not.pdf'}, |
---|
546 | n/a | [errors.InvalidHeaderDefect]*2, |
---|
547 | n/a | 'text/plain; name="This is even more ***fun*** is it not.pdf"', |
---|
548 | n/a | ('Content-Type: text/plain;\t' |
---|
549 | n/a | 'name*0*="us-ascii\'\'This%20is%20even%20more%20";\n' |
---|
550 | n/a | '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";\tname*2="is it not.pdf"\n'), |
---|
551 | n/a | ), |
---|
552 | n/a | |
---|
553 | n/a | 'rfc2231_single_quote_inside_double_quotes': ( |
---|
554 | n/a | ('text/plain; charset=us-ascii;' |
---|
555 | n/a | '\ttitle*0*="us-ascii\'en\'This%20is%20really%20";' |
---|
556 | n/a | '\ttitle*1*="%2A%2A%2Afun%2A%2A%2A%20";' |
---|
557 | n/a | '\ttitle*2="isn\'t it!"'), |
---|
558 | n/a | 'text/plain', |
---|
559 | n/a | 'text', |
---|
560 | n/a | 'plain', |
---|
561 | n/a | {'charset': 'us-ascii', 'title': "This is really ***fun*** isn't it!"}, |
---|
562 | n/a | [errors.InvalidHeaderDefect]*2, |
---|
563 | n/a | ('text/plain; charset="us-ascii"; ' |
---|
564 | n/a | 'title="This is really ***fun*** isn\'t it!"'), |
---|
565 | n/a | ('Content-Type: text/plain; charset=us-ascii;\n' |
---|
566 | n/a | '\ttitle*0*="us-ascii\'en\'This%20is%20really%20";\n' |
---|
567 | n/a | '\ttitle*1*="%2A%2A%2Afun%2A%2A%2A%20";\ttitle*2="isn\'t it!"\n'), |
---|
568 | n/a | ), |
---|
569 | n/a | |
---|
570 | n/a | 'rfc2231_single_quote_in_value_with_charset_and_lang': ( |
---|
571 | n/a | ('application/x-foo;' |
---|
572 | n/a | "\tname*0*=\"us-ascii'en-us'Frank's\"; name*1*=\" Document\""), |
---|
573 | n/a | 'application/x-foo', |
---|
574 | n/a | 'application', |
---|
575 | n/a | 'x-foo', |
---|
576 | n/a | {'name': "Frank's Document"}, |
---|
577 | n/a | [errors.InvalidHeaderDefect]*2, |
---|
578 | n/a | 'application/x-foo; name="Frank\'s Document"', |
---|
579 | n/a | ('Content-Type: application/x-foo;\t' |
---|
580 | n/a | 'name*0*="us-ascii\'en-us\'Frank\'s";\n' |
---|
581 | n/a | ' name*1*=" Document"\n'), |
---|
582 | n/a | ), |
---|
583 | n/a | |
---|
584 | n/a | 'rfc2231_single_quote_in_non_encoded_value': ( |
---|
585 | n/a | ('application/x-foo;' |
---|
586 | n/a | "\tname*0=\"us-ascii'en-us'Frank's\"; name*1=\" Document\""), |
---|
587 | n/a | 'application/x-foo', |
---|
588 | n/a | 'application', |
---|
589 | n/a | 'x-foo', |
---|
590 | n/a | {'name': "us-ascii'en-us'Frank's Document"}, |
---|
591 | n/a | [], |
---|
592 | n/a | 'application/x-foo; name="us-ascii\'en-us\'Frank\'s Document"', |
---|
593 | n/a | ('Content-Type: application/x-foo;\t' |
---|
594 | n/a | 'name*0="us-ascii\'en-us\'Frank\'s";\n' |
---|
595 | n/a | ' name*1=" Document"\n'), |
---|
596 | n/a | ), |
---|
597 | n/a | |
---|
598 | n/a | 'rfc2231_no_language_or_charset': ( |
---|
599 | n/a | 'text/plain; NAME*0*=english_is_the_default.html', |
---|
600 | n/a | 'text/plain', |
---|
601 | n/a | 'text', |
---|
602 | n/a | 'plain', |
---|
603 | n/a | {'name': 'english_is_the_default.html'}, |
---|
604 | n/a | [errors.InvalidHeaderDefect], |
---|
605 | n/a | 'text/plain; NAME="english_is_the_default.html"'), |
---|
606 | n/a | |
---|
607 | n/a | 'rfc2231_encoded_no_charset': ( |
---|
608 | n/a | ("text/plain;" |
---|
609 | n/a | '\tname*0*="\'\'This%20is%20even%20more%20";' |
---|
610 | n/a | '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";' |
---|
611 | n/a | '\tname*2="is it.pdf"'), |
---|
612 | n/a | 'text/plain', |
---|
613 | n/a | 'text', |
---|
614 | n/a | 'plain', |
---|
615 | n/a | {'name': 'This is even more ***fun*** is it.pdf'}, |
---|
616 | n/a | [errors.InvalidHeaderDefect]*2, |
---|
617 | n/a | 'text/plain; name="This is even more ***fun*** is it.pdf"', |
---|
618 | n/a | ('Content-Type: text/plain;\t' |
---|
619 | n/a | 'name*0*="\'\'This%20is%20even%20more%20";\n' |
---|
620 | n/a | '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";\tname*2="is it.pdf"\n'), |
---|
621 | n/a | ), |
---|
622 | n/a | |
---|
623 | n/a | # XXX: see below...the first name line here should be *0 not *0*. |
---|
624 | n/a | 'rfc2231_partly_encoded': ( |
---|
625 | n/a | ("text/plain;" |
---|
626 | n/a | '\tname*0*="\'\'This%20is%20even%20more%20";' |
---|
627 | n/a | '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";' |
---|
628 | n/a | '\tname*2="is it.pdf"'), |
---|
629 | n/a | 'text/plain', |
---|
630 | n/a | 'text', |
---|
631 | n/a | 'plain', |
---|
632 | n/a | {'name': 'This is even more ***fun*** is it.pdf'}, |
---|
633 | n/a | [errors.InvalidHeaderDefect]*2, |
---|
634 | n/a | 'text/plain; name="This is even more ***fun*** is it.pdf"', |
---|
635 | n/a | ('Content-Type: text/plain;\t' |
---|
636 | n/a | 'name*0*="\'\'This%20is%20even%20more%20";\n' |
---|
637 | n/a | '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";\tname*2="is it.pdf"\n'), |
---|
638 | n/a | ), |
---|
639 | n/a | |
---|
640 | n/a | 'rfc2231_partly_encoded_2': ( |
---|
641 | n/a | ("text/plain;" |
---|
642 | n/a | '\tname*0*="\'\'This%20is%20even%20more%20";' |
---|
643 | n/a | '\tname*1="%2A%2A%2Afun%2A%2A%2A%20";' |
---|
644 | n/a | '\tname*2="is it.pdf"'), |
---|
645 | n/a | 'text/plain', |
---|
646 | n/a | 'text', |
---|
647 | n/a | 'plain', |
---|
648 | n/a | {'name': 'This is even more %2A%2A%2Afun%2A%2A%2A%20is it.pdf'}, |
---|
649 | n/a | [errors.InvalidHeaderDefect], |
---|
650 | n/a | 'text/plain; name="This is even more %2A%2A%2Afun%2A%2A%2A%20is it.pdf"', |
---|
651 | n/a | ('Content-Type: text/plain;\t' |
---|
652 | n/a | 'name*0*="\'\'This%20is%20even%20more%20";\n' |
---|
653 | n/a | '\tname*1="%2A%2A%2Afun%2A%2A%2A%20";\tname*2="is it.pdf"\n'), |
---|
654 | n/a | ), |
---|
655 | n/a | |
---|
656 | n/a | 'rfc2231_unknown_charset_treated_as_ascii': ( |
---|
657 | n/a | "text/plain; name*0*=bogus'xx'ascii_is_the_default", |
---|
658 | n/a | 'text/plain', |
---|
659 | n/a | 'text', |
---|
660 | n/a | 'plain', |
---|
661 | n/a | {'name': 'ascii_is_the_default'}, |
---|
662 | n/a | [], |
---|
663 | n/a | 'text/plain; name="ascii_is_the_default"'), |
---|
664 | n/a | |
---|
665 | n/a | 'rfc2231_bad_character_in_charset_parameter_value': ( |
---|
666 | n/a | "text/plain; charset*=ascii''utf-8%F1%F2%F3", |
---|
667 | n/a | 'text/plain', |
---|
668 | n/a | 'text', |
---|
669 | n/a | 'plain', |
---|
670 | n/a | {'charset': 'utf-8\uFFFD\uFFFD\uFFFD'}, |
---|
671 | n/a | [errors.UndecodableBytesDefect], |
---|
672 | n/a | 'text/plain; charset="utf-8\uFFFD\uFFFD\uFFFD"'), |
---|
673 | n/a | |
---|
674 | n/a | 'rfc2231_utf_8_in_supposedly_ascii_charset_parameter_value': ( |
---|
675 | n/a | "text/plain; charset*=ascii''utf-8%E2%80%9D", |
---|
676 | n/a | 'text/plain', |
---|
677 | n/a | 'text', |
---|
678 | n/a | 'plain', |
---|
679 | n/a | {'charset': 'utf-8â'}, |
---|
680 | n/a | [errors.UndecodableBytesDefect], |
---|
681 | n/a | 'text/plain; charset="utf-8â"', |
---|
682 | n/a | ), |
---|
683 | n/a | # XXX: if the above were *re*folded, it would get tagged as utf-8 |
---|
684 | n/a | # instead of ascii in the param, since it now contains non-ASCII. |
---|
685 | n/a | |
---|
686 | n/a | 'rfc2231_encoded_then_unencoded_segments': ( |
---|
687 | n/a | ('application/x-foo;' |
---|
688 | n/a | '\tname*0*="us-ascii\'en-us\'My";' |
---|
689 | n/a | '\tname*1=" Document";' |
---|
690 | n/a | '\tname*2=" For You"'), |
---|
691 | n/a | 'application/x-foo', |
---|
692 | n/a | 'application', |
---|
693 | n/a | 'x-foo', |
---|
694 | n/a | {'name': 'My Document For You'}, |
---|
695 | n/a | [errors.InvalidHeaderDefect], |
---|
696 | n/a | 'application/x-foo; name="My Document For You"', |
---|
697 | n/a | ('Content-Type: application/x-foo;\t' |
---|
698 | n/a | 'name*0*="us-ascii\'en-us\'My";\n' |
---|
699 | n/a | '\tname*1=" Document";\tname*2=" For You"\n'), |
---|
700 | n/a | ), |
---|
701 | n/a | |
---|
702 | n/a | # My reading of the RFC is that this is an invalid header. The RFC |
---|
703 | n/a | # says that if charset and language information is given, the first |
---|
704 | n/a | # segment *must* be encoded. |
---|
705 | n/a | 'rfc2231_unencoded_then_encoded_segments': ( |
---|
706 | n/a | ('application/x-foo;' |
---|
707 | n/a | '\tname*0=us-ascii\'en-us\'My;' |
---|
708 | n/a | '\tname*1*=" Document";' |
---|
709 | n/a | '\tname*2*=" For You"'), |
---|
710 | n/a | 'application/x-foo', |
---|
711 | n/a | 'application', |
---|
712 | n/a | 'x-foo', |
---|
713 | n/a | {'name': 'My Document For You'}, |
---|
714 | n/a | [errors.InvalidHeaderDefect]*3, |
---|
715 | n/a | 'application/x-foo; name="My Document For You"', |
---|
716 | n/a | ("Content-Type: application/x-foo;\tname*0=us-ascii'en-us'My;\t" |
---|
717 | n/a | # XXX: the newline is in the wrong place, come back and fix |
---|
718 | n/a | # this when the rest of tests pass. |
---|
719 | n/a | 'name*1*=" Document"\n;' |
---|
720 | n/a | '\tname*2*=" For You"\n'), |
---|
721 | n/a | ), |
---|
722 | n/a | |
---|
723 | n/a | # XXX: I would say this one should default to ascii/en for the |
---|
724 | n/a | # "encoded" segment, since the first segment is not encoded and is |
---|
725 | n/a | # in double quotes, making the value a valid non-encoded string. The |
---|
726 | n/a | # old parser decodes this just like the previous case, which may be the |
---|
727 | n/a | # better Postel rule, but could equally result in borking headers that |
---|
728 | n/a | # intentionally have quoted quotes in them. We could get this 98% |
---|
729 | n/a | # right if we treat it as a quoted string *unless* it matches the |
---|
730 | n/a | # charset'lang'value pattern exactly *and* there is at least one |
---|
731 | n/a | # encoded segment. Implementing that algorithm will require some |
---|
732 | n/a | # refactoring, so I haven't done it (yet). |
---|
733 | n/a | |
---|
734 | n/a | 'rfc2231_qouted_unencoded_then_encoded_segments': ( |
---|
735 | n/a | ('application/x-foo;' |
---|
736 | n/a | '\tname*0="us-ascii\'en-us\'My";' |
---|
737 | n/a | '\tname*1*=" Document";' |
---|
738 | n/a | '\tname*2*=" For You"'), |
---|
739 | n/a | 'application/x-foo', |
---|
740 | n/a | 'application', |
---|
741 | n/a | 'x-foo', |
---|
742 | n/a | {'name': "us-ascii'en-us'My Document For You"}, |
---|
743 | n/a | [errors.InvalidHeaderDefect]*2, |
---|
744 | n/a | 'application/x-foo; name="us-ascii\'en-us\'My Document For You"', |
---|
745 | n/a | ('Content-Type: application/x-foo;\t' |
---|
746 | n/a | 'name*0="us-ascii\'en-us\'My";\n' |
---|
747 | n/a | '\tname*1*=" Document";\tname*2*=" For You"\n'), |
---|
748 | n/a | ), |
---|
749 | n/a | |
---|
750 | n/a | } |
---|
751 | n/a | |
---|
752 | n/a | |
---|
753 | n/a | @parameterize |
---|
754 | n/a | class TestContentTransferEncoding(TestHeaderBase): |
---|
755 | n/a | |
---|
756 | n/a | def cte_as_value(self, |
---|
757 | n/a | source, |
---|
758 | n/a | cte, |
---|
759 | n/a | *args): |
---|
760 | n/a | l = len(args) |
---|
761 | n/a | defects = args[0] if l>0 else [] |
---|
762 | n/a | decoded = args[1] if l>1 and args[1] is not DITTO else source |
---|
763 | n/a | header = 'Content-Transfer-Encoding:' + ' ' if source else '' |
---|
764 | n/a | folded = args[2] if l>2 else header + source + '\n' |
---|
765 | n/a | h = self.make_header('Content-Transfer-Encoding', source) |
---|
766 | n/a | self.assertEqual(h.cte, cte) |
---|
767 | n/a | self.assertDefectsEqual(h.defects, defects) |
---|
768 | n/a | self.assertEqual(h, decoded) |
---|
769 | n/a | self.assertEqual(h.fold(policy=policy.default), folded) |
---|
770 | n/a | |
---|
771 | n/a | cte_params = { |
---|
772 | n/a | |
---|
773 | n/a | 'RFC_2183_1': ( |
---|
774 | n/a | 'base64', |
---|
775 | n/a | 'base64',), |
---|
776 | n/a | |
---|
777 | n/a | 'no_value': ( |
---|
778 | n/a | '', |
---|
779 | n/a | '7bit', |
---|
780 | n/a | [errors.HeaderMissingRequiredValue], |
---|
781 | n/a | '', |
---|
782 | n/a | 'Content-Transfer-Encoding:\n', |
---|
783 | n/a | ), |
---|
784 | n/a | |
---|
785 | n/a | 'junk_after_cte': ( |
---|
786 | n/a | '7bit and a bunch more', |
---|
787 | n/a | '7bit', |
---|
788 | n/a | [errors.InvalidHeaderDefect]), |
---|
789 | n/a | |
---|
790 | n/a | } |
---|
791 | n/a | |
---|
792 | n/a | |
---|
793 | n/a | @parameterize |
---|
794 | n/a | class TestContentDisposition(TestHeaderBase): |
---|
795 | n/a | |
---|
796 | n/a | def content_disp_as_value(self, |
---|
797 | n/a | source, |
---|
798 | n/a | content_disposition, |
---|
799 | n/a | *args): |
---|
800 | n/a | l = len(args) |
---|
801 | n/a | parmdict = args[0] if l>0 else {} |
---|
802 | n/a | defects = args[1] if l>1 else [] |
---|
803 | n/a | decoded = args[2] if l>2 and args[2] is not DITTO else source |
---|
804 | n/a | header = 'Content-Disposition:' + ' ' if source else '' |
---|
805 | n/a | folded = args[3] if l>3 else header + source + '\n' |
---|
806 | n/a | h = self.make_header('Content-Disposition', source) |
---|
807 | n/a | self.assertEqual(h.content_disposition, content_disposition) |
---|
808 | n/a | self.assertEqual(h.params, parmdict) |
---|
809 | n/a | self.assertDefectsEqual(h.defects, defects) |
---|
810 | n/a | self.assertEqual(h, decoded) |
---|
811 | n/a | self.assertEqual(h.fold(policy=policy.default), folded) |
---|
812 | n/a | |
---|
813 | n/a | content_disp_params = { |
---|
814 | n/a | |
---|
815 | n/a | # Examples from RFC 2183. |
---|
816 | n/a | |
---|
817 | n/a | 'RFC_2183_1': ( |
---|
818 | n/a | 'inline', |
---|
819 | n/a | 'inline',), |
---|
820 | n/a | |
---|
821 | n/a | 'RFC_2183_2': ( |
---|
822 | n/a | ('attachment; filename=genome.jpeg;' |
---|
823 | n/a | ' modification-date="Wed, 12 Feb 1997 16:29:51 -0500";'), |
---|
824 | n/a | 'attachment', |
---|
825 | n/a | {'filename': 'genome.jpeg', |
---|
826 | n/a | 'modification-date': 'Wed, 12 Feb 1997 16:29:51 -0500'}, |
---|
827 | n/a | [], |
---|
828 | n/a | ('attachment; filename="genome.jpeg"; ' |
---|
829 | n/a | 'modification-date="Wed, 12 Feb 1997 16:29:51 -0500"'), |
---|
830 | n/a | ('Content-Disposition: attachment; filename=genome.jpeg;\n' |
---|
831 | n/a | ' modification-date="Wed, 12 Feb 1997 16:29:51 -0500";\n'), |
---|
832 | n/a | ), |
---|
833 | n/a | |
---|
834 | n/a | 'no_value': ( |
---|
835 | n/a | '', |
---|
836 | n/a | None, |
---|
837 | n/a | {}, |
---|
838 | n/a | [errors.HeaderMissingRequiredValue], |
---|
839 | n/a | '', |
---|
840 | n/a | 'Content-Disposition:\n'), |
---|
841 | n/a | |
---|
842 | n/a | 'invalid_value': ( |
---|
843 | n/a | 'ab./k', |
---|
844 | n/a | 'ab.', |
---|
845 | n/a | {}, |
---|
846 | n/a | [errors.InvalidHeaderDefect]), |
---|
847 | n/a | |
---|
848 | n/a | 'invalid_value_with_params': ( |
---|
849 | n/a | 'ab./k; filename="foo"', |
---|
850 | n/a | 'ab.', |
---|
851 | n/a | {'filename': 'foo'}, |
---|
852 | n/a | [errors.InvalidHeaderDefect]), |
---|
853 | n/a | |
---|
854 | n/a | } |
---|
855 | n/a | |
---|
856 | n/a | |
---|
857 | n/a | @parameterize |
---|
858 | n/a | class TestMIMEVersionHeader(TestHeaderBase): |
---|
859 | n/a | |
---|
860 | n/a | def version_string_as_MIME_Version(self, |
---|
861 | n/a | source, |
---|
862 | n/a | decoded, |
---|
863 | n/a | version, |
---|
864 | n/a | major, |
---|
865 | n/a | minor, |
---|
866 | n/a | defects): |
---|
867 | n/a | h = self.make_header('MIME-Version', source) |
---|
868 | n/a | self.assertEqual(h, decoded) |
---|
869 | n/a | self.assertEqual(h.version, version) |
---|
870 | n/a | self.assertEqual(h.major, major) |
---|
871 | n/a | self.assertEqual(h.minor, minor) |
---|
872 | n/a | self.assertDefectsEqual(h.defects, defects) |
---|
873 | n/a | if source: |
---|
874 | n/a | source = ' ' + source |
---|
875 | n/a | self.assertEqual(h.fold(policy=policy.default), |
---|
876 | n/a | 'MIME-Version:' + source + '\n') |
---|
877 | n/a | |
---|
878 | n/a | version_string_params = { |
---|
879 | n/a | |
---|
880 | n/a | # Examples from the RFC. |
---|
881 | n/a | |
---|
882 | n/a | 'RFC_2045_1': ( |
---|
883 | n/a | '1.0', |
---|
884 | n/a | '1.0', |
---|
885 | n/a | '1.0', |
---|
886 | n/a | 1, |
---|
887 | n/a | 0, |
---|
888 | n/a | []), |
---|
889 | n/a | |
---|
890 | n/a | 'RFC_2045_2': ( |
---|
891 | n/a | '1.0 (produced by MetaSend Vx.x)', |
---|
892 | n/a | '1.0 (produced by MetaSend Vx.x)', |
---|
893 | n/a | '1.0', |
---|
894 | n/a | 1, |
---|
895 | n/a | 0, |
---|
896 | n/a | []), |
---|
897 | n/a | |
---|
898 | n/a | 'RFC_2045_3': ( |
---|
899 | n/a | '(produced by MetaSend Vx.x) 1.0', |
---|
900 | n/a | '(produced by MetaSend Vx.x) 1.0', |
---|
901 | n/a | '1.0', |
---|
902 | n/a | 1, |
---|
903 | n/a | 0, |
---|
904 | n/a | []), |
---|
905 | n/a | |
---|
906 | n/a | 'RFC_2045_4': ( |
---|
907 | n/a | '1.(produced by MetaSend Vx.x)0', |
---|
908 | n/a | '1.(produced by MetaSend Vx.x)0', |
---|
909 | n/a | '1.0', |
---|
910 | n/a | 1, |
---|
911 | n/a | 0, |
---|
912 | n/a | []), |
---|
913 | n/a | |
---|
914 | n/a | # Other valid values. |
---|
915 | n/a | |
---|
916 | n/a | '1_1': ( |
---|
917 | n/a | '1.1', |
---|
918 | n/a | '1.1', |
---|
919 | n/a | '1.1', |
---|
920 | n/a | 1, |
---|
921 | n/a | 1, |
---|
922 | n/a | []), |
---|
923 | n/a | |
---|
924 | n/a | '2_1': ( |
---|
925 | n/a | '2.1', |
---|
926 | n/a | '2.1', |
---|
927 | n/a | '2.1', |
---|
928 | n/a | 2, |
---|
929 | n/a | 1, |
---|
930 | n/a | []), |
---|
931 | n/a | |
---|
932 | n/a | 'whitespace': ( |
---|
933 | n/a | '1 .0', |
---|
934 | n/a | '1 .0', |
---|
935 | n/a | '1.0', |
---|
936 | n/a | 1, |
---|
937 | n/a | 0, |
---|
938 | n/a | []), |
---|
939 | n/a | |
---|
940 | n/a | 'leading_trailing_whitespace_ignored': ( |
---|
941 | n/a | ' 1.0 ', |
---|
942 | n/a | ' 1.0 ', |
---|
943 | n/a | '1.0', |
---|
944 | n/a | 1, |
---|
945 | n/a | 0, |
---|
946 | n/a | []), |
---|
947 | n/a | |
---|
948 | n/a | # Recoverable invalid values. We can recover here only because we |
---|
949 | n/a | # already have a valid value by the time we encounter the garbage. |
---|
950 | n/a | # Anywhere else, and we don't know where the garbage ends. |
---|
951 | n/a | |
---|
952 | n/a | 'non_comment_garbage_after': ( |
---|
953 | n/a | '1.0 <abc>', |
---|
954 | n/a | '1.0 <abc>', |
---|
955 | n/a | '1.0', |
---|
956 | n/a | 1, |
---|
957 | n/a | 0, |
---|
958 | n/a | [errors.InvalidHeaderDefect]), |
---|
959 | n/a | |
---|
960 | n/a | # Unrecoverable invalid values. We *could* apply more heuristics to |
---|
961 | n/a | # get something out of the first two, but doing so is not worth the |
---|
962 | n/a | # effort. |
---|
963 | n/a | |
---|
964 | n/a | 'non_comment_garbage_before': ( |
---|
965 | n/a | '<abc> 1.0', |
---|
966 | n/a | '<abc> 1.0', |
---|
967 | n/a | None, |
---|
968 | n/a | None, |
---|
969 | n/a | None, |
---|
970 | n/a | [errors.InvalidHeaderDefect]), |
---|
971 | n/a | |
---|
972 | n/a | 'non_comment_garbage_inside': ( |
---|
973 | n/a | '1.<abc>0', |
---|
974 | n/a | '1.<abc>0', |
---|
975 | n/a | None, |
---|
976 | n/a | None, |
---|
977 | n/a | None, |
---|
978 | n/a | [errors.InvalidHeaderDefect]), |
---|
979 | n/a | |
---|
980 | n/a | 'two_periods': ( |
---|
981 | n/a | '1..0', |
---|
982 | n/a | '1..0', |
---|
983 | n/a | None, |
---|
984 | n/a | None, |
---|
985 | n/a | None, |
---|
986 | n/a | [errors.InvalidHeaderDefect]), |
---|
987 | n/a | |
---|
988 | n/a | '2_x': ( |
---|
989 | n/a | '2.x', |
---|
990 | n/a | '2.x', |
---|
991 | n/a | None, # This could be 2, but it seems safer to make it None. |
---|
992 | n/a | None, |
---|
993 | n/a | None, |
---|
994 | n/a | [errors.InvalidHeaderDefect]), |
---|
995 | n/a | |
---|
996 | n/a | 'foo': ( |
---|
997 | n/a | 'foo', |
---|
998 | n/a | 'foo', |
---|
999 | n/a | None, |
---|
1000 | n/a | None, |
---|
1001 | n/a | None, |
---|
1002 | n/a | [errors.InvalidHeaderDefect]), |
---|
1003 | n/a | |
---|
1004 | n/a | 'missing': ( |
---|
1005 | n/a | '', |
---|
1006 | n/a | '', |
---|
1007 | n/a | None, |
---|
1008 | n/a | None, |
---|
1009 | n/a | None, |
---|
1010 | n/a | [errors.HeaderMissingRequiredValue]), |
---|
1011 | n/a | |
---|
1012 | n/a | } |
---|
1013 | n/a | |
---|
1014 | n/a | |
---|
1015 | n/a | @parameterize |
---|
1016 | n/a | class TestAddressHeader(TestHeaderBase): |
---|
1017 | n/a | |
---|
1018 | n/a | example_params = { |
---|
1019 | n/a | |
---|
1020 | n/a | 'empty': |
---|
1021 | n/a | ('<>', |
---|
1022 | n/a | [errors.InvalidHeaderDefect], |
---|
1023 | n/a | '<>', |
---|
1024 | n/a | '', |
---|
1025 | n/a | '<>', |
---|
1026 | n/a | '', |
---|
1027 | n/a | '', |
---|
1028 | n/a | None), |
---|
1029 | n/a | |
---|
1030 | n/a | 'address_only': |
---|
1031 | n/a | ('zippy@pinhead.com', |
---|
1032 | n/a | [], |
---|
1033 | n/a | 'zippy@pinhead.com', |
---|
1034 | n/a | '', |
---|
1035 | n/a | 'zippy@pinhead.com', |
---|
1036 | n/a | 'zippy', |
---|
1037 | n/a | 'pinhead.com', |
---|
1038 | n/a | None), |
---|
1039 | n/a | |
---|
1040 | n/a | 'name_and_address': |
---|
1041 | n/a | ('Zaphrod Beblebrux <zippy@pinhead.com>', |
---|
1042 | n/a | [], |
---|
1043 | n/a | 'Zaphrod Beblebrux <zippy@pinhead.com>', |
---|
1044 | n/a | 'Zaphrod Beblebrux', |
---|
1045 | n/a | 'zippy@pinhead.com', |
---|
1046 | n/a | 'zippy', |
---|
1047 | n/a | 'pinhead.com', |
---|
1048 | n/a | None), |
---|
1049 | n/a | |
---|
1050 | n/a | 'quoted_local_part': |
---|
1051 | n/a | ('Zaphrod Beblebrux <"foo bar"@pinhead.com>', |
---|
1052 | n/a | [], |
---|
1053 | n/a | 'Zaphrod Beblebrux <"foo bar"@pinhead.com>', |
---|
1054 | n/a | 'Zaphrod Beblebrux', |
---|
1055 | n/a | '"foo bar"@pinhead.com', |
---|
1056 | n/a | 'foo bar', |
---|
1057 | n/a | 'pinhead.com', |
---|
1058 | n/a | None), |
---|
1059 | n/a | |
---|
1060 | n/a | 'quoted_parens_in_name': |
---|
1061 | n/a | (r'"A \(Special\) Person" <person@dom.ain>', |
---|
1062 | n/a | [], |
---|
1063 | n/a | '"A (Special) Person" <person@dom.ain>', |
---|
1064 | n/a | 'A (Special) Person', |
---|
1065 | n/a | 'person@dom.ain', |
---|
1066 | n/a | 'person', |
---|
1067 | n/a | 'dom.ain', |
---|
1068 | n/a | None), |
---|
1069 | n/a | |
---|
1070 | n/a | 'quoted_backslashes_in_name': |
---|
1071 | n/a | (r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>', |
---|
1072 | n/a | [], |
---|
1073 | n/a | r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>', |
---|
1074 | n/a | r'Arthur \Backslash\ Foobar', |
---|
1075 | n/a | 'person@dom.ain', |
---|
1076 | n/a | 'person', |
---|
1077 | n/a | 'dom.ain', |
---|
1078 | n/a | None), |
---|
1079 | n/a | |
---|
1080 | n/a | 'name_with_dot': |
---|
1081 | n/a | ('John X. Doe <jxd@example.com>', |
---|
1082 | n/a | [errors.ObsoleteHeaderDefect], |
---|
1083 | n/a | '"John X. Doe" <jxd@example.com>', |
---|
1084 | n/a | 'John X. Doe', |
---|
1085 | n/a | 'jxd@example.com', |
---|
1086 | n/a | 'jxd', |
---|
1087 | n/a | 'example.com', |
---|
1088 | n/a | None), |
---|
1089 | n/a | |
---|
1090 | n/a | 'quoted_strings_in_local_part': |
---|
1091 | n/a | ('""example" example"@example.com', |
---|
1092 | n/a | [errors.InvalidHeaderDefect]*3, |
---|
1093 | n/a | '"example example"@example.com', |
---|
1094 | n/a | '', |
---|
1095 | n/a | '"example example"@example.com', |
---|
1096 | n/a | 'example example', |
---|
1097 | n/a | 'example.com', |
---|
1098 | n/a | None), |
---|
1099 | n/a | |
---|
1100 | n/a | 'escaped_quoted_strings_in_local_part': |
---|
1101 | n/a | (r'"\"example\" example"@example.com', |
---|
1102 | n/a | [], |
---|
1103 | n/a | r'"\"example\" example"@example.com', |
---|
1104 | n/a | '', |
---|
1105 | n/a | r'"\"example\" example"@example.com', |
---|
1106 | n/a | r'"example" example', |
---|
1107 | n/a | 'example.com', |
---|
1108 | n/a | None), |
---|
1109 | n/a | |
---|
1110 | n/a | 'escaped_escapes_in_local_part': |
---|
1111 | n/a | (r'"\\"example\\" example"@example.com', |
---|
1112 | n/a | [errors.InvalidHeaderDefect]*5, |
---|
1113 | n/a | r'"\\example\\\\ example"@example.com', |
---|
1114 | n/a | '', |
---|
1115 | n/a | r'"\\example\\\\ example"@example.com', |
---|
1116 | n/a | r'\example\\ example', |
---|
1117 | n/a | 'example.com', |
---|
1118 | n/a | None), |
---|
1119 | n/a | |
---|
1120 | n/a | 'spaces_in_unquoted_local_part_collapsed': |
---|
1121 | n/a | ('merwok wok @example.com', |
---|
1122 | n/a | [errors.InvalidHeaderDefect]*2, |
---|
1123 | n/a | '"merwok wok"@example.com', |
---|
1124 | n/a | '', |
---|
1125 | n/a | '"merwok wok"@example.com', |
---|
1126 | n/a | 'merwok wok', |
---|
1127 | n/a | 'example.com', |
---|
1128 | n/a | None), |
---|
1129 | n/a | |
---|
1130 | n/a | 'spaces_around_dots_in_local_part_removed': |
---|
1131 | n/a | ('merwok. wok . wok@example.com', |
---|
1132 | n/a | [errors.ObsoleteHeaderDefect], |
---|
1133 | n/a | 'merwok.wok.wok@example.com', |
---|
1134 | n/a | '', |
---|
1135 | n/a | 'merwok.wok.wok@example.com', |
---|
1136 | n/a | 'merwok.wok.wok', |
---|
1137 | n/a | 'example.com', |
---|
1138 | n/a | None), |
---|
1139 | n/a | |
---|
1140 | n/a | 'rfc2047_atom_is_decoded': |
---|
1141 | n/a | ('=?utf-8?q?=C3=89ric?= <foo@example.com>', |
---|
1142 | n/a | [], |
---|
1143 | n/a | 'Ãric <foo@example.com>', |
---|
1144 | n/a | 'Ãric', |
---|
1145 | n/a | 'foo@example.com', |
---|
1146 | n/a | 'foo', |
---|
1147 | n/a | 'example.com', |
---|
1148 | n/a | None), |
---|
1149 | n/a | |
---|
1150 | n/a | 'rfc2047_atom_in_phrase_is_decoded': |
---|
1151 | n/a | ('The =?utf-8?q?=C3=89ric=2C?= Himself <foo@example.com>', |
---|
1152 | n/a | [], |
---|
1153 | n/a | '"The Ãric, Himself" <foo@example.com>', |
---|
1154 | n/a | 'The Ãric, Himself', |
---|
1155 | n/a | 'foo@example.com', |
---|
1156 | n/a | 'foo', |
---|
1157 | n/a | 'example.com', |
---|
1158 | n/a | None), |
---|
1159 | n/a | |
---|
1160 | n/a | 'rfc2047_atom_in_quoted_string_is_decoded': |
---|
1161 | n/a | ('"=?utf-8?q?=C3=89ric?=" <foo@example.com>', |
---|
1162 | n/a | [errors.InvalidHeaderDefect], |
---|
1163 | n/a | 'Ãric <foo@example.com>', |
---|
1164 | n/a | 'Ãric', |
---|
1165 | n/a | 'foo@example.com', |
---|
1166 | n/a | 'foo', |
---|
1167 | n/a | 'example.com', |
---|
1168 | n/a | None), |
---|
1169 | n/a | |
---|
1170 | n/a | } |
---|
1171 | n/a | |
---|
1172 | n/a | # XXX: Need many more examples, and in particular some with names in |
---|
1173 | n/a | # trailing comments, which aren't currently handled. comments in |
---|
1174 | n/a | # general are not handled yet. |
---|
1175 | n/a | |
---|
1176 | n/a | def example_as_address(self, source, defects, decoded, display_name, |
---|
1177 | n/a | addr_spec, username, domain, comment): |
---|
1178 | n/a | h = self.make_header('sender', source) |
---|
1179 | n/a | self.assertEqual(h, decoded) |
---|
1180 | n/a | self.assertDefectsEqual(h.defects, defects) |
---|
1181 | n/a | a = h.address |
---|
1182 | n/a | self.assertEqual(str(a), decoded) |
---|
1183 | n/a | self.assertEqual(len(h.groups), 1) |
---|
1184 | n/a | self.assertEqual([a], list(h.groups[0].addresses)) |
---|
1185 | n/a | self.assertEqual([a], list(h.addresses)) |
---|
1186 | n/a | self.assertEqual(a.display_name, display_name) |
---|
1187 | n/a | self.assertEqual(a.addr_spec, addr_spec) |
---|
1188 | n/a | self.assertEqual(a.username, username) |
---|
1189 | n/a | self.assertEqual(a.domain, domain) |
---|
1190 | n/a | # XXX: we have no comment support yet. |
---|
1191 | n/a | #self.assertEqual(a.comment, comment) |
---|
1192 | n/a | |
---|
1193 | n/a | def example_as_group(self, source, defects, decoded, display_name, |
---|
1194 | n/a | addr_spec, username, domain, comment): |
---|
1195 | n/a | source = 'foo: {};'.format(source) |
---|
1196 | n/a | gdecoded = 'foo: {};'.format(decoded) if decoded else 'foo:;' |
---|
1197 | n/a | h = self.make_header('to', source) |
---|
1198 | n/a | self.assertEqual(h, gdecoded) |
---|
1199 | n/a | self.assertDefectsEqual(h.defects, defects) |
---|
1200 | n/a | self.assertEqual(h.groups[0].addresses, h.addresses) |
---|
1201 | n/a | self.assertEqual(len(h.groups), 1) |
---|
1202 | n/a | self.assertEqual(len(h.addresses), 1) |
---|
1203 | n/a | a = h.addresses[0] |
---|
1204 | n/a | self.assertEqual(str(a), decoded) |
---|
1205 | n/a | self.assertEqual(a.display_name, display_name) |
---|
1206 | n/a | self.assertEqual(a.addr_spec, addr_spec) |
---|
1207 | n/a | self.assertEqual(a.username, username) |
---|
1208 | n/a | self.assertEqual(a.domain, domain) |
---|
1209 | n/a | |
---|
1210 | n/a | def test_simple_address_list(self): |
---|
1211 | n/a | value = ('Fred <dinsdale@python.org>, foo@example.com, ' |
---|
1212 | n/a | '"Harry W. Hastings" <hasty@example.com>') |
---|
1213 | n/a | h = self.make_header('to', value) |
---|
1214 | n/a | self.assertEqual(h, value) |
---|
1215 | n/a | self.assertEqual(len(h.groups), 3) |
---|
1216 | n/a | self.assertEqual(len(h.addresses), 3) |
---|
1217 | n/a | for i in range(3): |
---|
1218 | n/a | self.assertEqual(h.groups[i].addresses[0], h.addresses[i]) |
---|
1219 | n/a | self.assertEqual(str(h.addresses[0]), 'Fred <dinsdale@python.org>') |
---|
1220 | n/a | self.assertEqual(str(h.addresses[1]), 'foo@example.com') |
---|
1221 | n/a | self.assertEqual(str(h.addresses[2]), |
---|
1222 | n/a | '"Harry W. Hastings" <hasty@example.com>') |
---|
1223 | n/a | self.assertEqual(h.addresses[2].display_name, |
---|
1224 | n/a | 'Harry W. Hastings') |
---|
1225 | n/a | |
---|
1226 | n/a | def test_complex_address_list(self): |
---|
1227 | n/a | examples = list(self.example_params.values()) |
---|
1228 | n/a | source = ('dummy list:;, another: (empty);,' + |
---|
1229 | n/a | ', '.join([x[0] for x in examples[:4]]) + ', ' + |
---|
1230 | n/a | r'"A \"list\"": ' + |
---|
1231 | n/a | ', '.join([x[0] for x in examples[4:6]]) + ';,' + |
---|
1232 | n/a | ', '.join([x[0] for x in examples[6:]]) |
---|
1233 | n/a | ) |
---|
1234 | n/a | # XXX: the fact that (empty) disappears here is a potential API design |
---|
1235 | n/a | # bug. We don't currently have a way to preserve comments. |
---|
1236 | n/a | expected = ('dummy list:;, another:;, ' + |
---|
1237 | n/a | ', '.join([x[2] for x in examples[:4]]) + ', ' + |
---|
1238 | n/a | r'"A \"list\"": ' + |
---|
1239 | n/a | ', '.join([x[2] for x in examples[4:6]]) + ';, ' + |
---|
1240 | n/a | ', '.join([x[2] for x in examples[6:]]) |
---|
1241 | n/a | ) |
---|
1242 | n/a | |
---|
1243 | n/a | h = self.make_header('to', source) |
---|
1244 | n/a | self.assertEqual(h.split(','), expected.split(',')) |
---|
1245 | n/a | self.assertEqual(h, expected) |
---|
1246 | n/a | self.assertEqual(len(h.groups), 7 + len(examples) - 6) |
---|
1247 | n/a | self.assertEqual(h.groups[0].display_name, 'dummy list') |
---|
1248 | n/a | self.assertEqual(h.groups[1].display_name, 'another') |
---|
1249 | n/a | self.assertEqual(h.groups[6].display_name, 'A "list"') |
---|
1250 | n/a | self.assertEqual(len(h.addresses), len(examples)) |
---|
1251 | n/a | for i in range(4): |
---|
1252 | n/a | self.assertIsNone(h.groups[i+2].display_name) |
---|
1253 | n/a | self.assertEqual(str(h.groups[i+2].addresses[0]), examples[i][2]) |
---|
1254 | n/a | for i in range(7, 7 + len(examples) - 6): |
---|
1255 | n/a | self.assertIsNone(h.groups[i].display_name) |
---|
1256 | n/a | self.assertEqual(str(h.groups[i].addresses[0]), examples[i-1][2]) |
---|
1257 | n/a | for i in range(len(examples)): |
---|
1258 | n/a | self.assertEqual(str(h.addresses[i]), examples[i][2]) |
---|
1259 | n/a | self.assertEqual(h.addresses[i].addr_spec, examples[i][4]) |
---|
1260 | n/a | |
---|
1261 | n/a | def test_address_read_only(self): |
---|
1262 | n/a | h = self.make_header('sender', 'abc@xyz.com') |
---|
1263 | n/a | with self.assertRaises(AttributeError): |
---|
1264 | n/a | h.address = 'foo' |
---|
1265 | n/a | |
---|
1266 | n/a | def test_addresses_read_only(self): |
---|
1267 | n/a | h = self.make_header('sender', 'abc@xyz.com') |
---|
1268 | n/a | with self.assertRaises(AttributeError): |
---|
1269 | n/a | h.addresses = 'foo' |
---|
1270 | n/a | |
---|
1271 | n/a | def test_groups_read_only(self): |
---|
1272 | n/a | h = self.make_header('sender', 'abc@xyz.com') |
---|
1273 | n/a | with self.assertRaises(AttributeError): |
---|
1274 | n/a | h.groups = 'foo' |
---|
1275 | n/a | |
---|
1276 | n/a | def test_addresses_types(self): |
---|
1277 | n/a | source = 'me <who@example.com>' |
---|
1278 | n/a | h = self.make_header('to', source) |
---|
1279 | n/a | self.assertIsInstance(h.addresses, tuple) |
---|
1280 | n/a | self.assertIsInstance(h.addresses[0], Address) |
---|
1281 | n/a | |
---|
1282 | n/a | def test_groups_types(self): |
---|
1283 | n/a | source = 'me <who@example.com>' |
---|
1284 | n/a | h = self.make_header('to', source) |
---|
1285 | n/a | self.assertIsInstance(h.groups, tuple) |
---|
1286 | n/a | self.assertIsInstance(h.groups[0], Group) |
---|
1287 | n/a | |
---|
1288 | n/a | def test_set_from_Address(self): |
---|
1289 | n/a | h = self.make_header('to', Address('me', 'foo', 'example.com')) |
---|
1290 | n/a | self.assertEqual(h, 'me <foo@example.com>') |
---|
1291 | n/a | |
---|
1292 | n/a | def test_set_from_Address_list(self): |
---|
1293 | n/a | h = self.make_header('to', [Address('me', 'foo', 'example.com'), |
---|
1294 | n/a | Address('you', 'bar', 'example.com')]) |
---|
1295 | n/a | self.assertEqual(h, 'me <foo@example.com>, you <bar@example.com>') |
---|
1296 | n/a | |
---|
1297 | n/a | def test_set_from_Address_and_Group_list(self): |
---|
1298 | n/a | h = self.make_header('to', [Address('me', 'foo', 'example.com'), |
---|
1299 | n/a | Group('bing', [Address('fiz', 'z', 'b.com'), |
---|
1300 | n/a | Address('zif', 'f', 'c.com')]), |
---|
1301 | n/a | Address('you', 'bar', 'example.com')]) |
---|
1302 | n/a | self.assertEqual(h, 'me <foo@example.com>, bing: fiz <z@b.com>, ' |
---|
1303 | n/a | 'zif <f@c.com>;, you <bar@example.com>') |
---|
1304 | n/a | self.assertEqual(h.fold(policy=policy.default.clone(max_line_length=40)), |
---|
1305 | n/a | 'to: me <foo@example.com>,\n' |
---|
1306 | n/a | ' bing: fiz <z@b.com>, zif <f@c.com>;,\n' |
---|
1307 | n/a | ' you <bar@example.com>\n') |
---|
1308 | n/a | |
---|
1309 | n/a | def test_set_from_Group_list(self): |
---|
1310 | n/a | h = self.make_header('to', [Group('bing', [Address('fiz', 'z', 'b.com'), |
---|
1311 | n/a | Address('zif', 'f', 'c.com')])]) |
---|
1312 | n/a | self.assertEqual(h, 'bing: fiz <z@b.com>, zif <f@c.com>;') |
---|
1313 | n/a | |
---|
1314 | n/a | |
---|
1315 | n/a | class TestAddressAndGroup(TestEmailBase): |
---|
1316 | n/a | |
---|
1317 | n/a | def _test_attr_ro(self, obj, attr): |
---|
1318 | n/a | with self.assertRaises(AttributeError): |
---|
1319 | n/a | setattr(obj, attr, 'foo') |
---|
1320 | n/a | |
---|
1321 | n/a | def test_address_display_name_ro(self): |
---|
1322 | n/a | self._test_attr_ro(Address('foo', 'bar', 'baz'), 'display_name') |
---|
1323 | n/a | |
---|
1324 | n/a | def test_address_username_ro(self): |
---|
1325 | n/a | self._test_attr_ro(Address('foo', 'bar', 'baz'), 'username') |
---|
1326 | n/a | |
---|
1327 | n/a | def test_address_domain_ro(self): |
---|
1328 | n/a | self._test_attr_ro(Address('foo', 'bar', 'baz'), 'domain') |
---|
1329 | n/a | |
---|
1330 | n/a | def test_group_display_name_ro(self): |
---|
1331 | n/a | self._test_attr_ro(Group('foo'), 'display_name') |
---|
1332 | n/a | |
---|
1333 | n/a | def test_group_addresses_ro(self): |
---|
1334 | n/a | self._test_attr_ro(Group('foo'), 'addresses') |
---|
1335 | n/a | |
---|
1336 | n/a | def test_address_from_username_domain(self): |
---|
1337 | n/a | a = Address('foo', 'bar', 'baz') |
---|
1338 | n/a | self.assertEqual(a.display_name, 'foo') |
---|
1339 | n/a | self.assertEqual(a.username, 'bar') |
---|
1340 | n/a | self.assertEqual(a.domain, 'baz') |
---|
1341 | n/a | self.assertEqual(a.addr_spec, 'bar@baz') |
---|
1342 | n/a | self.assertEqual(str(a), 'foo <bar@baz>') |
---|
1343 | n/a | |
---|
1344 | n/a | def test_address_from_addr_spec(self): |
---|
1345 | n/a | a = Address('foo', addr_spec='bar@baz') |
---|
1346 | n/a | self.assertEqual(a.display_name, 'foo') |
---|
1347 | n/a | self.assertEqual(a.username, 'bar') |
---|
1348 | n/a | self.assertEqual(a.domain, 'baz') |
---|
1349 | n/a | self.assertEqual(a.addr_spec, 'bar@baz') |
---|
1350 | n/a | self.assertEqual(str(a), 'foo <bar@baz>') |
---|
1351 | n/a | |
---|
1352 | n/a | def test_address_with_no_display_name(self): |
---|
1353 | n/a | a = Address(addr_spec='bar@baz') |
---|
1354 | n/a | self.assertEqual(a.display_name, '') |
---|
1355 | n/a | self.assertEqual(a.username, 'bar') |
---|
1356 | n/a | self.assertEqual(a.domain, 'baz') |
---|
1357 | n/a | self.assertEqual(a.addr_spec, 'bar@baz') |
---|
1358 | n/a | self.assertEqual(str(a), 'bar@baz') |
---|
1359 | n/a | |
---|
1360 | n/a | def test_null_address(self): |
---|
1361 | n/a | a = Address() |
---|
1362 | n/a | self.assertEqual(a.display_name, '') |
---|
1363 | n/a | self.assertEqual(a.username, '') |
---|
1364 | n/a | self.assertEqual(a.domain, '') |
---|
1365 | n/a | self.assertEqual(a.addr_spec, '<>') |
---|
1366 | n/a | self.assertEqual(str(a), '<>') |
---|
1367 | n/a | |
---|
1368 | n/a | def test_domain_only(self): |
---|
1369 | n/a | # This isn't really a valid address. |
---|
1370 | n/a | a = Address(domain='buzz') |
---|
1371 | n/a | self.assertEqual(a.display_name, '') |
---|
1372 | n/a | self.assertEqual(a.username, '') |
---|
1373 | n/a | self.assertEqual(a.domain, 'buzz') |
---|
1374 | n/a | self.assertEqual(a.addr_spec, '@buzz') |
---|
1375 | n/a | self.assertEqual(str(a), '@buzz') |
---|
1376 | n/a | |
---|
1377 | n/a | def test_username_only(self): |
---|
1378 | n/a | # This isn't really a valid address. |
---|
1379 | n/a | a = Address(username='buzz') |
---|
1380 | n/a | self.assertEqual(a.display_name, '') |
---|
1381 | n/a | self.assertEqual(a.username, 'buzz') |
---|
1382 | n/a | self.assertEqual(a.domain, '') |
---|
1383 | n/a | self.assertEqual(a.addr_spec, 'buzz') |
---|
1384 | n/a | self.assertEqual(str(a), 'buzz') |
---|
1385 | n/a | |
---|
1386 | n/a | def test_display_name_only(self): |
---|
1387 | n/a | a = Address('buzz') |
---|
1388 | n/a | self.assertEqual(a.display_name, 'buzz') |
---|
1389 | n/a | self.assertEqual(a.username, '') |
---|
1390 | n/a | self.assertEqual(a.domain, '') |
---|
1391 | n/a | self.assertEqual(a.addr_spec, '<>') |
---|
1392 | n/a | self.assertEqual(str(a), 'buzz <>') |
---|
1393 | n/a | |
---|
1394 | n/a | def test_quoting(self): |
---|
1395 | n/a | # Ideally we'd check every special individually, but I'm not up for |
---|
1396 | n/a | # writing that many tests. |
---|
1397 | n/a | a = Address('Sara J.', 'bad name', 'example.com') |
---|
1398 | n/a | self.assertEqual(a.display_name, 'Sara J.') |
---|
1399 | n/a | self.assertEqual(a.username, 'bad name') |
---|
1400 | n/a | self.assertEqual(a.domain, 'example.com') |
---|
1401 | n/a | self.assertEqual(a.addr_spec, '"bad name"@example.com') |
---|
1402 | n/a | self.assertEqual(str(a), '"Sara J." <"bad name"@example.com>') |
---|
1403 | n/a | |
---|
1404 | n/a | def test_il8n(self): |
---|
1405 | n/a | a = Address('Ãric', 'wok', 'exà mple.com') |
---|
1406 | n/a | self.assertEqual(a.display_name, 'Ãric') |
---|
1407 | n/a | self.assertEqual(a.username, 'wok') |
---|
1408 | n/a | self.assertEqual(a.domain, 'exà mple.com') |
---|
1409 | n/a | self.assertEqual(a.addr_spec, 'wok@exà mple.com') |
---|
1410 | n/a | self.assertEqual(str(a), 'Ãric <wok@exà mple.com>') |
---|
1411 | n/a | |
---|
1412 | n/a | # XXX: there is an API design issue that needs to be solved here. |
---|
1413 | n/a | #def test_non_ascii_username_raises(self): |
---|
1414 | n/a | # with self.assertRaises(ValueError): |
---|
1415 | n/a | # Address('foo', 'wÅk', 'example.com') |
---|
1416 | n/a | |
---|
1417 | n/a | def test_non_ascii_username_in_addr_spec_raises(self): |
---|
1418 | n/a | with self.assertRaises(ValueError): |
---|
1419 | n/a | Address('foo', addr_spec='wÅk@example.com') |
---|
1420 | n/a | |
---|
1421 | n/a | def test_address_addr_spec_and_username_raises(self): |
---|
1422 | n/a | with self.assertRaises(TypeError): |
---|
1423 | n/a | Address('foo', username='bing', addr_spec='bar@baz') |
---|
1424 | n/a | |
---|
1425 | n/a | def test_address_addr_spec_and_domain_raises(self): |
---|
1426 | n/a | with self.assertRaises(TypeError): |
---|
1427 | n/a | Address('foo', domain='bing', addr_spec='bar@baz') |
---|
1428 | n/a | |
---|
1429 | n/a | def test_address_addr_spec_and_username_and_domain_raises(self): |
---|
1430 | n/a | with self.assertRaises(TypeError): |
---|
1431 | n/a | Address('foo', username='bong', domain='bing', addr_spec='bar@baz') |
---|
1432 | n/a | |
---|
1433 | n/a | def test_space_in_addr_spec_username_raises(self): |
---|
1434 | n/a | with self.assertRaises(ValueError): |
---|
1435 | n/a | Address('foo', addr_spec="bad name@example.com") |
---|
1436 | n/a | |
---|
1437 | n/a | def test_bad_addr_sepc_raises(self): |
---|
1438 | n/a | with self.assertRaises(ValueError): |
---|
1439 | n/a | Address('foo', addr_spec="name@ex[]ample.com") |
---|
1440 | n/a | |
---|
1441 | n/a | def test_empty_group(self): |
---|
1442 | n/a | g = Group('foo') |
---|
1443 | n/a | self.assertEqual(g.display_name, 'foo') |
---|
1444 | n/a | self.assertEqual(g.addresses, tuple()) |
---|
1445 | n/a | self.assertEqual(str(g), 'foo:;') |
---|
1446 | n/a | |
---|
1447 | n/a | def test_empty_group_list(self): |
---|
1448 | n/a | g = Group('foo', addresses=[]) |
---|
1449 | n/a | self.assertEqual(g.display_name, 'foo') |
---|
1450 | n/a | self.assertEqual(g.addresses, tuple()) |
---|
1451 | n/a | self.assertEqual(str(g), 'foo:;') |
---|
1452 | n/a | |
---|
1453 | n/a | def test_null_group(self): |
---|
1454 | n/a | g = Group() |
---|
1455 | n/a | self.assertIsNone(g.display_name) |
---|
1456 | n/a | self.assertEqual(g.addresses, tuple()) |
---|
1457 | n/a | self.assertEqual(str(g), 'None:;') |
---|
1458 | n/a | |
---|
1459 | n/a | def test_group_with_addresses(self): |
---|
1460 | n/a | addrs = [Address('b', 'b', 'c'), Address('a', 'b','c')] |
---|
1461 | n/a | g = Group('foo', addrs) |
---|
1462 | n/a | self.assertEqual(g.display_name, 'foo') |
---|
1463 | n/a | self.assertEqual(g.addresses, tuple(addrs)) |
---|
1464 | n/a | self.assertEqual(str(g), 'foo: b <b@c>, a <b@c>;') |
---|
1465 | n/a | |
---|
1466 | n/a | def test_group_with_addresses_no_display_name(self): |
---|
1467 | n/a | addrs = [Address('b', 'b', 'c'), Address('a', 'b','c')] |
---|
1468 | n/a | g = Group(addresses=addrs) |
---|
1469 | n/a | self.assertIsNone(g.display_name) |
---|
1470 | n/a | self.assertEqual(g.addresses, tuple(addrs)) |
---|
1471 | n/a | self.assertEqual(str(g), 'None: b <b@c>, a <b@c>;') |
---|
1472 | n/a | |
---|
1473 | n/a | def test_group_with_one_address_no_display_name(self): |
---|
1474 | n/a | addrs = [Address('b', 'b', 'c')] |
---|
1475 | n/a | g = Group(addresses=addrs) |
---|
1476 | n/a | self.assertIsNone(g.display_name) |
---|
1477 | n/a | self.assertEqual(g.addresses, tuple(addrs)) |
---|
1478 | n/a | self.assertEqual(str(g), 'b <b@c>') |
---|
1479 | n/a | |
---|
1480 | n/a | def test_display_name_quoting(self): |
---|
1481 | n/a | g = Group('foo.bar') |
---|
1482 | n/a | self.assertEqual(g.display_name, 'foo.bar') |
---|
1483 | n/a | self.assertEqual(g.addresses, tuple()) |
---|
1484 | n/a | self.assertEqual(str(g), '"foo.bar":;') |
---|
1485 | n/a | |
---|
1486 | n/a | def test_display_name_blanks_not_quoted(self): |
---|
1487 | n/a | g = Group('foo bar') |
---|
1488 | n/a | self.assertEqual(g.display_name, 'foo bar') |
---|
1489 | n/a | self.assertEqual(g.addresses, tuple()) |
---|
1490 | n/a | self.assertEqual(str(g), 'foo bar:;') |
---|
1491 | n/a | |
---|
1492 | n/a | def test_set_message_header_from_address(self): |
---|
1493 | n/a | a = Address('foo', 'bar', 'example.com') |
---|
1494 | n/a | m = Message(policy=policy.default) |
---|
1495 | n/a | m['To'] = a |
---|
1496 | n/a | self.assertEqual(m['to'], 'foo <bar@example.com>') |
---|
1497 | n/a | self.assertEqual(m['to'].addresses, (a,)) |
---|
1498 | n/a | |
---|
1499 | n/a | def test_set_message_header_from_group(self): |
---|
1500 | n/a | g = Group('foo bar') |
---|
1501 | n/a | m = Message(policy=policy.default) |
---|
1502 | n/a | m['To'] = g |
---|
1503 | n/a | self.assertEqual(m['to'], 'foo bar:;') |
---|
1504 | n/a | self.assertEqual(m['to'].addresses, g.addresses) |
---|
1505 | n/a | |
---|
1506 | n/a | |
---|
1507 | n/a | class TestFolding(TestHeaderBase): |
---|
1508 | n/a | |
---|
1509 | n/a | def test_short_unstructured(self): |
---|
1510 | n/a | h = self.make_header('subject', 'this is a test') |
---|
1511 | n/a | self.assertEqual(h.fold(policy=policy.default), |
---|
1512 | n/a | 'subject: this is a test\n') |
---|
1513 | n/a | |
---|
1514 | n/a | def test_long_unstructured(self): |
---|
1515 | n/a | h = self.make_header('Subject', 'This is a long header ' |
---|
1516 | n/a | 'line that will need to be folded into two lines ' |
---|
1517 | n/a | 'and will demonstrate basic folding') |
---|
1518 | n/a | self.assertEqual(h.fold(policy=policy.default), |
---|
1519 | n/a | 'Subject: This is a long header line that will ' |
---|
1520 | n/a | 'need to be folded into two lines\n' |
---|
1521 | n/a | ' and will demonstrate basic folding\n') |
---|
1522 | n/a | |
---|
1523 | n/a | def test_unstructured_short_max_line_length(self): |
---|
1524 | n/a | h = self.make_header('Subject', 'this is a short header ' |
---|
1525 | n/a | 'that will be folded anyway') |
---|
1526 | n/a | self.assertEqual( |
---|
1527 | n/a | h.fold(policy=policy.default.clone(max_line_length=20)), |
---|
1528 | n/a | textwrap.dedent("""\ |
---|
1529 | n/a | Subject: this is a |
---|
1530 | n/a | short header that |
---|
1531 | n/a | will be folded |
---|
1532 | n/a | anyway |
---|
1533 | n/a | """)) |
---|
1534 | n/a | |
---|
1535 | n/a | def test_fold_unstructured_single_word(self): |
---|
1536 | n/a | h = self.make_header('Subject', 'test') |
---|
1537 | n/a | self.assertEqual(h.fold(policy=policy.default), 'Subject: test\n') |
---|
1538 | n/a | |
---|
1539 | n/a | def test_fold_unstructured_short(self): |
---|
1540 | n/a | h = self.make_header('Subject', 'test test test') |
---|
1541 | n/a | self.assertEqual(h.fold(policy=policy.default), |
---|
1542 | n/a | 'Subject: test test test\n') |
---|
1543 | n/a | |
---|
1544 | n/a | def test_fold_unstructured_with_overlong_word(self): |
---|
1545 | n/a | h = self.make_header('Subject', 'thisisaverylonglineconsistingofa' |
---|
1546 | n/a | 'singlewordthatwontfit') |
---|
1547 | n/a | self.assertEqual( |
---|
1548 | n/a | h.fold(policy=policy.default.clone(max_line_length=20)), |
---|
1549 | n/a | 'Subject: thisisaverylonglineconsistingofasinglewordthatwontfit\n') |
---|
1550 | n/a | |
---|
1551 | n/a | def test_fold_unstructured_with_two_overlong_words(self): |
---|
1552 | n/a | h = self.make_header('Subject', 'thisisaverylonglineconsistingofa' |
---|
1553 | n/a | 'singlewordthatwontfit plusanotherverylongwordthatwontfit') |
---|
1554 | n/a | self.assertEqual( |
---|
1555 | n/a | h.fold(policy=policy.default.clone(max_line_length=20)), |
---|
1556 | n/a | 'Subject: thisisaverylonglineconsistingofasinglewordthatwontfit\n' |
---|
1557 | n/a | ' plusanotherverylongwordthatwontfit\n') |
---|
1558 | n/a | |
---|
1559 | n/a | def test_fold_unstructured_with_slightly_long_word(self): |
---|
1560 | n/a | h = self.make_header('Subject', 'thislongwordislessthanmaxlinelen') |
---|
1561 | n/a | self.assertEqual( |
---|
1562 | n/a | h.fold(policy=policy.default.clone(max_line_length=35)), |
---|
1563 | n/a | 'Subject:\n thislongwordislessthanmaxlinelen\n') |
---|
1564 | n/a | |
---|
1565 | n/a | def test_fold_unstructured_with_commas(self): |
---|
1566 | n/a | # The old wrapper would fold this at the commas. |
---|
1567 | n/a | h = self.make_header('Subject', "This header is intended to " |
---|
1568 | n/a | "demonstrate, in a fairly succinct way, that we now do " |
---|
1569 | n/a | "not give a , special treatment in unstructured headers.") |
---|
1570 | n/a | self.assertEqual( |
---|
1571 | n/a | h.fold(policy=policy.default.clone(max_line_length=60)), |
---|
1572 | n/a | textwrap.dedent("""\ |
---|
1573 | n/a | Subject: This header is intended to demonstrate, in a fairly |
---|
1574 | n/a | succinct way, that we now do not give a , special treatment |
---|
1575 | n/a | in unstructured headers. |
---|
1576 | n/a | """)) |
---|
1577 | n/a | |
---|
1578 | n/a | def test_fold_address_list(self): |
---|
1579 | n/a | h = self.make_header('To', '"Theodore H. Perfect" <yes@man.com>, ' |
---|
1580 | n/a | '"My address is very long because my name is long" <foo@bar.com>, ' |
---|
1581 | n/a | '"Only A. Friend" <no@yes.com>') |
---|
1582 | n/a | self.assertEqual(h.fold(policy=policy.default), textwrap.dedent("""\ |
---|
1583 | n/a | To: "Theodore H. Perfect" <yes@man.com>, |
---|
1584 | n/a | "My address is very long because my name is long" <foo@bar.com>, |
---|
1585 | n/a | "Only A. Friend" <no@yes.com> |
---|
1586 | n/a | """)) |
---|
1587 | n/a | |
---|
1588 | n/a | def test_fold_date_header(self): |
---|
1589 | n/a | h = self.make_header('Date', 'Sat, 2 Feb 2002 17:00:06 -0800') |
---|
1590 | n/a | self.assertEqual(h.fold(policy=policy.default), |
---|
1591 | n/a | 'Date: Sat, 02 Feb 2002 17:00:06 -0800\n') |
---|
1592 | n/a | |
---|
1593 | n/a | |
---|
1594 | n/a | |
---|
1595 | n/a | if __name__ == '__main__': |
---|
1596 | n/a | unittest.main() |
---|