@@ -411,34 +411,111 @@ def close_conn():
411411        reader .close ()
412412        return  body 
413413
414+     def  check_list_dir_dirname (self , dirname , quotedname = None ):
415+         fullpath  =  os .path .join (self .tempdir , dirname )
416+         try :
417+             os .mkdir (os .path .join (self .tempdir , dirname ))
418+         except  (OSError , UnicodeEncodeError ):
419+             self .skipTest (f'Can not create directory { dirname !a}  
420+                           f'on current file system' )
421+ 
422+         if  quotedname  is  None :
423+             quotedname  =  urllib .parse .quote (dirname , errors = 'surrogatepass' )
424+         response  =  self .request (self .base_url  +  '/'  +  quotedname  +  '/' )
425+         body  =  self .check_status_and_reason (response , HTTPStatus .OK )
426+         displaypath  =  html .escape (f'{ self .base_url } { dirname }  , quote = False )
427+         enc  =  sys .getfilesystemencoding ()
428+         prefix  =  f'listing for { displaypath }  .encode (enc , 'surrogateescape' )
429+         self .assertIn (prefix  +  b'title>' , body )
430+         self .assertIn (prefix  +  b'h1>' , body )
431+ 
432+     def  check_list_dir_filename (self , filename ):
433+         fullpath  =  os .path .join (self .tempdir , filename )
434+         content  =  ascii (fullpath ).encode () +  (os_helper .TESTFN_UNDECODABLE  or  b'\xff ' )
435+         try :
436+             with  open (fullpath , 'wb' ) as  f :
437+                 f .write (content )
438+         except  OSError :
439+             self .skipTest (f'Can not create file { filename !a}  
440+                           f'on current file system' )
441+ 
442+         response  =  self .request (self .base_url  +  '/' )
443+         body  =  self .check_status_and_reason (response , HTTPStatus .OK )
444+         quotedname  =  urllib .parse .quote (filename , errors = 'surrogatepass' )
445+         enc  =  response .headers .get_content_charset ()
446+         self .assertIsNotNone (enc )
447+         self .assertIn ((f'href="{ quotedname }  ).encode ('ascii' ), body )
448+         displayname  =  html .escape (filename , quote = False )
449+         self .assertIn (f'>{ displayname }  .encode (enc , 'surrogateescape' ), body )
450+ 
451+         response  =  self .request (self .base_url  +  '/'  +  quotedname )
452+         self .check_status_and_reason (response , HTTPStatus .OK , data = content )
453+ 
454+     @unittest .skipUnless (os_helper .TESTFN_NONASCII , 
455+                          'need os_helper.TESTFN_NONASCII' ) 
456+     def  test_list_dir_nonascii_dirname (self ):
457+         dirname  =  os_helper .TESTFN_NONASCII  +  '.dir' 
458+         self .check_list_dir_dirname (dirname )
459+ 
460+     @unittest .skipUnless (os_helper .TESTFN_NONASCII , 
461+                          'need os_helper.TESTFN_NONASCII' ) 
462+     def  test_list_dir_nonascii_filename (self ):
463+         filename  =  os_helper .TESTFN_NONASCII  +  '.txt' 
464+         self .check_list_dir_filename (filename )
465+ 
414466    @unittest .skipIf (is_apple , 
415467                     'undecodable name cannot always be decoded on Apple platforms' ) 
416468    @unittest .skipIf (sys .platform  ==  'win32' , 
417469                     'undecodable name cannot be decoded on win32' ) 
418470    @unittest .skipUnless (os_helper .TESTFN_UNDECODABLE , 
419471                         'need os_helper.TESTFN_UNDECODABLE' ) 
420-     def  test_undecodable_filename (self ):
421-         enc  =  sys .getfilesystemencoding ()
472+     def  test_list_dir_undecodable_dirname (self ):
473+         dirname  =  os .fsdecode (os_helper .TESTFN_UNDECODABLE ) +  '.dir' 
474+         self .check_list_dir_dirname (dirname )
475+ 
476+     @unittest .skipIf (is_apple , 
477+                      'undecodable name cannot always be decoded on Apple platforms' ) 
478+     @unittest .skipIf (sys .platform  ==  'win32' , 
479+                      'undecodable name cannot be decoded on win32' ) 
480+     @unittest .skipUnless (os_helper .TESTFN_UNDECODABLE , 
481+                          'need os_helper.TESTFN_UNDECODABLE' ) 
482+     def  test_list_dir_undecodable_filename (self ):
422483        filename  =  os .fsdecode (os_helper .TESTFN_UNDECODABLE ) +  '.txt' 
423-         with  open (os .path .join (self .tempdir , filename ), 'wb' ) as  f :
424-             f .write (os_helper .TESTFN_UNDECODABLE )
425-         response  =  self .request (self .base_url  +  '/' )
426-         if  is_apple :
427-             # On Apple platforms the HFS+ filesystem replaces bytes that 
428-             # aren't valid UTF-8 into a percent-encoded value. 
429-             for  name  in  os .listdir (self .tempdir ):
430-                 if  name  !=  'test' :  # Ignore a filename created in setUp(). 
431-                     filename  =  name 
432-                     break 
433-         body  =  self .check_status_and_reason (response , HTTPStatus .OK )
434-         quotedname  =  urllib .parse .quote (filename , errors = 'surrogatepass' )
435-         self .assertIn (('href="%s"'  %  quotedname )
436-                       .encode (enc , 'surrogateescape' ), body )
437-         self .assertIn (('>%s<'  %  html .escape (filename , quote = False ))
438-                       .encode (enc , 'surrogateescape' ), body )
439-         response  =  self .request (self .base_url  +  '/'  +  quotedname )
440-         self .check_status_and_reason (response , HTTPStatus .OK ,
441-                                      data = os_helper .TESTFN_UNDECODABLE )
484+         self .check_list_dir_filename (filename )
485+ 
486+     def  test_list_dir_undecodable_dirname2 (self ):
487+         dirname  =  '\ufffd .dir' 
488+         self .check_list_dir_dirname (dirname , quotedname = '%ff.dir' )
489+ 
490+     @unittest .skipUnless (os_helper .TESTFN_UNENCODABLE , 
491+                          'need os_helper.TESTFN_UNENCODABLE' ) 
492+     def  test_list_dir_unencodable_dirname (self ):
493+         dirname  =  os_helper .TESTFN_UNENCODABLE  +  '.dir' 
494+         self .check_list_dir_dirname (dirname )
495+ 
496+     @unittest .skipUnless (os_helper .TESTFN_UNENCODABLE , 
497+                          'need os_helper.TESTFN_UNENCODABLE' ) 
498+     def  test_list_dir_unencodable_filename (self ):
499+         filename  =  os_helper .TESTFN_UNENCODABLE  +  '.txt' 
500+         self .check_list_dir_filename (filename )
501+ 
502+     def  test_list_dir_escape_dirname (self ):
503+         # Characters that need special treating in URL or HTML. 
504+         for  name  in  ('q?' , 'f#' , '&' , '&' , '<i>' , '"dq"' , "'sq'" ,
505+                      '%A4' , '%E2%82%AC' ):
506+             with  self .subTest (name = name ):
507+                 dirname  =  name  +  '.dir' 
508+                 self .check_list_dir_dirname (dirname ,
509+                         quotedname = urllib .parse .quote (dirname , safe = '&<>\' "' ))
510+ 
511+     def  test_list_dir_escape_filename (self ):
512+         # Characters that need special treating in URL or HTML. 
513+         for  name  in  ('q?' , 'f#' , '&' , '&' , '<i>' , '"dq"' , "'sq'" ,
514+                      '%A4' , '%E2%82%AC' ):
515+             with  self .subTest (name = name ):
516+                 filename  =  name  +  '.txt' 
517+                 self .check_list_dir_filename (filename )
518+                 os_helper .unlink (os .path .join (self .tempdir , filename ))
442519
443520    def  test_undecodable_parameter (self ):
444521        # sanity check using a valid parameter 
@@ -620,27 +697,6 @@ def test_path_without_leading_slash(self):
620697        self .assertEqual (response .getheader ("Location" ),
621698                         self .tempdir_name  +  "/?hi=1" )
622699
623-     def  test_html_escape_filename (self ):
624-         filename  =  '<test&>.txt' 
625-         fullpath  =  os .path .join (self .tempdir , filename )
626- 
627-         try :
628-             open (fullpath , 'wb' ).close ()
629-         except  OSError :
630-             raise  unittest .SkipTest ('Can not create file %s on current file ' 
631-                                     'system'  %  filename )
632- 
633-         try :
634-             response  =  self .request (self .base_url  +  '/' )
635-             body  =  self .check_status_and_reason (response , HTTPStatus .OK )
636-             enc  =  response .headers .get_content_charset ()
637-         finally :
638-             os .unlink (fullpath )  # avoid affecting test_undecodable_filename 
639- 
640-         self .assertIsNotNone (enc )
641-         html_text  =  '>%s<'  %  html .escape (filename , quote = False )
642-         self .assertIn (html_text .encode (enc ), body )
643- 
644700
645701cgi_file1  =  """\  
646702
0 commit comments