@@ -281,6 +281,8 @@ def content_disposition_unquote(val, filename=False):
281281 but there are rare ambiguous edge cases where we have to guess. If in doubt,
282282 this function assumes a modern browser and follows the WHATWG HTML5
283283 specification (limited percent-encoding, no backslash-encoding).
284+
285+ If 'filename' is true, additional windows/ie6 legacy workarounds are applied.
284286 """
285287
286288 if val and '"' == val [0 ] == val [- 1 ]:
@@ -289,11 +291,11 @@ def content_disposition_unquote(val, filename=False):
289291 val = val .replace ("\\ \\ " , "\\ " ).replace ('\\ "' , '"' )
290292 elif "%" in val : # Modern (HTML5) limited percent-encoding
291293 val = val .replace ("%0D" , "\r " ).replace ("%0A" , "\n " ).replace ("%22" , '"' )
292- # ie6/windows bug: full path instead of just filename
293- if filename and (val [1 :3 ] == ":\\ " or val [:2 ] == "\\ \\ " ):
294- val = val .rpartition ("\\ " )[- 1 ]
295294 elif "%" in val : # Modern (HTML5) limited percent-encoding
296295 val = val .replace ("%0D" , "\r " ).replace ("%0A" , "\n " ).replace ("%22" , '"' )
296+ # ie6/windows bug: full path instead of just filename
297+ if filename and (val [1 :3 ] == ":\\ " or val [:2 ] == "\\ \\ " ):
298+ val = val .rpartition ("\\ " )[- 1 ]
297299 return val
298300
299301
@@ -317,6 +319,32 @@ def parse_options_header(header, options=None, unquote=header_unquote):
317319 return header [:i ].lower ().strip (), options
318320
319321
322+ def _parse_content_disposition (value ):
323+ """Specialized parser for Content-Disposition header values.
324+
325+ Returns a (disposition type, name, filename) tuple. All three
326+ can be empty or invalid, name and filename can be None.
327+ """
328+ # Fast path for Content-Disposition headers emitted by all modern browsers
329+ split = value .split ('"' , 4 )
330+ if split [0 ] == "form-data; name=" :
331+ if len (split ) == 3 and split [2 ] == "" :
332+ name = split [1 ]
333+ if "%" in name :
334+ name = content_disposition_unquote (name )
335+ return "form-data" , name , None
336+ if len (split ) == 5 and split [2 ] == "; filename=" and split [4 ] == "" :
337+ name , filename = split [1 ], split [3 ]
338+ if "%" in name :
339+ name = content_disposition_unquote (name )
340+ if "%" in filename or "\\ " in filename :
341+ filename = content_disposition_unquote (filename , True )
342+ return "form-data" , name , filename
343+ # Slow path for legacy browsers or non-browser clients
344+ dtype , opts = parse_options_header (value , unquote = content_disposition_unquote )
345+ return dtype , opts .get ("name" ), opts .get ("filename" )
346+
347+
320348##############################################################################
321349################################## SansIO Parser #############################
322350##############################################################################
@@ -857,11 +885,9 @@ def __init__(self, headerlist: List[Tuple[str, str]]):
857885
858886 for name , value in headerlist :
859887 if name == "Content-Disposition" :
860- self .disposition , args = parse_options_header (
861- value , unquote = content_disposition_unquote
888+ self .disposition , self . name , self . filename = _parse_content_disposition (
889+ value
862890 )
863- self .name = args .get ("name" )
864- self .filename = args .get ("filename" )
865891 elif name == "Content-Type" :
866892 self .content_type , args = parse_options_header (value )
867893 self .charset = args .get ("charset" )
0 commit comments