@@ -64,22 +64,68 @@ def _is_known_encoding(encoding: str) -> bool:
6464 return True
6565
6666
67- def _normalize_header_key (key : str | bytes , encoding : str | None = None ) -> bytes :
67+ def _normalize_header_key (
68+ key : str | bytes ,
69+ encoding : str | None = None ,
70+ header_name : str | bytes | None = None ,
71+ ) -> bytes :
6872 """
6973 Coerce str/bytes into a strictly byte-wise HTTP header key.
7074 """
71- return key if isinstance (key , bytes ) else key .encode (encoding or "ascii" )
72-
73-
74- def _normalize_header_value (value : str | bytes , encoding : str | None = None ) -> bytes :
75+ if isinstance (key , bytes ):
76+ return key
77+ try :
78+ return key .encode (encoding or "ascii" )
79+ except UnicodeEncodeError as exc :
80+ if header_name :
81+ name_str = (
82+ header_name
83+ if isinstance (header_name , str )
84+ else header_name .decode ("ascii" , errors = "replace" )
85+ )
86+ header_info = f" '{ name_str } '"
87+ else :
88+ header_info = ""
89+ raise UnicodeEncodeError (
90+ exc .encoding ,
91+ exc .object ,
92+ exc .start ,
93+ exc .end ,
94+ f"Header name{ header_info } contains non-ASCII characters" ,
95+ ) from exc
96+
97+
98+ def _normalize_header_value (
99+ value : str | bytes ,
100+ encoding : str | None = None ,
101+ header_name : str | bytes | None = None ,
102+ ) -> bytes :
75103 """
76104 Coerce str/bytes into a strictly byte-wise HTTP header value.
77105 """
78106 if isinstance (value , bytes ):
79107 return value
80108 if not isinstance (value , str ):
81109 raise TypeError (f"Header value must be str or bytes, not { type (value )} " )
82- return value .encode (encoding or "ascii" )
110+ try :
111+ return value .encode (encoding or "ascii" )
112+ except UnicodeEncodeError as exc :
113+ if header_name :
114+ name_str = (
115+ header_name
116+ if isinstance (header_name , str )
117+ else header_name .decode ("ascii" , errors = "replace" )
118+ )
119+ header_info = f" '{ name_str } '"
120+ else :
121+ header_info = ""
122+ raise UnicodeEncodeError (
123+ exc .encoding ,
124+ exc .object ,
125+ exc .start ,
126+ exc .end ,
127+ f"Header{ header_info } value contains non-ASCII characters" ,
128+ ) from exc
83129
84130
85131def _parse_content_type_charset (content_type : str ) -> str | None :
@@ -152,13 +198,13 @@ def __init__(
152198 self ._list = list (headers ._list )
153199 elif isinstance (headers , Mapping ):
154200 for k , v in headers .items ():
155- bytes_key = _normalize_header_key (k , encoding )
156- bytes_value = _normalize_header_value (v , encoding )
201+ bytes_key = _normalize_header_key (k , encoding , header_name = k )
202+ bytes_value = _normalize_header_value (v , encoding , header_name = k )
157203 self ._list .append ((bytes_key , bytes_key .lower (), bytes_value ))
158204 elif headers is not None :
159205 for k , v in headers :
160- bytes_key = _normalize_header_key (k , encoding )
161- bytes_value = _normalize_header_value (v , encoding )
206+ bytes_key = _normalize_header_key (k , encoding , header_name = k )
207+ bytes_value = _normalize_header_value (v , encoding , header_name = k )
162208 self ._list .append ((bytes_key , bytes_key .lower (), bytes_value ))
163209
164210 self ._encoding = encoding
0 commit comments