2 import sys, stat, cgi, shutil, urllib, mimetypes, posixpath
4 from bup import options, git, vfs
5 from bup.helpers import *
7 from cStringIO import StringIO
9 from StringIO import StringIO
13 class BupHTTPServer(BaseHTTPServer.HTTPServer):
14 def handle_error(self, request, client_address):
15 # If we get a KeyboardInterrupt error than just reraise it
16 # so that we cause the server to exit.
17 if sys.exc_info()[0] == KeyboardInterrupt:
20 class BupRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
21 server_version = 'BupHTTP/%s' % version_tag()
22 protocol_version = 'HTTP/1.1'
24 self._process_request()
27 self._process_request()
29 def _process_request(self):
30 """Common code for GET and HEAD commands.
32 This sends the response code and MIME headers along with the content
35 path = urllib.unquote(self.path)
38 except vfs.NoSuchFile:
42 if stat.S_ISDIR(n.mode):
43 self._list_directory(path, n)
45 self._get_file(path, n)
47 def _list_directory(self, path, n):
48 """Helper to produce a directory listing.
50 Return value is either a file object, or None (indicating an
51 error). In either case, the headers are sent.
53 if not path.endswith('/'):
54 # redirect browser - doing basically what apache does
55 self.send_response(301)
56 self.send_header("Location", path + "/")
57 self.send_header("Content-Length", 0)
61 # Note that it is necessary to buffer the output into a StringIO here
62 # so that we can compute the content length before we send the
63 # content. The only other option would be to do chunked encoding, or
64 # not support content length.
66 displaypath = cgi.escape(path)
70 <TITLE>Directory listing for %(displaypath)s</TITLE>
72 BODY, TABLE { font-family: sans-serif }
73 .dir-name { text-align: left }
74 .dir-size { text-align: right }
78 <H2>Directory listing for %(displaypath)s</H2>
81 <TH class="dir-name">Name</TH>
82 <TH class="dir-size">Size<TH>
84 """ % { 'displaypath': displaypath })
86 displayname = linkname = sub.name
87 # Append / for directories or @ for symbolic links
88 size = str(sub.size())
89 if stat.S_ISDIR(sub.mode):
90 displayname = sub.name + "/"
91 linkname = sub.name + "/"
93 if stat.S_ISLNK(sub.mode):
94 displayname = sub.name + "@"
95 # Note: a link to a directory displays with @ and links with /
98 <TD class="dir-name"><A href="%s">%s</A></TD>
99 <TD class="dir-size">%s</TD>
100 </TR>""" % (urllib.quote(linkname), cgi.escape(displayname), size))
107 self.send_response(200)
108 self.send_header("Content-type", "text/html")
109 self.send_header("Content-Length", str(length))
111 self._send_content(f)
114 def _get_file(self, path, n):
115 """Process a request on a file.
117 Return value is either a file object, or None (indicating an error).
118 In either case, the headers are sent.
120 ctype = self._guess_type(path)
122 self.send_response(200)
123 self.send_header("Content-type", ctype)
124 self.send_header("Content-Length", str(n.size()))
125 self.send_header("Last-Modified", self.date_time_string(n.mtime))
127 self._send_content(f)
130 def _send_content(self, f):
131 """Send the content file as the response if necessary."""
132 if self.command != 'HEAD':
133 for blob in chunkyreader(f):
134 self.wfile.write(blob)
136 def _guess_type(self, path):
137 """Guess the type of a file.
139 Argument is a PATH (a filename).
141 Return value is a string of the form type/subtype,
142 usable for a MIME Content-type header.
144 The default implementation looks the file's extension
145 up in the table self.extensions_map, using application/octet-stream
146 as a default; however it would be permissible (if
147 slow) to look inside the data to make a better guess.
149 base, ext = posixpath.splitext(path)
150 if ext in self.extensions_map:
151 return self.extensions_map[ext]
153 if ext in self.extensions_map:
154 return self.extensions_map[ext]
156 return self.extensions_map['']
158 if not mimetypes.inited:
159 mimetypes.init() # try to read system mime.types
160 extensions_map = mimetypes.types_map.copy()
161 extensions_map.update({
162 '': 'text/plain', # Default
170 bup web [[hostname]:port]
173 o = options.Options('bup web', optspec)
174 (opt, flags, extra) = o.parse(sys.argv[1:])
177 o.fatal("at most one argument expected")
179 address = ('127.0.0.1', 8080)
181 addressl = extra[0].split(':', 1)
182 addressl[1] = int(addressl[1])
183 address = tuple(addressl)
185 git.check_repo_or_die()
186 top = vfs.RefList(None)
189 httpd = BupHTTPServer(address, BupRequestHandler)
190 except socket.error, e:
191 log('socket%r: %s\n' % (address, e.args[1]))
194 sa = httpd.socket.getsockname()
195 log("Serving HTTP on %s:%d...\n" % sa)
196 httpd.serve_forever()