Results of a rewrite pass
[python/dscho.git] / Lib / SimpleHTTPServer.py
blobf8e3fc53050b675ff8148d968e856fb83c272a49
1 """Simple HTTP Server.
3 This module builds on BaseHTTPServer by implementing the standard GET
4 and HEAD requests in a fairly straightforward manner.
6 """
9 __version__ = "0.6"
11 __all__ = ["SimpleHTTPRequestHandler"]
13 import os
14 import posixpath
15 import BaseHTTPServer
16 import urllib
17 import cgi
18 import shutil
19 import mimetypes
20 from StringIO import StringIO
23 class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
25 """Simple HTTP request handler with GET and HEAD commands.
27 This serves files from the current directory and any of its
28 subdirectories. It assumes that all files are plain text files
29 unless they have the extension ".html" in which case it assumes
30 they are HTML files.
32 The GET and HEAD requests are identical except that the HEAD
33 request omits the actual contents of the file.
35 """
37 server_version = "SimpleHTTP/" + __version__
39 def do_GET(self):
40 """Serve a GET request."""
41 f = self.send_head()
42 if f:
43 self.copyfile(f, self.wfile)
44 f.close()
46 def do_HEAD(self):
47 """Serve a HEAD request."""
48 f = self.send_head()
49 if f:
50 f.close()
52 def send_head(self):
53 """Common code for GET and HEAD commands.
55 This sends the response code and MIME headers.
57 Return value is either a file object (which has to be copied
58 to the outputfile by the caller unless the command was HEAD,
59 and must be closed by the caller under all circumstances), or
60 None, in which case the caller has nothing further to do.
62 """
63 path = self.translate_path(self.path)
64 f = None
65 if os.path.isdir(path):
66 for index in "index.html", "index.htm":
67 index = os.path.join(path, index)
68 if os.path.exists(index):
69 path = index
70 break
71 else:
72 return self.list_directory(path)
73 ctype = self.guess_type(path)
74 if ctype.startswith('text/'):
75 mode = 'r'
76 else:
77 mode = 'rb'
78 try:
79 f = open(path, mode)
80 except IOError:
81 self.send_error(404, "File not found")
82 return None
83 self.send_response(200)
84 self.send_header("Content-type", ctype)
85 self.send_header("Content-Length", str(os.fstat(f.fileno())[6]))
86 self.end_headers()
87 return f
89 def list_directory(self, path):
90 """Helper to produce a directory listing (absent index.html).
92 Return value is either a file object, or None (indicating an
93 error). In either case, the headers are sent, making the
94 interface the same as for send_head().
96 """
97 try:
98 list = os.listdir(path)
99 except os.error:
100 self.send_error(404, "No permission to list directory")
101 return None
102 list.sort(lambda a, b: cmp(a.lower(), b.lower()))
103 f = StringIO()
104 f.write("<title>Directory listing for %s</title>\n" % self.path)
105 f.write("<h2>Directory listing for %s</h2>\n" % self.path)
106 f.write("<hr>\n<ul>\n")
107 for name in list:
108 fullname = os.path.join(path, name)
109 displayname = linkname = name = cgi.escape(name)
110 # Append / for directories or @ for symbolic links
111 if os.path.isdir(fullname):
112 displayname = name + "/"
113 linkname = name + "/"
114 if os.path.islink(fullname):
115 displayname = name + "@"
116 # Note: a link to a directory displays with @ and links with /
117 f.write('<li><a href="%s">%s</a>\n' % (linkname, displayname))
118 f.write("</ul>\n<hr>\n")
119 length = f.tell()
120 f.seek(0)
121 self.send_response(200)
122 self.send_header("Content-type", "text/html")
123 self.send_header("Content-Length", str(length))
124 self.end_headers()
125 return f
127 def translate_path(self, path):
128 """Translate a /-separated PATH to the local filename syntax.
130 Components that mean special things to the local file system
131 (e.g. drive or directory names) are ignored. (XXX They should
132 probably be diagnosed.)
135 path = posixpath.normpath(urllib.unquote(path))
136 words = path.split('/')
137 words = filter(None, words)
138 path = os.getcwd()
139 for word in words:
140 drive, word = os.path.splitdrive(word)
141 head, word = os.path.split(word)
142 if word in (os.curdir, os.pardir): continue
143 path = os.path.join(path, word)
144 return path
146 def copyfile(self, source, outputfile):
147 """Copy all data between two file objects.
149 The SOURCE argument is a file object open for reading
150 (or anything with a read() method) and the DESTINATION
151 argument is a file object open for writing (or
152 anything with a write() method).
154 The only reason for overriding this would be to change
155 the block size or perhaps to replace newlines by CRLF
156 -- note however that this the default server uses this
157 to copy binary data as well.
160 shutil.copyfileobj(source, outputfile)
162 def guess_type(self, path):
163 """Guess the type of a file.
165 Argument is a PATH (a filename).
167 Return value is a string of the form type/subtype,
168 usable for a MIME Content-type header.
170 The default implementation looks the file's extension
171 up in the table self.extensions_map, using text/plain
172 as a default; however it would be permissible (if
173 slow) to look inside the data to make a better guess.
177 base, ext = posixpath.splitext(path)
178 if ext in self.extensions_map:
179 return self.extensions_map[ext]
180 ext = ext.lower()
181 if ext in self.extensions_map:
182 return self.extensions_map[ext]
183 else:
184 return self.extensions_map['']
186 extensions_map = mimetypes.types_map.copy()
187 extensions_map.update({
188 '': 'application/octet-stream', # Default
189 '.py': 'text/plain',
190 '.c': 'text/plain',
191 '.h': 'text/plain',
195 def test(HandlerClass = SimpleHTTPRequestHandler,
196 ServerClass = BaseHTTPServer.HTTPServer):
197 BaseHTTPServer.test(HandlerClass, ServerClass)
200 if __name__ == '__main__':
201 test()