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