Home | History | Annotate | Download | only in test
      1 """Unittests for the various HTTPServer modules.
      2 
      3 Written by Cody A.W. Somerville <cody-somerville (at] ubuntu.com>,
      4 Josip Dzolonga, and Michael Otteneder for the 2007/08 GHOP contest.
      5 """
      6 
      7 import os
      8 import sys
      9 import re
     10 import base64
     11 import ntpath
     12 import shutil
     13 import urllib
     14 import httplib
     15 import tempfile
     16 import unittest
     17 import CGIHTTPServer
     18 
     19 
     20 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
     21 from SimpleHTTPServer import SimpleHTTPRequestHandler
     22 from CGIHTTPServer import CGIHTTPRequestHandler
     23 from StringIO import StringIO
     24 from test import test_support
     25 
     26 
     27 threading = test_support.import_module('threading')
     28 
     29 
     30 class NoLogRequestHandler:
     31     def log_message(self, *args):
     32         # don't write log messages to stderr
     33         pass
     34 
     35 class SocketlessRequestHandler(SimpleHTTPRequestHandler):
     36     def __init__(self):
     37         self.get_called = False
     38         self.protocol_version = "HTTP/1.1"
     39 
     40     def do_GET(self):
     41         self.get_called = True
     42         self.send_response(200)
     43         self.send_header('Content-Type', 'text/html')
     44         self.end_headers()
     45         self.wfile.write(b'<html><body>Data</body></html>\r\n')
     46 
     47     def log_message(self, fmt, *args):
     48         pass
     49 
     50 
     51 class TestServerThread(threading.Thread):
     52     def __init__(self, test_object, request_handler):
     53         threading.Thread.__init__(self)
     54         self.request_handler = request_handler
     55         self.test_object = test_object
     56 
     57     def run(self):
     58         self.server = HTTPServer(('', 0), self.request_handler)
     59         self.test_object.PORT = self.server.socket.getsockname()[1]
     60         self.test_object.server_started.set()
     61         self.test_object = None
     62         try:
     63             self.server.serve_forever(0.05)
     64         finally:
     65             self.server.server_close()
     66 
     67     def stop(self):
     68         self.server.shutdown()
     69 
     70 
     71 class BaseTestCase(unittest.TestCase):
     72     def setUp(self):
     73         self._threads = test_support.threading_setup()
     74         os.environ = test_support.EnvironmentVarGuard()
     75         self.server_started = threading.Event()
     76         self.thread = TestServerThread(self, self.request_handler)
     77         self.thread.start()
     78         self.server_started.wait()
     79 
     80     def tearDown(self):
     81         self.thread.stop()
     82         os.environ.__exit__()
     83         test_support.threading_cleanup(*self._threads)
     84 
     85     def request(self, uri, method='GET', body=None, headers={}):
     86         self.connection = httplib.HTTPConnection('localhost', self.PORT)
     87         self.connection.request(method, uri, body, headers)
     88         return self.connection.getresponse()
     89 
     90 class BaseHTTPRequestHandlerTestCase(unittest.TestCase):
     91     """Test the functionality of the BaseHTTPServer focussing on
     92     BaseHTTPRequestHandler.
     93     """
     94 
     95     HTTPResponseMatch = re.compile('HTTP/1.[0-9]+ 200 OK')
     96 
     97     def setUp (self):
     98         self.handler = SocketlessRequestHandler()
     99 
    100     def send_typical_request(self, message):
    101         input_msg = StringIO(message)
    102         output = StringIO()
    103         self.handler.rfile = input_msg
    104         self.handler.wfile = output
    105         self.handler.handle_one_request()
    106         output.seek(0)
    107         return output.readlines()
    108 
    109     def verify_get_called(self):
    110         self.assertTrue(self.handler.get_called)
    111 
    112     def verify_expected_headers(self, headers):
    113         for fieldName in 'Server: ', 'Date: ', 'Content-Type: ':
    114             self.assertEqual(sum(h.startswith(fieldName) for h in headers), 1)
    115 
    116     def verify_http_server_response(self, response):
    117         match = self.HTTPResponseMatch.search(response)
    118         self.assertIsNotNone(match)
    119 
    120     def test_http_1_1(self):
    121         result = self.send_typical_request('GET / HTTP/1.1\r\n\r\n')
    122         self.verify_http_server_response(result[0])
    123         self.verify_expected_headers(result[1:-1])
    124         self.verify_get_called()
    125         self.assertEqual(result[-1], '<html><body>Data</body></html>\r\n')
    126 
    127     def test_http_1_0(self):
    128         result = self.send_typical_request('GET / HTTP/1.0\r\n\r\n')
    129         self.verify_http_server_response(result[0])
    130         self.verify_expected_headers(result[1:-1])
    131         self.verify_get_called()
    132         self.assertEqual(result[-1], '<html><body>Data</body></html>\r\n')
    133 
    134     def test_http_0_9(self):
    135         result = self.send_typical_request('GET / HTTP/0.9\r\n\r\n')
    136         self.assertEqual(len(result), 1)
    137         self.assertEqual(result[0], '<html><body>Data</body></html>\r\n')
    138         self.verify_get_called()
    139 
    140     def test_with_continue_1_0(self):
    141         result = self.send_typical_request('GET / HTTP/1.0\r\nExpect: 100-continue\r\n\r\n')
    142         self.verify_http_server_response(result[0])
    143         self.verify_expected_headers(result[1:-1])
    144         self.verify_get_called()
    145         self.assertEqual(result[-1], '<html><body>Data</body></html>\r\n')
    146 
    147     def test_request_length(self):
    148         # Issue #10714: huge request lines are discarded, to avoid Denial
    149         # of Service attacks.
    150         result = self.send_typical_request(b'GET ' + b'x' * 65537)
    151         self.assertEqual(result[0], b'HTTP/1.1 414 Request-URI Too Long\r\n')
    152         self.assertFalse(self.handler.get_called)
    153 
    154 
    155 class BaseHTTPServerTestCase(BaseTestCase):
    156     class request_handler(NoLogRequestHandler, BaseHTTPRequestHandler):
    157         protocol_version = 'HTTP/1.1'
    158         default_request_version = 'HTTP/1.1'
    159 
    160         def do_TEST(self):
    161             self.send_response(204)
    162             self.send_header('Content-Type', 'text/html')
    163             self.send_header('Connection', 'close')
    164             self.end_headers()
    165 
    166         def do_KEEP(self):
    167             self.send_response(204)
    168             self.send_header('Content-Type', 'text/html')
    169             self.send_header('Connection', 'keep-alive')
    170             self.end_headers()
    171 
    172         def do_KEYERROR(self):
    173             self.send_error(999)
    174 
    175         def do_CUSTOM(self):
    176             self.send_response(999)
    177             self.send_header('Content-Type', 'text/html')
    178             self.send_header('Connection', 'close')
    179             self.end_headers()
    180 
    181         def do_SEND_ERROR(self):
    182             self.send_error(int(self.path[1:]))
    183 
    184         def do_HEAD(self):
    185             self.send_error(int(self.path[1:]))
    186 
    187     def setUp(self):
    188         BaseTestCase.setUp(self)
    189         self.con = httplib.HTTPConnection('localhost', self.PORT)
    190         self.con.connect()
    191 
    192     def test_command(self):
    193         self.con.request('GET', '/')
    194         res = self.con.getresponse()
    195         self.assertEqual(res.status, 501)
    196 
    197     def test_request_line_trimming(self):
    198         self.con._http_vsn_str = 'HTTP/1.1\n'
    199         self.con.putrequest('XYZBOGUS', '/')
    200         self.con.endheaders()
    201         res = self.con.getresponse()
    202         self.assertEqual(res.status, 501)
    203 
    204     def test_version_bogus(self):
    205         self.con._http_vsn_str = 'FUBAR'
    206         self.con.putrequest('GET', '/')
    207         self.con.endheaders()
    208         res = self.con.getresponse()
    209         self.assertEqual(res.status, 400)
    210 
    211     def test_version_digits(self):
    212         self.con._http_vsn_str = 'HTTP/9.9.9'
    213         self.con.putrequest('GET', '/')
    214         self.con.endheaders()
    215         res = self.con.getresponse()
    216         self.assertEqual(res.status, 400)
    217 
    218     def test_version_none_get(self):
    219         self.con._http_vsn_str = ''
    220         self.con.putrequest('GET', '/')
    221         self.con.endheaders()
    222         res = self.con.getresponse()
    223         self.assertEqual(res.status, 501)
    224 
    225     def test_version_none(self):
    226         # Test that a valid method is rejected when not HTTP/1.x
    227         self.con._http_vsn_str = ''
    228         self.con.putrequest('CUSTOM', '/')
    229         self.con.endheaders()
    230         res = self.con.getresponse()
    231         self.assertEqual(res.status, 400)
    232 
    233     def test_version_invalid(self):
    234         self.con._http_vsn = 99
    235         self.con._http_vsn_str = 'HTTP/9.9'
    236         self.con.putrequest('GET', '/')
    237         self.con.endheaders()
    238         res = self.con.getresponse()
    239         self.assertEqual(res.status, 505)
    240 
    241     def test_send_blank(self):
    242         self.con._http_vsn_str = ''
    243         self.con.putrequest('', '')
    244         self.con.endheaders()
    245         res = self.con.getresponse()
    246         self.assertEqual(res.status, 400)
    247 
    248     def test_header_close(self):
    249         self.con.putrequest('GET', '/')
    250         self.con.putheader('Connection', 'close')
    251         self.con.endheaders()
    252         res = self.con.getresponse()
    253         self.assertEqual(res.status, 501)
    254 
    255     def test_head_keep_alive(self):
    256         self.con._http_vsn_str = 'HTTP/1.1'
    257         self.con.putrequest('GET', '/')
    258         self.con.putheader('Connection', 'keep-alive')
    259         self.con.endheaders()
    260         res = self.con.getresponse()
    261         self.assertEqual(res.status, 501)
    262 
    263     def test_handler(self):
    264         self.con.request('TEST', '/')
    265         res = self.con.getresponse()
    266         self.assertEqual(res.status, 204)
    267 
    268     def test_return_header_keep_alive(self):
    269         self.con.request('KEEP', '/')
    270         res = self.con.getresponse()
    271         self.assertEqual(res.getheader('Connection'), 'keep-alive')
    272         self.con.request('TEST', '/')
    273         self.addCleanup(self.con.close)
    274 
    275     def test_internal_key_error(self):
    276         self.con.request('KEYERROR', '/')
    277         res = self.con.getresponse()
    278         self.assertEqual(res.status, 999)
    279 
    280     def test_return_custom_status(self):
    281         self.con.request('CUSTOM', '/')
    282         res = self.con.getresponse()
    283         self.assertEqual(res.status, 999)
    284 
    285     def test_send_error(self):
    286         allow_transfer_encoding_codes = (205, 304)
    287         for code in (101, 102, 204, 205, 304):
    288             self.con.request('SEND_ERROR', '/{}'.format(code))
    289             res = self.con.getresponse()
    290             self.assertEqual(code, res.status)
    291             self.assertEqual(None, res.getheader('Content-Length'))
    292             self.assertEqual(None, res.getheader('Content-Type'))
    293             if code not in allow_transfer_encoding_codes:
    294                 self.assertEqual(None, res.getheader('Transfer-Encoding'))
    295 
    296             data = res.read()
    297             self.assertEqual(b'', data)
    298 
    299     def test_head_via_send_error(self):
    300         allow_transfer_encoding_codes = (205, 304)
    301         for code in (101, 200, 204, 205, 304):
    302             self.con.request('HEAD', '/{}'.format(code))
    303             res = self.con.getresponse()
    304             self.assertEqual(code, res.status)
    305             if code == 200:
    306                 self.assertEqual(None, res.getheader('Content-Length'))
    307                 self.assertIn('text/html', res.getheader('Content-Type'))
    308             else:
    309                 self.assertEqual(None, res.getheader('Content-Length'))
    310                 self.assertEqual(None, res.getheader('Content-Type'))
    311             if code not in allow_transfer_encoding_codes:
    312                 self.assertEqual(None, res.getheader('Transfer-Encoding'))
    313 
    314             data = res.read()
    315             self.assertEqual(b'', data)
    316 
    317 
    318 class SimpleHTTPServerTestCase(BaseTestCase):
    319     class request_handler(NoLogRequestHandler, SimpleHTTPRequestHandler):
    320         pass
    321 
    322     def setUp(self):
    323         BaseTestCase.setUp(self)
    324         self.cwd = os.getcwd()
    325         basetempdir = tempfile.gettempdir()
    326         os.chdir(basetempdir)
    327         self.data = 'We are the knights who say Ni!'
    328         self.tempdir = tempfile.mkdtemp(dir=basetempdir)
    329         self.tempdir_name = os.path.basename(self.tempdir)
    330         self.base_url = '/' + self.tempdir_name
    331         temp = open(os.path.join(self.tempdir, 'test'), 'wb')
    332         temp.write(self.data)
    333         temp.close()
    334 
    335     def tearDown(self):
    336         try:
    337             os.chdir(self.cwd)
    338             try:
    339                 shutil.rmtree(self.tempdir)
    340             except OSError:
    341                 pass
    342         finally:
    343             BaseTestCase.tearDown(self)
    344 
    345     def check_status_and_reason(self, response, status, data=None):
    346         body = response.read()
    347         self.assertTrue(response)
    348         self.assertEqual(response.status, status)
    349         self.assertIsNotNone(response.reason)
    350         if data:
    351             self.assertEqual(data, body)
    352 
    353     def test_get(self):
    354         #constructs the path relative to the root directory of the HTTPServer
    355         response = self.request(self.base_url + '/test')
    356         self.check_status_and_reason(response, 200, data=self.data)
    357         # check for trailing "/" which should return 404. See Issue17324
    358         response = self.request(self.base_url + '/test/')
    359         self.check_status_and_reason(response, 404)
    360         response = self.request(self.base_url + '/')
    361         self.check_status_and_reason(response, 200)
    362         response = self.request(self.base_url)
    363         self.check_status_and_reason(response, 301)
    364         response = self.request(self.base_url + '/?hi=2')
    365         self.check_status_and_reason(response, 200)
    366         response = self.request(self.base_url + '?hi=1')
    367         self.check_status_and_reason(response, 301)
    368         self.assertEqual(response.getheader("Location"),
    369                          self.base_url + "/?hi=1")
    370         response = self.request('/ThisDoesNotExist')
    371         self.check_status_and_reason(response, 404)
    372         response = self.request('/' + 'ThisDoesNotExist' + '/')
    373         self.check_status_and_reason(response, 404)
    374         with open(os.path.join(self.tempdir_name, 'index.html'), 'w') as fp:
    375             response = self.request(self.base_url + '/')
    376             self.check_status_and_reason(response, 200)
    377             # chmod() doesn't work as expected on Windows, and filesystem
    378             # permissions are ignored by root on Unix.
    379             if os.name == 'posix' and os.geteuid() != 0:
    380                 os.chmod(self.tempdir, 0)
    381                 response = self.request(self.base_url + '/')
    382                 self.check_status_and_reason(response, 404)
    383                 os.chmod(self.tempdir, 0755)
    384 
    385     def test_head(self):
    386         response = self.request(
    387             self.base_url + '/test', method='HEAD')
    388         self.check_status_and_reason(response, 200)
    389         self.assertEqual(response.getheader('content-length'),
    390                          str(len(self.data)))
    391         self.assertEqual(response.getheader('content-type'),
    392                          'application/octet-stream')
    393 
    394     def test_invalid_requests(self):
    395         response = self.request('/', method='FOO')
    396         self.check_status_and_reason(response, 501)
    397         # requests must be case sensitive,so this should fail too
    398         response = self.request('/', method='custom')
    399         self.check_status_and_reason(response, 501)
    400         response = self.request('/', method='GETs')
    401         self.check_status_and_reason(response, 501)
    402 
    403     def test_path_without_leading_slash(self):
    404         response = self.request(self.tempdir_name + '/test')
    405         self.check_status_and_reason(response, 200, data=self.data)
    406         response = self.request(self.tempdir_name + '/test/')
    407         self.check_status_and_reason(response, 404)
    408         response = self.request(self.tempdir_name + '/')
    409         self.check_status_and_reason(response, 200)
    410         response = self.request(self.tempdir_name)
    411         self.check_status_and_reason(response, 301)
    412         response = self.request(self.tempdir_name + '/?hi=2')
    413         self.check_status_and_reason(response, 200)
    414         response = self.request(self.tempdir_name + '?hi=1')
    415         self.check_status_and_reason(response, 301)
    416         self.assertEqual(response.getheader("Location"),
    417                          self.tempdir_name + "/?hi=1")
    418 
    419 
    420 cgi_file1 = """\
    421 #!%s
    422 
    423 print "Content-type: text/html"
    424 print
    425 print "Hello World"
    426 """
    427 
    428 cgi_file2 = """\
    429 #!%s
    430 import cgi
    431 
    432 print "Content-type: text/html"
    433 print
    434 
    435 form = cgi.FieldStorage()
    436 print "%%s, %%s, %%s" %% (form.getfirst("spam"), form.getfirst("eggs"),
    437                           form.getfirst("bacon"))
    438 """
    439 
    440 cgi_file4 = """\
    441 #!%s
    442 import os
    443 
    444 print("Content-type: text/html")
    445 print("")
    446 
    447 print(os.environ["%s"])
    448 """
    449 
    450 
    451 @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
    452         "This test can't be run reliably as root (issue #13308).")
    453 class CGIHTTPServerTestCase(BaseTestCase):
    454     class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler):
    455         pass
    456 
    457     def setUp(self):
    458         BaseTestCase.setUp(self)
    459         self.parent_dir = tempfile.mkdtemp()
    460         self.cgi_dir = os.path.join(self.parent_dir, 'cgi-bin')
    461         self.cgi_child_dir = os.path.join(self.cgi_dir, 'child-dir')
    462         os.mkdir(self.cgi_dir)
    463         os.mkdir(self.cgi_child_dir)
    464 
    465         # The shebang line should be pure ASCII: use symlink if possible.
    466         # See issue #7668.
    467         if hasattr(os, 'symlink'):
    468             self.pythonexe = os.path.join(self.parent_dir, 'python')
    469             os.symlink(sys.executable, self.pythonexe)
    470         else:
    471             self.pythonexe = sys.executable
    472 
    473         self.nocgi_path = os.path.join(self.parent_dir, 'nocgi.py')
    474         with open(self.nocgi_path, 'w') as fp:
    475             fp.write(cgi_file1 % self.pythonexe)
    476         os.chmod(self.nocgi_path, 0777)
    477 
    478         self.file1_path = os.path.join(self.cgi_dir, 'file1.py')
    479         with open(self.file1_path, 'w') as file1:
    480             file1.write(cgi_file1 % self.pythonexe)
    481         os.chmod(self.file1_path, 0777)
    482 
    483         self.file2_path = os.path.join(self.cgi_dir, 'file2.py')
    484         with open(self.file2_path, 'w') as file2:
    485             file2.write(cgi_file2 % self.pythonexe)
    486         os.chmod(self.file2_path, 0777)
    487 
    488         self.file3_path = os.path.join(self.cgi_child_dir, 'file3.py')
    489         with open(self.file3_path, 'w') as file3:
    490             file3.write(cgi_file1 % self.pythonexe)
    491         os.chmod(self.file3_path, 0777)
    492 
    493         self.file4_path = os.path.join(self.cgi_dir, 'file4.py')
    494         with open(self.file4_path, 'w') as file4:
    495             file4.write(cgi_file4 % (self.pythonexe, 'QUERY_STRING'))
    496         os.chmod(self.file4_path, 0o777)
    497 
    498         self.cwd = os.getcwd()
    499         os.chdir(self.parent_dir)
    500 
    501     def tearDown(self):
    502         try:
    503             os.chdir(self.cwd)
    504             if self.pythonexe != sys.executable:
    505                 os.remove(self.pythonexe)
    506             os.remove(self.nocgi_path)
    507             os.remove(self.file1_path)
    508             os.remove(self.file2_path)
    509             os.remove(self.file3_path)
    510             os.remove(self.file4_path)
    511             os.rmdir(self.cgi_child_dir)
    512             os.rmdir(self.cgi_dir)
    513             os.rmdir(self.parent_dir)
    514         finally:
    515             BaseTestCase.tearDown(self)
    516 
    517     def test_url_collapse_path(self):
    518         # verify tail is the last portion and head is the rest on proper urls
    519         test_vectors = {
    520             '': '//',
    521             '..': IndexError,
    522             '/.//..': IndexError,
    523             '/': '//',
    524             '//': '//',
    525             '/\\': '//\\',
    526             '/.//': '//',
    527             'cgi-bin/file1.py': '/cgi-bin/file1.py',
    528             '/cgi-bin/file1.py': '/cgi-bin/file1.py',
    529             'a': '//a',
    530             '/a': '//a',
    531             '//a': '//a',
    532             './a': '//a',
    533             './C:/': '/C:/',
    534             '/a/b': '/a/b',
    535             '/a/b/': '/a/b/',
    536             '/a/b/.': '/a/b/',
    537             '/a/b/c/..': '/a/b/',
    538             '/a/b/c/../d': '/a/b/d',
    539             '/a/b/c/../d/e/../f': '/a/b/d/f',
    540             '/a/b/c/../d/e/../../f': '/a/b/f',
    541             '/a/b/c/../d/e/.././././..//f': '/a/b/f',
    542             '../a/b/c/../d/e/.././././..//f': IndexError,
    543             '/a/b/c/../d/e/../../../f': '/a/f',
    544             '/a/b/c/../d/e/../../../../f': '//f',
    545             '/a/b/c/../d/e/../../../../../f': IndexError,
    546             '/a/b/c/../d/e/../../../../f/..': '//',
    547             '/a/b/c/../d/e/../../../../f/../.': '//',
    548         }
    549         for path, expected in test_vectors.iteritems():
    550             if isinstance(expected, type) and issubclass(expected, Exception):
    551                 self.assertRaises(expected,
    552                                   CGIHTTPServer._url_collapse_path, path)
    553             else:
    554                 actual = CGIHTTPServer._url_collapse_path(path)
    555                 self.assertEqual(expected, actual,
    556                                  msg='path = %r\nGot:    %r\nWanted: %r' %
    557                                  (path, actual, expected))
    558 
    559     def test_headers_and_content(self):
    560         res = self.request('/cgi-bin/file1.py')
    561         self.assertEqual(('Hello World\n', 'text/html', 200),
    562             (res.read(), res.getheader('Content-type'), res.status))
    563 
    564     def test_issue19435(self):
    565         res = self.request('///////////nocgi.py/../cgi-bin/nothere.sh')
    566         self.assertEqual(res.status, 404)
    567 
    568     def test_post(self):
    569         params = urllib.urlencode({'spam' : 1, 'eggs' : 'python', 'bacon' : 123456})
    570         headers = {'Content-type' : 'application/x-www-form-urlencoded'}
    571         res = self.request('/cgi-bin/file2.py', 'POST', params, headers)
    572 
    573         self.assertEqual(res.read(), '1, python, 123456\n')
    574 
    575     def test_invaliduri(self):
    576         res = self.request('/cgi-bin/invalid')
    577         res.read()
    578         self.assertEqual(res.status, 404)
    579 
    580     def test_authorization(self):
    581         headers = {'Authorization' : 'Basic %s' %
    582                    base64.b64encode('username:pass')}
    583         res = self.request('/cgi-bin/file1.py', 'GET', headers=headers)
    584         self.assertEqual(('Hello World\n', 'text/html', 200),
    585                 (res.read(), res.getheader('Content-type'), res.status))
    586 
    587     def test_no_leading_slash(self):
    588         # http://bugs.python.org/issue2254
    589         res = self.request('cgi-bin/file1.py')
    590         self.assertEqual(('Hello World\n', 'text/html', 200),
    591              (res.read(), res.getheader('Content-type'), res.status))
    592 
    593     def test_os_environ_is_not_altered(self):
    594         signature = "Test CGI Server"
    595         os.environ['SERVER_SOFTWARE'] = signature
    596         res = self.request('/cgi-bin/file1.py')
    597         self.assertEqual((b'Hello World\n', 'text/html', 200),
    598                 (res.read(), res.getheader('Content-type'), res.status))
    599         self.assertEqual(os.environ['SERVER_SOFTWARE'], signature)
    600 
    601     def test_urlquote_decoding_in_cgi_check(self):
    602         res = self.request('/cgi-bin%2ffile1.py')
    603         self.assertEqual((b'Hello World\n', 'text/html', 200),
    604                 (res.read(), res.getheader('Content-type'), res.status))
    605 
    606     def test_nested_cgi_path_issue21323(self):
    607         res = self.request('/cgi-bin/child-dir/file3.py')
    608         self.assertEqual((b'Hello World\n', 'text/html', 200),
    609                 (res.read(), res.getheader('Content-type'), res.status))
    610 
    611     def test_query_with_multiple_question_mark(self):
    612         res = self.request('/cgi-bin/file4.py?a=b?c=d')
    613         self.assertEqual(
    614             (b'a=b?c=d\n', 'text/html', 200),
    615             (res.read(), res.getheader('Content-type'), res.status))
    616 
    617     def test_query_with_continuous_slashes(self):
    618         res = self.request('/cgi-bin/file4.py?k=aa%2F%2Fbb&//q//p//=//a//b//')
    619         self.assertEqual(
    620             (b'k=aa%2F%2Fbb&//q//p//=//a//b//\n',
    621              'text/html', 200),
    622             (res.read(), res.getheader('Content-type'), res.status))
    623 
    624 
    625 class SimpleHTTPRequestHandlerTestCase(unittest.TestCase):
    626     """ Test url parsing """
    627     def setUp(self):
    628         self.translated = os.getcwd()
    629         self.translated = os.path.join(self.translated, 'filename')
    630         self.handler = SocketlessRequestHandler()
    631 
    632     def test_query_arguments(self):
    633         path = self.handler.translate_path('/filename')
    634         self.assertEqual(path, self.translated)
    635         path = self.handler.translate_path('/filename?foo=bar')
    636         self.assertEqual(path, self.translated)
    637         path = self.handler.translate_path('/filename?a=b&spam=eggs#zot')
    638         self.assertEqual(path, self.translated)
    639 
    640     def test_start_with_double_slash(self):
    641         path = self.handler.translate_path('//filename')
    642         self.assertEqual(path, self.translated)
    643         path = self.handler.translate_path('//filename?foo=bar')
    644         self.assertEqual(path, self.translated)
    645 
    646     def test_windows_colon(self):
    647         import SimpleHTTPServer
    648         with test_support.swap_attr(SimpleHTTPServer.os, 'path', ntpath):
    649             path = self.handler.translate_path('c:c:c:foo/filename')
    650             path = path.replace(ntpath.sep, os.sep)
    651             self.assertEqual(path, self.translated)
    652 
    653             path = self.handler.translate_path('\\c:../filename')
    654             path = path.replace(ntpath.sep, os.sep)
    655             self.assertEqual(path, self.translated)
    656 
    657             path = self.handler.translate_path('c:\\c:..\\foo/filename')
    658             path = path.replace(ntpath.sep, os.sep)
    659             self.assertEqual(path, self.translated)
    660 
    661             path = self.handler.translate_path('c:c:foo\\c:c:bar/filename')
    662             path = path.replace(ntpath.sep, os.sep)
    663             self.assertEqual(path, self.translated)
    664 
    665 
    666 def test_main(verbose=None):
    667     try:
    668         cwd = os.getcwd()
    669         test_support.run_unittest(BaseHTTPRequestHandlerTestCase,
    670                                   SimpleHTTPRequestHandlerTestCase,
    671                                   BaseHTTPServerTestCase,
    672                                   SimpleHTTPServerTestCase,
    673                                   CGIHTTPServerTestCase
    674                                  )
    675     finally:
    676         os.chdir(cwd)
    677 
    678 if __name__ == '__main__':
    679     test_main()
    680