diff -r adb6b029b102 Lib/http/server.py --- a/Lib/http/server.py Wed Mar 09 15:02:31 2016 +0100 +++ b/Lib/http/server.py Sun Mar 20 02:09:45 2016 +0800 @@ -126,9 +126,6 @@ DEFAULT_ERROR_CONTENT_TYPE = "text/html;charset=utf-8" -def _quote_html(html): - return html.replace("&", "&").replace("<", "<").replace(">", ">") - class HTTPServer(socketserver.TCPServer): allow_reuse_address = 1 # Seems to make sense in testing environment @@ -443,9 +440,12 @@ if explain is None: explain = longmsg self.log_error("code %d, message %s", code, message) - # using _quote_html to prevent Cross Site Scripting attacks (see bug #1100201) - content = (self.error_message_format % - {'code': code, 'message': _quote_html(message), 'explain': _quote_html(explain)}) + # bug #1100201 + content = (self.error_message_format % { + 'code': code, + 'message': html.escape(message, False), + 'explain': html.escape(explain, False) + }) body = content.encode('UTF-8', 'replace') self.send_response(code, message) self.send_header("Content-Type", self.error_content_type) @@ -709,7 +709,7 @@ errors='surrogatepass') except UnicodeDecodeError: displaypath = urllib.parse.unquote(path) - displaypath = html.escape(displaypath) + displaypath = html.escape(displaypath, False) enc = sys.getfilesystemencoding() title = 'Directory listing for %s' % displaypath r.append('%s' % (urllib.parse.quote(linkname, errors='surrogatepass'), - html.escape(displayname))) + html.escape(displayname, False))) r.append('\n
\n\n\n') encoded = '\n'.join(r).encode(enc, 'surrogateescape') f = io.BytesIO() diff -r adb6b029b102 Lib/test/test_httpservers.py --- a/Lib/test/test_httpservers.py Wed Mar 09 15:02:31 2016 +0100 +++ b/Lib/test/test_httpservers.py Sun Mar 20 02:09:45 2016 +0800 @@ -342,7 +342,7 @@ quotedname = urllib.parse.quote(filename, errors='surrogatepass') self.assertIn(('href="%s"' % quotedname) .encode(enc, 'surrogateescape'), body) - self.assertIn(('>%s<' % html.escape(filename)) + self.assertIn(('>%s<' % html.escape(filename, False)) .encode(enc, 'surrogateescape'), body) response = self.request(self.tempdir_name + '/' + quotedname) self.check_status_and_reason(response, HTTPStatus.OK, @@ -404,6 +404,22 @@ response = self.request('/', method='GETs') self.check_status_and_reason(response, HTTPStatus.NOT_IMPLEMENTED) + def test_html_escape_filename(self): + filename = '.txt' + fullpath = os.path.join(self.tempdir, filename) + + try: + open(fullpath, 'w').close() + except OSError: + raise unittest.SkipTest('Can not create file %s on current file ' + 'system' % filename) + + response = self.request(self.tempdir_name + '/') + body = self.check_status_and_reason(response, HTTPStatus.OK) + enc = response.headers.get_content_charset('utf-8') + os.unlink(fullpath) # avoid affecting test_undecodable_filename + self.assertIn(('>%s<' % html.escape(filename, False)) + .encode(enc, 'surrogateescape'), body) cgi_file1 = """\ #!%s @@ -858,6 +874,11 @@ self.assertFalse(self.handler.get_called) self.assertEqual(self.handler.requestline, 'GET / HTTP/1.1') + def test_html_escape_on_error(self): + result = self.send_typical_request(b' / HTTP/1.1') + result = b''.join(result).decode('utf-8') + self.assertIn(html.escape('', False), result) + def test_close_connection(self): # handle_one_request() should be repeatedly called until # it sets close_connection