@@ -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 #!%s
0 commit comments