9595NLSET = {'\n ' , '\r ' }
9696SPECIALSNL = SPECIALS | NLSET
9797
98+
99+ def make_quoted_pairs (value ):
100+ """Escape dquote and backslash for use within a quoted-string."""
101+ return str (value ).replace ('\\ ' , '\\ \\ ' ).replace ('"' , '\\ "' )
102+
103+
98104def quote_string (value ):
99- return '"' + str (value ).replace ('\\ ' , '\\ \\ ' ).replace ('"' , r'\"' )+ '"'
105+ escaped = make_quoted_pairs (value )
106+ return f'"{ escaped } "'
107+
100108
101109# Match a RFC 2047 word, looks like =?utf-8?q?someword?=
102110rfc2047_matcher = re .compile (r'''
@@ -1012,6 +1020,8 @@ def _get_ptext_to_endchars(value, endchars):
10121020 a flag that is True iff there were any quoted printables decoded.
10131021
10141022 """
1023+ if not value :
1024+ return '' , '' , False
10151025 fragment , * remainder = _wsp_splitter (value , 1 )
10161026 vchars = []
10171027 escape = False
@@ -1045,7 +1055,7 @@ def get_fws(value):
10451055 fws = WhiteSpaceTerminal (value [:len (value )- len (newvalue )], 'fws' )
10461056 return fws , newvalue
10471057
1048- def get_encoded_word (value ):
1058+ def get_encoded_word (value , terminal_type = 'vtext' ):
10491059 """ encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
10501060
10511061 """
@@ -1084,7 +1094,7 @@ def get_encoded_word(value):
10841094 ew .append (token )
10851095 continue
10861096 chars , * remainder = _wsp_splitter (text , 1 )
1087- vtext = ValueTerminal (chars , 'vtext' )
1097+ vtext = ValueTerminal (chars , terminal_type )
10881098 _validate_xtext (vtext )
10891099 ew .append (vtext )
10901100 text = '' .join (remainder )
@@ -1126,7 +1136,7 @@ def get_unstructured(value):
11261136 valid_ew = True
11271137 if value .startswith ('=?' ):
11281138 try :
1129- token , value = get_encoded_word (value )
1139+ token , value = get_encoded_word (value , 'utext' )
11301140 except _InvalidEwError :
11311141 valid_ew = False
11321142 except errors .HeaderParseError :
@@ -1155,7 +1165,7 @@ def get_unstructured(value):
11551165 # the parser to go in an infinite loop.
11561166 if valid_ew and rfc2047_matcher .search (tok ):
11571167 tok , * remainder = value .partition ('=?' )
1158- vtext = ValueTerminal (tok , 'vtext ' )
1168+ vtext = ValueTerminal (tok , 'utext ' )
11591169 _validate_xtext (vtext )
11601170 unstructured .append (vtext )
11611171 value = '' .join (remainder )
@@ -1565,7 +1575,7 @@ def get_dtext(value):
15651575def _check_for_early_dl_end (value , domain_literal ):
15661576 if value :
15671577 return False
1568- domain_literal .append (errors .InvalidHeaderDefect (
1578+ domain_literal .defects . append (errors .InvalidHeaderDefect (
15691579 "end of input inside domain-literal" ))
15701580 domain_literal .append (ValueTerminal (']' , 'domain-literal-end' ))
15711581 return True
@@ -1584,9 +1594,9 @@ def get_domain_literal(value):
15841594 raise errors .HeaderParseError ("expected '[' at start of domain-literal "
15851595 "but found '{}'" .format (value ))
15861596 value = value [1 :]
1597+ domain_literal .append (ValueTerminal ('[' , 'domain-literal-start' ))
15871598 if _check_for_early_dl_end (value , domain_literal ):
15881599 return domain_literal , value
1589- domain_literal .append (ValueTerminal ('[' , 'domain-literal-start' ))
15901600 if value [0 ] in WSP :
15911601 token , value = get_fws (value )
15921602 domain_literal .append (token )
@@ -2805,7 +2815,7 @@ def _refold_parse_tree(parse_tree, *, policy):
28052815 continue
28062816 tstr = str (part )
28072817 if not want_encoding :
2808- if part .token_type == 'ptext' :
2818+ if part .token_type in ( 'ptext' , 'vtext' ) :
28092819 # Encode if tstr contains special characters.
28102820 want_encoding = not SPECIALSNL .isdisjoint (tstr )
28112821 else :
@@ -2905,6 +2915,15 @@ def _refold_parse_tree(parse_tree, *, policy):
29052915 if not hasattr (part , 'encode' ):
29062916 # It's not a terminal, try folding the subparts.
29072917 newparts = list (part )
2918+ if part .token_type == 'bare-quoted-string' :
2919+ # To fold a quoted string we need to create a list of terminal
2920+ # tokens that will render the leading and trailing quotes
2921+ # and use quoted pairs in the value as appropriate.
2922+ newparts = (
2923+ [ValueTerminal ('"' , 'ptext' )] +
2924+ [ValueTerminal (make_quoted_pairs (p ), 'ptext' )
2925+ for p in newparts ] +
2926+ [ValueTerminal ('"' , 'ptext' )])
29082927 if not part .as_ew_allowed :
29092928 wrap_as_ew_blocked += 1
29102929 newparts .append (end_ew_not_allowed )
0 commit comments