[PATCH 2 of 2 bfiles] Make bfserve serve and accept files over HTTP
alexandru
alex at hackd.net
Thu Jun 3 22:47:06 UTC 2010
bfiles.py | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 108 insertions(+), 2 deletions(-)
# HG changeset patch
# User alexandru <alex at hackd.net>
# Date 1275599619 25200
# Node ID 67358cbf0f23d51a017089160b6c4489869abdc2
# Parent 2bfadd624bb7ed9c29350ee9e5a791c3bbea222d
Make bfserve serve and accept files over HTTP
diff -r 2bfadd624bb7 -r 67358cbf0f23 bfiles.py
--- a/bfiles.py Thu Jun 03 13:58:19 2010 -0700
+++ b/bfiles.py Thu Jun 03 14:13:39 2010 -0700
@@ -36,6 +36,8 @@
import copy
import inspect
import fnmatch
+import posixpath
+import BaseHTTPServer
from mercurial import \
hg, cmdutil, util, error, extensions, commands, context, \
@@ -489,7 +491,10 @@
def bfserve(ui, **opts):
"""export the bfile store via SSH
"""
- s = sshstoreserver(ui)
+ if opts.get('http'):
+ s = httpstoreserver(ui, **opts)
+ else:
+ s = sshstoreserver(ui)
s.serve_forever()
@@ -1697,6 +1702,102 @@
# -- Private helper store classes --------------------------------------------
+class httpputhandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ """bfiles-customized request handler"""
+ def do_PUT(self):
+ relpath = self._normpath()
+ clen = int(self.headers.getheader('content-length'))
+ dirs = os.path.dirname(relpath)
+ if dirs != '' and not os.path.isdir(dirs):
+ util.makedirs(dirs)
+ try:
+ fd = util.atomictempfile(relpath, 'wb', 0644)
+ fhash = self._copier(self.rfile, fd, clen)
+ if fhash != os.path.basename(relpath):
+ self.send_response(httplib.CONFLICT)
+ self.send_header('Conflict', 'resource hash is: %s but'
+ ' the given name was: %s' % (fhash,
+ os.path.basename(relpath)))
+ else:
+ fd.rename() # hash is good, keep the file
+ self.send_response(httplib.CREATED)
+ self.send_header('Location', self.path)
+ except IOError:
+ self.send_response(httplib.FORBIDDEN)
+ self.send_header('Reason', 'cannot open resource for writing')
+
+ self.end_headers()
+
+ def do_GET(self):
+ """return a file to caller"""
+ if self._send_headers():
+ fpath = self._normpath()
+ clen = os.path.getsize(fpath)
+ with open(fpath, 'rb') as fd:
+ self._copier(fd, self.wfile, clen)
+
+ def do_HEAD(self):
+ """return a file hash to the caller"""
+ self._send_headers()
+
+ def _send_headers(self):
+ relpath = self._normpath()
+ if not os.path.isfile(relpath):
+ self.send_response(httplib.NOT_FOUND)
+ return False
+ clen = int(os.path.getsize(relpath))
+ self.send_response(httplib.OK)
+ self.send_header('Content-length', clen)
+ with open(relpath, 'rb') as fd:
+ self.send_header('Content-SHA1', _hashfile(fd))
+ self.end_headers()
+ return True
+
+ def _copier(self, sin, sout, clen):
+ chunksize = 4096 * 1024 # 4MBs
+ hasher = hashlib.sha1()
+ while clen > 0:
+ if chunksize > clen:
+ chunksize = clen
+ buff = sin.read(chunksize)
+ sout.write(buff)
+ hasher.update(buff)
+ clen -= len(buff)
+ return hasher.hexdigest()
+
+ def _normpath(self):
+ '''normalize and clean the path, and prevent traversal attacks.
+ adjusted from http://djangobook.com/en/2.0/chapter20/'''
+
+ path = posixpath.normpath(urllib.unquote(self.path))
+ newpath = ''
+ for part in path.split('/'):
+ if not part:
+ continue # strip empty path components
+
+ drive, part = os.path.splitdrive(part)
+ head, part = os.path.split(part)
+ if part in (os.curdir, os.pardir):
+ continue # strip '.' and '..' in path
+
+ newpath = os.path.join(newpath, part).replace('\\', '/')
+ return os.path.abspath(newpath)
+
+class httpstoreserver(object):
+ """Turn bfserve into a valid HTTP PUT target"""
+ def __init__(self, ui, **opts):
+ self.ui = ui
+ self.port = opts.get('port')
+
+ def serve_forever(self):
+ server_class = BaseHTTPServer.HTTPServer
+ httpd = server_class(('localhost', self.port), httpputhandler)
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ pass
+ httpd.server_close()
+
# XXX *snip* class heavily based on sshserver (in mercurial/sshserver.py).
# We do *not* subclass it here cause it does not have as many 'do_xxx'
@@ -1882,6 +1983,11 @@
_('check file contents, not just existence'))],
_('hg bfverify [--all] [--contents]')),
'bfserve' : (bfserve,
- [],
+ [('','http', False,
+ _('serve over HTTP')),
+ ('','port', 8080,
+ _('select HTTP port')),
+ ('','ssh', True,
+ _('server over SSH'))],
''),
}
-------------- next part --------------
A non-text attachment was scrubbed...
Name: PGP.sig
Type: application/pgp-signature
Size: 314 bytes
Desc: not available
URL: <http://lists.mercurial-scm.org/pipermail/mercurial-devel/attachments/20100603/7b500a62/attachment.sig>
More information about the Mercurial-devel
mailing list