blob: c6e5ed6ea0e0023075c25a7461ad4fb6fde1a16a [file] [log] [blame]
Georg Brandl24420152008-05-26 16:32:26 +00001"""HTTP server classes.
2
3Note: BaseHTTPRequestHandler doesn't implement any HTTP request; see
4SimpleHTTPRequestHandler for simple implementations of GET, HEAD and POST,
5and CGIHTTPRequestHandler for CGI scripts.
6
7It does, however, optionally implement HTTP/1.1 persistent connections,
8as of version 0.3.
9
10Notes on CGIHTTPRequestHandler
11------------------------------
12
13This class implements GET and POST requests to cgi-bin scripts.
14
15If the os.fork() function is not present (e.g. on Windows),
Amaury Forgeot d'Arccb0d2d72008-06-18 22:19:22 +000016subprocess.Popen() is used as a fallback, with slightly altered semantics.
Georg Brandl24420152008-05-26 16:32:26 +000017
18In all cases, the implementation is intentionally naive -- all
19requests are executed synchronously.
20
21SECURITY WARNING: DON'T USE THIS CODE UNLESS YOU ARE INSIDE A FIREWALL
22-- it may execute arbitrary Python code or external programs.
23
24Note that status code 200 is sent prior to execution of a CGI script, so
25scripts cannot send other status codes such as 302 (redirect).
26
27XXX To do:
28
29- log requests even later (to capture byte count)
30- log user-agent header and other interesting goodies
31- send error log to separate file
32"""
33
34
35# See also:
36#
37# HTTP Working Group T. Berners-Lee
38# INTERNET-DRAFT R. T. Fielding
39# <draft-ietf-http-v10-spec-00.txt> H. Frystyk Nielsen
40# Expires September 8, 1995 March 8, 1995
41#
42# URL: http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt
43#
44# and
45#
46# Network Working Group R. Fielding
47# Request for Comments: 2616 et al
48# Obsoletes: 2068 June 1999
49# Category: Standards Track
50#
51# URL: http://www.faqs.org/rfcs/rfc2616.html
52
53# Log files
54# ---------
55#
56# Here's a quote from the NCSA httpd docs about log file format.
57#
58# | The logfile format is as follows. Each line consists of:
59# |
60# | host rfc931 authuser [DD/Mon/YYYY:hh:mm:ss] "request" ddd bbbb
61# |
62# | host: Either the DNS name or the IP number of the remote client
63# | rfc931: Any information returned by identd for this person,
64# | - otherwise.
65# | authuser: If user sent a userid for authentication, the user name,
66# | - otherwise.
67# | DD: Day
68# | Mon: Month (calendar name)
69# | YYYY: Year
70# | hh: hour (24-hour format, the machine's timezone)
71# | mm: minutes
72# | ss: seconds
73# | request: The first line of the HTTP request as sent by the client.
74# | ddd: the status code returned by the server, - if not available.
75# | bbbb: the total number of bytes sent,
76# | *not including the HTTP/1.0 header*, - if not available
77# |
78# | You can determine the name of the file accessed through request.
79#
80# (Actually, the latter is only true if you know the server configuration
81# at the time the request was made!)
82
83__version__ = "0.6"
84
Berker Peksag366c5702015-02-13 20:48:15 +020085__all__ = [
Géry Ogam1cee2162018-05-29 22:10:30 +020086 "HTTPServer", "ThreadingHTTPServer", "BaseHTTPRequestHandler",
Berker Peksag366c5702015-02-13 20:48:15 +020087 "SimpleHTTPRequestHandler", "CGIHTTPRequestHandler",
88]
Georg Brandl24420152008-05-26 16:32:26 +000089
Pierre Quentel351adda2017-04-02 12:26:12 +020090import copy
91import datetime
Berker Peksag04bc5b92016-03-14 06:06:03 +020092import email.utils
Georg Brandl1f7fffb2010-10-15 15:57:45 +000093import html
Jeremy Hylton914ab452009-03-27 17:16:06 +000094import http.client
95import io
96import mimetypes
97import os
98import posixpath
99import select
100import shutil
101import socket # For gethostbyaddr()
102import socketserver
103import sys
104import time
105import urllib.parse
Jason R. Coombs7cdc31a2020-01-06 07:59:36 -0500106import contextlib
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200107from functools import partial
Senthil Kumaran1251faf2012-06-03 16:15:54 +0800108
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200109from http import HTTPStatus
110
Georg Brandl24420152008-05-26 16:32:26 +0000111
112# Default error message template
113DEFAULT_ERROR_MESSAGE = """\
Senthil Kumaran1b407fe2011-03-20 10:44:30 +0800114<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
115 "http://www.w3.org/TR/html4/strict.dtd">
Ezio Melottica897e92011-11-02 19:33:29 +0200116<html>
Senthil Kumaranb253c9f2011-03-17 16:43:22 +0800117 <head>
Senthil Kumaran1b407fe2011-03-20 10:44:30 +0800118 <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
Senthil Kumaranb253c9f2011-03-17 16:43:22 +0800119 <title>Error response</title>
120 </head>
121 <body>
122 <h1>Error response</h1>
123 <p>Error code: %(code)d</p>
124 <p>Message: %(message)s.</p>
125 <p>Error code explanation: %(code)s - %(explain)s.</p>
126 </body>
127</html>
Georg Brandl24420152008-05-26 16:32:26 +0000128"""
129
130DEFAULT_ERROR_CONTENT_TYPE = "text/html;charset=utf-8"
131
Georg Brandl24420152008-05-26 16:32:26 +0000132class HTTPServer(socketserver.TCPServer):
133
134 allow_reuse_address = 1 # Seems to make sense in testing environment
135
136 def server_bind(self):
137 """Override server_bind to store the server name."""
138 socketserver.TCPServer.server_bind(self)
Martin Panter50badad2016-04-03 01:28:53 +0000139 host, port = self.server_address[:2]
Georg Brandl24420152008-05-26 16:32:26 +0000140 self.server_name = socket.getfqdn(host)
141 self.server_port = port
142
143
Géry Ogam1cee2162018-05-29 22:10:30 +0200144class ThreadingHTTPServer(socketserver.ThreadingMixIn, HTTPServer):
Julien Palard8bcfa022018-03-23 17:40:33 +0100145 daemon_threads = True
146
147
Georg Brandl24420152008-05-26 16:32:26 +0000148class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
149
150 """HTTP request handler base class.
151
152 The following explanation of HTTP serves to guide you through the
153 code as well as to expose any misunderstandings I may have about
154 HTTP (so you don't need to read the code to figure out I'm wrong
155 :-).
156
157 HTTP (HyperText Transfer Protocol) is an extensible protocol on
158 top of a reliable stream transport (e.g. TCP/IP). The protocol
159 recognizes three parts to a request:
160
161 1. One line identifying the request type and path
162 2. An optional set of RFC-822-style headers
163 3. An optional data part
164
165 The headers and data are separated by a blank line.
166
167 The first line of the request has the form
168
169 <command> <path> <version>
170
171 where <command> is a (case-sensitive) keyword such as GET or POST,
172 <path> is a string containing path information for the request,
173 and <version> should be the string "HTTP/1.0" or "HTTP/1.1".
174 <path> is encoded using the URL encoding scheme (using %xx to signify
175 the ASCII character with hex code xx).
176
177 The specification specifies that lines are separated by CRLF but
178 for compatibility with the widest range of clients recommends
179 servers also handle LF. Similarly, whitespace in the request line
180 is treated sensibly (allowing multiple spaces between components
181 and allowing trailing whitespace).
182
183 Similarly, for output, lines ought to be separated by CRLF pairs
184 but most clients grok LF characters just fine.
185
186 If the first line of the request has the form
187
188 <command> <path>
189
190 (i.e. <version> is left out) then this is assumed to be an HTTP
191 0.9 request; this form has no optional headers and data part and
192 the reply consists of just the data.
193
194 The reply form of the HTTP 1.x protocol again has three parts:
195
196 1. One line giving the response code
197 2. An optional set of RFC-822-style headers
198 3. The data
199
200 Again, the headers and data are separated by a blank line.
201
202 The response code line has the form
203
204 <version> <responsecode> <responsestring>
205
206 where <version> is the protocol version ("HTTP/1.0" or "HTTP/1.1"),
207 <responsecode> is a 3-digit response code indicating success or
208 failure of the request, and <responsestring> is an optional
209 human-readable string explaining what the response code means.
210
211 This server parses the request and the headers, and then calls a
212 function specific to the request type (<command>). Specifically,
213 a request SPAM will be handled by a method do_SPAM(). If no
214 such method exists the server sends an error response to the
215 client. If it exists, it is called with no arguments:
216
217 do_SPAM()
218
219 Note that the request name is case sensitive (i.e. SPAM and spam
220 are different requests).
221
222 The various request details are stored in instance variables:
223
224 - client_address is the client IP address in the form (host,
225 port);
226
227 - command, path and version are the broken-down request line;
228
Barry Warsaw820c1202008-06-12 04:06:45 +0000229 - headers is an instance of email.message.Message (or a derived
Georg Brandl24420152008-05-26 16:32:26 +0000230 class) containing the header information;
231
232 - rfile is a file object open for reading positioned at the
233 start of the optional input data part;
234
235 - wfile is a file object open for writing.
236
237 IT IS IMPORTANT TO ADHERE TO THE PROTOCOL FOR WRITING!
238
239 The first thing to be written must be the response line. Then
240 follow 0 or more header lines, then a blank line, and then the
241 actual data (if any). The meaning of the header lines depends on
242 the command executed by the server; in most cases, when data is
243 returned, there should be at least one header line of the form
244
245 Content-type: <type>/<subtype>
246
247 where <type> and <subtype> should be registered MIME types,
248 e.g. "text/html" or "text/plain".
249
250 """
251
252 # The Python system version, truncated to its first component.
253 sys_version = "Python/" + sys.version.split()[0]
254
255 # The server software version. You may want to override this.
256 # The format is multiple whitespace-separated strings,
257 # where each string is of the form name[/version].
258 server_version = "BaseHTTP/" + __version__
259
260 error_message_format = DEFAULT_ERROR_MESSAGE
261 error_content_type = DEFAULT_ERROR_CONTENT_TYPE
262
263 # The default request version. This only affects responses up until
264 # the point where the request line is parsed, so it mainly decides what
265 # the client gets back when sending a malformed request line.
266 # Most web servers default to HTTP 0.9, i.e. don't send a status line.
267 default_request_version = "HTTP/0.9"
268
269 def parse_request(self):
270 """Parse a request (internal).
271
272 The request should be stored in self.raw_requestline; the results
273 are in self.command, self.path, self.request_version and
274 self.headers.
275
Martin Pantere82338d2016-11-19 01:06:37 +0000276 Return True for success, False for failure; on failure, any relevant
277 error response has already been sent back.
Georg Brandl24420152008-05-26 16:32:26 +0000278
279 """
280 self.command = None # set in case of error on the first line
281 self.request_version = version = self.default_request_version
Benjamin Peterson70e28472015-02-17 21:11:10 -0500282 self.close_connection = True
Georg Brandl24420152008-05-26 16:32:26 +0000283 requestline = str(self.raw_requestline, 'iso-8859-1')
Senthil Kumaran30755492011-12-23 17:03:41 +0800284 requestline = requestline.rstrip('\r\n')
Georg Brandl24420152008-05-26 16:32:26 +0000285 self.requestline = requestline
286 words = requestline.split()
Martin Pantere82338d2016-11-19 01:06:37 +0000287 if len(words) == 0:
288 return False
289
290 if len(words) >= 3: # Enough to determine protocol version
291 version = words[-1]
Georg Brandl24420152008-05-26 16:32:26 +0000292 try:
Martin Pantere82338d2016-11-19 01:06:37 +0000293 if not version.startswith('HTTP/'):
Martin Panter50badad2016-04-03 01:28:53 +0000294 raise ValueError
Georg Brandl24420152008-05-26 16:32:26 +0000295 base_version_number = version.split('/', 1)[1]
296 version_number = base_version_number.split(".")
297 # RFC 2145 section 3.1 says there can be only one "." and
298 # - major and minor numbers MUST be treated as
299 # separate integers;
300 # - HTTP/2.4 is a lower version than HTTP/2.13, which in
301 # turn is lower than HTTP/12.3;
302 # - Leading zeros MUST be ignored by recipients.
303 if len(version_number) != 2:
304 raise ValueError
305 version_number = int(version_number[0]), int(version_number[1])
306 except (ValueError, IndexError):
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200307 self.send_error(
308 HTTPStatus.BAD_REQUEST,
309 "Bad request version (%r)" % version)
Georg Brandl24420152008-05-26 16:32:26 +0000310 return False
311 if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1":
Benjamin Peterson70e28472015-02-17 21:11:10 -0500312 self.close_connection = False
Georg Brandl24420152008-05-26 16:32:26 +0000313 if version_number >= (2, 0):
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200314 self.send_error(
315 HTTPStatus.HTTP_VERSION_NOT_SUPPORTED,
Martin Panter50badad2016-04-03 01:28:53 +0000316 "Invalid HTTP version (%s)" % base_version_number)
Georg Brandl24420152008-05-26 16:32:26 +0000317 return False
Martin Pantere82338d2016-11-19 01:06:37 +0000318 self.request_version = version
319
320 if not 2 <= len(words) <= 3:
321 self.send_error(
322 HTTPStatus.BAD_REQUEST,
323 "Bad request syntax (%r)" % requestline)
324 return False
325 command, path = words[:2]
326 if len(words) == 2:
Benjamin Peterson70e28472015-02-17 21:11:10 -0500327 self.close_connection = True
Georg Brandl24420152008-05-26 16:32:26 +0000328 if command != 'GET':
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200329 self.send_error(
330 HTTPStatus.BAD_REQUEST,
331 "Bad HTTP/0.9 request type (%r)" % command)
Georg Brandl24420152008-05-26 16:32:26 +0000332 return False
Martin Pantere82338d2016-11-19 01:06:37 +0000333 self.command, self.path = command, path
Georg Brandl24420152008-05-26 16:32:26 +0000334
335 # Examine the headers and look for a Connection directive.
Senthil Kumaran5466bf12010-12-18 16:55:23 +0000336 try:
337 self.headers = http.client.parse_headers(self.rfile,
338 _class=self.MessageClass)
Martin Panter50badad2016-04-03 01:28:53 +0000339 except http.client.LineTooLong as err:
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200340 self.send_error(
Martin Panter50badad2016-04-03 01:28:53 +0000341 HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
342 "Line too long",
343 str(err))
Senthil Kumaran5466bf12010-12-18 16:55:23 +0000344 return False
Martin Panteracc03192016-04-03 00:45:46 +0000345 except http.client.HTTPException as err:
346 self.send_error(
347 HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
348 "Too many headers",
349 str(err)
350 )
351 return False
Georg Brandl24420152008-05-26 16:32:26 +0000352
353 conntype = self.headers.get('Connection', "")
354 if conntype.lower() == 'close':
Benjamin Peterson70e28472015-02-17 21:11:10 -0500355 self.close_connection = True
Georg Brandl24420152008-05-26 16:32:26 +0000356 elif (conntype.lower() == 'keep-alive' and
357 self.protocol_version >= "HTTP/1.1"):
Benjamin Peterson70e28472015-02-17 21:11:10 -0500358 self.close_connection = False
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000359 # Examine the headers and look for an Expect directive
360 expect = self.headers.get('Expect', "")
361 if (expect.lower() == "100-continue" and
362 self.protocol_version >= "HTTP/1.1" and
363 self.request_version >= "HTTP/1.1"):
364 if not self.handle_expect_100():
365 return False
366 return True
367
368 def handle_expect_100(self):
369 """Decide what to do with an "Expect: 100-continue" header.
370
371 If the client is expecting a 100 Continue response, we must
372 respond with either a 100 Continue or a final response before
373 waiting for the request body. The default is to always respond
374 with a 100 Continue. You can behave differently (for example,
375 reject unauthorized requests) by overriding this method.
376
377 This method should either return True (possibly after sending
378 a 100 Continue response) or send an error response and return
379 False.
380
381 """
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200382 self.send_response_only(HTTPStatus.CONTINUE)
Benjamin Peterson04424232014-01-18 21:50:18 -0500383 self.end_headers()
Georg Brandl24420152008-05-26 16:32:26 +0000384 return True
385
386 def handle_one_request(self):
387 """Handle a single HTTP request.
388
389 You normally don't need to override this method; see the class
390 __doc__ string for information on how to handle specific HTTP
391 commands such as GET and POST.
392
393 """
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000394 try:
Antoine Pitrouc4924372010-12-16 16:48:36 +0000395 self.raw_requestline = self.rfile.readline(65537)
396 if len(self.raw_requestline) > 65536:
397 self.requestline = ''
398 self.request_version = ''
399 self.command = ''
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200400 self.send_error(HTTPStatus.REQUEST_URI_TOO_LONG)
Antoine Pitrouc4924372010-12-16 16:48:36 +0000401 return
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000402 if not self.raw_requestline:
Benjamin Peterson70e28472015-02-17 21:11:10 -0500403 self.close_connection = True
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000404 return
405 if not self.parse_request():
406 # An error code has been sent, just exit
407 return
408 mname = 'do_' + self.command
409 if not hasattr(self, mname):
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200410 self.send_error(
411 HTTPStatus.NOT_IMPLEMENTED,
412 "Unsupported method (%r)" % self.command)
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000413 return
414 method = getattr(self, mname)
415 method()
416 self.wfile.flush() #actually send the response if not already done.
417 except socket.timeout as e:
418 #a read or a write timed out. Discard this connection
419 self.log_error("Request timed out: %r", e)
Benjamin Peterson70e28472015-02-17 21:11:10 -0500420 self.close_connection = True
Georg Brandl24420152008-05-26 16:32:26 +0000421 return
Georg Brandl24420152008-05-26 16:32:26 +0000422
423 def handle(self):
424 """Handle multiple requests if necessary."""
Benjamin Peterson70e28472015-02-17 21:11:10 -0500425 self.close_connection = True
Georg Brandl24420152008-05-26 16:32:26 +0000426
427 self.handle_one_request()
428 while not self.close_connection:
429 self.handle_one_request()
430
Senthil Kumaran26886442013-03-15 07:53:21 -0700431 def send_error(self, code, message=None, explain=None):
Georg Brandl24420152008-05-26 16:32:26 +0000432 """Send and log an error reply.
433
Senthil Kumaran26886442013-03-15 07:53:21 -0700434 Arguments are
435 * code: an HTTP error code
436 3 digits
437 * message: a simple optional 1 line reason phrase.
438 *( HTAB / SP / VCHAR / %x80-FF )
439 defaults to short entry matching the response code
440 * explain: a detailed message defaults to the long entry
441 matching the response code.
Georg Brandl24420152008-05-26 16:32:26 +0000442
443 This sends an error response (so it must be called before any
444 output has been generated), logs the error, and finally sends
445 a piece of HTML explaining the error to the user.
446
447 """
448
449 try:
450 shortmsg, longmsg = self.responses[code]
451 except KeyError:
452 shortmsg, longmsg = '???', '???'
453 if message is None:
454 message = shortmsg
Senthil Kumaran26886442013-03-15 07:53:21 -0700455 if explain is None:
456 explain = longmsg
Georg Brandl24420152008-05-26 16:32:26 +0000457 self.log_error("code %d, message %s", code, message)
Senthil Kumaran1e7551d2013-03-05 02:25:58 -0800458 self.send_response(code, message)
Georg Brandl24420152008-05-26 16:32:26 +0000459 self.send_header('Connection', 'close')
Martin Pantere42e1292016-06-08 08:29:13 +0000460
461 # Message body is omitted for cases described in:
462 # - RFC7230: 3.3. 1xx, 204(No Content), 304(Not Modified)
463 # - RFC7231: 6.3.6. 205(Reset Content)
464 body = None
465 if (code >= 200 and
466 code not in (HTTPStatus.NO_CONTENT,
467 HTTPStatus.RESET_CONTENT,
468 HTTPStatus.NOT_MODIFIED)):
469 # HTML encode to prevent Cross Site Scripting attacks
470 # (see bug #1100201)
471 content = (self.error_message_format % {
472 'code': code,
Martin Panter40de69a2016-06-08 09:45:58 +0000473 'message': html.escape(message, quote=False),
474 'explain': html.escape(explain, quote=False)
Martin Pantere42e1292016-06-08 08:29:13 +0000475 })
476 body = content.encode('UTF-8', 'replace')
477 self.send_header("Content-Type", self.error_content_type)
ValeriyaSinevichb36b0a32018-06-18 14:17:53 -0700478 self.send_header('Content-Length', str(len(body)))
Georg Brandl24420152008-05-26 16:32:26 +0000479 self.end_headers()
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200480
Martin Pantere42e1292016-06-08 08:29:13 +0000481 if self.command != 'HEAD' and body:
Senthil Kumaran52d27202012-10-10 23:16:21 -0700482 self.wfile.write(body)
Georg Brandl24420152008-05-26 16:32:26 +0000483
484 def send_response(self, code, message=None):
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800485 """Add the response header to the headers buffer and log the
486 response code.
Georg Brandl24420152008-05-26 16:32:26 +0000487
488 Also send two standard headers with the server software
489 version and the current date.
490
491 """
492 self.log_request(code)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000493 self.send_response_only(code, message)
494 self.send_header('Server', self.version_string())
495 self.send_header('Date', self.date_time_string())
496
497 def send_response_only(self, code, message=None):
498 """Send the response header only."""
Georg Brandl24420152008-05-26 16:32:26 +0000499 if self.request_version != 'HTTP/0.9':
Martin Panter50badad2016-04-03 01:28:53 +0000500 if message is None:
501 if code in self.responses:
502 message = self.responses[code][0]
503 else:
504 message = ''
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800505 if not hasattr(self, '_headers_buffer'):
506 self._headers_buffer = []
507 self._headers_buffer.append(("%s %d %s\r\n" %
508 (self.protocol_version, code, message)).encode(
509 'latin-1', 'strict'))
Georg Brandl24420152008-05-26 16:32:26 +0000510
511 def send_header(self, keyword, value):
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800512 """Send a MIME header to the headers buffer."""
Georg Brandl24420152008-05-26 16:32:26 +0000513 if self.request_version != 'HTTP/0.9':
Senthil Kumarane4dad4f2010-11-21 14:36:14 +0000514 if not hasattr(self, '_headers_buffer'):
515 self._headers_buffer = []
516 self._headers_buffer.append(
Marc-André Lemburg8f36af72011-02-25 15:42:01 +0000517 ("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict'))
Georg Brandl24420152008-05-26 16:32:26 +0000518
519 if keyword.lower() == 'connection':
520 if value.lower() == 'close':
Benjamin Peterson70e28472015-02-17 21:11:10 -0500521 self.close_connection = True
Georg Brandl24420152008-05-26 16:32:26 +0000522 elif value.lower() == 'keep-alive':
Benjamin Peterson70e28472015-02-17 21:11:10 -0500523 self.close_connection = False
Georg Brandl24420152008-05-26 16:32:26 +0000524
525 def end_headers(self):
526 """Send the blank line ending the MIME headers."""
527 if self.request_version != 'HTTP/0.9':
Senthil Kumarane4dad4f2010-11-21 14:36:14 +0000528 self._headers_buffer.append(b"\r\n")
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800529 self.flush_headers()
530
531 def flush_headers(self):
532 if hasattr(self, '_headers_buffer'):
Senthil Kumarane4dad4f2010-11-21 14:36:14 +0000533 self.wfile.write(b"".join(self._headers_buffer))
534 self._headers_buffer = []
Georg Brandl24420152008-05-26 16:32:26 +0000535
536 def log_request(self, code='-', size='-'):
537 """Log an accepted request.
538
539 This is called by send_response().
540
541 """
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200542 if isinstance(code, HTTPStatus):
543 code = code.value
Georg Brandl24420152008-05-26 16:32:26 +0000544 self.log_message('"%s" %s %s',
545 self.requestline, str(code), str(size))
546
547 def log_error(self, format, *args):
548 """Log an error.
549
550 This is called when a request cannot be fulfilled. By
551 default it passes the message on to log_message().
552
553 Arguments are the same as for log_message().
554
555 XXX This should go to the separate error log.
556
557 """
558
559 self.log_message(format, *args)
560
561 def log_message(self, format, *args):
562 """Log an arbitrary message.
563
564 This is used by all other logging functions. Override
565 it if you have specific logging wishes.
566
567 The first argument, FORMAT, is a format string for the
568 message to be logged. If the format string contains
569 any % escapes requiring parameters, they should be
570 specified as subsequent arguments (it's just like
571 printf!).
572
Senthil Kumarandb727b42012-04-29 13:41:03 +0800573 The client ip and current date/time are prefixed to
Georg Brandl24420152008-05-26 16:32:26 +0000574 every message.
575
576 """
577
578 sys.stderr.write("%s - - [%s] %s\n" %
579 (self.address_string(),
580 self.log_date_time_string(),
581 format%args))
582
583 def version_string(self):
584 """Return the server software version string."""
585 return self.server_version + ' ' + self.sys_version
586
587 def date_time_string(self, timestamp=None):
588 """Return the current date and time formatted for a message header."""
589 if timestamp is None:
590 timestamp = time.time()
Berker Peksag04bc5b92016-03-14 06:06:03 +0200591 return email.utils.formatdate(timestamp, usegmt=True)
Georg Brandl24420152008-05-26 16:32:26 +0000592
593 def log_date_time_string(self):
594 """Return the current time formatted for logging."""
595 now = time.time()
596 year, month, day, hh, mm, ss, x, y, z = time.localtime(now)
597 s = "%02d/%3s/%04d %02d:%02d:%02d" % (
598 day, self.monthname[month], year, hh, mm, ss)
599 return s
600
601 weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
602
603 monthname = [None,
604 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
605 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
606
607 def address_string(self):
Senthil Kumaran1aacba42012-04-29 12:51:54 +0800608 """Return the client address."""
Georg Brandl24420152008-05-26 16:32:26 +0000609
Senthil Kumaran1aacba42012-04-29 12:51:54 +0800610 return self.client_address[0]
Georg Brandl24420152008-05-26 16:32:26 +0000611
612 # Essentially static class variables
613
614 # The version of the HTTP protocol we support.
615 # Set this to HTTP/1.1 to enable automatic keepalive
616 protocol_version = "HTTP/1.0"
617
Barry Warsaw820c1202008-06-12 04:06:45 +0000618 # MessageClass used to parse headers
Barry Warsaw820c1202008-06-12 04:06:45 +0000619 MessageClass = http.client.HTTPMessage
Georg Brandl24420152008-05-26 16:32:26 +0000620
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200621 # hack to maintain backwards compatibility
Georg Brandl24420152008-05-26 16:32:26 +0000622 responses = {
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200623 v: (v.phrase, v.description)
624 for v in HTTPStatus.__members__.values()
625 }
Georg Brandl24420152008-05-26 16:32:26 +0000626
627
628class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
629
630 """Simple HTTP request handler with GET and HEAD commands.
631
632 This serves files from the current directory and any of its
633 subdirectories. The MIME type for files is determined by
634 calling the .guess_type() method.
635
636 The GET and HEAD requests are identical except that the HEAD
637 request omits the actual contents of the file.
638
639 """
640
641 server_version = "SimpleHTTP/" + __version__
642
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200643 def __init__(self, *args, directory=None, **kwargs):
644 if directory is None:
645 directory = os.getcwd()
Géry Ogam781266e2019-09-11 15:03:46 +0200646 self.directory = os.fspath(directory)
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200647 super().__init__(*args, **kwargs)
648
Georg Brandl24420152008-05-26 16:32:26 +0000649 def do_GET(self):
650 """Serve a GET request."""
651 f = self.send_head()
652 if f:
Serhiy Storchaka91b0bc22014-01-25 19:43:02 +0200653 try:
654 self.copyfile(f, self.wfile)
655 finally:
656 f.close()
Georg Brandl24420152008-05-26 16:32:26 +0000657
658 def do_HEAD(self):
659 """Serve a HEAD request."""
660 f = self.send_head()
661 if f:
662 f.close()
663
664 def send_head(self):
665 """Common code for GET and HEAD commands.
666
667 This sends the response code and MIME headers.
668
669 Return value is either a file object (which has to be copied
670 to the outputfile by the caller unless the command was HEAD,
671 and must be closed by the caller under all circumstances), or
672 None, in which case the caller has nothing further to do.
673
674 """
675 path = self.translate_path(self.path)
676 f = None
677 if os.path.isdir(path):
Benjamin Peterson94cb7a22014-12-26 10:53:43 -0600678 parts = urllib.parse.urlsplit(self.path)
679 if not parts.path.endswith('/'):
Georg Brandl24420152008-05-26 16:32:26 +0000680 # redirect browser - doing basically what apache does
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200681 self.send_response(HTTPStatus.MOVED_PERMANENTLY)
Benjamin Peterson94cb7a22014-12-26 10:53:43 -0600682 new_parts = (parts[0], parts[1], parts[2] + '/',
683 parts[3], parts[4])
684 new_url = urllib.parse.urlunsplit(new_parts)
685 self.send_header("Location", new_url)
Georg Brandl24420152008-05-26 16:32:26 +0000686 self.end_headers()
687 return None
688 for index in "index.html", "index.htm":
689 index = os.path.join(path, index)
690 if os.path.exists(index):
691 path = index
692 break
693 else:
694 return self.list_directory(path)
695 ctype = self.guess_type(path)
Michael Felt2062a202018-12-26 06:43:42 +0100696 # check for trailing "/" which should return 404. See Issue17324
697 # The test for this was added in test_httpserver.py
698 # However, some OS platforms accept a trailingSlash as a filename
699 # See discussion on python-dev and Issue34711 regarding
700 # parseing and rejection of filenames with a trailing slash
701 if path.endswith("/"):
702 self.send_error(HTTPStatus.NOT_FOUND, "File not found")
703 return None
Georg Brandl24420152008-05-26 16:32:26 +0000704 try:
705 f = open(path, 'rb')
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200706 except OSError:
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200707 self.send_error(HTTPStatus.NOT_FOUND, "File not found")
Georg Brandl24420152008-05-26 16:32:26 +0000708 return None
Pierre Quentel351adda2017-04-02 12:26:12 +0200709
Serhiy Storchaka91b0bc22014-01-25 19:43:02 +0200710 try:
Pierre Quentel351adda2017-04-02 12:26:12 +0200711 fs = os.fstat(f.fileno())
712 # Use browser cache if possible
713 if ("If-Modified-Since" in self.headers
714 and "If-None-Match" not in self.headers):
715 # compare If-Modified-Since and time of last file modification
716 try:
717 ims = email.utils.parsedate_to_datetime(
718 self.headers["If-Modified-Since"])
719 except (TypeError, IndexError, OverflowError, ValueError):
720 # ignore ill-formed values
721 pass
722 else:
723 if ims.tzinfo is None:
724 # obsolete format with no timezone, cf.
725 # https://tools.ietf.org/html/rfc7231#section-7.1.1.1
726 ims = ims.replace(tzinfo=datetime.timezone.utc)
727 if ims.tzinfo is datetime.timezone.utc:
728 # compare to UTC datetime of last modification
729 last_modif = datetime.datetime.fromtimestamp(
730 fs.st_mtime, datetime.timezone.utc)
731 # remove microseconds, like in If-Modified-Since
732 last_modif = last_modif.replace(microsecond=0)
Serhiy Storchaka13ad3b72017-09-14 09:38:36 +0300733
Pierre Quentel351adda2017-04-02 12:26:12 +0200734 if last_modif <= ims:
735 self.send_response(HTTPStatus.NOT_MODIFIED)
736 self.end_headers()
737 f.close()
738 return None
739
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200740 self.send_response(HTTPStatus.OK)
Serhiy Storchaka91b0bc22014-01-25 19:43:02 +0200741 self.send_header("Content-type", ctype)
Serhiy Storchaka91b0bc22014-01-25 19:43:02 +0200742 self.send_header("Content-Length", str(fs[6]))
Serhiy Storchaka13ad3b72017-09-14 09:38:36 +0300743 self.send_header("Last-Modified",
Pierre Quentel351adda2017-04-02 12:26:12 +0200744 self.date_time_string(fs.st_mtime))
Serhiy Storchaka91b0bc22014-01-25 19:43:02 +0200745 self.end_headers()
746 return f
747 except:
748 f.close()
749 raise
Georg Brandl24420152008-05-26 16:32:26 +0000750
751 def list_directory(self, path):
752 """Helper to produce a directory listing (absent index.html).
753
754 Return value is either a file object, or None (indicating an
755 error). In either case, the headers are sent, making the
756 interface the same as for send_head().
757
758 """
759 try:
760 list = os.listdir(path)
Andrew Svetlovad28c7f2012-12-18 22:02:39 +0200761 except OSError:
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200762 self.send_error(
763 HTTPStatus.NOT_FOUND,
764 "No permission to list directory")
Georg Brandl24420152008-05-26 16:32:26 +0000765 return None
766 list.sort(key=lambda a: a.lower())
767 r = []
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300768 try:
769 displaypath = urllib.parse.unquote(self.path,
770 errors='surrogatepass')
771 except UnicodeDecodeError:
772 displaypath = urllib.parse.unquote(path)
Martin Panterda3bb382016-04-11 00:40:08 +0000773 displaypath = html.escape(displaypath, quote=False)
Ezio Melottica897e92011-11-02 19:33:29 +0200774 enc = sys.getfilesystemencoding()
775 title = 'Directory listing for %s' % displaypath
776 r.append('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" '
777 '"http://www.w3.org/TR/html4/strict.dtd">')
778 r.append('<html>\n<head>')
779 r.append('<meta http-equiv="Content-Type" '
780 'content="text/html; charset=%s">' % enc)
781 r.append('<title>%s</title>\n</head>' % title)
782 r.append('<body>\n<h1>%s</h1>' % title)
783 r.append('<hr>\n<ul>')
Georg Brandl24420152008-05-26 16:32:26 +0000784 for name in list:
785 fullname = os.path.join(path, name)
786 displayname = linkname = name
787 # Append / for directories or @ for symbolic links
788 if os.path.isdir(fullname):
789 displayname = name + "/"
790 linkname = name + "/"
791 if os.path.islink(fullname):
792 displayname = name + "@"
793 # Note: a link to a directory displays with @ and links with /
Ezio Melottica897e92011-11-02 19:33:29 +0200794 r.append('<li><a href="%s">%s</a></li>'
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300795 % (urllib.parse.quote(linkname,
796 errors='surrogatepass'),
Martin Panterda3bb382016-04-11 00:40:08 +0000797 html.escape(displayname, quote=False)))
Ezio Melottica897e92011-11-02 19:33:29 +0200798 r.append('</ul>\n<hr>\n</body>\n</html>\n')
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300799 encoded = '\n'.join(r).encode(enc, 'surrogateescape')
Georg Brandl24420152008-05-26 16:32:26 +0000800 f = io.BytesIO()
801 f.write(encoded)
802 f.seek(0)
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200803 self.send_response(HTTPStatus.OK)
Georg Brandl24420152008-05-26 16:32:26 +0000804 self.send_header("Content-type", "text/html; charset=%s" % enc)
805 self.send_header("Content-Length", str(len(encoded)))
806 self.end_headers()
807 return f
808
809 def translate_path(self, path):
810 """Translate a /-separated PATH to the local filename syntax.
811
812 Components that mean special things to the local file system
813 (e.g. drive or directory names) are ignored. (XXX They should
814 probably be diagnosed.)
815
816 """
817 # abandon query parameters
818 path = path.split('?',1)[0]
819 path = path.split('#',1)[0]
Senthil Kumaran72c238e2013-09-13 00:21:18 -0700820 # Don't forget explicit trailing slash when normalizing. Issue17324
Senthil Kumaran600b7352013-09-29 18:59:04 -0700821 trailing_slash = path.rstrip().endswith('/')
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300822 try:
823 path = urllib.parse.unquote(path, errors='surrogatepass')
824 except UnicodeDecodeError:
825 path = urllib.parse.unquote(path)
826 path = posixpath.normpath(path)
Georg Brandl24420152008-05-26 16:32:26 +0000827 words = path.split('/')
828 words = filter(None, words)
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200829 path = self.directory
Georg Brandl24420152008-05-26 16:32:26 +0000830 for word in words:
Martin Panterd274b3f2016-04-18 03:45:18 +0000831 if os.path.dirname(word) or word in (os.curdir, os.pardir):
832 # Ignore components that are not a simple file/directory name
833 continue
Georg Brandl24420152008-05-26 16:32:26 +0000834 path = os.path.join(path, word)
Senthil Kumaran72c238e2013-09-13 00:21:18 -0700835 if trailing_slash:
836 path += '/'
Georg Brandl24420152008-05-26 16:32:26 +0000837 return path
838
839 def copyfile(self, source, outputfile):
840 """Copy all data between two file objects.
841
842 The SOURCE argument is a file object open for reading
843 (or anything with a read() method) and the DESTINATION
844 argument is a file object open for writing (or
845 anything with a write() method).
846
847 The only reason for overriding this would be to change
848 the block size or perhaps to replace newlines by CRLF
849 -- note however that this the default server uses this
850 to copy binary data as well.
851
852 """
853 shutil.copyfileobj(source, outputfile)
854
855 def guess_type(self, path):
856 """Guess the type of a file.
857
858 Argument is a PATH (a filename).
859
860 Return value is a string of the form type/subtype,
861 usable for a MIME Content-type header.
862
863 The default implementation looks the file's extension
864 up in the table self.extensions_map, using application/octet-stream
865 as a default; however it would be permissible (if
866 slow) to look inside the data to make a better guess.
867
868 """
869
870 base, ext = posixpath.splitext(path)
871 if ext in self.extensions_map:
872 return self.extensions_map[ext]
873 ext = ext.lower()
874 if ext in self.extensions_map:
875 return self.extensions_map[ext]
876 else:
877 return self.extensions_map['']
878
879 if not mimetypes.inited:
880 mimetypes.init() # try to read system mime.types
881 extensions_map = mimetypes.types_map.copy()
882 extensions_map.update({
883 '': 'application/octet-stream', # Default
884 '.py': 'text/plain',
885 '.c': 'text/plain',
886 '.h': 'text/plain',
887 })
888
889
890# Utilities for CGIHTTPRequestHandler
891
Senthil Kumarand70846b2012-04-12 02:34:32 +0800892def _url_collapse_path(path):
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000893 """
894 Given a URL path, remove extra '/'s and '.' path elements and collapse
Martin Panter9955a372015-10-07 10:26:23 +0000895 any '..' references and returns a collapsed path.
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000896
897 Implements something akin to RFC-2396 5.2 step 6 to parse relative paths.
Senthil Kumarand70846b2012-04-12 02:34:32 +0800898 The utility of this function is limited to is_cgi method and helps
899 preventing some security attacks.
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000900
Martin Pantercb29e8c2015-10-03 05:55:46 +0000901 Returns: The reconstituted URL, which will always start with a '/'.
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000902
903 Raises: IndexError if too many '..' occur within the path.
Senthil Kumarand70846b2012-04-12 02:34:32 +0800904
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000905 """
Martin Pantercb29e8c2015-10-03 05:55:46 +0000906 # Query component should not be involved.
907 path, _, query = path.partition('?')
908 path = urllib.parse.unquote(path)
909
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000910 # Similar to os.path.split(os.path.normpath(path)) but specific to URL
911 # path semantics rather than local operating system semantics.
Senthil Kumarand70846b2012-04-12 02:34:32 +0800912 path_parts = path.split('/')
913 head_parts = []
914 for part in path_parts[:-1]:
915 if part == '..':
916 head_parts.pop() # IndexError if more '..' than prior parts
917 elif part and part != '.':
918 head_parts.append( part )
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000919 if path_parts:
Senthil Kumarandbb369d2012-04-11 03:15:28 +0800920 tail_part = path_parts.pop()
Senthil Kumarand70846b2012-04-12 02:34:32 +0800921 if tail_part:
922 if tail_part == '..':
923 head_parts.pop()
924 tail_part = ''
925 elif tail_part == '.':
926 tail_part = ''
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000927 else:
928 tail_part = ''
Senthil Kumarand70846b2012-04-12 02:34:32 +0800929
Martin Pantercb29e8c2015-10-03 05:55:46 +0000930 if query:
931 tail_part = '?'.join((tail_part, query))
932
Senthil Kumarand70846b2012-04-12 02:34:32 +0800933 splitpath = ('/' + '/'.join(head_parts), tail_part)
934 collapsed_path = "/".join(splitpath)
935
936 return collapsed_path
937
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000938
939
Georg Brandl24420152008-05-26 16:32:26 +0000940nobody = None
941
942def nobody_uid():
943 """Internal routine to get nobody's uid"""
944 global nobody
945 if nobody:
946 return nobody
947 try:
948 import pwd
Brett Cannoncd171c82013-07-04 17:43:24 -0400949 except ImportError:
Georg Brandl24420152008-05-26 16:32:26 +0000950 return -1
951 try:
952 nobody = pwd.getpwnam('nobody')[2]
953 except KeyError:
Georg Brandlcbd2ab12010-12-04 10:39:14 +0000954 nobody = 1 + max(x[2] for x in pwd.getpwall())
Georg Brandl24420152008-05-26 16:32:26 +0000955 return nobody
956
957
958def executable(path):
959 """Test for executable file."""
Victor Stinnerfb25ba92011-06-20 17:45:54 +0200960 return os.access(path, os.X_OK)
Georg Brandl24420152008-05-26 16:32:26 +0000961
962
963class CGIHTTPRequestHandler(SimpleHTTPRequestHandler):
964
965 """Complete HTTP server with GET, HEAD and POST commands.
966
967 GET and HEAD also support running CGI scripts.
968
969 The POST command is *only* implemented for CGI scripts.
970
971 """
972
973 # Determine platform specifics
974 have_fork = hasattr(os, 'fork')
Georg Brandl24420152008-05-26 16:32:26 +0000975
976 # Make rfile unbuffered -- we need to read one line and then pass
977 # the rest to a subprocess, so we can't use buffered input.
978 rbufsize = 0
979
980 def do_POST(self):
981 """Serve a POST request.
982
983 This is only implemented for CGI scripts.
984
985 """
986
987 if self.is_cgi():
988 self.run_cgi()
989 else:
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200990 self.send_error(
991 HTTPStatus.NOT_IMPLEMENTED,
992 "Can only POST to CGI scripts")
Georg Brandl24420152008-05-26 16:32:26 +0000993
994 def send_head(self):
995 """Version of send_head that support CGI scripts"""
996 if self.is_cgi():
997 return self.run_cgi()
998 else:
999 return SimpleHTTPRequestHandler.send_head(self)
1000
1001 def is_cgi(self):
1002 """Test whether self.path corresponds to a CGI script.
1003
Benjamin Petersonad71f0f2009-04-11 20:12:10 +00001004 Returns True and updates the cgi_info attribute to the tuple
1005 (dir, rest) if self.path requires running a CGI script.
1006 Returns False otherwise.
Georg Brandl24420152008-05-26 16:32:26 +00001007
Benjamin Petersona7deeee2009-05-08 20:54:42 +00001008 If any exception is raised, the caller should assume that
1009 self.path was rejected as invalid and act accordingly.
1010
Benjamin Petersonad71f0f2009-04-11 20:12:10 +00001011 The default implementation tests whether the normalized url
1012 path begins with one of the strings in self.cgi_directories
1013 (and the next character is a '/' or the end of the string).
Georg Brandl24420152008-05-26 16:32:26 +00001014
1015 """
Martin Pantercb29e8c2015-10-03 05:55:46 +00001016 collapsed_path = _url_collapse_path(self.path)
Senthil Kumarand70846b2012-04-12 02:34:32 +08001017 dir_sep = collapsed_path.find('/', 1)
Siwon Kang91daa9d2019-11-22 18:13:05 +09001018 while dir_sep > 0 and not collapsed_path[:dir_sep] in self.cgi_directories:
1019 dir_sep = collapsed_path.find('/', dir_sep+1)
1020 if dir_sep > 0:
1021 head, tail = collapsed_path[:dir_sep], collapsed_path[dir_sep+1:]
Senthil Kumarandbb369d2012-04-11 03:15:28 +08001022 self.cgi_info = head, tail
Benjamin Petersonad71f0f2009-04-11 20:12:10 +00001023 return True
Georg Brandl24420152008-05-26 16:32:26 +00001024 return False
1025
Senthil Kumarand70846b2012-04-12 02:34:32 +08001026
Georg Brandl24420152008-05-26 16:32:26 +00001027 cgi_directories = ['/cgi-bin', '/htbin']
1028
1029 def is_executable(self, path):
1030 """Test whether argument path is an executable file."""
1031 return executable(path)
1032
1033 def is_python(self, path):
1034 """Test whether argument path is a Python script."""
1035 head, tail = os.path.splitext(path)
1036 return tail.lower() in (".py", ".pyw")
1037
1038 def run_cgi(self):
1039 """Execute a CGI script."""
Georg Brandl24420152008-05-26 16:32:26 +00001040 dir, rest = self.cgi_info
Ned Deily915a30f2014-07-12 22:06:26 -07001041 path = dir + '/' + rest
1042 i = path.find('/', len(dir)+1)
Georg Brandl24420152008-05-26 16:32:26 +00001043 while i >= 0:
Ned Deily915a30f2014-07-12 22:06:26 -07001044 nextdir = path[:i]
1045 nextrest = path[i+1:]
Georg Brandl24420152008-05-26 16:32:26 +00001046
1047 scriptdir = self.translate_path(nextdir)
1048 if os.path.isdir(scriptdir):
1049 dir, rest = nextdir, nextrest
Ned Deily915a30f2014-07-12 22:06:26 -07001050 i = path.find('/', len(dir)+1)
Georg Brandl24420152008-05-26 16:32:26 +00001051 else:
1052 break
1053
1054 # find an explicit query string, if present.
Martin Pantera02e18a2015-10-03 05:38:07 +00001055 rest, _, query = rest.partition('?')
Georg Brandl24420152008-05-26 16:32:26 +00001056
1057 # dissect the part after the directory name into a script name &
1058 # a possible additional path, to be stored in PATH_INFO.
1059 i = rest.find('/')
1060 if i >= 0:
1061 script, rest = rest[:i], rest[i:]
1062 else:
1063 script, rest = rest, ''
1064
1065 scriptname = dir + '/' + script
1066 scriptfile = self.translate_path(scriptname)
1067 if not os.path.exists(scriptfile):
Serhiy Storchakae4db7692014-12-23 16:28:28 +02001068 self.send_error(
1069 HTTPStatus.NOT_FOUND,
1070 "No such CGI script (%r)" % scriptname)
Georg Brandl24420152008-05-26 16:32:26 +00001071 return
1072 if not os.path.isfile(scriptfile):
Serhiy Storchakae4db7692014-12-23 16:28:28 +02001073 self.send_error(
1074 HTTPStatus.FORBIDDEN,
1075 "CGI script is not a plain file (%r)" % scriptname)
Georg Brandl24420152008-05-26 16:32:26 +00001076 return
1077 ispy = self.is_python(scriptname)
Victor Stinnerfb25ba92011-06-20 17:45:54 +02001078 if self.have_fork or not ispy:
Georg Brandl24420152008-05-26 16:32:26 +00001079 if not self.is_executable(scriptfile):
Serhiy Storchakae4db7692014-12-23 16:28:28 +02001080 self.send_error(
1081 HTTPStatus.FORBIDDEN,
1082 "CGI script is not executable (%r)" % scriptname)
Georg Brandl24420152008-05-26 16:32:26 +00001083 return
1084
1085 # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
1086 # XXX Much of the following could be prepared ahead of time!
Senthil Kumaran42713722010-10-03 17:55:45 +00001087 env = copy.deepcopy(os.environ)
Georg Brandl24420152008-05-26 16:32:26 +00001088 env['SERVER_SOFTWARE'] = self.version_string()
1089 env['SERVER_NAME'] = self.server.server_name
1090 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
1091 env['SERVER_PROTOCOL'] = self.protocol_version
1092 env['SERVER_PORT'] = str(self.server.server_port)
1093 env['REQUEST_METHOD'] = self.command
Jeremy Hylton1afc1692008-06-18 20:49:58 +00001094 uqrest = urllib.parse.unquote(rest)
Georg Brandl24420152008-05-26 16:32:26 +00001095 env['PATH_INFO'] = uqrest
1096 env['PATH_TRANSLATED'] = self.translate_path(uqrest)
1097 env['SCRIPT_NAME'] = scriptname
1098 if query:
1099 env['QUERY_STRING'] = query
Georg Brandl24420152008-05-26 16:32:26 +00001100 env['REMOTE_ADDR'] = self.client_address[0]
Barry Warsaw820c1202008-06-12 04:06:45 +00001101 authorization = self.headers.get("authorization")
Georg Brandl24420152008-05-26 16:32:26 +00001102 if authorization:
1103 authorization = authorization.split()
1104 if len(authorization) == 2:
1105 import base64, binascii
1106 env['AUTH_TYPE'] = authorization[0]
1107 if authorization[0].lower() == "basic":
1108 try:
1109 authorization = authorization[1].encode('ascii')
Georg Brandl706824f2009-06-04 09:42:55 +00001110 authorization = base64.decodebytes(authorization).\
Georg Brandl24420152008-05-26 16:32:26 +00001111 decode('ascii')
1112 except (binascii.Error, UnicodeError):
1113 pass
1114 else:
1115 authorization = authorization.split(':')
1116 if len(authorization) == 2:
1117 env['REMOTE_USER'] = authorization[0]
1118 # XXX REMOTE_IDENT
Barry Warsaw820c1202008-06-12 04:06:45 +00001119 if self.headers.get('content-type') is None:
1120 env['CONTENT_TYPE'] = self.headers.get_content_type()
Georg Brandl24420152008-05-26 16:32:26 +00001121 else:
Barry Warsaw820c1202008-06-12 04:06:45 +00001122 env['CONTENT_TYPE'] = self.headers['content-type']
1123 length = self.headers.get('content-length')
Georg Brandl24420152008-05-26 16:32:26 +00001124 if length:
1125 env['CONTENT_LENGTH'] = length
Barry Warsaw820c1202008-06-12 04:06:45 +00001126 referer = self.headers.get('referer')
Georg Brandl24420152008-05-26 16:32:26 +00001127 if referer:
1128 env['HTTP_REFERER'] = referer
1129 accept = []
1130 for line in self.headers.getallmatchingheaders('accept'):
1131 if line[:1] in "\t\n\r ":
1132 accept.append(line.strip())
1133 else:
1134 accept = accept + line[7:].split(',')
1135 env['HTTP_ACCEPT'] = ','.join(accept)
Barry Warsaw820c1202008-06-12 04:06:45 +00001136 ua = self.headers.get('user-agent')
Georg Brandl24420152008-05-26 16:32:26 +00001137 if ua:
1138 env['HTTP_USER_AGENT'] = ua
Barry Warsaw820c1202008-06-12 04:06:45 +00001139 co = filter(None, self.headers.get_all('cookie', []))
Georg Brandl62e2ca22010-07-31 21:54:24 +00001140 cookie_str = ', '.join(co)
1141 if cookie_str:
1142 env['HTTP_COOKIE'] = cookie_str
Georg Brandl24420152008-05-26 16:32:26 +00001143 # XXX Other HTTP_* headers
1144 # Since we're setting the env in the parent, provide empty
1145 # values to override previously set values
1146 for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
1147 'HTTP_USER_AGENT', 'HTTP_COOKIE', 'HTTP_REFERER'):
1148 env.setdefault(k, "")
Georg Brandl24420152008-05-26 16:32:26 +00001149
Serhiy Storchakae4db7692014-12-23 16:28:28 +02001150 self.send_response(HTTPStatus.OK, "Script output follows")
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +08001151 self.flush_headers()
Georg Brandl24420152008-05-26 16:32:26 +00001152
1153 decoded_query = query.replace('+', ' ')
1154
1155 if self.have_fork:
1156 # Unix -- fork as we should
1157 args = [script]
1158 if '=' not in decoded_query:
1159 args.append(decoded_query)
1160 nobody = nobody_uid()
1161 self.wfile.flush() # Always flush before forking
1162 pid = os.fork()
1163 if pid != 0:
1164 # Parent
1165 pid, sts = os.waitpid(pid, 0)
1166 # throw away additional data [see bug #427345]
1167 while select.select([self.rfile], [], [], 0)[0]:
1168 if not self.rfile.read(1):
1169 break
1170 if sts:
1171 self.log_error("CGI script exit status %#x", sts)
1172 return
1173 # Child
1174 try:
1175 try:
1176 os.setuid(nobody)
Andrew Svetlovad28c7f2012-12-18 22:02:39 +02001177 except OSError:
Georg Brandl24420152008-05-26 16:32:26 +00001178 pass
1179 os.dup2(self.rfile.fileno(), 0)
1180 os.dup2(self.wfile.fileno(), 1)
Senthil Kumaran42713722010-10-03 17:55:45 +00001181 os.execve(scriptfile, args, env)
Georg Brandl24420152008-05-26 16:32:26 +00001182 except:
1183 self.server.handle_error(self.request, self.client_address)
1184 os._exit(127)
1185
Amaury Forgeot d'Arccb0d2d72008-06-18 22:19:22 +00001186 else:
1187 # Non-Unix -- use subprocess
1188 import subprocess
Senthil Kumarane29cd162009-11-11 04:17:53 +00001189 cmdline = [scriptfile]
Georg Brandl24420152008-05-26 16:32:26 +00001190 if self.is_python(scriptfile):
1191 interp = sys.executable
1192 if interp.lower().endswith("w.exe"):
1193 # On Windows, use python.exe, not pythonw.exe
1194 interp = interp[:-5] + interp[-4:]
Senthil Kumarane29cd162009-11-11 04:17:53 +00001195 cmdline = [interp, '-u'] + cmdline
1196 if '=' not in query:
1197 cmdline.append(query)
1198 self.log_message("command: %s", subprocess.list2cmdline(cmdline))
Georg Brandl24420152008-05-26 16:32:26 +00001199 try:
1200 nbytes = int(length)
1201 except (TypeError, ValueError):
1202 nbytes = 0
Amaury Forgeot d'Arccb0d2d72008-06-18 22:19:22 +00001203 p = subprocess.Popen(cmdline,
1204 stdin=subprocess.PIPE,
1205 stdout=subprocess.PIPE,
Senthil Kumaran42713722010-10-03 17:55:45 +00001206 stderr=subprocess.PIPE,
1207 env = env
Amaury Forgeot d'Arccb0d2d72008-06-18 22:19:22 +00001208 )
Georg Brandl24420152008-05-26 16:32:26 +00001209 if self.command.lower() == "post" and nbytes > 0:
1210 data = self.rfile.read(nbytes)
Amaury Forgeot d'Arccb0d2d72008-06-18 22:19:22 +00001211 else:
1212 data = None
Georg Brandl24420152008-05-26 16:32:26 +00001213 # throw away additional data [see bug #427345]
1214 while select.select([self.rfile._sock], [], [], 0)[0]:
1215 if not self.rfile._sock.recv(1):
1216 break
Amaury Forgeot d'Arccb0d2d72008-06-18 22:19:22 +00001217 stdout, stderr = p.communicate(data)
1218 self.wfile.write(stdout)
1219 if stderr:
1220 self.log_error('%s', stderr)
Brian Curtincbad4df2010-11-05 15:04:48 +00001221 p.stderr.close()
1222 p.stdout.close()
Amaury Forgeot d'Arccb0d2d72008-06-18 22:19:22 +00001223 status = p.returncode
1224 if status:
1225 self.log_error("CGI script exit status %#x", status)
Georg Brandl24420152008-05-26 16:32:26 +00001226 else:
1227 self.log_message("CGI script exited OK")
1228
1229
Jason R. Coombsf2890842019-02-07 08:22:45 -05001230def _get_best_family(*address):
1231 infos = socket.getaddrinfo(
1232 *address,
1233 type=socket.SOCK_STREAM,
1234 flags=socket.AI_PASSIVE,
1235 )
1236 family, type, proto, canonname, sockaddr = next(iter(infos))
1237 return family, sockaddr
1238
1239
Senthil Kumarandefe7f42013-09-15 09:37:27 -07001240def test(HandlerClass=BaseHTTPRequestHandler,
Géry Ogam1cee2162018-05-29 22:10:30 +02001241 ServerClass=ThreadingHTTPServer,
Jason R. Coombsf2890842019-02-07 08:22:45 -05001242 protocol="HTTP/1.0", port=8000, bind=None):
Georg Brandl24420152008-05-26 16:32:26 +00001243 """Test the HTTP request handler class.
1244
Robert Collins9644f242015-08-17 12:18:35 +12001245 This runs an HTTP server on port 8000 (or the port argument).
Georg Brandl24420152008-05-26 16:32:26 +00001246
1247 """
Jason R. Coombsf2890842019-02-07 08:22:45 -05001248 ServerClass.address_family, addr = _get_best_family(bind, port)
Lisa Roach433433f2018-11-26 10:43:38 -08001249
Georg Brandl24420152008-05-26 16:32:26 +00001250 HandlerClass.protocol_version = protocol
Jason R. Coombsf2890842019-02-07 08:22:45 -05001251 with ServerClass(addr, HandlerClass) as httpd:
1252 host, port = httpd.socket.getsockname()[:2]
1253 url_host = f'[{host}]' if ':' in host else host
1254 print(
1255 f"Serving HTTP on {host} port {port} "
1256 f"(http://{url_host}:{port}/) ..."
1257 )
Martin Panter0cab9c12016-04-13 00:36:52 +00001258 try:
1259 httpd.serve_forever()
1260 except KeyboardInterrupt:
1261 print("\nKeyboard interrupt received, exiting.")
1262 sys.exit(0)
Georg Brandl24420152008-05-26 16:32:26 +00001263
1264if __name__ == '__main__':
Serhiy Storchaka7e4db2f2017-05-04 08:17:47 +03001265 import argparse
1266
Senthil Kumaran1251faf2012-06-03 16:15:54 +08001267 parser = argparse.ArgumentParser()
1268 parser.add_argument('--cgi', action='store_true',
1269 help='Run as CGI Server')
Jason R. Coombsf2890842019-02-07 08:22:45 -05001270 parser.add_argument('--bind', '-b', metavar='ADDRESS',
Senthil Kumarandefe7f42013-09-15 09:37:27 -07001271 help='Specify alternate bind address '
1272 '[default: all interfaces]')
Stéphane Wirtela17a2f52017-05-24 09:29:06 +02001273 parser.add_argument('--directory', '-d', default=os.getcwd(),
1274 help='Specify alternative directory '
1275 '[default:current directory]')
Senthil Kumaran1251faf2012-06-03 16:15:54 +08001276 parser.add_argument('port', action='store',
1277 default=8000, type=int,
1278 nargs='?',
1279 help='Specify alternate port [default: 8000]')
1280 args = parser.parse_args()
1281 if args.cgi:
Senthil Kumarandefe7f42013-09-15 09:37:27 -07001282 handler_class = CGIHTTPRequestHandler
Senthil Kumaran1251faf2012-06-03 16:15:54 +08001283 else:
Stéphane Wirtela17a2f52017-05-24 09:29:06 +02001284 handler_class = partial(SimpleHTTPRequestHandler,
1285 directory=args.directory)
Jason R. Coombsee94bdb2020-01-05 22:32:19 -05001286
1287 # ensure dual-stack is not disabled; ref #38907
1288 class DualStackServer(ThreadingHTTPServer):
1289 def server_bind(self):
Jason R. Coombs7cdc31a2020-01-06 07:59:36 -05001290 # suppress exception when protocol is IPv4
1291 with contextlib.suppress(Exception):
1292 self.socket.setsockopt(
1293 socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
Jason R. Coombsee94bdb2020-01-05 22:32:19 -05001294 return super().server_bind()
1295
1296 test(
1297 HandlerClass=handler_class,
1298 ServerClass=DualStackServer,
1299 port=args.port,
1300 bind=args.bind,
1301 )