diff -r 7323a865457a Doc/library/http.server.rst --- a/Doc/library/http.server.rst Sun Jun 05 20:43:30 2011 +0200 +++ b/Doc/library/http.server.rst Sun Jun 05 21:51:15 2011 -0400 @@ -308,7 +308,10 @@ .. method:: do_GET() The request is mapped to a local file by interpreting the request as a - path relative to the current working directory. + path relative to the current working directory. Relative paths are + translated. If the translated path points higher in the directory + hierarchy than the current working directory, a ``400`` error response is + returned. If the request was mapped to a directory, the directory is checked for a file named ``index.html`` or ``index.htm`` (in that order). If found, the diff -r 7323a865457a Lib/http/server.py --- a/Lib/http/server.py Sun Jun 05 20:43:30 2011 +0200 +++ b/Lib/http/server.py Sun Jun 05 21:51:15 2011 -0400 @@ -688,7 +688,13 @@ None, in which case the caller has nothing further to do. """ - path = self.translate_path(self.path) + + try: + path = self.translate_path(self.path) + except IndexError: + self.send_error(400) + return + f = None if os.path.isdir(path): if not self.path.endswith('/'): @@ -763,26 +769,32 @@ return f def translate_path(self, path): - """Translate a /-separated PATH to the local filename syntax. + """Translate a PATH to the local filename syntax. - Components that mean special things to the local file system - (e.g. drive or directory names) are ignored. (XXX They should - probably be diagnosed.) - + Path separators other than slash are normalized into slashes and and + interpreted. Relative path traversal (using '..' and '.') is permitted. + Raises an IndexError if the translated path would fall above the + server's root directory. """ - # abandon query parameters - path = path.split('?',1)[0] - path = path.split('#',1)[0] - path = posixpath.normpath(urllib.parse.unquote(path)) - words = path.split('/') - words = filter(None, words) - path = os.getcwd() - for word in words: - drive, word = os.path.splitdrive(word) - head, word = os.path.split(word) - if word in (os.curdir, os.pardir): continue - path = os.path.join(path, word) - return path + base_dir = os.getcwd() + # trim the query and fragment components, if present + for path_delimeter in ('?', '#'): + path = path.split(path_delimeter, 1)[0] + path = urllib.parse.unquote(path) + # normalize path seperators into slashes + for sep in (os.sep, os.altsep): + if sep and sep != '/': + path = path.replace(sep, '/') + fragments = [fragment for fragment in path.split('/') if fragment] + translated_path = [] + for fragment in fragments: + if fragment in (os.pardir, '..'): + translated_path.pop() + elif fragment in (os.curdir, '.'): + continue + else: + translated_path.append(fragment) + return os.path.join(os.getcwd(), *translated_path) def copyfile(self, source, outputfile): """Copy all data between two file objects. @@ -975,7 +987,11 @@ def run_cgi(self): """Execute a CGI script.""" - path = self.path + try: + path = self.translate_path(self.path) + except IndexError: + self.send_error(400) + return dir, rest = self.cgi_info i = path.find('/', len(dir) + 1) diff -r 7323a865457a Lib/test/test_httpservers.py --- a/Lib/test/test_httpservers.py Sun Jun 05 20:43:30 2011 +0200 +++ b/Lib/test/test_httpservers.py Sun Jun 05 21:51:15 2011 -0400 @@ -256,6 +256,10 @@ self.check_status_and_reason(response, 404) response = self.request('/' + 'ThisDoesNotExist' + '/') self.check_status_and_reason(response, 404) + response = self.request('/../test') + self.check_status_and_reason(response, 400) + response = self.request('/test/../../') + self.check_status_and_reason(response, 400) with open(os.path.join(self.tempdir_name, 'index.html'), 'w') as f: response = self.request('/' + self.tempdir_name + '/') self.check_status_and_reason(response, 200) @@ -638,8 +642,9 @@ class SimpleHTTPRequestHandlerTestCase(unittest.TestCase): """ Test url parsing """ def setUp(self): - self.translated = os.getcwd() - self.translated = os.path.join(self.translated, 'filename') + self.cwd = os.getcwd() + self.translated = os.path.join(self.cwd, 'filename') + self.nested = os.path.join(self.cwd, os.sep.join(('foo', 'bar'))) self.handler = SocketlessRequestHandler() def test_query_arguments(self): @@ -656,6 +661,28 @@ path = self.handler.translate_path('//filename?foo=bar') self.assertEqual(path, self.translated) + def test_relative_paths(self): + path = self.handler.translate_path('/foo/./bar') + self.assertEqual(path, self.nested) + path = self.handler.translate_path('/foo/../foo/bar') + self.assertEqual(path, self.nested) + path = self.handler.translate_path('/./foo/bar') + self.assertEqual(path, self.nested) + path = self.handler.translate_path(os.sep.join(( + 'foo', os.pardir, 'foo', 'bar'))) + self.assertEqual(path, self.nested) + path = self.handler.translate_path(os.sep.join(( + 'foo', os.curdir, 'bar'))) + self.assertEqual(path, self.nested) + + def test_chroot(self): + self.assertRaises(IndexError, self.handler.translate_path, + '/../foo/bar') + self.assertRaises(IndexError, self.handler.translate_path, + os.sep.join((os.pardir, 'foo'))) + self.assertRaises(IndexError, self.handler.translate_path, + os.sep.join(('foo', os.pardir, os.pardir))) + def test_main(verbose=None): cwd = os.getcwd() diff -r 7323a865457a Misc/ACKS --- a/Misc/ACKS Sun Jun 05 20:43:30 2011 +0200 +++ b/Misc/ACKS Sun Jun 05 21:51:15 2011 -0400 @@ -566,6 +566,7 @@ Per Lindqvist Eric Lindvall Gregor Lingl +Ori Livneh Nick Lockwood Stephanie Lockwood Anne Lord