Launchpad automatic translations update.
[odoo/odoo.git] / python25-compat / BaseHTTPServer.py
1 """HTTP server base class.
2
3 Note: the class in this module doesn't implement any HTTP request; see
4 SimpleHTTPServer for simple implementations of GET, HEAD and POST
5 (including CGI scripts).  It does, however, optionally implement HTTP/1.1
6 persistent connections, as of version 0.3.
7
8 Contents:
9
10 - BaseHTTPRequestHandler: HTTP request handler base class
11 - test: test function
12
13 XXX To do:
14
15 - log requests even later (to capture byte count)
16 - log user-agent header and other interesting goodies
17 - send error log to separate file
18 """
19
20
21 # See also:
22 #
23 # HTTP Working Group                                        T. Berners-Lee
24 # INTERNET-DRAFT                                            R. T. Fielding
25 # <draft-ietf-http-v10-spec-00.txt>                     H. Frystyk Nielsen
26 # Expires September 8, 1995                                  March 8, 1995
27 #
28 # URL: http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt
29 #
30 # and
31 #
32 # Network Working Group                                      R. Fielding
33 # Request for Comments: 2616                                       et al
34 # Obsoletes: 2068                                              June 1999
35 # Category: Standards Track
36 #
37 # URL: http://www.faqs.org/rfcs/rfc2616.html
38
39 # Log files
40 # ---------
41 #
42 # Here's a quote from the NCSA httpd docs about log file format.
43 #
44 # | The logfile format is as follows. Each line consists of:
45 # |
46 # | host rfc931 authuser [DD/Mon/YYYY:hh:mm:ss] "request" ddd bbbb
47 # |
48 # |        host: Either the DNS name or the IP number of the remote client
49 # |        rfc931: Any information returned by identd for this person,
50 # |                - otherwise.
51 # |        authuser: If user sent a userid for authentication, the user name,
52 # |                  - otherwise.
53 # |        DD: Day
54 # |        Mon: Month (calendar name)
55 # |        YYYY: Year
56 # |        hh: hour (24-hour format, the machine's timezone)
57 # |        mm: minutes
58 # |        ss: seconds
59 # |        request: The first line of the HTTP request as sent by the client.
60 # |        ddd: the status code returned by the server, - if not available.
61 # |        bbbb: the total number of bytes sent,
62 # |              *not including the HTTP/1.0 header*, - if not available
63 # |
64 # | You can determine the name of the file accessed through request.
65 #
66 # (Actually, the latter is only true if you know the server configuration
67 # at the time the request was made!)
68
69 __version__ = "0.3"
70
71 __all__ = ["HTTPServer", "BaseHTTPRequestHandler"]
72
73 import sys
74 import time
75 import socket # For gethostbyaddr()
76 import mimetools
77 import SocketServer
78
79 # Default error message template
80 DEFAULT_ERROR_MESSAGE = """\
81 <head>
82 <title>Error response</title>
83 </head>
84 <body>
85 <h1>Error response</h1>
86 <p>Error code %(code)d.
87 <p>Message: %(message)s.
88 <p>Error code explanation: %(code)s = %(explain)s.
89 </body>
90 """
91
92 DEFAULT_ERROR_CONTENT_TYPE = "text/html"
93
94 def _quote_html(html):
95     return html.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
96
97 class HTTPServer(SocketServer.TCPServer):
98
99     allow_reuse_address = 1    # Seems to make sense in testing environment
100
101     def server_bind(self):
102         """Override server_bind to store the server name."""
103         SocketServer.TCPServer.server_bind(self)
104         host, port = self.socket.getsockname()[:2]
105         self.server_name = socket.getfqdn(host)
106         self.server_port = port
107
108
109 class BaseHTTPRequestHandler(SocketServer.StreamRequestHandler):
110
111     """HTTP request handler base class.
112
113     The following explanation of HTTP serves to guide you through the
114     code as well as to expose any misunderstandings I may have about
115     HTTP (so you don't need to read the code to figure out I'm wrong
116     :-).
117
118     HTTP (HyperText Transfer Protocol) is an extensible protocol on
119     top of a reliable stream transport (e.g. TCP/IP).  The protocol
120     recognizes three parts to a request:
121
122     1. One line identifying the request type and path
123     2. An optional set of RFC-822-style headers
124     3. An optional data part
125
126     The headers and data are separated by a blank line.
127
128     The first line of the request has the form
129
130     <command> <path> <version>
131
132     where <command> is a (case-sensitive) keyword such as GET or POST,
133     <path> is a string containing path information for the request,
134     and <version> should be the string "HTTP/1.0" or "HTTP/1.1".
135     <path> is encoded using the URL encoding scheme (using %xx to signify
136     the ASCII character with hex code xx).
137
138     The specification specifies that lines are separated by CRLF but
139     for compatibility with the widest range of clients recommends
140     servers also handle LF.  Similarly, whitespace in the request line
141     is treated sensibly (allowing multiple spaces between components
142     and allowing trailing whitespace).
143
144     Similarly, for output, lines ought to be separated by CRLF pairs
145     but most clients grok LF characters just fine.
146
147     If the first line of the request has the form
148
149     <command> <path>
150
151     (i.e. <version> is left out) then this is assumed to be an HTTP
152     0.9 request; this form has no optional headers and data part and
153     the reply consists of just the data.
154
155     The reply form of the HTTP 1.x protocol again has three parts:
156
157     1. One line giving the response code
158     2. An optional set of RFC-822-style headers
159     3. The data
160
161     Again, the headers and data are separated by a blank line.
162
163     The response code line has the form
164
165     <version> <responsecode> <responsestring>
166
167     where <version> is the protocol version ("HTTP/1.0" or "HTTP/1.1"),
168     <responsecode> is a 3-digit response code indicating success or
169     failure of the request, and <responsestring> is an optional
170     human-readable string explaining what the response code means.
171
172     This server parses the request and the headers, and then calls a
173     function specific to the request type (<command>).  Specifically,
174     a request SPAM will be handled by a method do_SPAM().  If no
175     such method exists the server sends an error response to the
176     client.  If it exists, it is called with no arguments:
177
178     do_SPAM()
179
180     Note that the request name is case sensitive (i.e. SPAM and spam
181     are different requests).
182
183     The various request details are stored in instance variables:
184
185     - client_address is the client IP address in the form (host,
186     port);
187
188     - command, path and version are the broken-down request line;
189
190     - headers is an instance of mimetools.Message (or a derived
191     class) containing the header information;
192
193     - rfile is a file object open for reading positioned at the
194     start of the optional input data part;
195
196     - wfile is a file object open for writing.
197
198     IT IS IMPORTANT TO ADHERE TO THE PROTOCOL FOR WRITING!
199
200     The first thing to be written must be the response line.  Then
201     follow 0 or more header lines, then a blank line, and then the
202     actual data (if any).  The meaning of the header lines depends on
203     the command executed by the server; in most cases, when data is
204     returned, there should be at least one header line of the form
205
206     Content-type: <type>/<subtype>
207
208     where <type> and <subtype> should be registered MIME types,
209     e.g. "text/html" or "text/plain".
210
211     """
212
213     # The Python system version, truncated to its first component.
214     sys_version = "Python/" + sys.version.split()[0]
215
216     # The server software version.  You may want to override this.
217     # The format is multiple whitespace-separated strings,
218     # where each string is of the form name[/version].
219     server_version = "BaseHTTP/" + __version__
220
221     # The default request version.  This only affects responses up until
222     # the point where the request line is parsed, so it mainly decides what
223     # the client gets back when sending a malformed request line.
224     # Most web servers default to HTTP 0.9, i.e. don't send a status line.
225     default_request_version = "HTTP/0.9"
226
227     def parse_request(self):
228         """Parse a request (internal).
229
230         The request should be stored in self.raw_requestline; the results
231         are in self.command, self.path, self.request_version and
232         self.headers.
233
234         Return True for success, False for failure; on failure, an
235         error is sent back.
236
237         """
238         self.command = None  # set in case of error on the first line
239         self.request_version = version = self.default_request_version
240         self.close_connection = 1
241         requestline = self.raw_requestline
242         if requestline[-2:] == '\r\n':
243             requestline = requestline[:-2]
244         elif requestline[-1:] == '\n':
245             requestline = requestline[:-1]
246         self.requestline = requestline
247         words = requestline.split()
248         if len(words) == 3:
249             [command, path, version] = words
250             if version[:5] != 'HTTP/':
251                 self.send_error(400, "Bad request version (%r)" % version)
252                 return False
253             try:
254                 base_version_number = version.split('/', 1)[1]
255                 version_number = base_version_number.split(".")
256                 # RFC 2145 section 3.1 says there can be only one "." and
257                 #   - major and minor numbers MUST be treated as
258                 #      separate integers;
259                 #   - HTTP/2.4 is a lower version than HTTP/2.13, which in
260                 #      turn is lower than HTTP/12.3;
261                 #   - Leading zeros MUST be ignored by recipients.
262                 if len(version_number) != 2:
263                     raise ValueError
264                 version_number = int(version_number[0]), int(version_number[1])
265             except (ValueError, IndexError):
266                 self.send_error(400, "Bad request version (%r)" % version)
267                 return False
268             if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1":
269                 self.close_connection = 0
270             if version_number >= (2, 0):
271                 self.send_error(505,
272                           "Invalid HTTP Version (%s)" % base_version_number)
273                 return False
274         elif len(words) == 2:
275             [command, path] = words
276             self.close_connection = 1
277             if command != 'GET':
278                 self.send_error(400,
279                                 "Bad HTTP/0.9 request type (%r)" % command)
280                 return False
281         elif not words:
282             return False
283         else:
284             self.send_error(400, "Bad request syntax (%r)" % requestline)
285             return False
286         self.command, self.path, self.request_version = command, path, version
287
288         # Examine the headers and look for a Connection directive
289         self.headers = self.MessageClass(self.rfile, 0)
290
291         conntype = self.headers.get('Connection', "")
292         if conntype.lower() == 'close':
293             self.close_connection = 1
294         elif (conntype.lower() == 'keep-alive' and
295               self.protocol_version >= "HTTP/1.1"):
296             self.close_connection = 0
297         return True
298
299     def handle_one_request(self):
300         """Handle a single HTTP request.
301
302         You normally don't need to override this method; see the class
303         __doc__ string for information on how to handle specific HTTP
304         commands such as GET and POST.
305
306         """
307         self.raw_requestline = self.rfile.readline()
308         if not self.raw_requestline:
309             self.close_connection = 1
310             return
311         if not self.parse_request(): # An error code has been sent, just exit
312             return
313         mname = 'do_' + self.command
314         if not hasattr(self, mname):
315             self.send_error(501, "Unsupported method (%r)" % self.command)
316             return
317         method = getattr(self, mname)
318         method()
319
320     def handle(self):
321         """Handle multiple requests if necessary."""
322         self.close_connection = 1
323
324         self.handle_one_request()
325         while not self.close_connection:
326             self.handle_one_request()
327
328     def send_error(self, code, message=None):
329         """Send and log an error reply.
330
331         Arguments are the error code, and a detailed message.
332         The detailed message defaults to the short entry matching the
333         response code.
334
335         This sends an error response (so it must be called before any
336         output has been generated), logs the error, and finally sends
337         a piece of HTML explaining the error to the user.
338
339         """
340
341         try:
342             short, long = self.responses[code]
343         except KeyError:
344             short, long = '???', '???'
345         if message is None:
346             message = short
347         explain = long
348         self.log_error("code %d, message %s", code, message)
349         # using _quote_html to prevent Cross Site Scripting attacks (see bug #1100201)
350         content = (self.error_message_format %
351                    {'code': code, 'message': _quote_html(message), 'explain': explain})
352         self.send_response(code, message)
353         self.send_header("Content-Type", self.error_content_type)
354         self.send_header('Connection', 'close')
355         self.end_headers()
356         if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
357             self.wfile.write(content)
358
359     error_message_format = DEFAULT_ERROR_MESSAGE
360     error_content_type = DEFAULT_ERROR_CONTENT_TYPE
361
362     def send_response(self, code, message=None):
363         """Send the response header and log the response code.
364
365         Also send two standard headers with the server software
366         version and the current date.
367
368         """
369         self.log_request(code)
370         if message is None:
371             if code in self.responses:
372                 message = self.responses[code][0]
373             else:
374                 message = ''
375         if self.request_version != 'HTTP/0.9':
376             self.wfile.write("%s %d %s\r\n" %
377                              (self.protocol_version, code, message))
378             # print (self.protocol_version, code, message)
379         self.send_header('Server', self.version_string())
380         self.send_header('Date', self.date_time_string())
381
382     def send_header(self, keyword, value):
383         """Send a MIME header."""
384         if self.request_version != 'HTTP/0.9':
385             self.wfile.write("%s: %s\r\n" % (keyword, value))
386
387         if keyword.lower() == 'connection':
388             if value.lower() == 'close':
389                 self.close_connection = 1
390             elif value.lower() == 'keep-alive':
391                 self.close_connection = 0
392
393     def end_headers(self):
394         """Send the blank line ending the MIME headers."""
395         if self.request_version != 'HTTP/0.9':
396             self.wfile.write("\r\n")
397
398     def log_request(self, code='-', size='-'):
399         """Log an accepted request.
400
401         This is called by send_response().
402
403         """
404
405         self.log_message('"%s" %s %s',
406                          self.requestline, str(code), str(size))
407
408     def log_error(self, format, *args):
409         """Log an error.
410
411         This is called when a request cannot be fulfilled.  By
412         default it passes the message on to log_message().
413
414         Arguments are the same as for log_message().
415
416         XXX This should go to the separate error log.
417
418         """
419
420         self.log_message(format, *args)
421
422     def log_message(self, format, *args):
423         """Log an arbitrary message.
424
425         This is used by all other logging functions.  Override
426         it if you have specific logging wishes.
427
428         The first argument, FORMAT, is a format string for the
429         message to be logged.  If the format string contains
430         any % escapes requiring parameters, they should be
431         specified as subsequent arguments (it's just like
432         printf!).
433
434         The client host and current date/time are prefixed to
435         every message.
436
437         """
438
439         sys.stderr.write("%s - - [%s] %s\n" %
440                          (self.address_string(),
441                           self.log_date_time_string(),
442                           format%args))
443
444     def version_string(self):
445         """Return the server software version string."""
446         return self.server_version + ' ' + self.sys_version
447
448     def date_time_string(self, timestamp=None):
449         """Return the current date and time formatted for a message header."""
450         if timestamp is None:
451             timestamp = time.time()
452         year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp)
453         s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
454                 self.weekdayname[wd],
455                 day, self.monthname[month], year,
456                 hh, mm, ss)
457         return s
458
459     def log_date_time_string(self):
460         """Return the current time formatted for logging."""
461         now = time.time()
462         year, month, day, hh, mm, ss, x, y, z = time.localtime(now)
463         s = "%02d/%3s/%04d %02d:%02d:%02d" % (
464                 day, self.monthname[month], year, hh, mm, ss)
465         return s
466
467     weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
468
469     monthname = [None,
470                  'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
471                  'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
472
473     def address_string(self):
474         """Return the client address formatted for logging.
475
476         This version looks up the full hostname using gethostbyaddr(),
477         and tries to find a name that contains at least one dot.
478
479         """
480
481         host, port = self.client_address[:2]
482         return socket.getfqdn(host)
483
484     # Essentially static class variables
485
486     # The version of the HTTP protocol we support.
487     # Set this to HTTP/1.1 to enable automatic keepalive
488     protocol_version = "HTTP/1.0"
489
490     # The Message-like class used to parse headers
491     MessageClass = mimetools.Message
492
493     # Table mapping response codes to messages; entries have the
494     # form {code: (shortmessage, longmessage)}.
495     # See RFC 2616.
496     responses = {
497         100: ('Continue', 'Request received, please continue'),
498         101: ('Switching Protocols',
499               'Switching to new protocol; obey Upgrade header'),
500
501         200: ('OK', 'Request fulfilled, document follows'),
502         201: ('Created', 'Document created, URL follows'),
503         202: ('Accepted',
504               'Request accepted, processing continues off-line'),
505         203: ('Non-Authoritative Information', 'Request fulfilled from cache'),
506         204: ('No Content', 'Request fulfilled, nothing follows'),
507         205: ('Reset Content', 'Clear input form for further input.'),
508         206: ('Partial Content', 'Partial content follows.'),
509
510         300: ('Multiple Choices',
511               'Object has several resources -- see URI list'),
512         301: ('Moved Permanently', 'Object moved permanently -- see URI list'),
513         302: ('Found', 'Object moved temporarily -- see URI list'),
514         303: ('See Other', 'Object moved -- see Method and URL list'),
515         304: ('Not Modified',
516               'Document has not changed since given time'),
517         305: ('Use Proxy',
518               'You must use proxy specified in Location to access this '
519               'resource.'),
520         307: ('Temporary Redirect',
521               'Object moved temporarily -- see URI list'),
522
523         400: ('Bad Request',
524               'Bad request syntax or unsupported method'),
525         401: ('Unauthorized',
526               'No permission -- see authorization schemes'),
527         402: ('Payment Required',
528               'No payment -- see charging schemes'),
529         403: ('Forbidden',
530               'Request forbidden -- authorization will not help'),
531         404: ('Not Found', 'Nothing matches the given URI'),
532         405: ('Method Not Allowed',
533               'Specified method is invalid for this server.'),
534         406: ('Not Acceptable', 'URI not available in preferred format.'),
535         407: ('Proxy Authentication Required', 'You must authenticate with '
536               'this proxy before proceeding.'),
537         408: ('Request Timeout', 'Request timed out; try again later.'),
538         409: ('Conflict', 'Request conflict.'),
539         410: ('Gone',
540               'URI no longer exists and has been permanently removed.'),
541         411: ('Length Required', 'Client must specify Content-Length.'),
542         412: ('Precondition Failed', 'Precondition in headers is false.'),
543         413: ('Request Entity Too Large', 'Entity is too large.'),
544         414: ('Request-URI Too Long', 'URI is too long.'),
545         415: ('Unsupported Media Type', 'Entity body in unsupported format.'),
546         416: ('Requested Range Not Satisfiable',
547               'Cannot satisfy request range.'),
548         417: ('Expectation Failed',
549               'Expect condition could not be satisfied.'),
550
551         500: ('Internal Server Error', 'Server got itself in trouble'),
552         501: ('Not Implemented',
553               'Server does not support this operation'),
554         502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
555         503: ('Service Unavailable',
556               'The server cannot process the request due to a high load'),
557         504: ('Gateway Timeout',
558               'The gateway server did not receive a timely response'),
559         505: ('HTTP Version Not Supported', 'Cannot fulfill request.'),
560         }
561
562
563 def test(HandlerClass = BaseHTTPRequestHandler,
564          ServerClass = HTTPServer, protocol="HTTP/1.0"):
565     """Test the HTTP request handler class.
566
567     This runs an HTTP server on port 8000 (or the first command line
568     argument).
569
570     """
571
572     if sys.argv[1:]:
573         port = int(sys.argv[1])
574     else:
575         port = 8000
576     server_address = ('', port)
577
578     HandlerClass.protocol_version = protocol
579     httpd = ServerClass(server_address, HandlerClass)
580
581     sa = httpd.socket.getsockname()
582     print "Serving HTTP on", sa[0], "port", sa[1], "..."
583     httpd.serve_forever()
584
585
586 if __name__ == '__main__':
587     test()