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 + "/")
60 # Note that it is necessary to buffer the output into a StringIO here
61 # so that we can compute the content length before we send the
62 # content. The only other option would be to do chunked encoding, or
63 # not support content length.
65 displaypath = cgi.escape(path)
69 <TITLE>Directory listing for %(displaypath)s</TITLE>
71 BODY, TABLE { font-family: sans-serif }
72 .dir-name { text-align: left }
73 .dir-size { text-align: right }
77 <H2>Directory listing for %(displaypath)s</H2>
80 <TH class=dir-name>Name</TH>
81 <TH class=dir-size>Size<TH>
83 """ % { 'displaypath': displaypath })
85 displayname = linkname = sub.name
86 # Append / for directories or @ for symbolic links
87 size = str(sub.size())
88 if stat.S_ISDIR(sub.mode):
89 displayname = sub.name + "/"
90 linkname = sub.name + "/"
92 if stat.S_ISLNK(sub.mode):
93 displayname = sub.name + "@"
94 # Note: a link to a directory displays with @ and links with /
97 <TD class=dir-name><A href="%s">%s</A></TD>
98 <TD class=dir-size>%s</TD>
99 </TR>""" % (urllib.quote(linkname), cgi.escape(displayname), size))
106 self.send_response(200)
107 self.send_header("Content-type", "text/html")
108 self.send_header("Content-Length", str(length))
110 self._send_content(f)
113 def _get_file(self, path, n):
114 """Process a request on a file.
116 Return value is either a file object, or None (indicating an error).
117 In either case, the headers are sent.
119 ctype = self._guess_type(path)
121 self.send_response(200)
122 self.send_header("Content-type", ctype)
123 self.send_header("Content-Length", str(n.size()))
124 self.send_header("Last-Modified", self.date_time_string(n.mtime))
126 self._send_content(f)
129 def _send_content(self, f):
130 """Send the content file as the response if necessary."""
131 if self.command != 'HEAD':
132 for blob in chunkyreader(f):
133 self.wfile.write(blob)
135 def _guess_type(self, path):
136 """Guess the type of a file.
138 Argument is a PATH (a filename).
140 Return value is a string of the form type/subtype,
141 usable for a MIME Content-type header.
143 The default implementation looks the file's extension
144 up in the table self.extensions_map, using application/octet-stream
145 as a default; however it would be permissible (if
146 slow) to look inside the data to make a better guess.
148 base, ext = posixpath.splitext(path)
149 if ext in self.extensions_map:
150 return self.extensions_map[ext]
152 if ext in self.extensions_map:
153 return self.extensions_map[ext]
155 return self.extensions_map['']
157 if not mimetypes.inited:
158 mimetypes.init() # try to read system mime.types
159 extensions_map = mimetypes.types_map.copy()
160 extensions_map.update({
161 '': 'text/plain', # Default
169 bup web [[hostname]:port]
172 o = options.Options('bup web', optspec)
173 (opt, flags, extra) = o.parse(sys.argv[1:])
176 o.fatal("at most one argument expected")
178 address = ('127.0.0.1', 8080)
180 addressl = extra[0].split(':', 1)
181 addressl[1] = int(addressl[1])
182 address = tuple(addressl)
184 git.check_repo_or_die()
185 top = vfs.RefList(None)
187 httpd = BupHTTPServer(address, BupRequestHandler)
188 sa = httpd.socket.getsockname()
189 print "Serving HTTP on %s:%d..." % sa
191 httpd.serve_forever()