blob: 8a7775856c96f3bac853d3e5a850ce69741b135e [file] [log] [blame]
Guido van Rossum5c971671996-07-22 15:23:25 +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 Rossum3e06ab12000-06-29 19:35:29 +00009__version__ = "0.4"
Guido van Rossum5c971671996-07-22 15:23:25 +000010
11
12import os
Guido van Rossum5c971671996-07-22 15:23:25 +000013import string
14import posixpath
Guido van Rossum5c971671996-07-22 15:23:25 +000015import BaseHTTPServer
Guido van Rossumaad67612000-05-08 17:31:04 +000016import urllib
Guido van Rossum3e06ab12000-06-29 19:35:29 +000017import cgi
Guido van Rossum8d691c82000-09-01 19:25:51 +000018import shutil
Guido van Rossum3e06ab12000-06-29 19:35:29 +000019from StringIO import StringIO
Guido van Rossum5c971671996-07-22 15:23:25 +000020
21
Guido van Rossum5c971671996-07-22 15:23:25 +000022class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
23
24 """Simple HTTP request handler with GET and HEAD commands.
25
26 This serves files from the current directory and any of its
27 subdirectories. It assumes that all files are plain text files
28 unless they have the extension ".html" in which case it assumes
29 they are HTML files.
30
31 The GET and HEAD requests are identical except that the HEAD
32 request omits the actual contents of the file.
33
34 """
35
36 server_version = "SimpleHTTP/" + __version__
37
38 def do_GET(self):
Guido van Rossum548703a1998-03-26 22:14:20 +000039 """Serve a GET request."""
40 f = self.send_head()
41 if f:
42 self.copyfile(f, self.wfile)
43 f.close()
Guido van Rossum5c971671996-07-22 15:23:25 +000044
45 def do_HEAD(self):
Guido van Rossum548703a1998-03-26 22:14:20 +000046 """Serve a HEAD request."""
47 f = self.send_head()
48 if f:
49 f.close()
Guido van Rossum5c971671996-07-22 15:23:25 +000050
51 def send_head(self):
Guido van Rossum548703a1998-03-26 22:14:20 +000052 """Common code for GET and HEAD commands.
Guido van Rossum5c971671996-07-22 15:23:25 +000053
Guido van Rossum548703a1998-03-26 22:14:20 +000054 This sends the response code and MIME headers.
Guido van Rossum5c971671996-07-22 15:23:25 +000055
Guido van Rossum548703a1998-03-26 22:14:20 +000056 Return value is either a file object (which has to be copied
57 to the outputfile by the caller unless the command was HEAD,
58 and must be closed by the caller under all circumstances), or
59 None, in which case the caller has nothing further to do.
Guido van Rossum5c971671996-07-22 15:23:25 +000060
Guido van Rossum548703a1998-03-26 22:14:20 +000061 """
62 path = self.translate_path(self.path)
Guido van Rossum3e06ab12000-06-29 19:35:29 +000063 f = None
Guido van Rossum548703a1998-03-26 22:14:20 +000064 if os.path.isdir(path):
Guido van Rossum3e06ab12000-06-29 19:35:29 +000065 for index in "index.html", "index.htm":
66 index = os.path.join(path, index)
67 if os.path.exists(index):
68 path = index
69 break
70 else:
71 return self.list_directory(path)
72 ctype = self.guess_type(path)
73 if ctype.startswith('text/'):
74 mode = 'r'
75 else:
76 mode = 'rb'
Guido van Rossum548703a1998-03-26 22:14:20 +000077 try:
Guido van Rossum3e06ab12000-06-29 19:35:29 +000078 f = open(path, mode)
Guido van Rossum548703a1998-03-26 22:14:20 +000079 except IOError:
80 self.send_error(404, "File not found")
81 return None
82 self.send_response(200)
Guido van Rossum3e06ab12000-06-29 19:35:29 +000083 self.send_header("Content-type", ctype)
84 self.end_headers()
85 return f
86
87 def list_directory(self, path):
88 """Helper to produce a directory listing (absent index.html).
89
90 Return value is either a file object, or None (indicating an
91 error). In either case, the headers are sent, making the
92 interface the same as for send_head().
93
94 """
95 try:
96 list = os.listdir(path)
97 except os.error:
98 self.send_error(404, "No permission to list directory");
99 return None
100 list.sort(lambda a, b: cmp(a.lower(), b.lower()))
101 f = StringIO()
102 f.write("<h2>Directory listing for %s</h2>\n" % self.path)
103 f.write("<hr>\n<ul>\n")
104 for name in list:
105 fullname = os.path.join(path, name)
106 displayname = linkname = name = cgi.escape(name)
107 # Append / for directories or @ for symbolic links
108 if os.path.isdir(fullname):
109 displayname = name + "/"
110 linkname = name + os.sep
111 if os.path.islink(fullname):
112 displayname = name + "@"
113 # Note: a link to a directory displays with @ and links with /
114 f.write('<li><a href="%s">%s</a>\n' % (linkname, displayname))
115 f.write("</ul>\n<hr>\n")
116 f.seek(0)
117 self.send_response(200)
118 self.send_header("Content-type", "text/html")
Guido van Rossum548703a1998-03-26 22:14:20 +0000119 self.end_headers()
120 return f
Guido van Rossum5c971671996-07-22 15:23:25 +0000121
122 def translate_path(self, path):
Guido van Rossum548703a1998-03-26 22:14:20 +0000123 """Translate a /-separated PATH to the local filename syntax.
Guido van Rossum5c971671996-07-22 15:23:25 +0000124
Guido van Rossum548703a1998-03-26 22:14:20 +0000125 Components that mean special things to the local file system
126 (e.g. drive or directory names) are ignored. (XXX They should
127 probably be diagnosed.)
Guido van Rossum5c971671996-07-22 15:23:25 +0000128
Guido van Rossum548703a1998-03-26 22:14:20 +0000129 """
Guido van Rossumaad67612000-05-08 17:31:04 +0000130 path = posixpath.normpath(urllib.unquote(path))
Guido van Rossum548703a1998-03-26 22:14:20 +0000131 words = string.splitfields(path, '/')
132 words = filter(None, words)
133 path = os.getcwd()
134 for word in words:
135 drive, word = os.path.splitdrive(word)
136 head, word = os.path.split(word)
137 if word in (os.curdir, os.pardir): continue
138 path = os.path.join(path, word)
139 return path
Guido van Rossum5c971671996-07-22 15:23:25 +0000140
141 def copyfile(self, source, outputfile):
Guido van Rossum548703a1998-03-26 22:14:20 +0000142 """Copy all data between two file objects.
Guido van Rossum5c971671996-07-22 15:23:25 +0000143
Guido van Rossum548703a1998-03-26 22:14:20 +0000144 The SOURCE argument is a file object open for reading
145 (or anything with a read() method) and the DESTINATION
146 argument is a file object open for writing (or
147 anything with a write() method).
Guido van Rossum5c971671996-07-22 15:23:25 +0000148
Guido van Rossum548703a1998-03-26 22:14:20 +0000149 The only reason for overriding this would be to change
150 the block size or perhaps to replace newlines by CRLF
151 -- note however that this the default server uses this
152 to copy binary data as well.
Guido van Rossum5c971671996-07-22 15:23:25 +0000153
Guido van Rossum548703a1998-03-26 22:14:20 +0000154 """
Guido van Rossum8d691c82000-09-01 19:25:51 +0000155 shutil.copyfileobj(source, outputfile)
Guido van Rossum5c971671996-07-22 15:23:25 +0000156
157 def guess_type(self, path):
Guido van Rossum548703a1998-03-26 22:14:20 +0000158 """Guess the type of a file.
Guido van Rossum5c971671996-07-22 15:23:25 +0000159
Guido van Rossum548703a1998-03-26 22:14:20 +0000160 Argument is a PATH (a filename).
Guido van Rossum5c971671996-07-22 15:23:25 +0000161
Guido van Rossum548703a1998-03-26 22:14:20 +0000162 Return value is a string of the form type/subtype,
163 usable for a MIME Content-type header.
Guido van Rossum5c971671996-07-22 15:23:25 +0000164
Guido van Rossum548703a1998-03-26 22:14:20 +0000165 The default implementation looks the file's extension
166 up in the table self.extensions_map, using text/plain
167 as a default; however it would be permissible (if
168 slow) to look inside the data to make a better guess.
Guido van Rossum5c971671996-07-22 15:23:25 +0000169
Guido van Rossum548703a1998-03-26 22:14:20 +0000170 """
Guido van Rossum5c971671996-07-22 15:23:25 +0000171
Guido van Rossum548703a1998-03-26 22:14:20 +0000172 base, ext = posixpath.splitext(path)
173 if self.extensions_map.has_key(ext):
174 return self.extensions_map[ext]
175 ext = string.lower(ext)
176 if self.extensions_map.has_key(ext):
177 return self.extensions_map[ext]
178 else:
179 return self.extensions_map['']
Guido van Rossum5c971671996-07-22 15:23:25 +0000180
181 extensions_map = {
Guido van Rossum548703a1998-03-26 22:14:20 +0000182 '': 'text/plain', # Default, *must* be present
183 '.html': 'text/html',
184 '.htm': 'text/html',
185 '.gif': 'image/gif',
186 '.jpg': 'image/jpeg',
187 '.jpeg': 'image/jpeg',
188 }
Guido van Rossum5c971671996-07-22 15:23:25 +0000189
190
191def test(HandlerClass = SimpleHTTPRequestHandler,
Guido van Rossum7ea1d971998-12-22 13:50:33 +0000192 ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossum5c971671996-07-22 15:23:25 +0000193 BaseHTTPServer.test(HandlerClass, ServerClass)
194
195
196if __name__ == '__main__':
197 test()