blob: 71254131202b5423f75ccf135444610e73b8febf [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
18from StringIO import StringIO
Guido van Rossum5c971671996-07-22 15:23:25 +000019
20
Guido van Rossum5c971671996-07-22 15:23:25 +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 Rossum548703a1998-03-26 22:14:20 +000038 """Serve a GET request."""
39 f = self.send_head()
40 if f:
41 self.copyfile(f, self.wfile)
42 f.close()
Guido van Rossum5c971671996-07-22 15:23:25 +000043
44 def do_HEAD(self):
Guido van Rossum548703a1998-03-26 22:14:20 +000045 """Serve a HEAD request."""
46 f = self.send_head()
47 if f:
48 f.close()
Guido van Rossum5c971671996-07-22 15:23:25 +000049
50 def send_head(self):
Guido van Rossum548703a1998-03-26 22:14:20 +000051 """Common code for GET and HEAD commands.
Guido van Rossum5c971671996-07-22 15:23:25 +000052
Guido van Rossum548703a1998-03-26 22:14:20 +000053 This sends the response code and MIME headers.
Guido van Rossum5c971671996-07-22 15:23:25 +000054
Guido van Rossum548703a1998-03-26 22:14:20 +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 Rossum5c971671996-07-22 15:23:25 +000059
Guido van Rossum548703a1998-03-26 22:14:20 +000060 """
61 path = self.translate_path(self.path)
Guido van Rossum3e06ab12000-06-29 19:35:29 +000062 f = None
Guido van Rossum548703a1998-03-26 22:14:20 +000063 if os.path.isdir(path):
Guido van Rossum3e06ab12000-06-29 19:35: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'
74 else:
75 mode = 'rb'
Guido van Rossum548703a1998-03-26 22:14:20 +000076 try:
Guido van Rossum3e06ab12000-06-29 19:35:29 +000077 f = open(path, mode)
Guido van Rossum548703a1998-03-26 22:14:20 +000078 except IOError:
79 self.send_error(404, "File not found")
80 return None
81 self.send_response(200)
Guido van Rossum3e06ab12000-06-29 19:35:29 +000082 self.send_header("Content-type", ctype)
83 self.end_headers()
84 return f
85
86 def list_directory(self, path):
87 """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 """
94 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)
105 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
110 if os.path.islink(fullname):
111 displayname = name + "@"
112 # Note: a link to a directory displays with @ and links with /
113 f.write('<li><a href="%s">%s</a>\n' % (linkname, displayname))
114 f.write("</ul>\n<hr>\n")
115 f.seek(0)
116 self.send_response(200)
117 self.send_header("Content-type", "text/html")
Guido van Rossum548703a1998-03-26 22:14:20 +0000118 self.end_headers()
119 return f
Guido van Rossum5c971671996-07-22 15:23:25 +0000120
121 def translate_path(self, path):
Guido van Rossum548703a1998-03-26 22:14:20 +0000122 """Translate a /-separated PATH to the local filename syntax.
Guido van Rossum5c971671996-07-22 15:23:25 +0000123
Guido van Rossum548703a1998-03-26 22:14:20 +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 Rossum5c971671996-07-22 15:23:25 +0000127
Guido van Rossum548703a1998-03-26 22:14:20 +0000128 """
Guido van Rossumaad67612000-05-08 17:31:04 +0000129 path = posixpath.normpath(urllib.unquote(path))
Guido van Rossum548703a1998-03-26 22:14:20 +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 Rossum5c971671996-07-22 15:23:25 +0000139
140 def copyfile(self, source, outputfile):
Guido van Rossum548703a1998-03-26 22:14:20 +0000141 """Copy all data between two file objects.
Guido van Rossum5c971671996-07-22 15:23:25 +0000142
Guido van Rossum548703a1998-03-26 22:14:20 +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 Rossum5c971671996-07-22 15:23:25 +0000147
Guido van Rossum548703a1998-03-26 22:14:20 +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 Rossum5c971671996-07-22 15:23:25 +0000152
Guido van Rossum548703a1998-03-26 22:14:20 +0000153 """
Guido van Rossum5c971671996-07-22 15:23:25 +0000154
Guido van Rossum548703a1998-03-26 22:14:20 +0000155 BLOCKSIZE = 8192
156 while 1:
157 data = source.read(BLOCKSIZE)
158 if not data: break
159 outputfile.write(data)
Guido van Rossum5c971671996-07-22 15:23:25 +0000160
161 def guess_type(self, path):
Guido van Rossum548703a1998-03-26 22:14:20 +0000162 """Guess the type of a file.
Guido van Rossum5c971671996-07-22 15:23:25 +0000163
Guido van Rossum548703a1998-03-26 22:14:20 +0000164 Argument is a PATH (a filename).
Guido van Rossum5c971671996-07-22 15:23:25 +0000165
Guido van Rossum548703a1998-03-26 22:14:20 +0000166 Return value is a string of the form type/subtype,
167 usable for a MIME Content-type header.
Guido van Rossum5c971671996-07-22 15:23:25 +0000168
Guido van Rossum548703a1998-03-26 22:14:20 +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 Rossum5c971671996-07-22 15:23:25 +0000173
Guido van Rossum548703a1998-03-26 22:14:20 +0000174 """
Guido van Rossum5c971671996-07-22 15:23:25 +0000175
Guido van Rossum548703a1998-03-26 22:14:20 +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 Rossum5c971671996-07-22 15:23:25 +0000184
185 extensions_map = {
Guido van Rossum548703a1998-03-26 22:14:20 +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 Rossum5c971671996-07-22 15:23:25 +0000193
194
195def test(HandlerClass = SimpleHTTPRequestHandler,
Guido van Rossum7ea1d971998-12-22 13:50:33 +0000196 ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossum5c971671996-07-22 15:23:25 +0000197 BaseHTTPServer.test(HandlerClass, ServerClass)
198
199
200if __name__ == '__main__':
201 test()