blob: 58abadf73770e342bc515f98b5bad04742ba4ea7 [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
Senthil Kumaran1251faf2012-06-03 16:15:54 +0800106
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200107from http import HTTPStatus
108
Georg Brandl24420152008-05-26 16:32:26 +0000109
110# Default error message template
111DEFAULT_ERROR_MESSAGE = """\
Senthil Kumaran1b407fe2011-03-20 10:44:30 +0800112<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
113 "http://www.w3.org/TR/html4/strict.dtd">
Ezio Melottica897e92011-11-02 19:33:29 +0200114<html>
Senthil Kumaranb253c9f2011-03-17 16:43:22 +0800115 <head>
Senthil Kumaran1b407fe2011-03-20 10:44:30 +0800116 <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
Senthil Kumaranb253c9f2011-03-17 16:43:22 +0800117 <title>Error response</title>
118 </head>
119 <body>
120 <h1>Error response</h1>
121 <p>Error code: %(code)d</p>
122 <p>Message: %(message)s.</p>
123 <p>Error code explanation: %(code)s - %(explain)s.</p>
124 </body>
125</html>
Georg Brandl24420152008-05-26 16:32:26 +0000126"""
127
128DEFAULT_ERROR_CONTENT_TYPE = "text/html;charset=utf-8"
129
Georg Brandl24420152008-05-26 16:32:26 +0000130class HTTPServer(socketserver.TCPServer):
131
132 allow_reuse_address = 1 # Seems to make sense in testing environment
133
134 def server_bind(self):
135 """Override server_bind to store the server name."""
136 socketserver.TCPServer.server_bind(self)
Martin Panter50badad2016-04-03 01:28:53 +0000137 host, port = self.server_address[:2]
Georg Brandl24420152008-05-26 16:32:26 +0000138 self.server_name = socket.getfqdn(host)
139 self.server_port = port
140
141
Géry Ogam1cee2162018-05-29 22:10:30 +0200142class ThreadingHTTPServer(socketserver.ThreadingMixIn, HTTPServer):
Julien Palard8bcfa022018-03-23 17:40:33 +0100143 daemon_threads = True
144
145
Georg Brandl24420152008-05-26 16:32:26 +0000146class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
147
148 """HTTP request handler base class.
149
150 The following explanation of HTTP serves to guide you through the
151 code as well as to expose any misunderstandings I may have about
152 HTTP (so you don't need to read the code to figure out I'm wrong
153 :-).
154
155 HTTP (HyperText Transfer Protocol) is an extensible protocol on
156 top of a reliable stream transport (e.g. TCP/IP). The protocol
157 recognizes three parts to a request:
158
159 1. One line identifying the request type and path
160 2. An optional set of RFC-822-style headers
161 3. An optional data part
162
163 The headers and data are separated by a blank line.
164
165 The first line of the request has the form
166
167 <command> <path> <version>
168
169 where <command> is a (case-sensitive) keyword such as GET or POST,
170 <path> is a string containing path information for the request,
171 and <version> should be the string "HTTP/1.0" or "HTTP/1.1".
172 <path> is encoded using the URL encoding scheme (using %xx to signify
173 the ASCII character with hex code xx).
174
175 The specification specifies that lines are separated by CRLF but
176 for compatibility with the widest range of clients recommends
177 servers also handle LF. Similarly, whitespace in the request line
178 is treated sensibly (allowing multiple spaces between components
179 and allowing trailing whitespace).
180
181 Similarly, for output, lines ought to be separated by CRLF pairs
182 but most clients grok LF characters just fine.
183
184 If the first line of the request has the form
185
186 <command> <path>
187
188 (i.e. <version> is left out) then this is assumed to be an HTTP
189 0.9 request; this form has no optional headers and data part and
190 the reply consists of just the data.
191
192 The reply form of the HTTP 1.x protocol again has three parts:
193
194 1. One line giving the response code
195 2. An optional set of RFC-822-style headers
196 3. The data
197
198 Again, the headers and data are separated by a blank line.
199
200 The response code line has the form
201
202 <version> <responsecode> <responsestring>
203
204 where <version> is the protocol version ("HTTP/1.0" or "HTTP/1.1"),
205 <responsecode> is a 3-digit response code indicating success or
206 failure of the request, and <responsestring> is an optional
207 human-readable string explaining what the response code means.
208
209 This server parses the request and the headers, and then calls a
210 function specific to the request type (<command>). Specifically,
211 a request SPAM will be handled by a method do_SPAM(). If no
212 such method exists the server sends an error response to the
213 client. If it exists, it is called with no arguments:
214
215 do_SPAM()
216
217 Note that the request name is case sensitive (i.e. SPAM and spam
218 are different requests).
219
220 The various request details are stored in instance variables:
221
222 - client_address is the client IP address in the form (host,
223 port);
224
225 - command, path and version are the broken-down request line;
226
Barry Warsaw820c1202008-06-12 04:06:45 +0000227 - headers is an instance of email.message.Message (or a derived
Georg Brandl24420152008-05-26 16:32:26 +0000228 class) containing the header information;
229
230 - rfile is a file object open for reading positioned at the
231 start of the optional input data part;
232
233 - wfile is a file object open for writing.
234
235 IT IS IMPORTANT TO ADHERE TO THE PROTOCOL FOR WRITING!
236
237 The first thing to be written must be the response line. Then
238 follow 0 or more header lines, then a blank line, and then the
239 actual data (if any). The meaning of the header lines depends on
240 the command executed by the server; in most cases, when data is
241 returned, there should be at least one header line of the form
242
243 Content-type: <type>/<subtype>
244
245 where <type> and <subtype> should be registered MIME types,
246 e.g. "text/html" or "text/plain".
247
248 """
249
250 # The Python system version, truncated to its first component.
251 sys_version = "Python/" + sys.version.split()[0]
252
253 # The server software version. You may want to override this.
254 # The format is multiple whitespace-separated strings,
255 # where each string is of the form name[/version].
256 server_version = "BaseHTTP/" + __version__
257
258 error_message_format = DEFAULT_ERROR_MESSAGE
259 error_content_type = DEFAULT_ERROR_CONTENT_TYPE
260
261 # The default request version. This only affects responses up until
262 # the point where the request line is parsed, so it mainly decides what
263 # the client gets back when sending a malformed request line.
264 # Most web servers default to HTTP 0.9, i.e. don't send a status line.
265 default_request_version = "HTTP/0.9"
266
267 def parse_request(self):
268 """Parse a request (internal).
269
270 The request should be stored in self.raw_requestline; the results
271 are in self.command, self.path, self.request_version and
272 self.headers.
273
Martin Pantere82338d2016-11-19 01:06:37 +0000274 Return True for success, False for failure; on failure, any relevant
275 error response has already been sent back.
Georg Brandl24420152008-05-26 16:32:26 +0000276
277 """
278 self.command = None # set in case of error on the first line
279 self.request_version = version = self.default_request_version
Benjamin Peterson70e28472015-02-17 21:11:10 -0500280 self.close_connection = True
Georg Brandl24420152008-05-26 16:32:26 +0000281 requestline = str(self.raw_requestline, 'iso-8859-1')
Senthil Kumaran30755492011-12-23 17:03:41 +0800282 requestline = requestline.rstrip('\r\n')
Georg Brandl24420152008-05-26 16:32:26 +0000283 self.requestline = requestline
284 words = requestline.split()
Martin Pantere82338d2016-11-19 01:06:37 +0000285 if len(words) == 0:
286 return False
287
288 if len(words) >= 3: # Enough to determine protocol version
289 version = words[-1]
Georg Brandl24420152008-05-26 16:32:26 +0000290 try:
Martin Pantere82338d2016-11-19 01:06:37 +0000291 if not version.startswith('HTTP/'):
Martin Panter50badad2016-04-03 01:28:53 +0000292 raise ValueError
Georg Brandl24420152008-05-26 16:32:26 +0000293 base_version_number = version.split('/', 1)[1]
294 version_number = base_version_number.split(".")
295 # RFC 2145 section 3.1 says there can be only one "." and
296 # - major and minor numbers MUST be treated as
297 # separate integers;
298 # - HTTP/2.4 is a lower version than HTTP/2.13, which in
299 # turn is lower than HTTP/12.3;
300 # - Leading zeros MUST be ignored by recipients.
301 if len(version_number) != 2:
302 raise ValueError
303 version_number = int(version_number[0]), int(version_number[1])
304 except (ValueError, IndexError):
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200305 self.send_error(
306 HTTPStatus.BAD_REQUEST,
307 "Bad request version (%r)" % version)
Georg Brandl24420152008-05-26 16:32:26 +0000308 return False
309 if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1":
Benjamin Peterson70e28472015-02-17 21:11:10 -0500310 self.close_connection = False
Georg Brandl24420152008-05-26 16:32:26 +0000311 if version_number >= (2, 0):
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200312 self.send_error(
313 HTTPStatus.HTTP_VERSION_NOT_SUPPORTED,
Martin Panter50badad2016-04-03 01:28:53 +0000314 "Invalid HTTP version (%s)" % base_version_number)
Georg Brandl24420152008-05-26 16:32:26 +0000315 return False
Martin Pantere82338d2016-11-19 01:06:37 +0000316 self.request_version = version
317
318 if not 2 <= len(words) <= 3:
319 self.send_error(
320 HTTPStatus.BAD_REQUEST,
321 "Bad request syntax (%r)" % requestline)
322 return False
323 command, path = words[:2]
324 if len(words) == 2:
Benjamin Peterson70e28472015-02-17 21:11:10 -0500325 self.close_connection = True
Georg Brandl24420152008-05-26 16:32:26 +0000326 if command != 'GET':
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200327 self.send_error(
328 HTTPStatus.BAD_REQUEST,
329 "Bad HTTP/0.9 request type (%r)" % command)
Georg Brandl24420152008-05-26 16:32:26 +0000330 return False
Martin Pantere82338d2016-11-19 01:06:37 +0000331 self.command, self.path = command, path
Georg Brandl24420152008-05-26 16:32:26 +0000332
333 # Examine the headers and look for a Connection directive.
Senthil Kumaran5466bf12010-12-18 16:55:23 +0000334 try:
335 self.headers = http.client.parse_headers(self.rfile,
336 _class=self.MessageClass)
Martin Panter50badad2016-04-03 01:28:53 +0000337 except http.client.LineTooLong as err:
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200338 self.send_error(
Martin Panter50badad2016-04-03 01:28:53 +0000339 HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
340 "Line too long",
341 str(err))
Senthil Kumaran5466bf12010-12-18 16:55:23 +0000342 return False
Martin Panteracc03192016-04-03 00:45:46 +0000343 except http.client.HTTPException as err:
344 self.send_error(
345 HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
346 "Too many headers",
347 str(err)
348 )
349 return False
Georg Brandl24420152008-05-26 16:32:26 +0000350
351 conntype = self.headers.get('Connection', "")
352 if conntype.lower() == 'close':
Benjamin Peterson70e28472015-02-17 21:11:10 -0500353 self.close_connection = True
Georg Brandl24420152008-05-26 16:32:26 +0000354 elif (conntype.lower() == 'keep-alive' and
355 self.protocol_version >= "HTTP/1.1"):
Benjamin Peterson70e28472015-02-17 21:11:10 -0500356 self.close_connection = False
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000357 # Examine the headers and look for an Expect directive
358 expect = self.headers.get('Expect', "")
359 if (expect.lower() == "100-continue" and
360 self.protocol_version >= "HTTP/1.1" and
361 self.request_version >= "HTTP/1.1"):
362 if not self.handle_expect_100():
363 return False
364 return True
365
366 def handle_expect_100(self):
367 """Decide what to do with an "Expect: 100-continue" header.
368
369 If the client is expecting a 100 Continue response, we must
370 respond with either a 100 Continue or a final response before
371 waiting for the request body. The default is to always respond
372 with a 100 Continue. You can behave differently (for example,
373 reject unauthorized requests) by overriding this method.
374
375 This method should either return True (possibly after sending
376 a 100 Continue response) or send an error response and return
377 False.
378
379 """
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200380 self.send_response_only(HTTPStatus.CONTINUE)
Benjamin Peterson04424232014-01-18 21:50:18 -0500381 self.end_headers()
Georg Brandl24420152008-05-26 16:32:26 +0000382 return True
383
384 def handle_one_request(self):
385 """Handle a single HTTP request.
386
387 You normally don't need to override this method; see the class
388 __doc__ string for information on how to handle specific HTTP
389 commands such as GET and POST.
390
391 """
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000392 try:
Antoine Pitrouc4924372010-12-16 16:48:36 +0000393 self.raw_requestline = self.rfile.readline(65537)
394 if len(self.raw_requestline) > 65536:
395 self.requestline = ''
396 self.request_version = ''
397 self.command = ''
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200398 self.send_error(HTTPStatus.REQUEST_URI_TOO_LONG)
Antoine Pitrouc4924372010-12-16 16:48:36 +0000399 return
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000400 if not self.raw_requestline:
Benjamin Peterson70e28472015-02-17 21:11:10 -0500401 self.close_connection = True
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000402 return
403 if not self.parse_request():
404 # An error code has been sent, just exit
405 return
406 mname = 'do_' + self.command
407 if not hasattr(self, mname):
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200408 self.send_error(
409 HTTPStatus.NOT_IMPLEMENTED,
410 "Unsupported method (%r)" % self.command)
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000411 return
412 method = getattr(self, mname)
413 method()
414 self.wfile.flush() #actually send the response if not already done.
Christian Heimes03c8ddd2020-11-20 09:26:07 +0100415 except TimeoutError as e:
Kristján Valur Jónsson985fc6a2009-07-01 10:01:31 +0000416 #a read or a write timed out. Discard this connection
417 self.log_error("Request timed out: %r", e)
Benjamin Peterson70e28472015-02-17 21:11:10 -0500418 self.close_connection = True
Georg Brandl24420152008-05-26 16:32:26 +0000419 return
Georg Brandl24420152008-05-26 16:32:26 +0000420
421 def handle(self):
422 """Handle multiple requests if necessary."""
Benjamin Peterson70e28472015-02-17 21:11:10 -0500423 self.close_connection = True
Georg Brandl24420152008-05-26 16:32:26 +0000424
425 self.handle_one_request()
426 while not self.close_connection:
427 self.handle_one_request()
428
Senthil Kumaran26886442013-03-15 07:53:21 -0700429 def send_error(self, code, message=None, explain=None):
Georg Brandl24420152008-05-26 16:32:26 +0000430 """Send and log an error reply.
431
Senthil Kumaran26886442013-03-15 07:53:21 -0700432 Arguments are
433 * code: an HTTP error code
434 3 digits
435 * message: a simple optional 1 line reason phrase.
436 *( HTAB / SP / VCHAR / %x80-FF )
437 defaults to short entry matching the response code
438 * explain: a detailed message defaults to the long entry
439 matching the response code.
Georg Brandl24420152008-05-26 16:32:26 +0000440
441 This sends an error response (so it must be called before any
442 output has been generated), logs the error, and finally sends
443 a piece of HTML explaining the error to the user.
444
445 """
446
447 try:
448 shortmsg, longmsg = self.responses[code]
449 except KeyError:
450 shortmsg, longmsg = '???', '???'
451 if message is None:
452 message = shortmsg
Senthil Kumaran26886442013-03-15 07:53:21 -0700453 if explain is None:
454 explain = longmsg
Georg Brandl24420152008-05-26 16:32:26 +0000455 self.log_error("code %d, message %s", code, message)
Senthil Kumaran1e7551d2013-03-05 02:25:58 -0800456 self.send_response(code, message)
Georg Brandl24420152008-05-26 16:32:26 +0000457 self.send_header('Connection', 'close')
Martin Pantere42e1292016-06-08 08:29:13 +0000458
459 # Message body is omitted for cases described in:
460 # - RFC7230: 3.3. 1xx, 204(No Content), 304(Not Modified)
461 # - RFC7231: 6.3.6. 205(Reset Content)
462 body = None
463 if (code >= 200 and
464 code not in (HTTPStatus.NO_CONTENT,
465 HTTPStatus.RESET_CONTENT,
466 HTTPStatus.NOT_MODIFIED)):
467 # HTML encode to prevent Cross Site Scripting attacks
468 # (see bug #1100201)
469 content = (self.error_message_format % {
470 'code': code,
Martin Panter40de69a2016-06-08 09:45:58 +0000471 'message': html.escape(message, quote=False),
472 'explain': html.escape(explain, quote=False)
Martin Pantere42e1292016-06-08 08:29:13 +0000473 })
474 body = content.encode('UTF-8', 'replace')
475 self.send_header("Content-Type", self.error_content_type)
ValeriyaSinevichb36b0a32018-06-18 14:17:53 -0700476 self.send_header('Content-Length', str(len(body)))
Georg Brandl24420152008-05-26 16:32:26 +0000477 self.end_headers()
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200478
Martin Pantere42e1292016-06-08 08:29:13 +0000479 if self.command != 'HEAD' and body:
Senthil Kumaran52d27202012-10-10 23:16:21 -0700480 self.wfile.write(body)
Georg Brandl24420152008-05-26 16:32:26 +0000481
482 def send_response(self, code, message=None):
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800483 """Add the response header to the headers buffer and log the
484 response code.
Georg Brandl24420152008-05-26 16:32:26 +0000485
486 Also send two standard headers with the server software
487 version and the current date.
488
489 """
490 self.log_request(code)
Senthil Kumaran0f476d42010-09-30 06:09:18 +0000491 self.send_response_only(code, message)
492 self.send_header('Server', self.version_string())
493 self.send_header('Date', self.date_time_string())
494
495 def send_response_only(self, code, message=None):
496 """Send the response header only."""
Georg Brandl24420152008-05-26 16:32:26 +0000497 if self.request_version != 'HTTP/0.9':
Martin Panter50badad2016-04-03 01:28:53 +0000498 if message is None:
499 if code in self.responses:
500 message = self.responses[code][0]
501 else:
502 message = ''
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800503 if not hasattr(self, '_headers_buffer'):
504 self._headers_buffer = []
505 self._headers_buffer.append(("%s %d %s\r\n" %
506 (self.protocol_version, code, message)).encode(
507 'latin-1', 'strict'))
Georg Brandl24420152008-05-26 16:32:26 +0000508
509 def send_header(self, keyword, value):
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800510 """Send a MIME header to the headers buffer."""
Georg Brandl24420152008-05-26 16:32:26 +0000511 if self.request_version != 'HTTP/0.9':
Senthil Kumarane4dad4f2010-11-21 14:36:14 +0000512 if not hasattr(self, '_headers_buffer'):
513 self._headers_buffer = []
514 self._headers_buffer.append(
Marc-André Lemburg8f36af72011-02-25 15:42:01 +0000515 ("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict'))
Georg Brandl24420152008-05-26 16:32:26 +0000516
517 if keyword.lower() == 'connection':
518 if value.lower() == 'close':
Benjamin Peterson70e28472015-02-17 21:11:10 -0500519 self.close_connection = True
Georg Brandl24420152008-05-26 16:32:26 +0000520 elif value.lower() == 'keep-alive':
Benjamin Peterson70e28472015-02-17 21:11:10 -0500521 self.close_connection = False
Georg Brandl24420152008-05-26 16:32:26 +0000522
523 def end_headers(self):
524 """Send the blank line ending the MIME headers."""
525 if self.request_version != 'HTTP/0.9':
Senthil Kumarane4dad4f2010-11-21 14:36:14 +0000526 self._headers_buffer.append(b"\r\n")
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +0800527 self.flush_headers()
528
529 def flush_headers(self):
530 if hasattr(self, '_headers_buffer'):
Senthil Kumarane4dad4f2010-11-21 14:36:14 +0000531 self.wfile.write(b"".join(self._headers_buffer))
532 self._headers_buffer = []
Georg Brandl24420152008-05-26 16:32:26 +0000533
534 def log_request(self, code='-', size='-'):
535 """Log an accepted request.
536
537 This is called by send_response().
538
539 """
Serhiy Storchakac0a23e62015-03-07 11:51:37 +0200540 if isinstance(code, HTTPStatus):
541 code = code.value
Georg Brandl24420152008-05-26 16:32:26 +0000542 self.log_message('"%s" %s %s',
543 self.requestline, str(code), str(size))
544
545 def log_error(self, format, *args):
546 """Log an error.
547
548 This is called when a request cannot be fulfilled. By
549 default it passes the message on to log_message().
550
551 Arguments are the same as for log_message().
552
553 XXX This should go to the separate error log.
554
555 """
556
557 self.log_message(format, *args)
558
559 def log_message(self, format, *args):
560 """Log an arbitrary message.
561
562 This is used by all other logging functions. Override
563 it if you have specific logging wishes.
564
565 The first argument, FORMAT, is a format string for the
566 message to be logged. If the format string contains
567 any % escapes requiring parameters, they should be
568 specified as subsequent arguments (it's just like
569 printf!).
570
Senthil Kumarandb727b42012-04-29 13:41:03 +0800571 The client ip and current date/time are prefixed to
Georg Brandl24420152008-05-26 16:32:26 +0000572 every message.
573
574 """
575
576 sys.stderr.write("%s - - [%s] %s\n" %
577 (self.address_string(),
578 self.log_date_time_string(),
579 format%args))
580
581 def version_string(self):
582 """Return the server software version string."""
583 return self.server_version + ' ' + self.sys_version
584
585 def date_time_string(self, timestamp=None):
586 """Return the current date and time formatted for a message header."""
587 if timestamp is None:
588 timestamp = time.time()
Berker Peksag04bc5b92016-03-14 06:06:03 +0200589 return email.utils.formatdate(timestamp, usegmt=True)
Georg Brandl24420152008-05-26 16:32:26 +0000590
591 def log_date_time_string(self):
592 """Return the current time formatted for logging."""
593 now = time.time()
594 year, month, day, hh, mm, ss, x, y, z = time.localtime(now)
595 s = "%02d/%3s/%04d %02d:%02d:%02d" % (
596 day, self.monthname[month], year, hh, mm, ss)
597 return s
598
599 weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
600
601 monthname = [None,
602 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
603 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
604
605 def address_string(self):
Senthil Kumaran1aacba42012-04-29 12:51:54 +0800606 """Return the client address."""
Georg Brandl24420152008-05-26 16:32:26 +0000607
Senthil Kumaran1aacba42012-04-29 12:51:54 +0800608 return self.client_address[0]
Georg Brandl24420152008-05-26 16:32:26 +0000609
610 # Essentially static class variables
611
612 # The version of the HTTP protocol we support.
613 # Set this to HTTP/1.1 to enable automatic keepalive
614 protocol_version = "HTTP/1.0"
615
Barry Warsaw820c1202008-06-12 04:06:45 +0000616 # MessageClass used to parse headers
Barry Warsaw820c1202008-06-12 04:06:45 +0000617 MessageClass = http.client.HTTPMessage
Georg Brandl24420152008-05-26 16:32:26 +0000618
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200619 # hack to maintain backwards compatibility
Georg Brandl24420152008-05-26 16:32:26 +0000620 responses = {
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200621 v: (v.phrase, v.description)
622 for v in HTTPStatus.__members__.values()
623 }
Georg Brandl24420152008-05-26 16:32:26 +0000624
625
626class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
627
628 """Simple HTTP request handler with GET and HEAD commands.
629
630 This serves files from the current directory and any of its
631 subdirectories. The MIME type for files is determined by
632 calling the .guess_type() method.
633
634 The GET and HEAD requests are identical except that the HEAD
635 request omits the actual contents of the file.
636
637 """
638
639 server_version = "SimpleHTTP/" + __version__
An Long5907e612020-01-09 02:28:14 +0800640 extensions_map = _encodings_map_default = {
641 '.gz': 'application/gzip',
642 '.Z': 'application/octet-stream',
643 '.bz2': 'application/x-bzip2',
644 '.xz': 'application/x-xz',
645 }
Georg Brandl24420152008-05-26 16:32:26 +0000646
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200647 def __init__(self, *args, directory=None, **kwargs):
648 if directory is None:
649 directory = os.getcwd()
Géry Ogam781266e2019-09-11 15:03:46 +0200650 self.directory = os.fspath(directory)
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200651 super().__init__(*args, **kwargs)
652
Georg Brandl24420152008-05-26 16:32:26 +0000653 def do_GET(self):
654 """Serve a GET request."""
655 f = self.send_head()
656 if f:
Serhiy Storchaka91b0bc22014-01-25 19:43:02 +0200657 try:
658 self.copyfile(f, self.wfile)
659 finally:
660 f.close()
Georg Brandl24420152008-05-26 16:32:26 +0000661
662 def do_HEAD(self):
663 """Serve a HEAD request."""
664 f = self.send_head()
665 if f:
666 f.close()
667
668 def send_head(self):
669 """Common code for GET and HEAD commands.
670
671 This sends the response code and MIME headers.
672
673 Return value is either a file object (which has to be copied
674 to the outputfile by the caller unless the command was HEAD,
675 and must be closed by the caller under all circumstances), or
676 None, in which case the caller has nothing further to do.
677
678 """
679 path = self.translate_path(self.path)
680 f = None
681 if os.path.isdir(path):
Benjamin Peterson94cb7a22014-12-26 10:53:43 -0600682 parts = urllib.parse.urlsplit(self.path)
683 if not parts.path.endswith('/'):
Georg Brandl24420152008-05-26 16:32:26 +0000684 # redirect browser - doing basically what apache does
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200685 self.send_response(HTTPStatus.MOVED_PERMANENTLY)
Benjamin Peterson94cb7a22014-12-26 10:53:43 -0600686 new_parts = (parts[0], parts[1], parts[2] + '/',
687 parts[3], parts[4])
688 new_url = urllib.parse.urlunsplit(new_parts)
689 self.send_header("Location", new_url)
Miss Islington (bot)058f9b22021-05-06 12:48:36 -0700690 self.send_header("Content-Length", "0")
Georg Brandl24420152008-05-26 16:32:26 +0000691 self.end_headers()
692 return None
693 for index in "index.html", "index.htm":
694 index = os.path.join(path, index)
695 if os.path.exists(index):
696 path = index
697 break
698 else:
699 return self.list_directory(path)
700 ctype = self.guess_type(path)
Michael Felt2062a202018-12-26 06:43:42 +0100701 # check for trailing "/" which should return 404. See Issue17324
702 # The test for this was added in test_httpserver.py
703 # However, some OS platforms accept a trailingSlash as a filename
704 # See discussion on python-dev and Issue34711 regarding
705 # parseing and rejection of filenames with a trailing slash
706 if path.endswith("/"):
707 self.send_error(HTTPStatus.NOT_FOUND, "File not found")
708 return None
Georg Brandl24420152008-05-26 16:32:26 +0000709 try:
710 f = open(path, 'rb')
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200711 except OSError:
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200712 self.send_error(HTTPStatus.NOT_FOUND, "File not found")
Georg Brandl24420152008-05-26 16:32:26 +0000713 return None
Pierre Quentel351adda2017-04-02 12:26:12 +0200714
Serhiy Storchaka91b0bc22014-01-25 19:43:02 +0200715 try:
Pierre Quentel351adda2017-04-02 12:26:12 +0200716 fs = os.fstat(f.fileno())
717 # Use browser cache if possible
718 if ("If-Modified-Since" in self.headers
719 and "If-None-Match" not in self.headers):
720 # compare If-Modified-Since and time of last file modification
721 try:
722 ims = email.utils.parsedate_to_datetime(
723 self.headers["If-Modified-Since"])
724 except (TypeError, IndexError, OverflowError, ValueError):
725 # ignore ill-formed values
726 pass
727 else:
728 if ims.tzinfo is None:
729 # obsolete format with no timezone, cf.
730 # https://tools.ietf.org/html/rfc7231#section-7.1.1.1
731 ims = ims.replace(tzinfo=datetime.timezone.utc)
732 if ims.tzinfo is datetime.timezone.utc:
733 # compare to UTC datetime of last modification
734 last_modif = datetime.datetime.fromtimestamp(
735 fs.st_mtime, datetime.timezone.utc)
736 # remove microseconds, like in If-Modified-Since
737 last_modif = last_modif.replace(microsecond=0)
Serhiy Storchaka13ad3b72017-09-14 09:38:36 +0300738
Pierre Quentel351adda2017-04-02 12:26:12 +0200739 if last_modif <= ims:
740 self.send_response(HTTPStatus.NOT_MODIFIED)
741 self.end_headers()
742 f.close()
743 return None
744
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200745 self.send_response(HTTPStatus.OK)
Serhiy Storchaka91b0bc22014-01-25 19:43:02 +0200746 self.send_header("Content-type", ctype)
Serhiy Storchaka91b0bc22014-01-25 19:43:02 +0200747 self.send_header("Content-Length", str(fs[6]))
Serhiy Storchaka13ad3b72017-09-14 09:38:36 +0300748 self.send_header("Last-Modified",
Pierre Quentel351adda2017-04-02 12:26:12 +0200749 self.date_time_string(fs.st_mtime))
Serhiy Storchaka91b0bc22014-01-25 19:43:02 +0200750 self.end_headers()
751 return f
752 except:
753 f.close()
754 raise
Georg Brandl24420152008-05-26 16:32:26 +0000755
756 def list_directory(self, path):
757 """Helper to produce a directory listing (absent index.html).
758
759 Return value is either a file object, or None (indicating an
760 error). In either case, the headers are sent, making the
761 interface the same as for send_head().
762
763 """
764 try:
765 list = os.listdir(path)
Andrew Svetlovad28c7f2012-12-18 22:02:39 +0200766 except OSError:
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200767 self.send_error(
768 HTTPStatus.NOT_FOUND,
769 "No permission to list directory")
Georg Brandl24420152008-05-26 16:32:26 +0000770 return None
771 list.sort(key=lambda a: a.lower())
772 r = []
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300773 try:
774 displaypath = urllib.parse.unquote(self.path,
775 errors='surrogatepass')
776 except UnicodeDecodeError:
777 displaypath = urllib.parse.unquote(path)
Martin Panterda3bb382016-04-11 00:40:08 +0000778 displaypath = html.escape(displaypath, quote=False)
Ezio Melottica897e92011-11-02 19:33:29 +0200779 enc = sys.getfilesystemencoding()
780 title = 'Directory listing for %s' % displaypath
781 r.append('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" '
782 '"http://www.w3.org/TR/html4/strict.dtd">')
783 r.append('<html>\n<head>')
784 r.append('<meta http-equiv="Content-Type" '
785 'content="text/html; charset=%s">' % enc)
786 r.append('<title>%s</title>\n</head>' % title)
787 r.append('<body>\n<h1>%s</h1>' % title)
788 r.append('<hr>\n<ul>')
Georg Brandl24420152008-05-26 16:32:26 +0000789 for name in list:
790 fullname = os.path.join(path, name)
791 displayname = linkname = name
792 # Append / for directories or @ for symbolic links
793 if os.path.isdir(fullname):
794 displayname = name + "/"
795 linkname = name + "/"
796 if os.path.islink(fullname):
797 displayname = name + "@"
798 # Note: a link to a directory displays with @ and links with /
Ezio Melottica897e92011-11-02 19:33:29 +0200799 r.append('<li><a href="%s">%s</a></li>'
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300800 % (urllib.parse.quote(linkname,
801 errors='surrogatepass'),
Martin Panterda3bb382016-04-11 00:40:08 +0000802 html.escape(displayname, quote=False)))
Ezio Melottica897e92011-11-02 19:33:29 +0200803 r.append('</ul>\n<hr>\n</body>\n</html>\n')
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300804 encoded = '\n'.join(r).encode(enc, 'surrogateescape')
Georg Brandl24420152008-05-26 16:32:26 +0000805 f = io.BytesIO()
806 f.write(encoded)
807 f.seek(0)
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200808 self.send_response(HTTPStatus.OK)
Georg Brandl24420152008-05-26 16:32:26 +0000809 self.send_header("Content-type", "text/html; charset=%s" % enc)
810 self.send_header("Content-Length", str(len(encoded)))
811 self.end_headers()
812 return f
813
814 def translate_path(self, path):
815 """Translate a /-separated PATH to the local filename syntax.
816
817 Components that mean special things to the local file system
818 (e.g. drive or directory names) are ignored. (XXX They should
819 probably be diagnosed.)
820
821 """
822 # abandon query parameters
823 path = path.split('?',1)[0]
824 path = path.split('#',1)[0]
Senthil Kumaran72c238e2013-09-13 00:21:18 -0700825 # Don't forget explicit trailing slash when normalizing. Issue17324
Senthil Kumaran600b7352013-09-29 18:59:04 -0700826 trailing_slash = path.rstrip().endswith('/')
Serhiy Storchakacb5bc402014-08-17 08:22:11 +0300827 try:
828 path = urllib.parse.unquote(path, errors='surrogatepass')
829 except UnicodeDecodeError:
830 path = urllib.parse.unquote(path)
831 path = posixpath.normpath(path)
Georg Brandl24420152008-05-26 16:32:26 +0000832 words = path.split('/')
833 words = filter(None, words)
Stéphane Wirtela17a2f52017-05-24 09:29:06 +0200834 path = self.directory
Georg Brandl24420152008-05-26 16:32:26 +0000835 for word in words:
Martin Panterd274b3f2016-04-18 03:45:18 +0000836 if os.path.dirname(word) or word in (os.curdir, os.pardir):
837 # Ignore components that are not a simple file/directory name
838 continue
Georg Brandl24420152008-05-26 16:32:26 +0000839 path = os.path.join(path, word)
Senthil Kumaran72c238e2013-09-13 00:21:18 -0700840 if trailing_slash:
841 path += '/'
Georg Brandl24420152008-05-26 16:32:26 +0000842 return path
843
844 def copyfile(self, source, outputfile):
845 """Copy all data between two file objects.
846
847 The SOURCE argument is a file object open for reading
848 (or anything with a read() method) and the DESTINATION
849 argument is a file object open for writing (or
850 anything with a write() method).
851
852 The only reason for overriding this would be to change
853 the block size or perhaps to replace newlines by CRLF
854 -- note however that this the default server uses this
855 to copy binary data as well.
856
857 """
858 shutil.copyfileobj(source, outputfile)
859
860 def guess_type(self, path):
861 """Guess the type of a file.
862
863 Argument is a PATH (a filename).
864
865 Return value is a string of the form type/subtype,
866 usable for a MIME Content-type header.
867
868 The default implementation looks the file's extension
869 up in the table self.extensions_map, using application/octet-stream
870 as a default; however it would be permissible (if
871 slow) to look inside the data to make a better guess.
872
873 """
Georg Brandl24420152008-05-26 16:32:26 +0000874 base, ext = posixpath.splitext(path)
875 if ext in self.extensions_map:
876 return self.extensions_map[ext]
877 ext = ext.lower()
878 if ext in self.extensions_map:
879 return self.extensions_map[ext]
An Long5907e612020-01-09 02:28:14 +0800880 guess, _ = mimetypes.guess_type(path)
881 if guess:
882 return guess
883 return 'application/octet-stream'
Georg Brandl24420152008-05-26 16:32:26 +0000884
885
886# Utilities for CGIHTTPRequestHandler
887
Senthil Kumarand70846b2012-04-12 02:34:32 +0800888def _url_collapse_path(path):
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000889 """
890 Given a URL path, remove extra '/'s and '.' path elements and collapse
Martin Panter9955a372015-10-07 10:26:23 +0000891 any '..' references and returns a collapsed path.
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000892
893 Implements something akin to RFC-2396 5.2 step 6 to parse relative paths.
Senthil Kumarand70846b2012-04-12 02:34:32 +0800894 The utility of this function is limited to is_cgi method and helps
895 preventing some security attacks.
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000896
Martin Pantercb29e8c2015-10-03 05:55:46 +0000897 Returns: The reconstituted URL, which will always start with a '/'.
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000898
899 Raises: IndexError if too many '..' occur within the path.
Senthil Kumarand70846b2012-04-12 02:34:32 +0800900
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000901 """
Martin Pantercb29e8c2015-10-03 05:55:46 +0000902 # Query component should not be involved.
903 path, _, query = path.partition('?')
904 path = urllib.parse.unquote(path)
905
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000906 # Similar to os.path.split(os.path.normpath(path)) but specific to URL
907 # path semantics rather than local operating system semantics.
Senthil Kumarand70846b2012-04-12 02:34:32 +0800908 path_parts = path.split('/')
909 head_parts = []
910 for part in path_parts[:-1]:
911 if part == '..':
912 head_parts.pop() # IndexError if more '..' than prior parts
913 elif part and part != '.':
914 head_parts.append( part )
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000915 if path_parts:
Senthil Kumarandbb369d2012-04-11 03:15:28 +0800916 tail_part = path_parts.pop()
Senthil Kumarand70846b2012-04-12 02:34:32 +0800917 if tail_part:
918 if tail_part == '..':
919 head_parts.pop()
920 tail_part = ''
921 elif tail_part == '.':
922 tail_part = ''
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000923 else:
924 tail_part = ''
Senthil Kumarand70846b2012-04-12 02:34:32 +0800925
Martin Pantercb29e8c2015-10-03 05:55:46 +0000926 if query:
927 tail_part = '?'.join((tail_part, query))
928
Senthil Kumarand70846b2012-04-12 02:34:32 +0800929 splitpath = ('/' + '/'.join(head_parts), tail_part)
930 collapsed_path = "/".join(splitpath)
931
932 return collapsed_path
933
Benjamin Petersonad71f0f2009-04-11 20:12:10 +0000934
935
Georg Brandl24420152008-05-26 16:32:26 +0000936nobody = None
937
938def nobody_uid():
939 """Internal routine to get nobody's uid"""
940 global nobody
941 if nobody:
942 return nobody
943 try:
944 import pwd
Brett Cannoncd171c82013-07-04 17:43:24 -0400945 except ImportError:
Georg Brandl24420152008-05-26 16:32:26 +0000946 return -1
947 try:
948 nobody = pwd.getpwnam('nobody')[2]
949 except KeyError:
Georg Brandlcbd2ab12010-12-04 10:39:14 +0000950 nobody = 1 + max(x[2] for x in pwd.getpwall())
Georg Brandl24420152008-05-26 16:32:26 +0000951 return nobody
952
953
954def executable(path):
955 """Test for executable file."""
Victor Stinnerfb25ba92011-06-20 17:45:54 +0200956 return os.access(path, os.X_OK)
Georg Brandl24420152008-05-26 16:32:26 +0000957
958
959class CGIHTTPRequestHandler(SimpleHTTPRequestHandler):
960
961 """Complete HTTP server with GET, HEAD and POST commands.
962
963 GET and HEAD also support running CGI scripts.
964
965 The POST command is *only* implemented for CGI scripts.
966
967 """
968
969 # Determine platform specifics
970 have_fork = hasattr(os, 'fork')
Georg Brandl24420152008-05-26 16:32:26 +0000971
972 # Make rfile unbuffered -- we need to read one line and then pass
973 # the rest to a subprocess, so we can't use buffered input.
974 rbufsize = 0
975
976 def do_POST(self):
977 """Serve a POST request.
978
979 This is only implemented for CGI scripts.
980
981 """
982
983 if self.is_cgi():
984 self.run_cgi()
985 else:
Serhiy Storchakae4db7692014-12-23 16:28:28 +0200986 self.send_error(
987 HTTPStatus.NOT_IMPLEMENTED,
988 "Can only POST to CGI scripts")
Georg Brandl24420152008-05-26 16:32:26 +0000989
990 def send_head(self):
991 """Version of send_head that support CGI scripts"""
992 if self.is_cgi():
993 return self.run_cgi()
994 else:
995 return SimpleHTTPRequestHandler.send_head(self)
996
997 def is_cgi(self):
998 """Test whether self.path corresponds to a CGI script.
999
Benjamin Petersonad71f0f2009-04-11 20:12:10 +00001000 Returns True and updates the cgi_info attribute to the tuple
1001 (dir, rest) if self.path requires running a CGI script.
1002 Returns False otherwise.
Georg Brandl24420152008-05-26 16:32:26 +00001003
Benjamin Petersona7deeee2009-05-08 20:54:42 +00001004 If any exception is raised, the caller should assume that
1005 self.path was rejected as invalid and act accordingly.
1006
Benjamin Petersonad71f0f2009-04-11 20:12:10 +00001007 The default implementation tests whether the normalized url
1008 path begins with one of the strings in self.cgi_directories
1009 (and the next character is a '/' or the end of the string).
Georg Brandl24420152008-05-26 16:32:26 +00001010
1011 """
Martin Pantercb29e8c2015-10-03 05:55:46 +00001012 collapsed_path = _url_collapse_path(self.path)
Senthil Kumarand70846b2012-04-12 02:34:32 +08001013 dir_sep = collapsed_path.find('/', 1)
Siwon Kang91daa9d2019-11-22 18:13:05 +09001014 while dir_sep > 0 and not collapsed_path[:dir_sep] in self.cgi_directories:
1015 dir_sep = collapsed_path.find('/', dir_sep+1)
1016 if dir_sep > 0:
1017 head, tail = collapsed_path[:dir_sep], collapsed_path[dir_sep+1:]
Senthil Kumarandbb369d2012-04-11 03:15:28 +08001018 self.cgi_info = head, tail
Benjamin Petersonad71f0f2009-04-11 20:12:10 +00001019 return True
Georg Brandl24420152008-05-26 16:32:26 +00001020 return False
1021
Senthil Kumarand70846b2012-04-12 02:34:32 +08001022
Georg Brandl24420152008-05-26 16:32:26 +00001023 cgi_directories = ['/cgi-bin', '/htbin']
1024
1025 def is_executable(self, path):
1026 """Test whether argument path is an executable file."""
1027 return executable(path)
1028
1029 def is_python(self, path):
1030 """Test whether argument path is a Python script."""
1031 head, tail = os.path.splitext(path)
1032 return tail.lower() in (".py", ".pyw")
1033
1034 def run_cgi(self):
1035 """Execute a CGI script."""
Georg Brandl24420152008-05-26 16:32:26 +00001036 dir, rest = self.cgi_info
Ned Deily915a30f2014-07-12 22:06:26 -07001037 path = dir + '/' + rest
1038 i = path.find('/', len(dir)+1)
Georg Brandl24420152008-05-26 16:32:26 +00001039 while i >= 0:
Ned Deily915a30f2014-07-12 22:06:26 -07001040 nextdir = path[:i]
1041 nextrest = path[i+1:]
Georg Brandl24420152008-05-26 16:32:26 +00001042
1043 scriptdir = self.translate_path(nextdir)
1044 if os.path.isdir(scriptdir):
1045 dir, rest = nextdir, nextrest
Ned Deily915a30f2014-07-12 22:06:26 -07001046 i = path.find('/', len(dir)+1)
Georg Brandl24420152008-05-26 16:32:26 +00001047 else:
1048 break
1049
1050 # find an explicit query string, if present.
Martin Pantera02e18a2015-10-03 05:38:07 +00001051 rest, _, query = rest.partition('?')
Georg Brandl24420152008-05-26 16:32:26 +00001052
1053 # dissect the part after the directory name into a script name &
1054 # a possible additional path, to be stored in PATH_INFO.
1055 i = rest.find('/')
1056 if i >= 0:
1057 script, rest = rest[:i], rest[i:]
1058 else:
1059 script, rest = rest, ''
1060
1061 scriptname = dir + '/' + script
1062 scriptfile = self.translate_path(scriptname)
1063 if not os.path.exists(scriptfile):
Serhiy Storchakae4db7692014-12-23 16:28:28 +02001064 self.send_error(
1065 HTTPStatus.NOT_FOUND,
1066 "No such CGI script (%r)" % scriptname)
Georg Brandl24420152008-05-26 16:32:26 +00001067 return
1068 if not os.path.isfile(scriptfile):
Serhiy Storchakae4db7692014-12-23 16:28:28 +02001069 self.send_error(
1070 HTTPStatus.FORBIDDEN,
1071 "CGI script is not a plain file (%r)" % scriptname)
Georg Brandl24420152008-05-26 16:32:26 +00001072 return
1073 ispy = self.is_python(scriptname)
Victor Stinnerfb25ba92011-06-20 17:45:54 +02001074 if self.have_fork or not ispy:
Georg Brandl24420152008-05-26 16:32:26 +00001075 if not self.is_executable(scriptfile):
Serhiy Storchakae4db7692014-12-23 16:28:28 +02001076 self.send_error(
1077 HTTPStatus.FORBIDDEN,
1078 "CGI script is not executable (%r)" % scriptname)
Georg Brandl24420152008-05-26 16:32:26 +00001079 return
1080
1081 # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
1082 # XXX Much of the following could be prepared ahead of time!
Senthil Kumaran42713722010-10-03 17:55:45 +00001083 env = copy.deepcopy(os.environ)
Georg Brandl24420152008-05-26 16:32:26 +00001084 env['SERVER_SOFTWARE'] = self.version_string()
1085 env['SERVER_NAME'] = self.server.server_name
1086 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
1087 env['SERVER_PROTOCOL'] = self.protocol_version
1088 env['SERVER_PORT'] = str(self.server.server_port)
1089 env['REQUEST_METHOD'] = self.command
Jeremy Hylton1afc1692008-06-18 20:49:58 +00001090 uqrest = urllib.parse.unquote(rest)
Georg Brandl24420152008-05-26 16:32:26 +00001091 env['PATH_INFO'] = uqrest
1092 env['PATH_TRANSLATED'] = self.translate_path(uqrest)
1093 env['SCRIPT_NAME'] = scriptname
Senthil Kumaran3ec9d012020-12-02 19:48:14 -08001094 env['QUERY_STRING'] = query
Georg Brandl24420152008-05-26 16:32:26 +00001095 env['REMOTE_ADDR'] = self.client_address[0]
Barry Warsaw820c1202008-06-12 04:06:45 +00001096 authorization = self.headers.get("authorization")
Georg Brandl24420152008-05-26 16:32:26 +00001097 if authorization:
1098 authorization = authorization.split()
1099 if len(authorization) == 2:
1100 import base64, binascii
1101 env['AUTH_TYPE'] = authorization[0]
1102 if authorization[0].lower() == "basic":
1103 try:
1104 authorization = authorization[1].encode('ascii')
Georg Brandl706824f2009-06-04 09:42:55 +00001105 authorization = base64.decodebytes(authorization).\
Georg Brandl24420152008-05-26 16:32:26 +00001106 decode('ascii')
1107 except (binascii.Error, UnicodeError):
1108 pass
1109 else:
1110 authorization = authorization.split(':')
1111 if len(authorization) == 2:
1112 env['REMOTE_USER'] = authorization[0]
1113 # XXX REMOTE_IDENT
Barry Warsaw820c1202008-06-12 04:06:45 +00001114 if self.headers.get('content-type') is None:
1115 env['CONTENT_TYPE'] = self.headers.get_content_type()
Georg Brandl24420152008-05-26 16:32:26 +00001116 else:
Barry Warsaw820c1202008-06-12 04:06:45 +00001117 env['CONTENT_TYPE'] = self.headers['content-type']
1118 length = self.headers.get('content-length')
Georg Brandl24420152008-05-26 16:32:26 +00001119 if length:
1120 env['CONTENT_LENGTH'] = length
Barry Warsaw820c1202008-06-12 04:06:45 +00001121 referer = self.headers.get('referer')
Georg Brandl24420152008-05-26 16:32:26 +00001122 if referer:
1123 env['HTTP_REFERER'] = referer
Senthil Kumaranda3d2ab2020-12-05 05:26:24 -08001124 accept = self.headers.get_all('accept', ())
Georg Brandl24420152008-05-26 16:32:26 +00001125 env['HTTP_ACCEPT'] = ','.join(accept)
Barry Warsaw820c1202008-06-12 04:06:45 +00001126 ua = self.headers.get('user-agent')
Georg Brandl24420152008-05-26 16:32:26 +00001127 if ua:
1128 env['HTTP_USER_AGENT'] = ua
Barry Warsaw820c1202008-06-12 04:06:45 +00001129 co = filter(None, self.headers.get_all('cookie', []))
Georg Brandl62e2ca22010-07-31 21:54:24 +00001130 cookie_str = ', '.join(co)
1131 if cookie_str:
1132 env['HTTP_COOKIE'] = cookie_str
Georg Brandl24420152008-05-26 16:32:26 +00001133 # XXX Other HTTP_* headers
1134 # Since we're setting the env in the parent, provide empty
1135 # values to override previously set values
1136 for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
1137 'HTTP_USER_AGENT', 'HTTP_COOKIE', 'HTTP_REFERER'):
1138 env.setdefault(k, "")
Georg Brandl24420152008-05-26 16:32:26 +00001139
Serhiy Storchakae4db7692014-12-23 16:28:28 +02001140 self.send_response(HTTPStatus.OK, "Script output follows")
Senthil Kumaranc7ae19b2011-05-09 23:25:02 +08001141 self.flush_headers()
Georg Brandl24420152008-05-26 16:32:26 +00001142
1143 decoded_query = query.replace('+', ' ')
1144
1145 if self.have_fork:
1146 # Unix -- fork as we should
1147 args = [script]
1148 if '=' not in decoded_query:
1149 args.append(decoded_query)
1150 nobody = nobody_uid()
1151 self.wfile.flush() # Always flush before forking
1152 pid = os.fork()
1153 if pid != 0:
1154 # Parent
1155 pid, sts = os.waitpid(pid, 0)
1156 # throw away additional data [see bug #427345]
1157 while select.select([self.rfile], [], [], 0)[0]:
1158 if not self.rfile.read(1):
1159 break
Victor Stinner9a679a02020-04-02 03:42:05 +02001160 exitcode = os.waitstatus_to_exitcode(sts)
1161 if exitcode:
1162 self.log_error(f"CGI script exit code {exitcode}")
Georg Brandl24420152008-05-26 16:32:26 +00001163 return
1164 # Child
1165 try:
1166 try:
1167 os.setuid(nobody)
Andrew Svetlovad28c7f2012-12-18 22:02:39 +02001168 except OSError:
Georg Brandl24420152008-05-26 16:32:26 +00001169 pass
1170 os.dup2(self.rfile.fileno(), 0)
1171 os.dup2(self.wfile.fileno(), 1)
Senthil Kumaran42713722010-10-03 17:55:45 +00001172 os.execve(scriptfile, args, env)
Georg Brandl24420152008-05-26 16:32:26 +00001173 except:
1174 self.server.handle_error(self.request, self.client_address)
1175 os._exit(127)
1176
Amaury Forgeot d'Arccb0d2d72008-06-18 22:19:22 +00001177 else:
1178 # Non-Unix -- use subprocess
1179 import subprocess
Senthil Kumarane29cd162009-11-11 04:17:53 +00001180 cmdline = [scriptfile]
Georg Brandl24420152008-05-26 16:32:26 +00001181 if self.is_python(scriptfile):
1182 interp = sys.executable
1183 if interp.lower().endswith("w.exe"):
1184 # On Windows, use python.exe, not pythonw.exe
1185 interp = interp[:-5] + interp[-4:]
Senthil Kumarane29cd162009-11-11 04:17:53 +00001186 cmdline = [interp, '-u'] + cmdline
1187 if '=' not in query:
1188 cmdline.append(query)
1189 self.log_message("command: %s", subprocess.list2cmdline(cmdline))
Georg Brandl24420152008-05-26 16:32:26 +00001190 try:
1191 nbytes = int(length)
1192 except (TypeError, ValueError):
1193 nbytes = 0
Amaury Forgeot d'Arccb0d2d72008-06-18 22:19:22 +00001194 p = subprocess.Popen(cmdline,
1195 stdin=subprocess.PIPE,
1196 stdout=subprocess.PIPE,
Senthil Kumaran42713722010-10-03 17:55:45 +00001197 stderr=subprocess.PIPE,
1198 env = env
Amaury Forgeot d'Arccb0d2d72008-06-18 22:19:22 +00001199 )
Georg Brandl24420152008-05-26 16:32:26 +00001200 if self.command.lower() == "post" and nbytes > 0:
1201 data = self.rfile.read(nbytes)
Amaury Forgeot d'Arccb0d2d72008-06-18 22:19:22 +00001202 else:
1203 data = None
Georg Brandl24420152008-05-26 16:32:26 +00001204 # throw away additional data [see bug #427345]
1205 while select.select([self.rfile._sock], [], [], 0)[0]:
1206 if not self.rfile._sock.recv(1):
1207 break
Amaury Forgeot d'Arccb0d2d72008-06-18 22:19:22 +00001208 stdout, stderr = p.communicate(data)
1209 self.wfile.write(stdout)
1210 if stderr:
1211 self.log_error('%s', stderr)
Brian Curtincbad4df2010-11-05 15:04:48 +00001212 p.stderr.close()
1213 p.stdout.close()
Amaury Forgeot d'Arccb0d2d72008-06-18 22:19:22 +00001214 status = p.returncode
1215 if status:
1216 self.log_error("CGI script exit status %#x", status)
Georg Brandl24420152008-05-26 16:32:26 +00001217 else:
1218 self.log_message("CGI script exited OK")
1219
1220
Jason R. Coombsf2890842019-02-07 08:22:45 -05001221def _get_best_family(*address):
1222 infos = socket.getaddrinfo(
1223 *address,
1224 type=socket.SOCK_STREAM,
1225 flags=socket.AI_PASSIVE,
1226 )
1227 family, type, proto, canonname, sockaddr = next(iter(infos))
1228 return family, sockaddr
1229
1230
Senthil Kumarandefe7f42013-09-15 09:37:27 -07001231def test(HandlerClass=BaseHTTPRequestHandler,
Géry Ogam1cee2162018-05-29 22:10:30 +02001232 ServerClass=ThreadingHTTPServer,
Jason R. Coombsf2890842019-02-07 08:22:45 -05001233 protocol="HTTP/1.0", port=8000, bind=None):
Georg Brandl24420152008-05-26 16:32:26 +00001234 """Test the HTTP request handler class.
1235
Robert Collins9644f242015-08-17 12:18:35 +12001236 This runs an HTTP server on port 8000 (or the port argument).
Georg Brandl24420152008-05-26 16:32:26 +00001237
1238 """
Jason R. Coombsf2890842019-02-07 08:22:45 -05001239 ServerClass.address_family, addr = _get_best_family(bind, port)
Georg Brandl24420152008-05-26 16:32:26 +00001240 HandlerClass.protocol_version = protocol
Jason R. Coombsf2890842019-02-07 08:22:45 -05001241 with ServerClass(addr, HandlerClass) as httpd:
1242 host, port = httpd.socket.getsockname()[:2]
1243 url_host = f'[{host}]' if ':' in host else host
1244 print(
1245 f"Serving HTTP on {host} port {port} "
1246 f"(http://{url_host}:{port}/) ..."
1247 )
Martin Panter0cab9c12016-04-13 00:36:52 +00001248 try:
1249 httpd.serve_forever()
1250 except KeyboardInterrupt:
1251 print("\nKeyboard interrupt received, exiting.")
1252 sys.exit(0)
Georg Brandl24420152008-05-26 16:32:26 +00001253
1254if __name__ == '__main__':
Serhiy Storchaka7e4db2f2017-05-04 08:17:47 +03001255 import argparse
Miss Islington (bot)b2719532022-02-14 12:12:30 -08001256 import contextlib
Serhiy Storchaka7e4db2f2017-05-04 08:17:47 +03001257
Senthil Kumaran1251faf2012-06-03 16:15:54 +08001258 parser = argparse.ArgumentParser()
1259 parser.add_argument('--cgi', action='store_true',
Miss Islington (bot)b2719532022-02-14 12:12:30 -08001260 help='run as CGI server')
Jason R. Coombsf2890842019-02-07 08:22:45 -05001261 parser.add_argument('--bind', '-b', metavar='ADDRESS',
Miss Islington (bot)b2719532022-02-14 12:12:30 -08001262 help='specify alternate bind address '
1263 '(default: all interfaces)')
Stéphane Wirtela17a2f52017-05-24 09:29:06 +02001264 parser.add_argument('--directory', '-d', default=os.getcwd(),
Miss Islington (bot)b2719532022-02-14 12:12:30 -08001265 help='specify alternate directory '
1266 '(default: current directory)')
1267 parser.add_argument('port', action='store', default=8000, type=int,
Senthil Kumaran1251faf2012-06-03 16:15:54 +08001268 nargs='?',
Miss Islington (bot)b2719532022-02-14 12:12:30 -08001269 help='specify alternate port (default: 8000)')
Senthil Kumaran1251faf2012-06-03 16:15:54 +08001270 args = parser.parse_args()
1271 if args.cgi:
Senthil Kumarandefe7f42013-09-15 09:37:27 -07001272 handler_class = CGIHTTPRequestHandler
Senthil Kumaran1251faf2012-06-03 16:15:54 +08001273 else:
Miss Islington (bot)b2719532022-02-14 12:12:30 -08001274 handler_class = SimpleHTTPRequestHandler
Jason R. Coombsee94bdb2020-01-05 22:32:19 -05001275
1276 # ensure dual-stack is not disabled; ref #38907
1277 class DualStackServer(ThreadingHTTPServer):
Miss Islington (bot)b2719532022-02-14 12:12:30 -08001278
Jason R. Coombsee94bdb2020-01-05 22:32:19 -05001279 def server_bind(self):
Jason R. Coombs7cdc31a2020-01-06 07:59:36 -05001280 # suppress exception when protocol is IPv4
1281 with contextlib.suppress(Exception):
1282 self.socket.setsockopt(
1283 socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
Jason R. Coombsee94bdb2020-01-05 22:32:19 -05001284 return super().server_bind()
1285
Miss Islington (bot)b2719532022-02-14 12:12:30 -08001286 def finish_request(self, request, client_address):
1287 self.RequestHandlerClass(request, client_address, self,
1288 directory=args.directory)
1289
Jason R. Coombsee94bdb2020-01-05 22:32:19 -05001290 test(
1291 HandlerClass=handler_class,
1292 ServerClass=DualStackServer,
1293 port=args.port,
1294 bind=args.bind,
1295 )