@@ -522,34 +522,111 @@ def close_conn():
522522        reader .close ()
523523        return  body 
524524
525+     def  check_list_dir_dirname (self , dirname , quotedname = None ):
526+         fullpath  =  os .path .join (self .tempdir , dirname )
527+         try :
528+             os .mkdir (os .path .join (self .tempdir , dirname ))
529+         except  (OSError , UnicodeEncodeError ):
530+             self .skipTest (f'Can not create directory { dirname !a}  
531+                           f'on current file system' )
532+ 
533+         if  quotedname  is  None :
534+             quotedname  =  urllib .parse .quote (dirname , errors = 'surrogatepass' )
535+         response  =  self .request (self .base_url  +  '/'  +  quotedname  +  '/' )
536+         body  =  self .check_status_and_reason (response , HTTPStatus .OK )
537+         displaypath  =  html .escape (f'{ self .base_url } { dirname }  , quote = False )
538+         enc  =  sys .getfilesystemencoding ()
539+         prefix  =  f'listing for { displaypath }  .encode (enc , 'surrogateescape' )
540+         self .assertIn (prefix  +  b'title>' , body )
541+         self .assertIn (prefix  +  b'h1>' , body )
542+ 
543+     def  check_list_dir_filename (self , filename ):
544+         fullpath  =  os .path .join (self .tempdir , filename )
545+         content  =  ascii (fullpath ).encode () +  (os_helper .TESTFN_UNDECODABLE  or  b'\xff ' )
546+         try :
547+             with  open (fullpath , 'wb' ) as  f :
548+                 f .write (content )
549+         except  OSError :
550+             self .skipTest (f'Can not create file { filename !a}  
551+                           f'on current file system' )
552+ 
553+         response  =  self .request (self .base_url  +  '/' )
554+         body  =  self .check_status_and_reason (response , HTTPStatus .OK )
555+         quotedname  =  urllib .parse .quote (filename , errors = 'surrogatepass' )
556+         enc  =  response .headers .get_content_charset ()
557+         self .assertIsNotNone (enc )
558+         self .assertIn ((f'href="{ quotedname }  ).encode ('ascii' ), body )
559+         displayname  =  html .escape (filename , quote = False )
560+         self .assertIn (f'>{ displayname }  .encode (enc , 'surrogateescape' ), body )
561+ 
562+         response  =  self .request (self .base_url  +  '/'  +  quotedname )
563+         self .check_status_and_reason (response , HTTPStatus .OK , data = content )
564+ 
565+     @unittest .skipUnless (os_helper .TESTFN_NONASCII , 
566+                          'need os_helper.TESTFN_NONASCII' ) 
567+     def  test_list_dir_nonascii_dirname (self ):
568+         dirname  =  os_helper .TESTFN_NONASCII  +  '.dir' 
569+         self .check_list_dir_dirname (dirname )
570+ 
571+     @unittest .skipUnless (os_helper .TESTFN_NONASCII , 
572+                          'need os_helper.TESTFN_NONASCII' ) 
573+     def  test_list_dir_nonascii_filename (self ):
574+         filename  =  os_helper .TESTFN_NONASCII  +  '.txt' 
575+         self .check_list_dir_filename (filename )
576+ 
525577    @unittest .skipIf (is_apple , 
526578                     'undecodable name cannot always be decoded on Apple platforms' ) 
527579    @unittest .skipIf (sys .platform  ==  'win32' , 
528580                     'undecodable name cannot be decoded on win32' ) 
529581    @unittest .skipUnless (os_helper .TESTFN_UNDECODABLE , 
530582                         'need os_helper.TESTFN_UNDECODABLE' ) 
531-     def  test_undecodable_filename (self ):
532-         enc  =  sys .getfilesystemencoding ()
583+     def  test_list_dir_undecodable_dirname (self ):
584+         dirname  =  os .fsdecode (os_helper .TESTFN_UNDECODABLE ) +  '.dir' 
585+         self .check_list_dir_dirname (dirname )
586+ 
587+     @unittest .skipIf (is_apple , 
588+                      'undecodable name cannot always be decoded on Apple platforms' ) 
589+     @unittest .skipIf (sys .platform  ==  'win32' , 
590+                      'undecodable name cannot be decoded on win32' ) 
591+     @unittest .skipUnless (os_helper .TESTFN_UNDECODABLE , 
592+                          'need os_helper.TESTFN_UNDECODABLE' ) 
593+     def  test_list_dir_undecodable_filename (self ):
533594        filename  =  os .fsdecode (os_helper .TESTFN_UNDECODABLE ) +  '.txt' 
534-         with  open (os .path .join (self .tempdir , filename ), 'wb' ) as  f :
535-             f .write (os_helper .TESTFN_UNDECODABLE )
536-         response  =  self .request (self .base_url  +  '/' )
537-         if  is_apple :
538-             # On Apple platforms the HFS+ filesystem replaces bytes that 
539-             # aren't valid UTF-8 into a percent-encoded value. 
540-             for  name  in  os .listdir (self .tempdir ):
541-                 if  name  !=  'test' :  # Ignore a filename created in setUp(). 
542-                     filename  =  name 
543-                     break 
544-         body  =  self .check_status_and_reason (response , HTTPStatus .OK )
545-         quotedname  =  urllib .parse .quote (filename , errors = 'surrogatepass' )
546-         self .assertIn (('href="%s"'  %  quotedname )
547-                       .encode (enc , 'surrogateescape' ), body )
548-         self .assertIn (('>%s<'  %  html .escape (filename , quote = False ))
549-                       .encode (enc , 'surrogateescape' ), body )
550-         response  =  self .request (self .base_url  +  '/'  +  quotedname )
551-         self .check_status_and_reason (response , HTTPStatus .OK ,
552-                                      data = os_helper .TESTFN_UNDECODABLE )
595+         self .check_list_dir_filename (filename )
596+ 
597+     def  test_list_dir_undecodable_dirname2 (self ):
598+         dirname  =  '\ufffd .dir' 
599+         self .check_list_dir_dirname (dirname , quotedname = '%ff.dir' )
600+ 
601+     @unittest .skipUnless (os_helper .TESTFN_UNENCODABLE , 
602+                          'need os_helper.TESTFN_UNENCODABLE' ) 
603+     def  test_list_dir_unencodable_dirname (self ):
604+         dirname  =  os_helper .TESTFN_UNENCODABLE  +  '.dir' 
605+         self .check_list_dir_dirname (dirname )
606+ 
607+     @unittest .skipUnless (os_helper .TESTFN_UNENCODABLE , 
608+                          'need os_helper.TESTFN_UNENCODABLE' ) 
609+     def  test_list_dir_unencodable_filename (self ):
610+         filename  =  os_helper .TESTFN_UNENCODABLE  +  '.txt' 
611+         self .check_list_dir_filename (filename )
612+ 
613+     def  test_list_dir_escape_dirname (self ):
614+         # Characters that need special treating in URL or HTML. 
615+         for  name  in  ('q?' , 'f#' , '&' , '&' , '<i>' , '"dq"' , "'sq'" ,
616+                      '%A4' , '%E2%82%AC' ):
617+             with  self .subTest (name = name ):
618+                 dirname  =  name  +  '.dir' 
619+                 self .check_list_dir_dirname (dirname ,
620+                         quotedname = urllib .parse .quote (dirname , safe = '&<>\' "' ))
621+ 
622+     def  test_list_dir_escape_filename (self ):
623+         # Characters that need special treating in URL or HTML. 
624+         for  name  in  ('q?' , 'f#' , '&' , '&' , '<i>' , '"dq"' , "'sq'" ,
625+                      '%A4' , '%E2%82%AC' ):
626+             with  self .subTest (name = name ):
627+                 filename  =  name  +  '.txt' 
628+                 self .check_list_dir_filename (filename )
629+                 os_helper .unlink (os .path .join (self .tempdir , filename ))
553630
554631    def  test_undecodable_parameter (self ):
555632        # sanity check using a valid parameter 
@@ -731,27 +808,6 @@ def test_path_without_leading_slash(self):
731808        self .assertEqual (response .getheader ("Location" ),
732809                         self .tempdir_name  +  "/?hi=1" )
733810
734-     def  test_html_escape_filename (self ):
735-         filename  =  '<test&>.txt' 
736-         fullpath  =  os .path .join (self .tempdir , filename )
737- 
738-         try :
739-             open (fullpath , 'wb' ).close ()
740-         except  OSError :
741-             raise  unittest .SkipTest ('Can not create file %s on current file ' 
742-                                     'system'  %  filename )
743- 
744-         try :
745-             response  =  self .request (self .base_url  +  '/' )
746-             body  =  self .check_status_and_reason (response , HTTPStatus .OK )
747-             enc  =  response .headers .get_content_charset ()
748-         finally :
749-             os .unlink (fullpath )  # avoid affecting test_undecodable_filename 
750- 
751-         self .assertIsNotNone (enc )
752-         html_text  =  '>%s<'  %  html .escape (filename , quote = False )
753-         self .assertIn (html_text .encode (enc ), body )
754- 
755811
756812cgi_file1  =  """\  
757813
0 commit comments