blob: 71254131202b5423f75ccf135444610e73b8febf [file] [log] [blame]
Guido van Rossume7e578f1995-08-04 04:00:20 +00001"""Simple HTTP Server.
2
3This module builds on BaseHTTPServer by implementing the standard GET
4and HEAD requests in a fairly straightforward manner.
5
6"""
7
8
Guido van Rossum57af0722000-05-09 14:57:09 +00009__version__ = "0.4"
Guido van Rossume7e578f1995-08-04 04:00:20 +000010
11
12import os
Guido van Rossume7e578f1995-08-04 04:00:20 +000013import string
14import posixpath
Guido van Rossume7e578f1995-08-04 04:00:20 +000015import BaseHTTPServer
Guido van Rossumd7b147b1999-11-16 19:04:32 +000016import urllib
Guido van Rossum57af0722000-05-09 14:57:09 +000017import cgi
18from StringIO import StringIO
Guido van Rossume7e578f1995-08-04 04:00:20 +000019
20
Guido van Rossume7e578f1995-08-04 04:00:20 +000021class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
22
23 """Simple HTTP request handler with GET and HEAD commands.
24
25 This serves files from the current directory and any of its
26 subdirectories. It assumes that all files are plain text files
27 unless they have the extension ".html" in which case it assumes
28 they are HTML files.
29
30 The GET and HEAD requests are identical except that the HEAD
31 request omits the actual contents of the file.
32
33 """
34
35 server_version = "SimpleHTTP/" + __version__
36
37 def do_GET(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000038 """Serve a GET request."""
39 f = self.send_head()
40 if f:
41 self.copyfile(f, self.wfile)
42 f.close()
Guido van Rossume7e578f1995-08-04 04:00:20 +000043
44 def do_HEAD(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000045 """Serve a HEAD request."""
46 f = self.send_head()
47 if f:
48 f.close()
Guido van Rossume7e578f1995-08-04 04:00:20 +000049
50 def send_head(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000051 """Common code for GET and HEAD commands.
Guido van Rossume7e578f1995-08-04 04:00:20 +000052
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000053 This sends the response code and MIME headers.
Guido van Rossume7e578f1995-08-04 04:00:20 +000054
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000055 Return value is either a file object (which has to be copied
56 to the outputfile by the caller unless the command was HEAD,
57 and must be closed by the caller under all circumstances), or
58 None, in which case the caller has nothing further to do.
Guido van Rossume7e578f1995-08-04 04:00:20 +000059
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000060 """
61 path = self.translate_path(self.path)
Guido van Rossum1d10f3e2000-05-21 16:25:29 +000062 f = None
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000063 if os.path.isdir(path):
Guido van Rossum1d10f3e2000-05-21 16:25:29 +000064 for index in "index.html", "index.htm":
65 index = os.path.join(path, index)
66 if os.path.exists(index):
67 path = index
68 break
69 else:
70 return self.list_directory(path)
71 ctype = self.guess_type(path)
72 if ctype.startswith('text/'):
73 mode = 'r'
Guido van Rossum57af0722000-05-09 14:57:09 +000074 else:
Guido van Rossum1d10f3e2000-05-21 16:25:29 +000075 mode = 'rb'
76 try:
77 f = open(path, mode)
78 except IOError:
79 self.send_error(404, "File not found")
80 return None
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000081 self.send_response(200)
Guido van Rossum57af0722000-05-09 14:57:09 +000082 self.send_header("Content-type", ctype)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000083 self.end_headers()
84 return f
Guido van Rossume7e578f1995-08-04 04:00:20 +000085
Guido van Rossum57af0722000-05-09 14:57:09 +000086 def list_directory(self, path):
Guido van Rossum1d10f3e2000-05-21 16:25:29 +000087 """Helper to produce a directory listing (absent index.html).
88
89 Return value is either a file object, or None (indicating an
90 error). In either case, the headers are sent, making the
91 interface the same as for send_head().
92
93 """
Guido van Rossum57af0722000-05-09 14:57:09 +000094 try:
95 list = os.listdir(path)
96 except os.error:
97 self.send_error(404, "No permission to list directory");
98 return None
99 list.sort(lambda a, b: cmp(a.lower(), b.lower()))
100 f = StringIO()
101 f.write("<h2>Directory listing for %s</h2>\n" % self.path)
102 f.write("<hr>\n<ul>\n")
103 for name in list:
104 fullname = os.path.join(path, name)
Guido van Rossum1d10f3e2000-05-21 16:25:29 +0000105 displayname = linkname = name = cgi.escape(name)
106 # Append / for directories or @ for symbolic links
107 if os.path.isdir(fullname):
108 displayname = name + "/"
109 linkname = name + os.sep
Guido van Rossum57af0722000-05-09 14:57:09 +0000110 if os.path.islink(fullname):
111 displayname = name + "@"
Guido van Rossum1d10f3e2000-05-21 16:25:29 +0000112 # Note: a link to a directory displays with @ and links with /
113 f.write('<li><a href="%s">%s</a>\n' % (linkname, displayname))
Guido van Rossum57af0722000-05-09 14:57:09 +0000114 f.write("</ul>\n<hr>\n")
115 f.seek(0)
Guido van Rossum1d10f3e2000-05-21 16:25:29 +0000116 self.send_response(200)
117 self.send_header("Content-type", "text/html")
118 self.end_headers()
Guido van Rossum57af0722000-05-09 14:57:09 +0000119 return f
120
Guido van Rossume7e578f1995-08-04 04:00:20 +0000121 def translate_path(self, path):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000122 """Translate a /-separated PATH to the local filename syntax.
Guido van Rossume7e578f1995-08-04 04:00:20 +0000123
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000124 Components that mean special things to the local file system
125 (e.g. drive or directory names) are ignored. (XXX They should
126 probably be diagnosed.)
Guido van Rossume7e578f1995-08-04 04:00:20 +0000127
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000128 """
Guido van Rossumd7b147b1999-11-16 19:04:32 +0000129 path = posixpath.normpath(urllib.unquote(path))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000130 words = string.splitfields(path, '/')
131 words = filter(None, words)
132 path = os.getcwd()
133 for word in words:
134 drive, word = os.path.splitdrive(word)
135 head, word = os.path.split(word)
136 if word in (os.curdir, os.pardir): continue
137 path = os.path.join(path, word)
138 return path
Guido van Rossume7e578f1995-08-04 04:00:20 +0000139
140 def copyfile(self, source, outputfile):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000141 """Copy all data between two file objects.
Guido van Rossume7e578f1995-08-04 04:00:20 +0000142
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000143 The SOURCE argument is a file object open for reading
144 (or anything with a read() method) and the DESTINATION
145 argument is a file object open for writing (or
146 anything with a write() method).
Guido van Rossume7e578f1995-08-04 04:00:20 +0000147
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000148 The only reason for overriding this would be to change
149 the block size or perhaps to replace newlines by CRLF
150 -- note however that this the default server uses this
151 to copy binary data as well.
Guido van Rossume7e578f1995-08-04 04:00:20 +0000152
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000153 """
Guido van Rossume7e578f1995-08-04 04:00:20 +0000154
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000155 BLOCKSIZE = 8192
156 while 1:
157 data = source.read(BLOCKSIZE)
158 if not data: break
159 outputfile.write(data)
Guido van Rossume7e578f1995-08-04 04:00:20 +0000160
161 def guess_type(self, path):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000162 """Guess the type of a file.
Guido van Rossume7e578f1995-08-04 04:00:20 +0000163
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000164 Argument is a PATH (a filename).
Guido van Rossume7e578f1995-08-04 04:00:20 +0000165
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000166 Return value is a string of the form type/subtype,
167 usable for a MIME Content-type header.
Guido van Rossume7e578f1995-08-04 04:00:20 +0000168
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000169 The default implementation looks the file's extension
170 up in the table self.extensions_map, using text/plain
171 as a default; however it would be permissible (if
172 slow) to look inside the data to make a better guess.
Guido van Rossume7e578f1995-08-04 04:00:20 +0000173
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000174 """
Guido van Rossume7e578f1995-08-04 04:00:20 +0000175
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000176 base, ext = posixpath.splitext(path)
177 if self.extensions_map.has_key(ext):
178 return self.extensions_map[ext]
179 ext = string.lower(ext)
180 if self.extensions_map.has_key(ext):
181 return self.extensions_map[ext]
182 else:
183 return self.extensions_map['']
Guido van Rossume7e578f1995-08-04 04:00:20 +0000184
185 extensions_map = {
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000186 '': 'text/plain', # Default, *must* be present
187 '.html': 'text/html',
188 '.htm': 'text/html',
189 '.gif': 'image/gif',
190 '.jpg': 'image/jpeg',
191 '.jpeg': 'image/jpeg',
192 }
Guido van Rossume7e578f1995-08-04 04:00:20 +0000193
194
195def test(HandlerClass = SimpleHTTPRequestHandler,
Guido van Rossum5c3b3841998-12-07 04:08:30 +0000196 ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossume7e578f1995-08-04 04:00:20 +0000197 BaseHTTPServer.test(HandlerClass, ServerClass)
198
199
200if __name__ == '__main__':
201 test()