#1153769: document PEP 237 changes to string formatting.
[python.git] / Lib / SimpleHTTPServer.py
blobe79a478d97e93648e09a01509ade28bdaad120af
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 try:
21 from cStringIO import StringIO
22 except ImportError:
23 from StringIO import StringIO
26 class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
28 """Simple HTTP request handler with GET and HEAD commands.
30 This serves files from the current directory and any of its
31 subdirectories. The MIME type for files is determined by
32 calling the .guess_type() method.
34 The GET and HEAD requests are identical except that the HEAD
35 request omits the actual contents of the file.
37 """
39 server_version = "SimpleHTTP/" + __version__
41 def do_GET(self):
42 """Serve a GET request."""
43 f = self.send_head()
44 if f:
45 self.copyfile(f, self.wfile)
46 f.close()
48 def do_HEAD(self):
49 """Serve a HEAD request."""
50 f = self.send_head()
51 if f:
52 f.close()
54 def send_head(self):
55 """Common code for GET and HEAD commands.
57 This sends the response code and MIME headers.
59 Return value is either a file object (which has to be copied
60 to the outputfile by the caller unless the command was HEAD,
61 and must be closed by the caller under all circumstances), or
62 None, in which case the caller has nothing further to do.
64 """
65 path = self.translate_path(self.path)
66 f = None
67 if os.path.isdir(path):
68 if not self.path.endswith('/'):
69 # redirect browser - doing basically what apache does
70 self.send_response(301)
71 self.send_header("Location", self.path + "/")
72 self.end_headers()
73 return None
74 for index in "index.html", "index.htm":
75 index = os.path.join(path, index)
76 if os.path.exists(index):
77 path = index
78 break
79 else:
80 return self.list_directory(path)
81 ctype = self.guess_type(path)
82 if ctype.startswith('text/'):
83 mode = 'r'
84 else:
85 mode = 'rb'
86 try:
87 f = open(path, mode)
88 except IOError:
89 self.send_error(404, "File not found")
90 return None
91 self.send_response(200)
92 self.send_header("Content-type", ctype)
93 fs = os.fstat(f.fileno())
94 self.send_header("Content-Length", str(fs[6]))
95 self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
96 self.end_headers()
97 return f
99 def list_directory(self, path):
100 """Helper to produce a directory listing (absent index.html).
102 Return value is either a file object, or None (indicating an
103 error). In either case, the headers are sent, making the
104 interface the same as for send_head().
107 try:
108 list = os.listdir(path)
109 except os.error:
110 self.send_error(404, "No permission to list directory")
111 return None
112 list.sort(key=lambda a: a.lower())
113 f = StringIO()
114 displaypath = cgi.escape(urllib.unquote(self.path))
115 f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
116 f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
117 f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
118 f.write("<hr>\n<ul>\n")
119 for name in list:
120 fullname = os.path.join(path, name)
121 displayname = linkname = name
122 # Append / for directories or @ for symbolic links
123 if os.path.isdir(fullname):
124 displayname = name + "/"
125 linkname = name + "/"
126 if os.path.islink(fullname):
127 displayname = name + "@"
128 # Note: a link to a directory displays with @ and links with /
129 f.write('<li><a href="%s">%s</a>\n'
130 % (urllib.quote(linkname), cgi.escape(displayname)))
131 f.write("</ul>\n<hr>\n</body>\n</html>\n")
132 length = f.tell()
133 f.seek(0)
134 self.send_response(200)
135 self.send_header("Content-type", "text/html")
136 self.send_header("Content-Length", str(length))
137 self.end_headers()
138 return f
140 def translate_path(self, path):
141 """Translate a /-separated PATH to the local filename syntax.
143 Components that mean special things to the local file system
144 (e.g. drive or directory names) are ignored. (XXX They should
145 probably be diagnosed.)
148 # abandon query parameters
149 path = path.split('?',1)[0]
150 path = path.split('#',1)[0]
151 path = posixpath.normpath(urllib.unquote(path))
152 words = path.split('/')
153 words = filter(None, words)
154 path = os.getcwd()
155 for word in words:
156 drive, word = os.path.splitdrive(word)
157 head, word = os.path.split(word)
158 if word in (os.curdir, os.pardir): continue
159 path = os.path.join(path, word)
160 return path
162 def copyfile(self, source, outputfile):
163 """Copy all data between two file objects.
165 The SOURCE argument is a file object open for reading
166 (or anything with a read() method) and the DESTINATION
167 argument is a file object open for writing (or
168 anything with a write() method).
170 The only reason for overriding this would be to change
171 the block size or perhaps to replace newlines by CRLF
172 -- note however that this the default server uses this
173 to copy binary data as well.
176 shutil.copyfileobj(source, outputfile)
178 def guess_type(self, path):
179 """Guess the type of a file.
181 Argument is a PATH (a filename).
183 Return value is a string of the form type/subtype,
184 usable for a MIME Content-type header.
186 The default implementation looks the file's extension
187 up in the table self.extensions_map, using application/octet-stream
188 as a default; however it would be permissible (if
189 slow) to look inside the data to make a better guess.
193 base, ext = posixpath.splitext(path)
194 if ext in self.extensions_map:
195 return self.extensions_map[ext]
196 ext = ext.lower()
197 if ext in self.extensions_map:
198 return self.extensions_map[ext]
199 else:
200 return self.extensions_map['']
202 if not mimetypes.inited:
203 mimetypes.init() # try to read system mime.types
204 extensions_map = mimetypes.types_map.copy()
205 extensions_map.update({
206 '': 'application/octet-stream', # Default
207 '.py': 'text/plain',
208 '.c': 'text/plain',
209 '.h': 'text/plain',
213 def test(HandlerClass = SimpleHTTPRequestHandler,
214 ServerClass = BaseHTTPServer.HTTPServer):
215 BaseHTTPServer.test(HandlerClass, ServerClass)
218 if __name__ == '__main__':
219 test()