@@ -157,10 +157,7 @@ def all_defects(self):
157157 def startswith_fws (self ):
158158 return self [0 ].startswith_fws ()
159159
160- @property
161- def as_ew_allowed (self ):
162- """True if all top level tokens of this part may be RFC2047 encoded."""
163- return all (part .as_ew_allowed for part in self )
160+ as_ew_allowed = True
164161
165162 @property
166163 def comments (self ):
@@ -429,6 +426,7 @@ def addr_spec(self):
429426class AngleAddr (TokenList ):
430427
431428 token_type = 'angle-addr'
429+ as_ew_allowed = False
432430
433431 @property
434432 def local_part (self ):
@@ -847,26 +845,22 @@ def params(self):
847845
848846class ContentType (ParameterizedHeaderValue ):
849847 token_type = 'content-type'
850- as_ew_allowed = False
851848 maintype = 'text'
852849 subtype = 'plain'
853850
854851
855852class ContentDisposition (ParameterizedHeaderValue ):
856853 token_type = 'content-disposition'
857- as_ew_allowed = False
858854 content_disposition = None
859855
860856
861857class ContentTransferEncoding (TokenList ):
862858 token_type = 'content-transfer-encoding'
863- as_ew_allowed = False
864859 cte = '7bit'
865860
866861
867862class HeaderLabel (TokenList ):
868863 token_type = 'header-label'
869- as_ew_allowed = False
870864
871865
872866class MsgID (TokenList ):
@@ -2838,13 +2832,68 @@ def _steal_trailing_WSP_if_exists(lines):
28382832
28392833
28402834def _refold_parse_tree (parse_tree , * , policy ):
2841- """Return string of contents of parse_tree folded according to RFC rules.
2842-
2843- """
28442835 # max_line_length 0/None means no limit, ie: infinitely long.
28452836 maxlen = policy .max_line_length or sys .maxsize
28462837 encoding = 'utf-8' if policy .utf8 else 'us-ascii'
28472838 lines = ['' ] # Folded lines to be output
2839+ if parse_tree .as_ew_allowed :
2840+ _refold_with_ew (parse_tree , lines , maxlen , encoding , policy = policy )
2841+ else :
2842+ _refold_without_ew (parse_tree , lines , maxlen , encoding , policy = policy )
2843+ return policy .linesep .join (lines ) + policy .linesep
2844+
2845+ def _refold_without_ew (parse_tree , lines , maxlen , encoding , * , policy ):
2846+ parts = list (parse_tree )
2847+ while parts :
2848+ part = parts .pop (0 )
2849+ tstr = str (part )
2850+ try :
2851+ tstr .encode (encoding )
2852+ except UnicodeEncodeError :
2853+ if any (isinstance (x , errors .UndecodableBytesDefect )
2854+ for x in part .all_defects ):
2855+ # There is garbage data from parsing a message in binary mode,
2856+ # just pass it through. Not good, but the best we can do.
2857+ pass
2858+ elif policy .utf8 :
2859+ # If this happens, it's a programmer error.
2860+ raise
2861+ else :
2862+ raise errors .HeaderWriteError (
2863+ f"Non-ASCII { part .token_type } '{ part } ' is invalid"
2864+ " under current policy setting (utf8=False)"
2865+ )
2866+ if len (tstr ) <= maxlen - len (lines [- 1 ]):
2867+ lines [- 1 ] += tstr
2868+ continue
2869+ # This part is too long to fit. The RFC wants us to break at
2870+ # "major syntactic breaks", so unless we don't consider this
2871+ # to be one, check if it will fit on the next line by itself.
2872+ if (part .syntactic_break and
2873+ len (tstr ) + 1 <= maxlen ):
2874+ newline = _steal_trailing_WSP_if_exists (lines )
2875+ if newline or part .startswith_fws ():
2876+ lines .append (newline + tstr )
2877+ continue
2878+ if not hasattr (part , 'encode' ):
2879+ # It's not a terminal, try folding the subparts.
2880+ newparts = list (part )
2881+ parts = newparts + parts
2882+ continue
2883+ # We can't figure out how to wrap, it, so give up.
2884+ newline = _steal_trailing_WSP_if_exists (lines )
2885+ if newline or part .startswith_fws ():
2886+ lines .append (newline + tstr )
2887+ else :
2888+ # We can't fold it onto the next line either...
2889+ lines [- 1 ] += tstr
2890+ return
2891+
2892+
2893+ def _refold_with_ew (parse_tree , lines , maxlen , encoding , * , policy ):
2894+ """Return string of contents of parse_tree folded according to RFC rules.
2895+
2896+ """
28482897 last_word_is_ew = False
28492898 last_ew = None # if there is an encoded word in the last line of lines,
28502899 # points to the encoded word's first character
@@ -2885,7 +2934,10 @@ def _refold_parse_tree(parse_tree, *, policy):
28852934 want_encoding = True
28862935
28872936 if want_encoding and not wrap_as_ew_blocked :
2888- if not part .as_ew_allowed :
2937+ if any (
2938+ not x .as_ew_allowed for x in part
2939+ if hasattr (x , 'as_ew_allowed' )
2940+ ):
28892941 want_encoding = False
28902942 last_ew = None
28912943 if part .syntactic_break :
@@ -2966,6 +3018,8 @@ def _refold_parse_tree(parse_tree, *, policy):
29663018 [ValueTerminal (make_quoted_pairs (p ), 'ptext' )
29673019 for p in newparts ] +
29683020 [ValueTerminal ('"' , 'ptext' )])
3021+ _refold_without_ew (newparts , lines , maxlen , encoding , policy = policy )
3022+ continue
29693023 if part .token_type == 'comment' :
29703024 newparts = (
29713025 [ValueTerminal ('(' , 'ptext' )] +
@@ -2993,7 +3047,7 @@ def _refold_parse_tree(parse_tree, *, policy):
29933047 lines [- 1 ] += tstr
29943048 last_word_is_ew = last_word_is_ew and not bool (tstr .strip (_WSP ))
29953049
2996- return policy . linesep . join ( lines ) + policy . linesep
3050+ return
29973051
29983052def _fold_as_ew (to_encode , lines , maxlen , last_ew , ew_combine_allowed , charset , last_word_is_ew ):
29993053 """Fold string to_encode into lines as encoded word, combining if allowed.
0 commit comments