Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit 0475a65

Browse files
author
Anselm Kruis
committed
Merge branch main into main-slp
This is the last merge before starting branch 3.8-slp.
2 parents 6f00f52 + 23d7ce7 commit 0475a65

12 files changed

+279
-31
lines changed

‎Doc/library/email.headerregistry.rst

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -321,19 +321,26 @@ variant, :attr:`~.BaseHeader.max_count` is set to 1.
321321

322322
The default mappings are:
323323

324-
:subject: UniqueUnstructuredHeader
325-
:date: UniqueDateHeader
326-
:resent-date: DateHeader
327-
:orig-date: UniqueDateHeader
328-
:sender: UniqueSingleAddressHeader
329-
:resent-sender: SingleAddressHeader
330-
:to: UniqueAddressHeader
331-
:resent-to: AddressHeader
332-
:cc: UniqueAddressHeader
333-
:resent-cc: AddressHeader
334-
:from: UniqueAddressHeader
335-
:resent-from: AddressHeader
336-
:reply-to: UniqueAddressHeader
324+
:subject: UniqueUnstructuredHeader
325+
:date: UniqueDateHeader
326+
:resent-date: DateHeader
327+
:orig-date: UniqueDateHeader
328+
:sender: UniqueSingleAddressHeader
329+
:resent-sender: SingleAddressHeader
330+
:to: UniqueAddressHeader
331+
:resent-to: AddressHeader
332+
:cc: UniqueAddressHeader
333+
:resent-cc: AddressHeader
334+
:bcc: UniqueAddressHeader
335+
:resent-bcc: AddressHeader
336+
:from: UniqueAddressHeader
337+
:resent-from: AddressHeader
338+
:reply-to: UniqueAddressHeader
339+
:mime-version: MIMEVersionHeader
340+
:content-type: ContentTypeHeader
341+
:content-disposition: ContentDispositionHeader
342+
:content-transfer-encoding: ContentTransferEncodingHeader
343+
:message-id: MessageIDHeader
337344

338345
``HeaderRegistry`` has the following methods:
339346

‎Doc/whatsnew/3.8.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,9 @@ unittest
760760
:meth:`~unittest.TestCase.setUpClass()`.
761761
(Contributed by Lisa Roach in :issue:`24412`.)
762762

763+
* Several mock assert functions now also print a list of actual calls upon
764+
failure. (Contributed by Petter Strandmark in :issue:`35047`.)
765+
763766
venv
764767
----
765768

‎Lib/email/_header_value_parser.py

Lines changed: 122 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -179,37 +179,30 @@ def comments(self):
179179

180180

181181
class UnstructuredTokenList(TokenList):
182-
183182
token_type = 'unstructured'
184183

185184

186185
class Phrase(TokenList):
187-
188186
token_type = 'phrase'
189187

190188
class Word(TokenList):
191-
192189
token_type = 'word'
193190

194191

195192
class CFWSList(WhiteSpaceTokenList):
196-
197193
token_type = 'cfws'
198194

199195

200196
class Atom(TokenList):
201-
202197
token_type = 'atom'
203198

204199

205200
class Token(TokenList):
206-
207201
token_type = 'token'
208202
encode_as_ew = False
209203

210204

211205
class EncodedWord(TokenList):
212-
213206
token_type = 'encoded-word'
214207
cte = None
215208
charset = None
@@ -496,16 +489,19 @@ def domain(self):
496489

497490

498491
class DotAtom(TokenList):
499-
500492
token_type = 'dot-atom'
501493

502494

503495
class DotAtomText(TokenList):
504-
505496
token_type = 'dot-atom-text'
506497
as_ew_allowed = True
507498

508499

500+
class NoFoldLiteral(TokenList):
501+
token_type = 'no-fold-literal'
502+
as_ew_allowed = False
503+
504+
509505
class AddrSpec(TokenList):
510506

511507
token_type = 'addr-spec'
@@ -809,35 +805,42 @@ def params(self):
809805

810806

811807
class ContentType(ParameterizedHeaderValue):
812-
813808
token_type = 'content-type'
814809
as_ew_allowed = False
815810
maintype = 'text'
816811
subtype = 'plain'
817812

818813

819814
class ContentDisposition(ParameterizedHeaderValue):
820-
821815
token_type = 'content-disposition'
822816
as_ew_allowed = False
823817
content_disposition = None
824818

825819

826820
class ContentTransferEncoding(TokenList):
827-
828821
token_type = 'content-transfer-encoding'
829822
as_ew_allowed = False
830823
cte = '7bit'
831824

832825

833826
class HeaderLabel(TokenList):
834-
835827
token_type = 'header-label'
836828
as_ew_allowed = False
837829

838830

839-
class Header(TokenList):
831+
class MsgID(TokenList):
832+
token_type = 'msg-id'
833+
as_ew_allowed = False
834+
835+
def fold(self, policy):
836+
# message-id tokens may not be folded.
837+
return str(self) + policy.linesep
838+
839+
class MessageID(MsgID):
840+
token_type = 'message-id'
840841

842+
843+
class Header(TokenList):
841844
token_type = 'header'
842845

843846

@@ -1583,7 +1586,7 @@ def get_addr_spec(value):
15831586
addr_spec.append(token)
15841587
if not value or value[0] != '@':
15851588
addr_spec.defects.append(errors.InvalidHeaderDefect(
1586-
"add-spec local part with no domain"))
1589+
"addr-spec local part with no domain"))
15871590
return addr_spec, value
15881591
addr_spec.append(ValueTerminal('@', 'address-at-symbol'))
15891592
token, value = get_domain(value[1:])
@@ -1968,6 +1971,110 @@ def get_address_list(value):
19681971
value = value[1:]
19691972
return address_list, value
19701973

1974+
1975+
def get_no_fold_literal(value):
1976+
""" no-fold-literal = "[" *dtext "]"
1977+
"""
1978+
no_fold_literal = NoFoldLiteral()
1979+
if not value:
1980+
raise errors.HeaderParseError(
1981+
"expected no-fold-literal but found '{}'".format(value))
1982+
if value[0] != '[':
1983+
raise errors.HeaderParseError(
1984+
"expected '[' at the start of no-fold-literal "
1985+
"but found '{}'".format(value))
1986+
no_fold_literal.append(ValueTerminal('[', 'no-fold-literal-start'))
1987+
value = value[1:]
1988+
token, value = get_dtext(value)
1989+
no_fold_literal.append(token)
1990+
if not value or value[0] != ']':
1991+
raise errors.HeaderParseError(
1992+
"expected ']' at the end of no-fold-literal "
1993+
"but found '{}'".format(value))
1994+
no_fold_literal.append(ValueTerminal(']', 'no-fold-literal-end'))
1995+
return no_fold_literal, value[1:]
1996+
1997+
def get_msg_id(value):
1998+
"""msg-id = [CFWS] "<" id-left '@' id-right ">" [CFWS]
1999+
id-left = dot-atom-text / obs-id-left
2000+
id-right = dot-atom-text / no-fold-literal / obs-id-right
2001+
no-fold-literal = "[" *dtext "]"
2002+
"""
2003+
msg_id = MsgID()
2004+
if value[0] in CFWS_LEADER:
2005+
token, value = get_cfws(value)
2006+
msg_id.append(token)
2007+
if not value or value[0] != '<':
2008+
raise errors.HeaderParseError(
2009+
"expected msg-id but found '{}'".format(value))
2010+
msg_id.append(ValueTerminal('<', 'msg-id-start'))
2011+
value = value[1:]
2012+
# Parse id-left.
2013+
try:
2014+
token, value = get_dot_atom_text(value)
2015+
except errors.HeaderParseError:
2016+
try:
2017+
# obs-id-left is same as local-part of add-spec.
2018+
token, value = get_obs_local_part(value)
2019+
msg_id.defects.append(errors.ObsoleteHeaderDefect(
2020+
"obsolete id-left in msg-id"))
2021+
except errors.HeaderParseError:
2022+
raise errors.HeaderParseError(
2023+
"expected dot-atom-text or obs-id-left"
2024+
" but found '{}'".format(value))
2025+
msg_id.append(token)
2026+
if not value or value[0] != '@':
2027+
msg_id.defects.append(errors.InvalidHeaderDefect(
2028+
"msg-id with no id-right"))
2029+
# Even though there is no id-right, if the local part
2030+
# ends with `>` let's just parse it too and return
2031+
# along with the defect.
2032+
if value and value[0] == '>':
2033+
msg_id.append(ValueTerminal('>', 'msg-id-end'))
2034+
value = value[1:]
2035+
return msg_id, value
2036+
msg_id.append(ValueTerminal('@', 'address-at-symbol'))
2037+
value = value[1:]
2038+
# Parse id-right.
2039+
try:
2040+
token, value = get_dot_atom_text(value)
2041+
except errors.HeaderParseError:
2042+
try:
2043+
token, value = get_no_fold_literal(value)
2044+
except errors.HeaderParseError as e:
2045+
try:
2046+
token, value = get_domain(value)
2047+
msg_id.defects.append(errors.ObsoleteHeaderDefect(
2048+
"obsolete id-right in msg-id"))
2049+
except errors.HeaderParseError:
2050+
raise errors.HeaderParseError(
2051+
"expected dot-atom-text, no-fold-literal or obs-id-right"
2052+
" but found '{}'".format(value))
2053+
msg_id.append(token)
2054+
if value and value[0] == '>':
2055+
value = value[1:]
2056+
else:
2057+
msg_id.defects.append(errors.InvalidHeaderDefect(
2058+
"missing trailing '>' on msg-id"))
2059+
msg_id.append(ValueTerminal('>', 'msg-id-end'))
2060+
if value and value[0] in CFWS_LEADER:
2061+
token, value = get_cfws(value)
2062+
msg_id.append(token)
2063+
return msg_id, value
2064+
2065+
2066+
def parse_message_id(value):
2067+
"""message-id = "Message-ID:" msg-id CRLF
2068+
"""
2069+
message_id = MessageID()
2070+
try:
2071+
token, value = get_msg_id(value)
2072+
except errors.HeaderParseError:
2073+
message_id.defects.append(errors.InvalidHeaderDefect(
2074+
"Expected msg-id but found {!r}".format(value)))
2075+
message_id.append(token)
2076+
return message_id
2077+
19712078
#
19722079
# XXX: As I begin to add additional header parsers, I'm realizing we probably
19732080
# have two level of parser routines: the get_XXX methods that get a token in

‎Lib/email/feedparser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ def _parsegen(self):
320320
self._cur.set_payload(EMPTYSTRING.join(lines))
321321
return
322322
# Make sure a valid content type was specified per RFC 2045:6.4.
323-
if (self._cur.get('content-transfer-encoding', '8bit').lower()
323+
if (str(self._cur.get('content-transfer-encoding', '8bit')).lower()
324324
not in ('7bit', '8bit', 'binary')):
325325
defect = errors.InvalidMultipartContentTransferEncodingDefect()
326326
self.policy.handle_defect(self._cur, defect)

‎Lib/email/headerregistry.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,18 @@ def cte(self):
520520
return self._cte
521521

522522

523+
class MessageIDHeader:
524+
525+
max_count = 1
526+
value_parser = staticmethod(parser.parse_message_id)
527+
528+
@classmethod
529+
def parse(cls, value, kwds):
530+
kwds['parse_tree'] = parse_tree = cls.value_parser(value)
531+
kwds['decoded'] = str(parse_tree)
532+
kwds['defects'].extend(parse_tree.all_defects)
533+
534+
523535
# The header factory #
524536

525537
_default_header_map = {
@@ -542,6 +554,7 @@ def cte(self):
542554
'content-type': ContentTypeHeader,
543555
'content-disposition': ContentDispositionHeader,
544556
'content-transfer-encoding': ContentTransferEncodingHeader,
557+
'message-id': MessageIDHeader,
545558
}
546559

547560
class HeaderRegistry:

‎Lib/test/test_email/test__header_value_parser.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2494,6 +2494,78 @@ def test_invalid_content_transfer_encoding(self):
24942494
";foo", ";foo", ";foo", [errors.InvalidHeaderDefect]*3
24952495
)
24962496

2497+
# get_msg_id
2498+
2499+
def test_get_msg_id_valid(self):
2500+
msg_id = self._test_get_x(
2501+
parser.get_msg_id,
2502+
"<simeple.local@example.something.com>",
2503+
"<simeple.local@example.something.com>",
2504+
"<simeple.local@example.something.com>",
2505+
[],
2506+
'',
2507+
)
2508+
self.assertEqual(msg_id.token_type, 'msg-id')
2509+
2510+
def test_get_msg_id_obsolete_local(self):
2511+
msg_id = self._test_get_x(
2512+
parser.get_msg_id,
2513+
'<"simeple.local"@example.com>',
2514+
'<"simeple.local"@example.com>',
2515+
'<simeple.local@example.com>',
2516+
[errors.ObsoleteHeaderDefect],
2517+
'',
2518+
)
2519+
self.assertEqual(msg_id.token_type, 'msg-id')
2520+
2521+
def test_get_msg_id_non_folding_literal_domain(self):
2522+
msg_id = self._test_get_x(
2523+
parser.get_msg_id,
2524+
"<simple.local@[someexamplecom.domain]>",
2525+
"<simple.local@[someexamplecom.domain]>",
2526+
"<simple.local@[someexamplecom.domain]>",
2527+
[],
2528+
"",
2529+
)
2530+
self.assertEqual(msg_id.token_type, 'msg-id')
2531+
2532+
2533+
def test_get_msg_id_obsolete_domain_part(self):
2534+
msg_id = self._test_get_x(
2535+
parser.get_msg_id,
2536+
"<simplelocal@(old)example.com>",
2537+
"<simplelocal@(old)example.com>",
2538+
"<simplelocal@ example.com>",
2539+
[errors.ObsoleteHeaderDefect],
2540+
""
2541+
)
2542+
2543+
def test_get_msg_id_no_id_right_part(self):
2544+
msg_id = self._test_get_x(
2545+
parser.get_msg_id,
2546+
"<simplelocal>",
2547+
"<simplelocal>",
2548+
"<simplelocal>",
2549+
[errors.InvalidHeaderDefect],
2550+
""
2551+
)
2552+
self.assertEqual(msg_id.token_type, 'msg-id')
2553+
2554+
def test_get_msg_id_no_angle_start(self):
2555+
with self.assertRaises(errors.HeaderParseError):
2556+
parser.get_msg_id("msgwithnoankle")
2557+
2558+
def test_get_msg_id_no_angle_end(self):
2559+
msg_id = self._test_get_x(
2560+
parser.get_msg_id,
2561+
"<simplelocal@domain",
2562+
"<simplelocal@domain>",
2563+
"<simplelocal@domain>",
2564+
[errors.InvalidHeaderDefect],
2565+
""
2566+
)
2567+
self.assertEqual(msg_id.token_type, 'msg-id')
2568+
24972569

24982570
@parameterize
24992571
class Test_parse_mime_parameters(TestParserMixin, TestEmailBase):

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /