"""HTTP server for dulwich that implements the git smart HTTP protocol."""
from cStringIO import StringIO
-import cgi
import re
import time
+try:
+ from urlparse import parse_qs
+except ImportError:
+ from dulwich.misc import parse_qs
+from dulwich.protocol import (
+ ReceivableProtocol,
+ )
from dulwich.server import (
ReceivePackHandler,
UploadPackHandler,
HTTP_FORBIDDEN = '403 Forbidden'
-def date_time_string(self, timestamp=None):
+def date_time_string(timestamp=None):
# Based on BaseHTTPServer.py in python2.5
weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
months = [None,
weekdays[wd], day, months[month], year, hh, mm, ss)
+def url_prefix(mat):
+ """Extract the URL prefix from a regex match.
+
+ :param mat: A regex match object.
+ :returns: The URL prefix, defined as the text before the match in the
+ original string. Normalized to start with one leading slash and end with
+ zero.
+ """
+ return '/' + mat.string[:mat.start()].strip('/')
+
+
+def get_repo(backend, mat):
+ """Get a Repo instance for the given backend and URL regex match."""
+ return backend.open_repository(url_prefix(mat))
+
+
def send_file(req, f, content_type):
"""Send a file-like object to the request output.
def get_text_file(req, backend, mat):
req.nocache()
- return send_file(req, backend.repo.get_named_file(mat.group()),
+ return send_file(req, get_repo(backend, mat).get_named_file(mat.group()),
'text/plain')
def get_loose_object(req, backend, mat):
sha = mat.group(1) + mat.group(2)
- object_store = backend.object_store
+ object_store = get_repo(backend, mat).object_store
if not object_store.contains_loose(sha):
yield req.not_found('Object not found')
return
def get_pack_file(req, backend, mat):
req.cache_forever()
- return send_file(req, backend.repo.get_named_file(mat.group()),
+ return send_file(req, get_repo(backend, mat).get_named_file(mat.group()),
'application/x-git-packed-objects')
def get_idx_file(req, backend, mat):
req.cache_forever()
- return send_file(req, backend.repo.get_named_file(mat.group()),
+ return send_file(req, get_repo(backend, mat).get_named_file(mat.group()),
'application/x-git-packed-objects-toc')
def get_info_refs(req, backend, mat, services=None):
if services is None:
services = default_services
- params = cgi.parse_qs(req.environ['QUERY_STRING'])
+ params = parse_qs(req.environ['QUERY_STRING'])
service = params.get('service', [None])[0]
if service and not req.dumb:
handler_cls = services.get(service, None)
req.nocache()
req.respond(HTTP_OK, 'application/x-%s-advertisement' % service)
output = StringIO()
- dummy_input = StringIO() # GET request, handler doesn't need to read
- handler = handler_cls(backend, dummy_input.read, output.write,
+ proto = ReceivableProtocol(StringIO().read, output.write)
+ handler = handler_cls(backend, [url_prefix(mat)], proto,
stateless_rpc=True, advertise_refs=True)
handler.proto.write_pkt_line('# service=%s\n' % service)
handler.proto.write_pkt_line(None)
# TODO: select_getanyfile() (see http-backend.c)
req.nocache()
req.respond(HTTP_OK, 'text/plain')
- refs = backend.get_refs()
+ repo = get_repo(backend, mat)
+ refs = repo.get_refs()
for name in sorted(refs.iterkeys()):
# get_refs() includes HEAD as a special case, but we don't want to
# advertise it
if name == 'HEAD':
continue
sha = refs[name]
- o = backend.repo[sha]
+ o = repo[sha]
if not o:
continue
yield '%s\t%s\n' % (sha, name)
- peeled_sha = backend.repo.get_peeled(name)
+ peeled_sha = repo.get_peeled(name)
if peeled_sha != sha:
yield '%s\t%s^{}\n' % (peeled_sha, name)
def get_info_packs(req, backend, mat):
req.nocache()
req.respond(HTTP_OK, 'text/plain')
- for pack in backend.object_store.packs:
+ for pack in get_repo(backend, mat).object_store.packs:
yield 'P pack-%s.pack\n' % pack.name()
Content-Length bytes are read. This behavior is required by the WSGI spec
but not implemented in wsgiref as of 2.5.
"""
+
def __init__(self, input, max_bytes):
self._input = input
self._bytes_avail = max_bytes
# content-length
if 'CONTENT_LENGTH' in req.environ:
input = _LengthLimitedFile(input, int(req.environ['CONTENT_LENGTH']))
- handler = handler_cls(backend, input.read, output.write, stateless_rpc=True)
+ proto = ReceivableProtocol(input.read, output.write)
+ handler = handler_cls(backend, [url_prefix(mat)], proto, stateless_rpc=True)
handler.handle()
yield output.getvalue()
def nocache(self):
"""Set the response to never be cached by the client."""
self._cache_headers = [
- ('Expires', 'Fri, 01 Jan 1980 00:00:00 GMT'),
- ('Pragma', 'no-cache'),
- ('Cache-Control', 'no-cache, max-age=0, must-revalidate'),
- ]
+ ('Expires', 'Fri, 01 Jan 1980 00:00:00 GMT'),
+ ('Pragma', 'no-cache'),
+ ('Cache-Control', 'no-cache, max-age=0, must-revalidate'),
+ ]
def cache_forever(self):
"""Set the response to be cached forever by the client."""
now = time.time()
self._cache_headers = [
- ('Date', date_time_string(now)),
- ('Expires', date_time_string(now + 31536000)),
- ('Cache-Control', 'public, max-age=31536000'),
- ]
+ ('Date', date_time_string(now)),
+ ('Expires', date_time_string(now + 31536000)),
+ ('Cache-Control', 'public, max-age=31536000'),
+ ]
class HTTPGitApplication(object):
"""
services = {
- ('GET', re.compile('/HEAD$')): get_text_file,
- ('GET', re.compile('/info/refs$')): get_info_refs,
- ('GET', re.compile('/objects/info/alternates$')): get_text_file,
- ('GET', re.compile('/objects/info/http-alternates$')): get_text_file,
- ('GET', re.compile('/objects/info/packs$')): get_info_packs,
- ('GET', re.compile('/objects/([0-9a-f]{2})/([0-9a-f]{38})$')): get_loose_object,
- ('GET', re.compile('/objects/pack/pack-([0-9a-f]{40})\\.pack$')): get_pack_file,
- ('GET', re.compile('/objects/pack/pack-([0-9a-f]{40})\\.idx$')): get_idx_file,
-
- ('POST', re.compile('/git-upload-pack$')): handle_service_request,
- ('POST', re.compile('/git-receive-pack$')): handle_service_request,
+ ('GET', re.compile('/HEAD$')): get_text_file,
+ ('GET', re.compile('/info/refs$')): get_info_refs,
+ ('GET', re.compile('/objects/info/alternates$')): get_text_file,
+ ('GET', re.compile('/objects/info/http-alternates$')): get_text_file,
+ ('GET', re.compile('/objects/info/packs$')): get_info_packs,
+ ('GET', re.compile('/objects/([0-9a-f]{2})/([0-9a-f]{38})$')): get_loose_object,
+ ('GET', re.compile('/objects/pack/pack-([0-9a-f]{40})\\.pack$')): get_pack_file,
+ ('GET', re.compile('/objects/pack/pack-([0-9a-f]{40})\\.idx$')): get_idx_file,
+
+ ('POST', re.compile('/git-upload-pack$')): handle_service_request,
+ ('POST', re.compile('/git-receive-pack$')): handle_service_request,
}
def __init__(self, backend, dumb=False):