blob: 4cfedbc9fe9ba8b1962fee713d163f4f6039ec2a [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 Rossum1d105d12000-09-04 15:55:31 +00009__version__ = "0.5"
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
Moshe Zadka37c03ff2000-07-29 05:15:56 +000018import shutil
Guido van Rossum57af0722000-05-09 14:57:09 +000019from StringIO import StringIO
Guido van Rossume7e578f1995-08-04 04:00:20 +000020
21
Guido van Rossume7e578f1995-08-04 04:00:20 +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 Rossum45e2fbc1998-03-26 21:13:24 +000039 """Serve a GET request."""
40 f = self.send_head()
41 if f:
42 self.copyfile(f, self.wfile)
43 f.close()
Guido van Rossume7e578f1995-08-04 04:00:20 +000044
45 def do_HEAD(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000046 """Serve a HEAD request."""
47 f = self.send_head()
48 if f:
49 f.close()
Guido van Rossume7e578f1995-08-04 04:00:20 +000050
51 def send_head(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000052 """Common code for GET and HEAD commands.
Guido van Rossume7e578f1995-08-04 04:00:20 +000053
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000054 This sends the response code and MIME headers.
Guido van Rossume7e578f1995-08-04 04:00:20 +000055
Guido van Rossum45e2fbc1998-03-26 21:13:24 +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 Rossume7e578f1995-08-04 04:00:20 +000060
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000061 """
62 path = self.translate_path(self.path)
Guido van Rossum1d10f3e2000-05-21 16:25:29 +000063 f = None
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000064 if os.path.isdir(path):
Guido van Rossum1d10f3e2000-05-21 16:25: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'
Guido van Rossum57af0722000-05-09 14:57:09 +000075 else:
Guido van Rossum1d10f3e2000-05-21 16:25:29 +000076 mode = 'rb'
77 try:
78 f = open(path, mode)
79 except IOError:
80 self.send_error(404, "File not found")
81 return None
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000082 self.send_response(200)
Guido van Rossum57af0722000-05-09 14:57:09 +000083 self.send_header("Content-type", ctype)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000084 self.end_headers()
85 return f
Guido van Rossume7e578f1995-08-04 04:00:20 +000086
Guido van Rossum57af0722000-05-09 14:57:09 +000087 def list_directory(self, path):
Guido van Rossum1d10f3e2000-05-21 16:25:29 +000088 """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 """
Guido van Rossum57af0722000-05-09 14:57:09 +000095 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()
Guido van Rossum1d105d12000-09-04 15:55:31 +0000102 f.write("<title>Directory listing for %s</title>\n" % self.path)
Guido van Rossum57af0722000-05-09 14:57:09 +0000103 f.write("<h2>Directory listing for %s</h2>\n" % self.path)
104 f.write("<hr>\n<ul>\n")
105 for name in list:
106 fullname = os.path.join(path, name)
Guido van Rossum1d10f3e2000-05-21 16:25:29 +0000107 displayname = linkname = name = cgi.escape(name)
108 # Append / for directories or @ for symbolic links
109 if os.path.isdir(fullname):
110 displayname = name + "/"
Guido van Rossum1d105d12000-09-04 15:55:31 +0000111 linkname = name + "/"
Guido van Rossum57af0722000-05-09 14:57:09 +0000112 if os.path.islink(fullname):
113 displayname = name + "@"
Guido van Rossum1d10f3e2000-05-21 16:25:29 +0000114 # Note: a link to a directory displays with @ and links with /
115 f.write('<li><a href="%s">%s</a>\n' % (linkname, displayname))
Guido van Rossum57af0722000-05-09 14:57:09 +0000116 f.write("</ul>\n<hr>\n")
117 f.seek(0)
Guido van Rossum1d10f3e2000-05-21 16:25:29 +0000118 self.send_response(200)
119 self.send_header("Content-type", "text/html")
120 self.end_headers()
Guido van Rossum57af0722000-05-09 14:57:09 +0000121 return f
122
Guido van Rossume7e578f1995-08-04 04:00:20 +0000123 def translate_path(self, path):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000124 """Translate a /-separated PATH to the local filename syntax.
Guido van Rossume7e578f1995-08-04 04:00:20 +0000125
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000126 Components that mean special things to the local file system
127 (e.g. drive or directory names) are ignored. (XXX They should
128 probably be diagnosed.)
Guido van Rossume7e578f1995-08-04 04:00:20 +0000129
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000130 """
Guido van Rossumd7b147b1999-11-16 19:04:32 +0000131 path = posixpath.normpath(urllib.unquote(path))
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000132 words = string.splitfields(path, '/')
133 words = filter(None, words)
134 path = os.getcwd()
135 for word in words:
136 drive, word = os.path.splitdrive(word)
137 head, word = os.path.split(word)
138 if word in (os.curdir, os.pardir): continue
139 path = os.path.join(path, word)
140 return path
Guido van Rossume7e578f1995-08-04 04:00:20 +0000141
142 def copyfile(self, source, outputfile):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000143 """Copy all data between two file objects.
Guido van Rossume7e578f1995-08-04 04:00:20 +0000144
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000145 The SOURCE argument is a file object open for reading
146 (or anything with a read() method) and the DESTINATION
147 argument is a file object open for writing (or
148 anything with a write() method).
Guido van Rossume7e578f1995-08-04 04:00:20 +0000149
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000150 The only reason for overriding this would be to change
151 the block size or perhaps to replace newlines by CRLF
152 -- note however that this the default server uses this
153 to copy binary data as well.
Guido van Rossume7e578f1995-08-04 04:00:20 +0000154
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000155 """
Moshe Zadka37c03ff2000-07-29 05:15:56 +0000156 shutil.copyfileobj(source, outputfile)
Guido van Rossume7e578f1995-08-04 04:00:20 +0000157
158 def guess_type(self, path):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000159 """Guess the type of a file.
Guido van Rossume7e578f1995-08-04 04:00:20 +0000160
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000161 Argument is a PATH (a filename).
Guido van Rossume7e578f1995-08-04 04:00:20 +0000162
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000163 Return value is a string of the form type/subtype,
164 usable for a MIME Content-type header.
Guido van Rossume7e578f1995-08-04 04:00:20 +0000165
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000166 The default implementation looks the file's extension
167 up in the table self.extensions_map, using text/plain
168 as a default; however it would be permissible (if
169 slow) to look inside the data to make a better guess.
Guido van Rossume7e578f1995-08-04 04:00:20 +0000170
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000171 """
Guido van Rossume7e578f1995-08-04 04:00:20 +0000172
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000173 base, ext = posixpath.splitext(path)
174 if self.extensions_map.has_key(ext):
175 return self.extensions_map[ext]
176 ext = string.lower(ext)
177 if self.extensions_map.has_key(ext):
178 return self.extensions_map[ext]
179 else:
180 return self.extensions_map['']
Guido van Rossume7e578f1995-08-04 04:00:20 +0000181
182 extensions_map = {
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000183 '': 'text/plain', # Default, *must* be present
184 '.html': 'text/html',
185 '.htm': 'text/html',
186 '.gif': 'image/gif',
187 '.jpg': 'image/jpeg',
188 '.jpeg': 'image/jpeg',
189 }
Guido van Rossume7e578f1995-08-04 04:00:20 +0000190
191
192def test(HandlerClass = SimpleHTTPRequestHandler,
Guido van Rossum5c3b3841998-12-07 04:08:30 +0000193 ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossume7e578f1995-08-04 04:00:20 +0000194 BaseHTTPServer.test(HandlerClass, ServerClass)
195
196
197if __name__ == '__main__':
198 test()