From 78de682ffcd089564ba08d4e575825e35af9ce94 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 23 Dec 2014 16:28:28 +0200 Subject: Issue #21793: Added http.HTTPStatus enums (i.e. HTTPStatus.OK, HTTPStatus.NOT_FOUND). Patch by Demian Brecht. --- Lib/http/server.py | 153 +++++++++++++++++++---------------------------------- 1 file changed, 54 insertions(+), 99 deletions(-) (limited to 'Lib/http/server.py') diff --git a/Lib/http/server.py b/Lib/http/server.py index f916fdd95c..02e9cc2af8 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -100,6 +100,8 @@ import urllib.parse import copy import argparse +from http import HTTPStatus + # Default error message template DEFAULT_ERROR_MESSAGE = """\ @@ -278,7 +280,9 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): if len(words) == 3: command, path, version = words if version[:5] != 'HTTP/': - self.send_error(400, "Bad request version (%r)" % version) + self.send_error( + HTTPStatus.BAD_REQUEST, + "Bad request version (%r)" % version) return False try: base_version_number = version.split('/', 1)[1] @@ -293,25 +297,31 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): raise ValueError version_number = int(version_number[0]), int(version_number[1]) except (ValueError, IndexError): - self.send_error(400, "Bad request version (%r)" % version) + self.send_error( + HTTPStatus.BAD_REQUEST, + "Bad request version (%r)" % version) return False if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1": self.close_connection = 0 if version_number >= (2, 0): - self.send_error(505, - "Invalid HTTP Version (%s)" % base_version_number) + self.send_error( + HTTPStatus.HTTP_VERSION_NOT_SUPPORTED, + "Invalid HTTP Version (%s)" % base_version_number) return False elif len(words) == 2: command, path = words self.close_connection = 1 if command != 'GET': - self.send_error(400, - "Bad HTTP/0.9 request type (%r)" % command) + self.send_error( + HTTPStatus.BAD_REQUEST, + "Bad HTTP/0.9 request type (%r)" % command) return False elif not words: return False else: - self.send_error(400, "Bad request syntax (%r)" % requestline) + self.send_error( + HTTPStatus.BAD_REQUEST, + "Bad request syntax (%r)" % requestline) return False self.command, self.path, self.request_version = command, path, version @@ -320,7 +330,9 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): self.headers = http.client.parse_headers(self.rfile, _class=self.MessageClass) except http.client.LineTooLong: - self.send_error(400, "Line too long") + self.send_error( + HTTPStatus.BAD_REQUEST, + "Line too long") return False conntype = self.headers.get('Connection', "") @@ -352,7 +364,7 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): False. """ - self.send_response_only(100) + self.send_response_only(HTTPStatus.CONTINUE) self.end_headers() return True @@ -370,7 +382,7 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): self.requestline = '' self.request_version = '' self.command = '' - self.send_error(414) + self.send_error(HTTPStatus.REQUEST_URI_TOO_LONG) return if not self.raw_requestline: self.close_connection = 1 @@ -380,7 +392,9 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): return mname = 'do_' + self.command if not hasattr(self, mname): - self.send_error(501, "Unsupported method (%r)" % self.command) + self.send_error( + HTTPStatus.NOT_IMPLEMENTED, + "Unsupported method (%r)" % self.command) return method = getattr(self, mname) method() @@ -435,7 +449,11 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): self.send_header('Connection', 'close') self.send_header('Content-Length', int(len(body))) self.end_headers() - if self.command != 'HEAD' and code >= 200 and code not in (204, 304): + + if (self.command != 'HEAD' and + code >= 200 and + code not in ( + HTTPStatus.NO_CONTENT, HTTPStatus.NOT_MODIFIED)): self.wfile.write(body) def send_response(self, code, message=None): @@ -579,82 +597,11 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): # MessageClass used to parse headers MessageClass = http.client.HTTPMessage - # Table mapping response codes to messages; entries have the - # form {code: (shortmessage, longmessage)}. - # See RFC 2616 and 6585. + # hack to maintain backwards compatibility responses = { - 100: ('Continue', 'Request received, please continue'), - 101: ('Switching Protocols', - 'Switching to new protocol; obey Upgrade header'), - - 200: ('OK', 'Request fulfilled, document follows'), - 201: ('Created', 'Document created, URL follows'), - 202: ('Accepted', - 'Request accepted, processing continues off-line'), - 203: ('Non-Authoritative Information', 'Request fulfilled from cache'), - 204: ('No Content', 'Request fulfilled, nothing follows'), - 205: ('Reset Content', 'Clear input form for further input.'), - 206: ('Partial Content', 'Partial content follows.'), - - 300: ('Multiple Choices', - 'Object has several resources -- see URI list'), - 301: ('Moved Permanently', 'Object moved permanently -- see URI list'), - 302: ('Found', 'Object moved temporarily -- see URI list'), - 303: ('See Other', 'Object moved -- see Method and URL list'), - 304: ('Not Modified', - 'Document has not changed since given time'), - 305: ('Use Proxy', - 'You must use proxy specified in Location to access this ' - 'resource.'), - 307: ('Temporary Redirect', - 'Object moved temporarily -- see URI list'), - - 400: ('Bad Request', - 'Bad request syntax or unsupported method'), - 401: ('Unauthorized', - 'No permission -- see authorization schemes'), - 402: ('Payment Required', - 'No payment -- see charging schemes'), - 403: ('Forbidden', - 'Request forbidden -- authorization will not help'), - 404: ('Not Found', 'Nothing matches the given URI'), - 405: ('Method Not Allowed', - 'Specified method is invalid for this resource.'), - 406: ('Not Acceptable', 'URI not available in preferred format.'), - 407: ('Proxy Authentication Required', 'You must authenticate with ' - 'this proxy before proceeding.'), - 408: ('Request Timeout', 'Request timed out; try again later.'), - 409: ('Conflict', 'Request conflict.'), - 410: ('Gone', - 'URI no longer exists and has been permanently removed.'), - 411: ('Length Required', 'Client must specify Content-Length.'), - 412: ('Precondition Failed', 'Precondition in headers is false.'), - 413: ('Request Entity Too Large', 'Entity is too large.'), - 414: ('Request-URI Too Long', 'URI is too long.'), - 415: ('Unsupported Media Type', 'Entity body in unsupported format.'), - 416: ('Requested Range Not Satisfiable', - 'Cannot satisfy request range.'), - 417: ('Expectation Failed', - 'Expect condition could not be satisfied.'), - 428: ('Precondition Required', - 'The origin server requires the request to be conditional.'), - 429: ('Too Many Requests', 'The user has sent too many requests ' - 'in a given amount of time ("rate limiting").'), - 431: ('Request Header Fields Too Large', 'The server is unwilling to ' - 'process the request because its header fields are too large.'), - - 500: ('Internal Server Error', 'Server got itself in trouble'), - 501: ('Not Implemented', - 'Server does not support this operation'), - 502: ('Bad Gateway', 'Invalid responses from another server/proxy.'), - 503: ('Service Unavailable', - 'The server cannot process the request due to a high load'), - 504: ('Gateway Timeout', - 'The gateway server did not receive a timely response'), - 505: ('HTTP Version Not Supported', 'Cannot fulfill request.'), - 511: ('Network Authentication Required', - 'The client needs to authenticate to gain network access.'), - } + v: (v.phrase, v.description) + for v in HTTPStatus.__members__.values() + } class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): @@ -703,7 +650,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): if os.path.isdir(path): if not self.path.endswith('/'): # redirect browser - doing basically what apache does - self.send_response(301) + self.send_response(HTTPStatus.MOVED_PERMANENTLY) self.send_header("Location", self.path + "/") self.end_headers() return None @@ -718,10 +665,10 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): try: f = open(path, 'rb') except OSError: - self.send_error(404, "File not found") + self.send_error(HTTPStatus.NOT_FOUND, "File not found") return None try: - self.send_response(200) + self.send_response(HTTPStatus.OK) self.send_header("Content-type", ctype) fs = os.fstat(f.fileno()) self.send_header("Content-Length", str(fs[6])) @@ -743,7 +690,9 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): try: list = os.listdir(path) except OSError: - self.send_error(404, "No permission to list directory") + self.send_error( + HTTPStatus.NOT_FOUND, + "No permission to list directory") return None list.sort(key=lambda a: a.lower()) r = [] @@ -782,7 +731,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): f = io.BytesIO() f.write(encoded) f.seek(0) - self.send_response(200) + self.send_response(HTTPStatus.OK) self.send_header("Content-type", "text/html; charset=%s" % enc) self.send_header("Content-Length", str(len(encoded))) self.end_headers() @@ -964,7 +913,9 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): if self.is_cgi(): self.run_cgi() else: - self.send_error(501, "Can only POST to CGI scripts") + self.send_error( + HTTPStatus.NOT_IMPLEMENTED, + "Can only POST to CGI scripts") def send_head(self): """Version of send_head that support CGI scripts""" @@ -1042,17 +993,21 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): scriptname = dir + '/' + script scriptfile = self.translate_path(scriptname) if not os.path.exists(scriptfile): - self.send_error(404, "No such CGI script (%r)" % scriptname) + self.send_error( + HTTPStatus.NOT_FOUND, + "No such CGI script (%r)" % scriptname) return if not os.path.isfile(scriptfile): - self.send_error(403, "CGI script is not a plain file (%r)" % - scriptname) + self.send_error( + HTTPStatus.FORBIDDEN, + "CGI script is not a plain file (%r)" % scriptname) return ispy = self.is_python(scriptname) if self.have_fork or not ispy: if not self.is_executable(scriptfile): - self.send_error(403, "CGI script is not executable (%r)" % - scriptname) + self.send_error( + HTTPStatus.FORBIDDEN, + "CGI script is not executable (%r)" % scriptname) return # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html @@ -1120,7 +1075,7 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): 'HTTP_USER_AGENT', 'HTTP_COOKIE', 'HTTP_REFERER'): env.setdefault(k, "") - self.send_response(200, "Script output follows") + self.send_response(HTTPStatus.OK, "Script output follows") self.flush_headers() decoded_query = query.replace('+', ' ') -- cgit v1.2.1 From 4cb28d52a7a3002b84d69bdf4406c121932c4a9a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 7 Mar 2015 11:51:37 +0200 Subject: Issue #21793: BaseHTTPRequestHandler again logs response code as numeric, not as stringified enum. Patch by Demian Brecht. --- Lib/http/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Lib/http/server.py') diff --git a/Lib/http/server.py b/Lib/http/server.py index 4704d5141a..fd13be3731 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -517,7 +517,8 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): This is called by send_response(). """ - + if isinstance(code, HTTPStatus): + code = code.value self.log_message('"%s" %s %s', self.requestline, str(code), str(size)) -- cgit v1.2.1 From a101607578fa04a97470e4e7654bb2a1ec065d2c Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Mon, 14 Mar 2016 06:06:03 +0200 Subject: Issue #747320: Use email.utils.formatdate() to avoid code duplication in BaseHTTPRequestHandler Initial patch by karlcow. --- Lib/http/server.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'Lib/http/server.py') diff --git a/Lib/http/server.py b/Lib/http/server.py index e1b71abf37..5e91826d15 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -87,6 +87,7 @@ __all__ = [ "SimpleHTTPRequestHandler", "CGIHTTPRequestHandler", ] +import email.utils import html import http.client import io @@ -566,12 +567,7 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): """Return the current date and time formatted for a message header.""" if timestamp is None: timestamp = time.time() - year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp) - s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % ( - self.weekdayname[wd], - day, self.monthname[month], year, - hh, mm, ss) - return s + return email.utils.formatdate(timestamp, usegmt=True) def log_date_time_string(self): """Return the current time formatted for logging.""" -- cgit v1.2.1 From 5cbbf7c0d1eaf06b7bdbf251653fc344838ea148 Mon Sep 17 00:00:00 2001 From: Martin Panter Date: Sun, 3 Apr 2016 01:28:53 +0000 Subject: Issue #26586: Simple enhancements to BaseHTTPRequestHandler by Xiang Zhang --- Lib/http/server.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'Lib/http/server.py') diff --git a/Lib/http/server.py b/Lib/http/server.py index de6b5312d8..f4ad260925 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -137,7 +137,7 @@ class HTTPServer(socketserver.TCPServer): def server_bind(self): """Override server_bind to store the server name.""" socketserver.TCPServer.server_bind(self) - host, port = self.socket.getsockname()[:2] + host, port = self.server_address[:2] self.server_name = socket.getfqdn(host) self.server_port = port @@ -283,12 +283,9 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): words = requestline.split() if len(words) == 3: command, path, version = words - if version[:5] != 'HTTP/': - self.send_error( - HTTPStatus.BAD_REQUEST, - "Bad request version (%r)" % version) - return False try: + if version[:5] != 'HTTP/': + raise ValueError base_version_number = version.split('/', 1)[1] version_number = base_version_number.split(".") # RFC 2145 section 3.1 says there can be only one "." and @@ -310,7 +307,7 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): if version_number >= (2, 0): self.send_error( HTTPStatus.HTTP_VERSION_NOT_SUPPORTED, - "Invalid HTTP Version (%s)" % base_version_number) + "Invalid HTTP version (%s)" % base_version_number) return False elif len(words) == 2: command, path = words @@ -333,10 +330,11 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): try: self.headers = http.client.parse_headers(self.rfile, _class=self.MessageClass) - except http.client.LineTooLong: + except http.client.LineTooLong as err: self.send_error( - HTTPStatus.BAD_REQUEST, - "Line too long") + HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE, + "Line too long", + str(err)) return False except http.client.HTTPException as err: self.send_error( @@ -482,12 +480,12 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): def send_response_only(self, code, message=None): """Send the response header only.""" - if message is None: - if code in self.responses: - message = self.responses[code][0] - else: - message = '' if self.request_version != 'HTTP/0.9': + if message is None: + if code in self.responses: + message = self.responses[code][0] + else: + message = '' if not hasattr(self, '_headers_buffer'): self._headers_buffer = [] self._headers_buffer.append(("%s %d %s\r\n" % -- cgit v1.2.1 From b59189eea76f27113c896ab563e9551420a7a838 Mon Sep 17 00:00:00 2001 From: Martin Panter Date: Mon, 11 Apr 2016 00:40:08 +0000 Subject: Issue #26585: Eliminate _quote_html() and use html.escape(quote=False) Patch by Xiang Zhang. --- Lib/http/server.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'Lib/http/server.py') diff --git a/Lib/http/server.py b/Lib/http/server.py index f4ad260925..fbee6a932d 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -127,9 +127,6 @@ DEFAULT_ERROR_MESSAGE = """\ 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 @@ -449,9 +446,12 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): 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)}) + # HTML encode to prevent Cross Site Scripting attacks (see bug #1100201) + content = (self.error_message_format % { + 'code': code, + 'message': html.escape(message, quote=False), + 'explain': html.escape(explain, quote=False) + }) body = content.encode('UTF-8', 'replace') self.send_response(code, message) self.send_header("Content-Type", self.error_content_type) @@ -710,7 +710,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): errors='surrogatepass') except UnicodeDecodeError: displaypath = urllib.parse.unquote(path) - displaypath = html.escape(displaypath) + displaypath = html.escape(displaypath, quote=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, quote=False))) r.append('\n
\n\n\n') encoded = '\n'.join(r).encode(enc, 'surrogateescape') f = io.BytesIO() -- cgit v1.2.1 From 847d234435fc269bbd705656e022f06f986d8980 Mon Sep 17 00:00:00 2001 From: Martin Panter Date: Wed, 13 Apr 2016 00:36:52 +0000 Subject: Issue #26404: Add context manager to socketserver, by Aviv Palivoda --- Lib/http/server.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'Lib/http/server.py') diff --git a/Lib/http/server.py b/Lib/http/server.py index fbee6a932d..c1607b36b0 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -1175,16 +1175,14 @@ def test(HandlerClass=BaseHTTPRequestHandler, server_address = (bind, port) HandlerClass.protocol_version = protocol - httpd = ServerClass(server_address, HandlerClass) - - sa = httpd.socket.getsockname() - print("Serving HTTP on", sa[0], "port", sa[1], "...") - try: - httpd.serve_forever() - except KeyboardInterrupt: - print("\nKeyboard interrupt received, exiting.") - httpd.server_close() - sys.exit(0) + with ServerClass(server_address, HandlerClass) as httpd: + sa = httpd.socket.getsockname() + print("Serving HTTP on", sa[0], "port", sa[1], "...") + try: + httpd.serve_forever() + except KeyboardInterrupt: + print("\nKeyboard interrupt received, exiting.") + sys.exit(0) if __name__ == '__main__': parser = argparse.ArgumentParser() -- cgit v1.2.1 From 41f25b7d8cc0d3ca63df4398bb018908c4c9243f Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Fri, 29 Apr 2016 16:48:11 +0300 Subject: Issue #24902: Print server URL on http.server startup Initial patch by Felix Kaiser. --- Lib/http/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Lib/http/server.py') diff --git a/Lib/http/server.py b/Lib/http/server.py index 1f6a62bff9..bd94eaa01b 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -1177,7 +1177,8 @@ def test(HandlerClass=BaseHTTPRequestHandler, HandlerClass.protocol_version = protocol with ServerClass(server_address, HandlerClass) as httpd: sa = httpd.socket.getsockname() - print("Serving HTTP on", sa[0], "port", sa[1], "...") + serve_message = "Serving HTTP on {host} port {port} (http://{host}:{port}/) ..." + print(serve_message.format(host=sa[0], port=sa[1])) try: httpd.serve_forever() except KeyboardInterrupt: -- cgit v1.2.1