synapse 2.180.1__py311-none-any.whl → 2.181.0__py311-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of synapse might be problematic. Click here for more details.

Files changed (85) hide show
  1. synapse/assets/__init__.py +35 -0
  2. synapse/assets/storm/migrations/model-0.2.28.storm +355 -0
  3. synapse/common.py +2 -1
  4. synapse/cortex.py +49 -35
  5. synapse/cryotank.py +1 -1
  6. synapse/datamodel.py +30 -0
  7. synapse/lib/ast.py +12 -7
  8. synapse/lib/cell.py +1 -1
  9. synapse/lib/chop.py +0 -1
  10. synapse/lib/drive.py +8 -8
  11. synapse/lib/layer.py +55 -13
  12. synapse/lib/lmdbslab.py +26 -5
  13. synapse/lib/modelrev.py +28 -1
  14. synapse/lib/modules.py +1 -0
  15. synapse/lib/nexus.py +1 -1
  16. synapse/lib/node.py +5 -0
  17. synapse/lib/parser.py +23 -16
  18. synapse/lib/scrape.py +1 -1
  19. synapse/lib/slabseqn.py +2 -2
  20. synapse/lib/snap.py +129 -0
  21. synapse/lib/storm.lark +16 -2
  22. synapse/lib/storm.py +3 -0
  23. synapse/lib/storm_format.py +1 -0
  24. synapse/lib/stormhttp.py +34 -1
  25. synapse/lib/stormlib/auth.py +1 -1
  26. synapse/lib/stormlib/cortex.py +5 -2
  27. synapse/lib/stormlib/ipv6.py +2 -2
  28. synapse/lib/stormlib/model.py +114 -12
  29. synapse/lib/stormlib/project.py +1 -1
  30. synapse/lib/stormtypes.py +81 -7
  31. synapse/lib/types.py +7 -0
  32. synapse/lib/version.py +2 -2
  33. synapse/lib/view.py +47 -0
  34. synapse/models/inet.py +10 -3
  35. synapse/models/infotech.py +2 -1
  36. synapse/models/language.py +4 -0
  37. synapse/models/math.py +50 -0
  38. synapse/models/orgs.py +8 -0
  39. synapse/models/risk.py +9 -0
  40. synapse/tests/files/stormcov/pragma-nocov.storm +18 -0
  41. synapse/tests/test_assets.py +25 -0
  42. synapse/tests/test_cortex.py +129 -0
  43. synapse/tests/test_datamodel.py +6 -0
  44. synapse/tests/test_lib_grammar.py +7 -1
  45. synapse/tests/test_lib_layer.py +35 -0
  46. synapse/tests/test_lib_lmdbslab.py +11 -9
  47. synapse/tests/test_lib_modelrev.py +655 -1
  48. synapse/tests/test_lib_slabseqn.py +5 -4
  49. synapse/tests/test_lib_snap.py +4 -0
  50. synapse/tests/test_lib_storm.py +72 -1
  51. synapse/tests/test_lib_stormhttp.py +99 -1
  52. synapse/tests/test_lib_stormlib_cortex.py +21 -4
  53. synapse/tests/test_lib_stormlib_iters.py +8 -5
  54. synapse/tests/test_lib_stormlib_model.py +45 -6
  55. synapse/tests/test_lib_stormtypes.py +158 -2
  56. synapse/tests/test_lib_types.py +6 -0
  57. synapse/tests/test_model_inet.py +10 -0
  58. synapse/tests/test_model_language.py +4 -0
  59. synapse/tests/test_model_math.py +22 -0
  60. synapse/tests/test_model_orgs.py +6 -2
  61. synapse/tests/test_model_risk.py +4 -0
  62. synapse/tests/test_utils_stormcov.py +5 -0
  63. synapse/tests/utils.py +18 -5
  64. synapse/utils/stormcov/plugin.py +31 -1
  65. synapse/vendor/cpython/LICENSE +279 -0
  66. synapse/vendor/cpython/__init__.py +0 -0
  67. synapse/vendor/cpython/lib/__init__.py +0 -0
  68. synapse/vendor/cpython/lib/email/__init__.py +0 -0
  69. synapse/vendor/cpython/lib/email/_parseaddr.py +560 -0
  70. synapse/vendor/cpython/lib/email/utils.py +505 -0
  71. synapse/vendor/cpython/lib/ipaddress.py +2366 -0
  72. synapse/vendor/cpython/lib/test/__init__.py +0 -0
  73. synapse/vendor/cpython/lib/test/support/__init__.py +114 -0
  74. synapse/vendor/cpython/lib/test/test_email/__init__.py +0 -0
  75. synapse/vendor/cpython/lib/test/test_email/test_email.py +480 -0
  76. synapse/vendor/cpython/lib/test/test_email/test_utils.py +167 -0
  77. synapse/vendor/cpython/lib/test/test_ipaddress.py +2672 -0
  78. synapse/vendor/utils.py +4 -3
  79. {synapse-2.180.1.dist-info → synapse-2.181.0.dist-info}/METADATA +1 -1
  80. {synapse-2.180.1.dist-info → synapse-2.181.0.dist-info}/RECORD +83 -66
  81. {synapse-2.180.1.dist-info → synapse-2.181.0.dist-info}/WHEEL +1 -1
  82. synapse/lib/jupyter.py +0 -505
  83. synapse/tests/test_lib_jupyter.py +0 -224
  84. {synapse-2.180.1.dist-info → synapse-2.181.0.dist-info}/LICENSE +0 -0
  85. {synapse-2.180.1.dist-info → synapse-2.181.0.dist-info}/top_level.txt +0 -0
File without changes
@@ -0,0 +1,114 @@
1
+ ##############################################################################
2
+ # Taken from the cpython 3.11 source branch after the 3.11.10 release.
3
+ # It has been modified for vendored imports and to remove unused items.
4
+ ##############################################################################
5
+ # """Supporting definitions for the Python regression tests."""
6
+
7
+ if __name__ != 'synapse.vendor.cpython.lib.test.support':
8
+ raise ImportError('support must be imported from the test package')
9
+
10
+ import contextlib
11
+ import dataclasses
12
+ import functools
13
+ import os
14
+ import re
15
+ import stat
16
+ import sys
17
+ import sysconfig
18
+ import time
19
+ import types
20
+ import unittest
21
+ import warnings
22
+
23
+ __all__ = [
24
+ # miscellaneous
25
+ "run_with_tz",
26
+ "LARGEST", "SMALLEST",
27
+ ]
28
+
29
+ def run_with_tz(tz):
30
+ def decorator(func):
31
+ def inner(*args, **kwds):
32
+ try:
33
+ tzset = time.tzset
34
+ except AttributeError:
35
+ raise unittest.SkipTest("tzset required")
36
+ if 'TZ' in os.environ:
37
+ orig_tz = os.environ['TZ']
38
+ else:
39
+ orig_tz = None
40
+ os.environ['TZ'] = tz
41
+ tzset()
42
+
43
+ # now run the function, resetting the tz on exceptions
44
+ try:
45
+ return func(*args, **kwds)
46
+ finally:
47
+ if orig_tz is None:
48
+ del os.environ['TZ']
49
+ else:
50
+ os.environ['TZ'] = orig_tz
51
+ time.tzset()
52
+
53
+ inner.__name__ = func.__name__
54
+ inner.__doc__ = func.__doc__
55
+ return inner
56
+ return decorator
57
+
58
+ def patch(test_instance, object_to_patch, attr_name, new_value):
59
+ """Override 'object_to_patch'.'attr_name' with 'new_value'.
60
+
61
+ Also, add a cleanup procedure to 'test_instance' to restore
62
+ 'object_to_patch' value for 'attr_name'.
63
+ The 'attr_name' should be a valid attribute for 'object_to_patch'.
64
+
65
+ """
66
+ # check that 'attr_name' is a real attribute for 'object_to_patch'
67
+ # will raise AttributeError if it does not exist
68
+ getattr(object_to_patch, attr_name)
69
+
70
+ # keep a copy of the old value
71
+ attr_is_local = False
72
+ try:
73
+ old_value = object_to_patch.__dict__[attr_name]
74
+ except (AttributeError, KeyError):
75
+ old_value = getattr(object_to_patch, attr_name, None)
76
+ else:
77
+ attr_is_local = True
78
+
79
+ # restore the value when the test is done
80
+ def cleanup():
81
+ if attr_is_local:
82
+ setattr(object_to_patch, attr_name, old_value)
83
+ else:
84
+ delattr(object_to_patch, attr_name)
85
+
86
+ test_instance.addCleanup(cleanup)
87
+
88
+ # actually override the attribute
89
+ setattr(object_to_patch, attr_name, new_value)
90
+
91
+
92
+ @functools.total_ordering
93
+ class _LARGEST:
94
+ """
95
+ Object that is greater than anything (except itself).
96
+ """
97
+ def __eq__(self, other):
98
+ return isinstance(other, _LARGEST)
99
+ def __lt__(self, other):
100
+ return False
101
+
102
+ LARGEST = _LARGEST()
103
+
104
+ @functools.total_ordering
105
+ class _SMALLEST:
106
+ """
107
+ Object that is less than anything (except itself).
108
+ """
109
+ def __eq__(self, other):
110
+ return isinstance(other, _SMALLEST)
111
+ def __gt__(self, other):
112
+ return False
113
+
114
+ SMALLEST = _SMALLEST()
File without changes
@@ -0,0 +1,480 @@
1
+ ##############################################################################
2
+ # Taken from the cpython 3.11 source branch after the 3.11.10 release.
3
+ # It has been modified for vendored imports and vendored test harness, and
4
+ # then downselected for coverage of email.utils / email._parseaddr functions.
5
+ ##############################################################################
6
+ # Copyright (C) 2001-2010 Python Software Foundation
7
+ # Contact: email-sig@python.org
8
+ # email package unit tests
9
+
10
+ import time
11
+ import unittest
12
+
13
+ import synapse.vendor.cpython.lib.email.utils as utils
14
+
15
+ import synapse.vendor.utils as s_v_utils
16
+
17
+ class TestMiscellaneous(s_v_utils.VendorTest):
18
+
19
+ def test_formatdate(self):
20
+ now = time.time()
21
+ self.assertEqual(utils.parsedate(utils.formatdate(now))[:6],
22
+ time.gmtime(now)[:6])
23
+
24
+ def test_formatdate_localtime(self):
25
+ now = time.time()
26
+ self.assertEqual(
27
+ utils.parsedate(utils.formatdate(now, localtime=True))[:6],
28
+ time.localtime(now)[:6])
29
+
30
+ def test_formatdate_usegmt(self):
31
+ now = time.time()
32
+ self.assertEqual(
33
+ utils.formatdate(now, localtime=False),
34
+ time.strftime('%a, %d %b %Y %H:%M:%S -0000', time.gmtime(now)))
35
+ self.assertEqual(
36
+ utils.formatdate(now, localtime=False, usegmt=True),
37
+ time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(now)))
38
+
39
+ # parsedate and parsedate_tz will become deprecated interfaces someday
40
+ def test_parsedate_returns_None_for_invalid_strings(self):
41
+ # See also test_parsedate_to_datetime_with_invalid_raises_valueerror
42
+ # in test_utils.
43
+ invalid_dates = [
44
+ '',
45
+ ' ',
46
+ '0',
47
+ 'A Complete Waste of Time',
48
+ 'Wed, 3 Apr 2002 12.34.56.78+0800',
49
+ '17 June , 2022',
50
+ 'Friday, -Nov-82 16:14:55 EST',
51
+ 'Friday, Nov--82 16:14:55 EST',
52
+ 'Friday, 19-Nov- 16:14:55 EST',
53
+ ]
54
+ for dtstr in invalid_dates:
55
+ with self.subTest(dtstr=dtstr):
56
+ self.assertIsNone(utils.parsedate(dtstr))
57
+ self.assertIsNone(utils.parsedate_tz(dtstr))
58
+ # Not a part of the spec but, but this has historically worked:
59
+ self.assertIsNone(utils.parsedate(None))
60
+ self.assertIsNone(utils.parsedate_tz(None))
61
+
62
+ def test_parsedate_compact(self):
63
+ self.assertEqual(utils.parsedate_tz('Wed, 3 Apr 2002 14:58:26 +0800'),
64
+ (2002, 4, 3, 14, 58, 26, 0, 1, -1, 28800))
65
+ # The FWS after the comma is optional
66
+ self.assertEqual(utils.parsedate_tz('Wed,3 Apr 2002 14:58:26 +0800'),
67
+ (2002, 4, 3, 14, 58, 26, 0, 1, -1, 28800))
68
+ # The comma is optional
69
+ self.assertEqual(utils.parsedate_tz('Wed 3 Apr 2002 14:58:26 +0800'),
70
+ (2002, 4, 3, 14, 58, 26, 0, 1, -1, 28800))
71
+
72
+ def test_parsedate_no_dayofweek(self):
73
+ eq = self.assertEqual
74
+ eq(utils.parsedate_tz('5 Feb 2003 13:47:26 -0800'),
75
+ (2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800))
76
+ eq(utils.parsedate_tz('February 5, 2003 13:47:26 -0800'),
77
+ (2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800))
78
+
79
+ def test_parsedate_no_space_before_positive_offset(self):
80
+ self.assertEqual(utils.parsedate_tz('Wed, 3 Apr 2002 14:58:26+0800'),
81
+ (2002, 4, 3, 14, 58, 26, 0, 1, -1, 28800))
82
+
83
+ def test_parsedate_no_space_before_negative_offset(self):
84
+ # Issue 1155362: we already handled '+' for this case.
85
+ self.assertEqual(utils.parsedate_tz('Wed, 3 Apr 2002 14:58:26-0800'),
86
+ (2002, 4, 3, 14, 58, 26, 0, 1, -1, -28800))
87
+
88
+ def test_parsedate_accepts_time_with_dots(self):
89
+ eq = self.assertEqual
90
+ eq(utils.parsedate_tz('5 Feb 2003 13.47.26 -0800'),
91
+ (2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800))
92
+ eq(utils.parsedate_tz('5 Feb 2003 13.47 -0800'),
93
+ (2003, 2, 5, 13, 47, 0, 0, 1, -1, -28800))
94
+
95
+ def test_parsedate_rfc_850(self):
96
+ self.assertEqual(utils.parsedate_tz('Friday, 19-Nov-82 16:14:55 EST'),
97
+ (1982, 11, 19, 16, 14, 55, 0, 1, -1, -18000))
98
+
99
+ def test_parsedate_no_seconds(self):
100
+ self.assertEqual(utils.parsedate_tz('Wed, 3 Apr 2002 14:58 +0800'),
101
+ (2002, 4, 3, 14, 58, 0, 0, 1, -1, 28800))
102
+
103
+ def test_parsedate_dot_time_delimiter(self):
104
+ self.assertEqual(utils.parsedate_tz('Wed, 3 Apr 2002 14.58.26 +0800'),
105
+ (2002, 4, 3, 14, 58, 26, 0, 1, -1, 28800))
106
+ self.assertEqual(utils.parsedate_tz('Wed, 3 Apr 2002 14.58 +0800'),
107
+ (2002, 4, 3, 14, 58, 0, 0, 1, -1, 28800))
108
+
109
+ def test_parsedate_acceptable_to_time_functions(self):
110
+ eq = self.assertEqual
111
+ timetup = utils.parsedate('5 Feb 2003 13:47:26 -0800')
112
+ t = int(time.mktime(timetup))
113
+ eq(time.localtime(t)[:6], timetup[:6])
114
+ eq(int(time.strftime('%Y', timetup)), 2003)
115
+ timetup = utils.parsedate_tz('5 Feb 2003 13:47:26 -0800')
116
+ t = int(time.mktime(timetup[:9]))
117
+ eq(time.localtime(t)[:6], timetup[:6])
118
+ eq(int(time.strftime('%Y', timetup[:9])), 2003)
119
+
120
+ def test_mktime_tz(self):
121
+ self.assertEqual(utils.mktime_tz((1970, 1, 1, 0, 0, 0,
122
+ -1, -1, -1, 0)), 0)
123
+ self.assertEqual(utils.mktime_tz((1970, 1, 1, 0, 0, 0,
124
+ -1, -1, -1, 1234)), -1234)
125
+
126
+ def test_parsedate_y2k(self):
127
+ """Test for parsing a date with a two-digit year.
128
+
129
+ Parsing a date with a two-digit year should return the correct
130
+ four-digit year. RFC822 allows two-digit years, but RFC2822 (which
131
+ obsoletes RFC822) requires four-digit years.
132
+
133
+ """
134
+ self.assertEqual(utils.parsedate_tz('25 Feb 03 13:47:26 -0800'),
135
+ utils.parsedate_tz('25 Feb 2003 13:47:26 -0800'))
136
+ self.assertEqual(utils.parsedate_tz('25 Feb 71 13:47:26 -0800'),
137
+ utils.parsedate_tz('25 Feb 1971 13:47:26 -0800'))
138
+
139
+ def test_parseaddr_empty(self):
140
+ self.assertEqual(utils.parseaddr('<>'), ('', ''))
141
+ self.assertEqual(utils.formataddr(utils.parseaddr('<>')), '')
142
+
143
+ def test_parseaddr_multiple_domains(self):
144
+ self.assertEqual(
145
+ utils.parseaddr('a@b@c'),
146
+ ('', '')
147
+ )
148
+ self.assertEqual(
149
+ utils.parseaddr('a@b.c@c'),
150
+ ('', '')
151
+ )
152
+ self.assertEqual(
153
+ utils.parseaddr('a@172.17.0.1@c'),
154
+ ('', '')
155
+ )
156
+
157
+ def test_noquote_dump(self):
158
+ self.assertEqual(
159
+ utils.formataddr(('A Silly Person', 'person@dom.ain')),
160
+ 'A Silly Person <person@dom.ain>')
161
+
162
+ def test_escape_dump(self):
163
+ self.assertEqual(
164
+ utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
165
+ r'"A (Very) Silly Person" <person@dom.ain>')
166
+ self.assertEqual(
167
+ utils.parseaddr(r'"A \(Very\) Silly Person" <person@dom.ain>'),
168
+ ('A (Very) Silly Person', 'person@dom.ain'))
169
+ a = r'A \(Special\) Person'
170
+ b = 'person@dom.ain'
171
+ self.assertEqual(utils.parseaddr(utils.formataddr((a, b))), (a, b))
172
+
173
+ def test_escape_backslashes(self):
174
+ self.assertEqual(
175
+ utils.formataddr((r'Arthur \Backslash\ Foobar', 'person@dom.ain')),
176
+ r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>')
177
+ a = r'Arthur \Backslash\ Foobar'
178
+ b = 'person@dom.ain'
179
+ self.assertEqual(utils.parseaddr(utils.formataddr((a, b))), (a, b))
180
+
181
+ def test_quotes_unicode_names(self):
182
+ # issue 1690608. email.utils.formataddr() should be rfc2047 aware.
183
+ name = "H\u00e4ns W\u00fcrst"
184
+ addr = 'person@dom.ain'
185
+ utf8_base64 = "=?utf-8?b?SMOkbnMgV8O8cnN0?= <person@dom.ain>"
186
+ latin1_quopri = "=?iso-8859-1?q?H=E4ns_W=FCrst?= <person@dom.ain>"
187
+ self.assertEqual(utils.formataddr((name, addr)), utf8_base64)
188
+ self.assertEqual(utils.formataddr((name, addr), 'iso-8859-1'),
189
+ latin1_quopri)
190
+
191
+ def test_invalid_charset_like_object_raises_error(self):
192
+ # issue 1690608. email.utils.formataddr() should be rfc2047 aware.
193
+ name = "H\u00e4ns W\u00fcrst"
194
+ addr = 'person@dom.ain'
195
+ # An object without a header_encode method:
196
+ bad_charset = object()
197
+ self.assertRaises(AttributeError, utils.formataddr, (name, addr),
198
+ bad_charset)
199
+
200
+ def test_unicode_address_raises_error(self):
201
+ # issue 1690608. email.utils.formataddr() should be rfc2047 aware.
202
+ addr = 'pers\u00f6n@dom.in'
203
+ self.assertRaises(UnicodeError, utils.formataddr, (None, addr))
204
+ self.assertRaises(UnicodeError, utils.formataddr, ("Name", addr))
205
+
206
+ def test_name_with_dot(self):
207
+ x = 'John X. Doe <jxd@example.com>'
208
+ y = '"John X. Doe" <jxd@example.com>'
209
+ a, b = ('John X. Doe', 'jxd@example.com')
210
+ self.assertEqual(utils.parseaddr(x), (a, b))
211
+ self.assertEqual(utils.parseaddr(y), (a, b))
212
+ # formataddr() quotes the name if there's a dot in it
213
+ self.assertEqual(utils.formataddr((a, b)), y)
214
+
215
+ def test_parseaddr_preserves_quoted_pairs_in_addresses(self):
216
+ # issue 10005. Note that in the third test the second pair of
217
+ # backslashes is not actually a quoted pair because it is not inside a
218
+ # comment or quoted string: the address being parsed has a quoted
219
+ # string containing a quoted backslash, followed by 'example' and two
220
+ # backslashes, followed by another quoted string containing a space and
221
+ # the word 'example'. parseaddr copies those two backslashes
222
+ # literally. Per rfc5322 this is not technically correct since a \ may
223
+ # not appear in an address outside of a quoted string. It is probably
224
+ # a sensible Postel interpretation, though.
225
+ eq = self.assertEqual
226
+ eq(utils.parseaddr('""example" example"@example.com'),
227
+ ('', '""example" example"@example.com'))
228
+ eq(utils.parseaddr('"\\"example\\" example"@example.com'),
229
+ ('', '"\\"example\\" example"@example.com'))
230
+ eq(utils.parseaddr('"\\\\"example\\\\" example"@example.com'),
231
+ ('', '"\\\\"example\\\\" example"@example.com'))
232
+
233
+ def test_parseaddr_preserves_spaces_in_local_part(self):
234
+ # issue 9286. A normal RFC5322 local part should not contain any
235
+ # folding white space, but legacy local parts can (they are a sequence
236
+ # of atoms, not dotatoms). On the other hand we strip whitespace from
237
+ # before the @ and around dots, on the assumption that the whitespace
238
+ # around the punctuation is a mistake in what would otherwise be
239
+ # an RFC5322 local part. Leading whitespace is, usual, stripped as well.
240
+ self.assertEqual(('', "merwok wok@xample.com"),
241
+ utils.parseaddr("merwok wok@xample.com"))
242
+ self.assertEqual(('', "merwok wok@xample.com"),
243
+ utils.parseaddr("merwok wok@xample.com"))
244
+ self.assertEqual(('', "merwok wok@xample.com"),
245
+ utils.parseaddr(" merwok wok @xample.com"))
246
+ self.assertEqual(('', 'merwok"wok" wok@xample.com'),
247
+ utils.parseaddr('merwok"wok" wok@xample.com'))
248
+ self.assertEqual(('', 'merwok.wok.wok@xample.com'),
249
+ utils.parseaddr('merwok. wok . wok@xample.com'))
250
+
251
+ def test_formataddr_does_not_quote_parens_in_quoted_string(self):
252
+ addr = ("'foo@example.com' (foo@example.com)",
253
+ 'foo@example.com')
254
+ addrstr = ('"\'foo@example.com\' '
255
+ '(foo@example.com)" <foo@example.com>')
256
+ self.assertEqual(utils.parseaddr(addrstr), addr)
257
+ self.assertEqual(utils.formataddr(addr), addrstr)
258
+
259
+ def test_multiline_from_comment(self):
260
+ x = """\
261
+ Foo
262
+ \tBar <foo@example.com>"""
263
+ self.assertEqual(utils.parseaddr(x), ('Foo Bar', 'foo@example.com'))
264
+
265
+ def test_quote_dump(self):
266
+ self.assertEqual(
267
+ utils.formataddr(('A Silly; Person', 'person@dom.ain')),
268
+ r'"A Silly; Person" <person@dom.ain>')
269
+
270
+ def test_getaddresses(self):
271
+ eq = self.assertEqual
272
+ eq(utils.getaddresses(['aperson@dom.ain (Al Person)',
273
+ 'Bud Person <bperson@dom.ain>']),
274
+ [('Al Person', 'aperson@dom.ain'),
275
+ ('Bud Person', 'bperson@dom.ain')])
276
+
277
+ def test_getaddresses_comma_in_name(self):
278
+ """GH-106669 regression test."""
279
+ self.assertEqual(
280
+ utils.getaddresses(
281
+ [
282
+ '"Bud, Person" <bperson@dom.ain>',
283
+ 'aperson@dom.ain (Al Person)',
284
+ '"Mariusz Felisiak" <to@example.com>',
285
+ ]
286
+ ),
287
+ [
288
+ ('Bud, Person', 'bperson@dom.ain'),
289
+ ('Al Person', 'aperson@dom.ain'),
290
+ ('Mariusz Felisiak', 'to@example.com'),
291
+ ],
292
+ )
293
+
294
+ def test_parsing_errors(self):
295
+ """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056"""
296
+ alice = 'alice@example.org'
297
+ bob = 'bob@example.com'
298
+ empty = ('', '')
299
+
300
+ # Test utils.getaddresses() and utils.parseaddr() on malformed email
301
+ # addresses: default behavior (strict=True) rejects malformed address,
302
+ # and strict=False which tolerates malformed address.
303
+ for invalid_separator, expected_non_strict in (
304
+ ('(', [(f'<{bob}>', alice)]),
305
+ (')', [('', alice), empty, ('', bob)]),
306
+ ('<', [('', alice), empty, ('', bob), empty]),
307
+ ('>', [('', alice), empty, ('', bob)]),
308
+ ('[', [('', f'{alice}[<{bob}>]')]),
309
+ (']', [('', alice), empty, ('', bob)]),
310
+ ('@', [empty, empty, ('', bob)]),
311
+ (';', [('', alice), empty, ('', bob)]),
312
+ (':', [('', alice), ('', bob)]),
313
+ ('.', [('', alice + '.'), ('', bob)]),
314
+ ('"', [('', alice), ('', f'<{bob}>')]),
315
+ ):
316
+ address = f'{alice}{invalid_separator}<{bob}>'
317
+ with self.subTest(address=address):
318
+ self.assertEqual(utils.getaddresses([address]),
319
+ [empty])
320
+ self.assertEqual(utils.getaddresses([address], strict=False),
321
+ expected_non_strict)
322
+
323
+ self.assertEqual(utils.parseaddr([address]),
324
+ empty)
325
+ self.assertEqual(utils.parseaddr([address], strict=False),
326
+ ('', address))
327
+
328
+ # Comma (',') is treated differently depending on strict parameter.
329
+ # Comma without quotes.
330
+ address = f'{alice},<{bob}>'
331
+ self.assertEqual(utils.getaddresses([address]),
332
+ [('', alice), ('', bob)])
333
+ self.assertEqual(utils.getaddresses([address], strict=False),
334
+ [('', alice), ('', bob)])
335
+ self.assertEqual(utils.parseaddr([address]),
336
+ empty)
337
+ self.assertEqual(utils.parseaddr([address], strict=False),
338
+ ('', address))
339
+
340
+ # Real name between quotes containing comma.
341
+ address = '"Alice, alice@example.org" <bob@example.com>'
342
+ expected_strict = ('Alice, alice@example.org', 'bob@example.com')
343
+ self.assertEqual(utils.getaddresses([address]), [expected_strict])
344
+ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict])
345
+ self.assertEqual(utils.parseaddr([address]), expected_strict)
346
+ self.assertEqual(utils.parseaddr([address], strict=False),
347
+ ('', address))
348
+
349
+ # Valid parenthesis in comments.
350
+ address = 'alice@example.org (Alice)'
351
+ expected_strict = ('Alice', 'alice@example.org')
352
+ self.assertEqual(utils.getaddresses([address]), [expected_strict])
353
+ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict])
354
+ self.assertEqual(utils.parseaddr([address]), expected_strict)
355
+ self.assertEqual(utils.parseaddr([address], strict=False),
356
+ ('', address))
357
+
358
+ # Invalid parenthesis in comments.
359
+ address = 'alice@example.org )Alice('
360
+ self.assertEqual(utils.getaddresses([address]), [empty])
361
+ self.assertEqual(utils.getaddresses([address], strict=False),
362
+ [('', 'alice@example.org'), ('', ''), ('', 'Alice')])
363
+ self.assertEqual(utils.parseaddr([address]), empty)
364
+ self.assertEqual(utils.parseaddr([address], strict=False),
365
+ ('', address))
366
+
367
+ # Two addresses with quotes separated by comma.
368
+ address = '"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>'
369
+ self.assertEqual(utils.getaddresses([address]),
370
+ [('Jane Doe', 'jane@example.net'),
371
+ ('John Doe', 'john@example.net')])
372
+ self.assertEqual(utils.getaddresses([address], strict=False),
373
+ [('Jane Doe', 'jane@example.net'),
374
+ ('John Doe', 'john@example.net')])
375
+ self.assertEqual(utils.parseaddr([address]), empty)
376
+ self.assertEqual(utils.parseaddr([address], strict=False),
377
+ ('', address))
378
+
379
+ # Test email.utils.supports_strict_parsing attribute
380
+ self.assertEqual(utils.supports_strict_parsing, True)
381
+
382
+ def test_getaddresses_nasty(self):
383
+ for addresses, expected in (
384
+ (['"Sürname, Firstname" <to@example.com>'],
385
+ [('Sürname, Firstname', 'to@example.com')]),
386
+
387
+ (['foo: ;'],
388
+ [('', '')]),
389
+
390
+ (['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>'],
391
+ [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]),
392
+
393
+ ([r'Pete(A nice \) chap) <pete(his account)@silly.test(his host)>'],
394
+ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]),
395
+
396
+ (['(Empty list)(start)Undisclosed recipients :(nobody(I know))'],
397
+ [('', '')]),
398
+
399
+ (['Mary <@machine.tld:mary@example.net>, , jdoe@test . example'],
400
+ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]),
401
+
402
+ (['John Doe <jdoe@machine(comment). example>'],
403
+ [('John Doe (comment)', 'jdoe@machine.example')]),
404
+
405
+ (['"Mary Smith: Personal Account" <smith@home.example>'],
406
+ [('Mary Smith: Personal Account', 'smith@home.example')]),
407
+
408
+ (['Undisclosed recipients:;'],
409
+ [('', '')]),
410
+
411
+ ([r'<boss@nil.test>, "Giant; \"Big\" Box" <bob@example.net>'],
412
+ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]),
413
+ ):
414
+ with self.subTest(addresses=addresses):
415
+ self.assertEqual(utils.getaddresses(addresses),
416
+ expected)
417
+ self.assertEqual(utils.getaddresses(addresses, strict=False),
418
+ expected)
419
+
420
+ addresses = ['[]*-- =~$']
421
+ self.assertEqual(utils.getaddresses(addresses),
422
+ [('', '')])
423
+ self.assertEqual(utils.getaddresses(addresses, strict=False),
424
+ [('', ''), ('', ''), ('', '*--')])
425
+
426
+ def test_getaddresses_embedded_comment(self):
427
+ """Test proper handling of a nested comment"""
428
+ eq = self.assertEqual
429
+ addrs = utils.getaddresses(['User ((nested comment)) <foo@bar.com>'])
430
+ eq(addrs[0][1], 'foo@bar.com')
431
+
432
+ def test_iter_escaped_chars(self):
433
+ self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')),
434
+ [(0, 'a'),
435
+ (2, '\\\\'),
436
+ (3, 'b'),
437
+ (5, '\\"'),
438
+ (6, 'c'),
439
+ (8, '\\\\'),
440
+ (9, '"'),
441
+ (10, 'd')])
442
+ self.assertEqual(list(utils._iter_escaped_chars('a\\')),
443
+ [(0, 'a'), (1, '\\')])
444
+
445
+ def test_strip_quoted_realnames(self):
446
+ def check(addr, expected):
447
+ self.assertEqual(utils._strip_quoted_realnames(addr), expected)
448
+
449
+ check('"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>',
450
+ ' <jane@example.net>, <john@example.net>')
451
+ check(r'"Jane \"Doe\"." <jane@example.net>',
452
+ ' <jane@example.net>')
453
+
454
+ # special cases
455
+ check(r'before"name"after', 'beforeafter')
456
+ check(r'before"name"', 'before')
457
+ check(r'b"name"', 'b') # single char
458
+ check(r'"name"after', 'after')
459
+ check(r'"name"a', 'a') # single char
460
+ check(r'"name"', '')
461
+
462
+ # no change
463
+ for addr in (
464
+ 'Jane Doe <jane@example.net>, John Doe <john@example.net>',
465
+ 'lone " quote',
466
+ ):
467
+ self.assertEqual(utils._strip_quoted_realnames(addr), addr)
468
+
469
+ def test_check_parenthesis(self):
470
+ addr = 'alice@example.net'
471
+ self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)'))
472
+ self.assertFalse(utils._check_parenthesis(f'{addr} )Alice('))
473
+ self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))'))
474
+ self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)'))
475
+
476
+ # Ignore real name between quotes
477
+ self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}'))
478
+
479
+ if __name__ == '__main__': # pragma: no cover
480
+ unittest.main()