Fixed bug in time-to-midnight calculation.
[python.git] / Lib / SimpleHTTPServer.py
blobea10fb7e927a0b3f12efbf3921563e7e59d7a500
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 urlparse
18 import cgi
19 import shutil
20 import mimetypes
21 try:
22 from cStringIO import StringIO
23 except ImportError:
24 from StringIO import StringIO
27 class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
29 """Simple HTTP request handler with GET and HEAD commands.
31 This serves files from the current directory and any of its
32 subdirectories. The MIME type for files is determined by
33 calling the .guess_type() method.
35 The GET and HEAD requests are identical except that the HEAD
36 request omits the actual contents of the file.
38 """
40 server_version = "SimpleHTTP/" + __version__
42 def do_GET(self):
43 """Serve a GET request."""
44 f = self.send_head()
45 if f:
46 self.copyfile(f, self.wfile)
47 f.close()
49 def do_HEAD(self):
50 """Serve a HEAD request."""
51 f = self.send_head()
52 if f:
53 f.close()
55 def send_head(self):
56 """Common code for GET and HEAD commands.
58 This sends the response code and MIME headers.
60 Return value is either a file object (which has to be copied
61 to the outputfile by the caller unless the command was HEAD,
62 and must be closed by the caller under all circumstances), or
63 None, in which case the caller has nothing further to do.
65 """
66 path = self.translate_path(self.path)
67 f = None
68 if os.path.isdir(path):
69 for index in "index.html", "index.htm":
70 index = os.path.join(path, index)
71 if os.path.exists(index):
72 path = index
73 break
74 else:
75 return self.list_directory(path)
76 ctype = self.guess_type(path)
77 if ctype.startswith('text/'):
78 mode = 'r'
79 else:
80 mode = 'rb'
81 try:
82 f = open(path, mode)
83 except IOError:
84 self.send_error(404, "File not found")
85 return None
86 self.send_response(200)
87 self.send_header("Content-type", ctype)
88 self.send_header("Content-Length", str(os.fstat(f.fileno())[6]))
89 self.end_headers()
90 return f
92 def list_directory(self, path):
93 """Helper to produce a directory listing (absent index.html).
95 Return value is either a file object, or None (indicating an
96 error). In either case, the headers are sent, making the
97 interface the same as for send_head().
99 """
100 try:
101 list = os.listdir(path)
102 except os.error:
103 self.send_error(404, "No permission to list directory")
104 return None
105 list.sort(key=lambda a: a.lower())
106 f = StringIO()
107 displaypath = cgi.escape(urllib.unquote(self.path))
108 f.write("<title>Directory listing for %s</title>\n" % displaypath)
109 f.write("<h2>Directory listing for %s</h2>\n" % displaypath)
110 f.write("<hr>\n<ul>\n")
111 for name in list:
112 fullname = os.path.join(path, name)
113 displayname = linkname = name
114 # Append / for directories or @ for symbolic links
115 if os.path.isdir(fullname):
116 displayname = name + "/"
117 linkname = name + "/"
118 if os.path.islink(fullname):
119 displayname = name + "@"
120 # Note: a link to a directory displays with @ and links with /
121 f.write('<li><a href="%s">%s</a>\n'
122 % (urllib.quote(linkname), cgi.escape(displayname)))
123 f.write("</ul>\n<hr>\n")
124 length = f.tell()
125 f.seek(0)
126 self.send_response(200)
127 self.send_header("Content-type", "text/html")
128 self.send_header("Content-Length", str(length))
129 self.end_headers()
130 return f
132 def translate_path(self, path):
133 """Translate a /-separated PATH to the local filename syntax.
135 Components that mean special things to the local file system
136 (e.g. drive or directory names) are ignored. (XXX They should
137 probably be diagnosed.)
140 # abandon query parameters
141 path = urlparse.urlparse(path)[2]
142 path = posixpath.normpath(urllib.unquote(path))
143 words = path.split('/')
144 words = filter(None, words)
145 path = os.getcwd()
146 for word in words:
147 drive, word = os.path.splitdrive(word)
148 head, word = os.path.split(word)
149 if word in (os.curdir, os.pardir): continue
150 path = os.path.join(path, word)
151 return path
153 def copyfile(self, source, outputfile):
154 """Copy all data between two file objects.
156 The SOURCE argument is a file object open for reading
157 (or anything with a read() method) and the DESTINATION
158 argument is a file object open for writing (or
159 anything with a write() method).
161 The only reason for overriding this would be to change
162 the block size or perhaps to replace newlines by CRLF
163 -- note however that this the default server uses this
164 to copy binary data as well.
167 shutil.copyfileobj(source, outputfile)
169 def guess_type(self, path):
170 """Guess the type of a file.
172 Argument is a PATH (a filename).
174 Return value is a string of the form type/subtype,
175 usable for a MIME Content-type header.
177 The default implementation looks the file's extension
178 up in the table self.extensions_map, using application/octet-stream
179 as a default; however it would be permissible (if
180 slow) to look inside the data to make a better guess.
184 base, ext = posixpath.splitext(path)
185 if ext in self.extensions_map:
186 return self.extensions_map[ext]
187 ext = ext.lower()
188 if ext in self.extensions_map:
189 return self.extensions_map[ext]
190 else:
191 return self.extensions_map['']
193 extensions_map = mimetypes.types_map.copy()
194 extensions_map.update({
195 '': 'application/octet-stream', # Default
196 '.py': 'text/plain',
197 '.c': 'text/plain',
198 '.h': 'text/plain',
202 def test(HandlerClass = SimpleHTTPRequestHandler,
203 ServerClass = BaseHTTPServer.HTTPServer):
204 BaseHTTPServer.test(HandlerClass, ServerClass)
207 if __name__ == '__main__':
208 test()