]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/SimpleHTTPServer.py
hgwebfs: remove prefixing // from file path
[plan9front.git] / sys / lib / python / SimpleHTTPServer.py
1 """Simple HTTP Server.
2
3 This module builds on BaseHTTPServer by implementing the standard GET
4 and HEAD requests in a fairly straightforward manner.
5
6 """
7
8
9 __version__ = "0.6"
10
11 __all__ = ["SimpleHTTPRequestHandler"]
12
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
25
26
27 class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
28
29     """Simple HTTP request handler with GET and HEAD commands.
30
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.
34
35     The GET and HEAD requests are identical except that the HEAD
36     request omits the actual contents of the file.
37
38     """
39
40     server_version = "SimpleHTTP/" + __version__
41
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()
48
49     def do_HEAD(self):
50         """Serve a HEAD request."""
51         f = self.send_head()
52         if f:
53             f.close()
54
55     def send_head(self):
56         """Common code for GET and HEAD commands.
57
58         This sends the response code and MIME headers.
59
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.
64
65         """
66         path = self.translate_path(self.path)
67         f = None
68         if os.path.isdir(path):
69             if not self.path.endswith('/'):
70                 # redirect browser - doing basically what apache does
71                 self.send_response(301)
72                 self.send_header("Location", self.path + "/")
73                 self.end_headers()
74                 return None
75             for index in "index.html", "index.htm":
76                 index = os.path.join(path, index)
77                 if os.path.exists(index):
78                     path = index
79                     break
80             else:
81                 return self.list_directory(path)
82         ctype = self.guess_type(path)
83         if ctype.startswith('text/'):
84             mode = 'r'
85         else:
86             mode = 'rb'
87         try:
88             f = open(path, mode)
89         except IOError:
90             self.send_error(404, "File not found")
91             return None
92         self.send_response(200)
93         self.send_header("Content-type", ctype)
94         fs = os.fstat(f.fileno())
95         self.send_header("Content-Length", str(fs[6]))
96         self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
97         self.end_headers()
98         return f
99
100     def list_directory(self, path):
101         """Helper to produce a directory listing (absent index.html).
102
103         Return value is either a file object, or None (indicating an
104         error).  In either case, the headers are sent, making the
105         interface the same as for send_head().
106
107         """
108         try:
109             list = os.listdir(path)
110         except os.error:
111             self.send_error(404, "No permission to list directory")
112             return None
113         list.sort(key=lambda a: a.lower())
114         f = StringIO()
115         displaypath = cgi.escape(urllib.unquote(self.path))
116         f.write("<title>Directory listing for %s</title>\n" % displaypath)
117         f.write("<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")
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
139
140     def translate_path(self, path):
141         """Translate a /-separated PATH to the local filename syntax.
142
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.)
146
147         """
148         # abandon query parameters
149         path = urlparse.urlparse(path)[2]
150         path = posixpath.normpath(urllib.unquote(path))
151         words = path.split('/')
152         words = filter(None, words)
153         path = os.getcwd()
154         for word in words:
155             drive, word = os.path.splitdrive(word)
156             head, word = os.path.split(word)
157             if word in (os.curdir, os.pardir): continue
158             path = os.path.join(path, word)
159         return path
160
161     def copyfile(self, source, outputfile):
162         """Copy all data between two file objects.
163
164         The SOURCE argument is a file object open for reading
165         (or anything with a read() method) and the DESTINATION
166         argument is a file object open for writing (or
167         anything with a write() method).
168
169         The only reason for overriding this would be to change
170         the block size or perhaps to replace newlines by CRLF
171         -- note however that this the default server uses this
172         to copy binary data as well.
173
174         """
175         shutil.copyfileobj(source, outputfile)
176
177     def guess_type(self, path):
178         """Guess the type of a file.
179
180         Argument is a PATH (a filename).
181
182         Return value is a string of the form type/subtype,
183         usable for a MIME Content-type header.
184
185         The default implementation looks the file's extension
186         up in the table self.extensions_map, using application/octet-stream
187         as a default; however it would be permissible (if
188         slow) to look inside the data to make a better guess.
189
190         """
191
192         base, ext = posixpath.splitext(path)
193         if ext in self.extensions_map:
194             return self.extensions_map[ext]
195         ext = ext.lower()
196         if ext in self.extensions_map:
197             return self.extensions_map[ext]
198         else:
199             return self.extensions_map['']
200
201     if not mimetypes.inited:
202         mimetypes.init() # try to read system mime.types
203     extensions_map = mimetypes.types_map.copy()
204     extensions_map.update({
205         '': 'application/octet-stream', # Default
206         '.py': 'text/plain',
207         '.c': 'text/plain',
208         '.h': 'text/plain',
209         })
210
211
212 def test(HandlerClass = SimpleHTTPRequestHandler,
213          ServerClass = BaseHTTPServer.HTTPServer):
214     BaseHTTPServer.test(HandlerClass, ServerClass)
215
216
217 if __name__ == '__main__':
218     test()