Mercurial > public > mercurial-scm > hg
view mercurial/hgweb/hgweb_mod.py @ 4834:439e2f2fde42
Fix inconsistency for the stream_out capability in hgweb
During some experiments of mine, the uncompressed cloning could not
be enabled for hgweb.cgi nor hgwebdir.cgi though the server claimed
to be stream_out capable.
The only solution was to enable it using the user's .hgrc file.
This solution is not acceptable when publishing the repos through
an HTTP server because the CGI runs as a www dedicated user whose's
home hgrc file may not be accessible to users publishing their repos
through their userdir.
For such cases we could end up with this typical debug output:
hg --debug clone --uncompressed http://server/hg/project
destination directory: project
sending capabilities command
capabilities: lookup changegroupsubset stream=1
unbundle=HG10GZ,HG10BZ,HG10UN
sending stream_out command
abort: operation forbidden by server
The error lies in the fact the hgweb object defines new accessors
to the repo configuration that trust things by default (untrusted=True)
but the streamclone:stream_out function uses the usual accessors to the
repo.ui object, which do not trust by default (untrusted=False)
Fix this inconsistency, adding a new parameter to the stream_out function.
hgweb then forces a "trust by default" behavior.
author | Edouard Gomez <ed.gomez@free.fr> |
---|---|
date | Sat, 12 May 2007 00:41:30 +0200 |
parents | e21a0e12ff10 |
children | 192cd95c2ba8 ccdc8db02bdf |
line wrap: on
line source
# hgweb/hgweb_mod.py - Web interface for a repository. # # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> # # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. import os, mimetypes, re, zlib, mimetools, cStringIO, sys import tempfile, urllib, bz2 from mercurial.node import * from mercurial.i18n import gettext as _ from mercurial import mdiff, ui, hg, util, archival, streamclone, patch from mercurial import revlog, templater from common import get_mtime, staticfile, style_map, paritygen def _up(p): if p[0] != "/": p = "/" + p if p[-1] == "/": p = p[:-1] up = os.path.dirname(p) if up == "/": return "/" return up + "/" def revnavgen(pos, pagelen, limit, nodefunc): def seq(factor, limit=None): if limit: yield limit if limit >= 20 and limit <= 40: yield 50 else: yield 1 * factor yield 3 * factor for f in seq(factor * 10): yield f def nav(**map): l = [] last = 0 for f in seq(1, pagelen): if f < pagelen or f <= last: continue if f > limit: break last = f if pos + f < limit: l.append(("+%d" % f, hex(nodefunc(pos + f).node()))) if pos - f >= 0: l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node()))) try: yield {"label": "(0)", "node": hex(nodefunc('0').node())} for label, node in l: yield {"label": label, "node": node} yield {"label": "tip", "node": "tip"} except hg.RepoError: pass return nav class hgweb(object): def __init__(self, repo, name=None): if type(repo) == type(""): self.repo = hg.repository(ui.ui(report_untrusted=False), repo) else: self.repo = repo self.mtime = -1 self.reponame = name self.archives = 'zip', 'gz', 'bz2' self.stripecount = 1 # a repo owner may set web.templates in .hg/hgrc to get any file # readable by the user running the CGI script self.templatepath = self.config("web", "templates", templater.templatepath(), untrusted=False) # The CGI scripts are often run by a user different from the repo owner. # Trust the settings from the .hg/hgrc files by default. def config(self, section, name, default=None, untrusted=True): return self.repo.ui.config(section, name, default, untrusted=untrusted) def configbool(self, section, name, default=False, untrusted=True): return self.repo.ui.configbool(section, name, default, untrusted=untrusted) def configlist(self, section, name, default=None, untrusted=True): return self.repo.ui.configlist(section, name, default, untrusted=untrusted) def refresh(self): mtime = get_mtime(self.repo.root) if mtime != self.mtime: self.mtime = mtime self.repo = hg.repository(self.repo.ui, self.repo.root) self.maxchanges = int(self.config("web", "maxchanges", 10)) self.stripecount = int(self.config("web", "stripes", 1)) self.maxshortchanges = int(self.config("web", "maxshortchanges", 60)) self.maxfiles = int(self.config("web", "maxfiles", 10)) self.allowpull = self.configbool("web", "allowpull", True) self.encoding = self.config("web", "encoding", util._encoding) def archivelist(self, nodeid): allowed = self.configlist("web", "allow_archive") for i, spec in self.archive_specs.iteritems(): if i in allowed or self.configbool("web", "allow" + i): yield {"type" : i, "extension" : spec[2], "node" : nodeid} def listfilediffs(self, files, changeset): for f in files[:self.maxfiles]: yield self.t("filedifflink", node=hex(changeset), file=f) if len(files) > self.maxfiles: yield self.t("fileellipses") def siblings(self, siblings=[], hiderev=None, **args): siblings = [s for s in siblings if s.node() != nullid] if len(siblings) == 1 and siblings[0].rev() == hiderev: return for s in siblings: d = {'node': hex(s.node()), 'rev': s.rev()} if hasattr(s, 'path'): d['file'] = s.path() d.update(args) yield d def renamelink(self, fl, node): r = fl.renamed(node) if r: return [dict(file=r[0], node=hex(r[1]))] return [] def nodetagsdict(self, node): return [{"name": i} for i in self.repo.nodetags(node)] def nodebranchdict(self, ctx): branches = [] branch = ctx.branch() if self.repo.branchtags()[branch] == ctx.node(): branches.append({"name": branch}) return branches def showtag(self, t1, node=nullid, **args): for t in self.repo.nodetags(node): yield self.t(t1, tag=t, **args) def diff(self, node1, node2, files): def filterfiles(filters, files): l = [x for x in files if x in filters] for t in filters: if t and t[-1] != os.sep: t += os.sep l += [x for x in files if x.startswith(t)] return l parity = paritygen(self.stripecount) def diffblock(diff, f, fn): yield self.t("diffblock", lines=prettyprintlines(diff), parity=parity.next(), file=f, filenode=hex(fn or nullid)) def prettyprintlines(diff): for l in diff.splitlines(1): if l.startswith('+'): yield self.t("difflineplus", line=l) elif l.startswith('-'): yield self.t("difflineminus", line=l) elif l.startswith('@'): yield self.t("difflineat", line=l) else: yield self.t("diffline", line=l) r = self.repo c1 = r.changectx(node1) c2 = r.changectx(node2) date1 = util.datestr(c1.date()) date2 = util.datestr(c2.date()) modified, added, removed, deleted, unknown = r.status(node1, node2)[:5] if files: modified, added, removed = map(lambda x: filterfiles(files, x), (modified, added, removed)) diffopts = patch.diffopts(self.repo.ui, untrusted=True) for f in modified: to = c1.filectx(f).data() tn = c2.filectx(f).data() yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, opts=diffopts), f, tn) for f in added: to = None tn = c2.filectx(f).data() yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, opts=diffopts), f, tn) for f in removed: to = c1.filectx(f).data() tn = None yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, opts=diffopts), f, tn) def changelog(self, ctx, shortlog=False): def changelist(**map): cl = self.repo.changelog l = [] # build a list in forward order for efficiency for i in xrange(start, end): ctx = self.repo.changectx(i) n = ctx.node() l.insert(0, {"parity": parity.next(), "author": ctx.user(), "parent": self.siblings(ctx.parents(), i - 1), "child": self.siblings(ctx.children(), i + 1), "changelogtag": self.showtag("changelogtag",n), "desc": ctx.description(), "date": ctx.date(), "files": self.listfilediffs(ctx.files(), n), "rev": i, "node": hex(n), "tags": self.nodetagsdict(n), "branches": self.nodebranchdict(ctx)}) for e in l: yield e maxchanges = shortlog and self.maxshortchanges or self.maxchanges cl = self.repo.changelog count = cl.count() pos = ctx.rev() start = max(0, pos - maxchanges + 1) end = min(count, start + maxchanges) pos = end - 1 parity = paritygen(self.stripecount, offset=start-end) changenav = revnavgen(pos, maxchanges, count, self.repo.changectx) yield self.t(shortlog and 'shortlog' or 'changelog', changenav=changenav, node=hex(cl.tip()), rev=pos, changesets=count, entries=changelist, archives=self.archivelist("tip")) def search(self, query): def changelist(**map): cl = self.repo.changelog count = 0 qw = query.lower().split() def revgen(): for i in xrange(cl.count() - 1, 0, -100): l = [] for j in xrange(max(0, i - 100), i): ctx = self.repo.changectx(j) l.append(ctx) l.reverse() for e in l: yield e for ctx in revgen(): miss = 0 for q in qw: if not (q in ctx.user().lower() or q in ctx.description().lower() or q in " ".join(ctx.files()).lower()): miss = 1 break if miss: continue count += 1 n = ctx.node() yield self.t('searchentry', parity=parity.next(), author=ctx.user(), parent=self.siblings(ctx.parents()), child=self.siblings(ctx.children()), changelogtag=self.showtag("changelogtag",n), desc=ctx.description(), date=ctx.date(), files=self.listfilediffs(ctx.files(), n), rev=ctx.rev(), node=hex(n), tags=self.nodetagsdict(n), branches=self.nodebranchdict(ctx)) if count >= self.maxchanges: break cl = self.repo.changelog parity = paritygen(self.stripecount) yield self.t('search', query=query, node=hex(cl.tip()), entries=changelist, archives=self.archivelist("tip")) def changeset(self, ctx): n = ctx.node() parents = ctx.parents() p1 = parents[0].node() files = [] parity = paritygen(self.stripecount) for f in ctx.files(): files.append(self.t("filenodelink", node=hex(n), file=f, parity=parity.next())) def diff(**map): yield self.diff(p1, n, None) yield self.t('changeset', diff=diff, rev=ctx.rev(), node=hex(n), parent=self.siblings(parents), child=self.siblings(ctx.children()), changesettag=self.showtag("changesettag",n), author=ctx.user(), desc=ctx.description(), date=ctx.date(), files=files, archives=self.archivelist(hex(n)), tags=self.nodetagsdict(n), branches=self.nodebranchdict(ctx)) def filelog(self, fctx): f = fctx.path() fl = fctx.filelog() count = fl.count() pagelen = self.maxshortchanges pos = fctx.filerev() start = max(0, pos - pagelen + 1) end = min(count, start + pagelen) pos = end - 1 parity = paritygen(self.stripecount, offset=start-end) def entries(**map): l = [] for i in xrange(start, end): ctx = fctx.filectx(i) n = fl.node(i) l.insert(0, {"parity": parity.next(), "filerev": i, "file": f, "node": hex(ctx.node()), "author": ctx.user(), "date": ctx.date(), "rename": self.renamelink(fl, n), "parent": self.siblings(fctx.parents()), "child": self.siblings(fctx.children()), "desc": ctx.description()}) for e in l: yield e nodefunc = lambda x: fctx.filectx(fileid=x) nav = revnavgen(pos, pagelen, count, nodefunc) yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav, entries=entries) def filerevision(self, fctx): f = fctx.path() text = fctx.data() fl = fctx.filelog() n = fctx.filenode() parity = paritygen(self.stripecount) mt = mimetypes.guess_type(f)[0] rawtext = text if util.binary(text): mt = mt or 'application/octet-stream' text = "(binary:%s)" % mt mt = mt or 'text/plain' def lines(): for l, t in enumerate(text.splitlines(1)): yield {"line": t, "linenumber": "% 6d" % (l + 1), "parity": parity.next()} yield self.t("filerevision", file=f, path=_up(f), text=lines(), raw=rawtext, mimetype=mt, rev=fctx.rev(), node=hex(fctx.node()), author=fctx.user(), date=fctx.date(), desc=fctx.description(), parent=self.siblings(fctx.parents()), child=self.siblings(fctx.children()), rename=self.renamelink(fl, n), permissions=fctx.manifest().flags(f)) def fileannotate(self, fctx): f = fctx.path() n = fctx.filenode() fl = fctx.filelog() parity = paritygen(self.stripecount) def annotate(**map): last = None for f, l in fctx.annotate(follow=True): fnode = f.filenode() name = self.repo.ui.shortuser(f.user()) if last != fnode: last = fnode yield {"parity": parity.next(), "node": hex(f.node()), "rev": f.rev(), "author": name, "file": f.path(), "line": l} yield self.t("fileannotate", file=f, annotate=annotate, path=_up(f), rev=fctx.rev(), node=hex(fctx.node()), author=fctx.user(), date=fctx.date(), desc=fctx.description(), rename=self.renamelink(fl, n), parent=self.siblings(fctx.parents()), child=self.siblings(fctx.children()), permissions=fctx.manifest().flags(f)) def manifest(self, ctx, path): mf = ctx.manifest() node = ctx.node() files = {} parity = paritygen(self.stripecount) if path and path[-1] != "/": path += "/" l = len(path) abspath = "/" + path for f, n in mf.items(): if f[:l] != path: continue remain = f[l:] if "/" in remain: short = remain[:remain.index("/") + 1] # bleah files[short] = (f, None) else: short = os.path.basename(remain) files[short] = (f, n) def filelist(**map): fl = files.keys() fl.sort() for f in fl: full, fnode = files[f] if not fnode: continue yield {"file": full, "parity": parity.next(), "basename": f, "size": ctx.filectx(full).size(), "permissions": mf.flags(full)} def dirlist(**map): fl = files.keys() fl.sort() for f in fl: full, fnode = files[f] if fnode: continue yield {"parity": parity.next(), "path": os.path.join(abspath, f), "basename": f[:-1]} yield self.t("manifest", rev=ctx.rev(), node=hex(node), path=abspath, up=_up(abspath), upparity=parity.next(), fentries=filelist, dentries=dirlist, archives=self.archivelist(hex(node)), tags=self.nodetagsdict(node), branches=self.nodebranchdict(ctx)) def tags(self): i = self.repo.tagslist() i.reverse() parity = paritygen(self.stripecount) def entries(notip=False, **map): for k, n in i: if notip and k == "tip": continue yield {"parity": parity.next(), "tag": k, "date": self.repo.changectx(n).date(), "node": hex(n)} yield self.t("tags", node=hex(self.repo.changelog.tip()), entries=lambda **x: entries(False, **x), entriesnotip=lambda **x: entries(True, **x)) def summary(self): i = self.repo.tagslist() i.reverse() def tagentries(**map): parity = paritygen(self.stripecount) count = 0 for k, n in i: if k == "tip": # skip tip continue; count += 1 if count > 10: # limit to 10 tags break; yield self.t("tagentry", parity=parity.next(), tag=k, node=hex(n), date=self.repo.changectx(n).date()) def branches(**map): parity = paritygen(self.stripecount) b = self.repo.branchtags() l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()] l.sort() for r,n,t in l: ctx = self.repo.changectx(n) yield {'parity': parity.next(), 'branch': t, 'node': hex(n), 'date': ctx.date()} def changelist(**map): parity = paritygen(self.stripecount, offset=start-end) l = [] # build a list in forward order for efficiency for i in xrange(start, end): ctx = self.repo.changectx(i) n = ctx.node() hn = hex(n) l.insert(0, self.t( 'shortlogentry', parity=parity.next(), author=ctx.user(), desc=ctx.description(), date=ctx.date(), rev=i, node=hn, tags=self.nodetagsdict(n), branches=self.nodebranchdict(ctx))) yield l cl = self.repo.changelog count = cl.count() start = max(0, count - self.maxchanges) end = min(count, start + self.maxchanges) yield self.t("summary", desc=self.config("web", "description", "unknown"), owner=(self.config("ui", "username") or # preferred self.config("web", "contact") or # deprecated self.config("web", "author", "unknown")), # also lastchange=cl.read(cl.tip())[2], tags=tagentries, branches=branches, shortlog=changelist, node=hex(cl.tip()), archives=self.archivelist("tip")) def filediff(self, fctx): n = fctx.node() path = fctx.path() parents = fctx.parents() p1 = parents and parents[0].node() or nullid def diff(**map): yield self.diff(p1, n, [path]) yield self.t("filediff", file=path, node=hex(n), rev=fctx.rev(), parent=self.siblings(parents), child=self.siblings(fctx.children()), diff=diff) archive_specs = { 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None), 'gz': ('application/x-tar', 'tgz', '.tar.gz', None), 'zip': ('application/zip', 'zip', '.zip', None), } def archive(self, req, key, type_): reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame)) cnode = self.repo.lookup(key) arch_version = key if cnode == key or key == 'tip': arch_version = short(cnode) name = "%s-%s" % (reponame, arch_version) mimetype, artype, extension, encoding = self.archive_specs[type_] headers = [('Content-type', mimetype), ('Content-disposition', 'attachment; filename=%s%s' % (name, extension))] if encoding: headers.append(('Content-encoding', encoding)) req.header(headers) archival.archive(self.repo, req.out, cnode, artype, prefix=name) # add tags to things # tags -> list of changesets corresponding to tags # find tag, changeset, file def cleanpath(self, path): path = path.lstrip('/') return util.canonpath(self.repo.root, '', path) def run(self): if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."): raise RuntimeError("This function is only intended to be called while running as a CGI script.") import mercurial.hgweb.wsgicgi as wsgicgi from request import wsgiapplication def make_web_app(): return self wsgicgi.launch(wsgiapplication(make_web_app)) def run_wsgi(self, req): def header(**map): header_file = cStringIO.StringIO( ''.join(self.t("header", encoding=self.encoding, **map))) msg = mimetools.Message(header_file, 0) req.header(msg.items()) yield header_file.read() def rawfileheader(**map): req.header([('Content-type', map['mimetype']), ('Content-disposition', 'filename=%s' % map['file']), ('Content-length', str(len(map['raw'])))]) yield '' def footer(**map): yield self.t("footer", **map) def motd(**map): yield self.config("web", "motd", "") def expand_form(form): shortcuts = { 'cl': [('cmd', ['changelog']), ('rev', None)], 'sl': [('cmd', ['shortlog']), ('rev', None)], 'cs': [('cmd', ['changeset']), ('node', None)], 'f': [('cmd', ['file']), ('filenode', None)], 'fl': [('cmd', ['filelog']), ('filenode', None)], 'fd': [('cmd', ['filediff']), ('node', None)], 'fa': [('cmd', ['annotate']), ('filenode', None)], 'mf': [('cmd', ['manifest']), ('manifest', None)], 'ca': [('cmd', ['archive']), ('node', None)], 'tags': [('cmd', ['tags'])], 'tip': [('cmd', ['changeset']), ('node', ['tip'])], 'static': [('cmd', ['static']), ('file', None)] } for k in shortcuts.iterkeys(): if form.has_key(k): for name, value in shortcuts[k]: if value is None: value = form[k] form[name] = value del form[k] def rewrite_request(req): '''translate new web interface to traditional format''' def spliturl(req): def firstitem(query): return query.split('&', 1)[0].split(';', 1)[0] def normurl(url): inner = '/'.join([x for x in url.split('/') if x]) tl = len(url) > 1 and url.endswith('/') and '/' or '' return '%s%s%s' % (url.startswith('/') and '/' or '', inner, tl) root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0])) pi = normurl(req.env.get('PATH_INFO', '')) if pi: # strip leading / pi = pi[1:] if pi: root = root[:root.rfind(pi)] if req.env.has_key('REPO_NAME'): rn = req.env['REPO_NAME'] + '/' root += rn query = pi[len(rn):] else: query = pi else: root += '?' query = firstitem(req.env['QUERY_STRING']) return (root, query) req.url, query = spliturl(req) if req.form.has_key('cmd'): # old style return args = query.split('/', 2) if not args or not args[0]: return cmd = args.pop(0) style = cmd.rfind('-') if style != -1: req.form['style'] = [cmd[:style]] cmd = cmd[style+1:] # avoid accepting e.g. style parameter as command if hasattr(self, 'do_' + cmd): req.form['cmd'] = [cmd] if args and args[0]: node = args.pop(0) req.form['node'] = [node] if args: req.form['file'] = args if cmd == 'static': req.form['file'] = req.form['node'] elif cmd == 'archive': fn = req.form['node'][0] for type_, spec in self.archive_specs.iteritems(): ext = spec[2] if fn.endswith(ext): req.form['node'] = [fn[:-len(ext)]] req.form['type'] = [type_] def sessionvars(**map): fields = [] if req.form.has_key('style'): style = req.form['style'][0] if style != self.config('web', 'style', ''): fields.append(('style', style)) separator = req.url[-1] == '?' and ';' or '?' for name, value in fields: yield dict(name=name, value=value, separator=separator) separator = ';' self.refresh() expand_form(req.form) rewrite_request(req) style = self.config("web", "style", "") if req.form.has_key('style'): style = req.form['style'][0] mapfile = style_map(self.templatepath, style) port = req.env["SERVER_PORT"] port = port != "80" and (":" + port) or "" urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port) staticurl = self.config("web", "staticurl") or req.url + 'static/' if not staticurl.endswith('/'): staticurl += '/' if not self.reponame: self.reponame = (self.config("web", "name") or req.env.get('REPO_NAME') or req.url.strip('/') or self.repo.root) self.t = templater.templater(mapfile, templater.common_filters, defaults={"url": req.url, "staticurl": staticurl, "urlbase": urlbase, "repo": self.reponame, "header": header, "footer": footer, "motd": motd, "rawfileheader": rawfileheader, "sessionvars": sessionvars }) try: if not req.form.has_key('cmd'): req.form['cmd'] = [self.t.cache['default']] cmd = req.form['cmd'][0] method = getattr(self, 'do_' + cmd, None) if method: try: method(req) except (hg.RepoError, revlog.RevlogError), inst: req.write(self.t("error", error=str(inst))) else: req.write(self.t("error", error='No such method: ' + cmd)) finally: self.t = None def changectx(self, req): if req.form.has_key('node'): changeid = req.form['node'][0] elif req.form.has_key('manifest'): changeid = req.form['manifest'][0] else: changeid = self.repo.changelog.count() - 1 try: ctx = self.repo.changectx(changeid) except hg.RepoError: man = self.repo.manifest mn = man.lookup(changeid) ctx = self.repo.changectx(man.linkrev(mn)) return ctx def filectx(self, req): path = self.cleanpath(req.form['file'][0]) if req.form.has_key('node'): changeid = req.form['node'][0] else: changeid = req.form['filenode'][0] try: ctx = self.repo.changectx(changeid) fctx = ctx.filectx(path) except hg.RepoError: fctx = self.repo.filectx(path, fileid=changeid) return fctx def do_log(self, req): if req.form.has_key('file') and req.form['file'][0]: self.do_filelog(req) else: self.do_changelog(req) def do_rev(self, req): self.do_changeset(req) def do_file(self, req): path = self.cleanpath(req.form.get('file', [''])[0]) if path: try: req.write(self.filerevision(self.filectx(req))) return except revlog.LookupError: pass req.write(self.manifest(self.changectx(req), path)) def do_diff(self, req): self.do_filediff(req) def do_changelog(self, req, shortlog = False): if req.form.has_key('node'): ctx = self.changectx(req) else: if req.form.has_key('rev'): hi = req.form['rev'][0] else: hi = self.repo.changelog.count() - 1 try: ctx = self.repo.changectx(hi) except hg.RepoError: req.write(self.search(hi)) # XXX redirect to 404 page? return req.write(self.changelog(ctx, shortlog = shortlog)) def do_shortlog(self, req): self.do_changelog(req, shortlog = True) def do_changeset(self, req): req.write(self.changeset(self.changectx(req))) def do_manifest(self, req): req.write(self.manifest(self.changectx(req), self.cleanpath(req.form['path'][0]))) def do_tags(self, req): req.write(self.tags()) def do_summary(self, req): req.write(self.summary()) def do_filediff(self, req): req.write(self.filediff(self.filectx(req))) def do_annotate(self, req): req.write(self.fileannotate(self.filectx(req))) def do_filelog(self, req): req.write(self.filelog(self.filectx(req))) def do_lookup(self, req): try: r = hex(self.repo.lookup(req.form['key'][0])) success = 1 except Exception,inst: r = str(inst) success = 0 resp = "%s %s\n" % (success, r) req.httphdr("application/mercurial-0.1", length=len(resp)) req.write(resp) def do_heads(self, req): resp = " ".join(map(hex, self.repo.heads())) + "\n" req.httphdr("application/mercurial-0.1", length=len(resp)) req.write(resp) def do_branches(self, req): nodes = [] if req.form.has_key('nodes'): nodes = map(bin, req.form['nodes'][0].split(" ")) resp = cStringIO.StringIO() for b in self.repo.branches(nodes): resp.write(" ".join(map(hex, b)) + "\n") resp = resp.getvalue() req.httphdr("application/mercurial-0.1", length=len(resp)) req.write(resp) def do_between(self, req): if req.form.has_key('pairs'): pairs = [map(bin, p.split("-")) for p in req.form['pairs'][0].split(" ")] resp = cStringIO.StringIO() for b in self.repo.between(pairs): resp.write(" ".join(map(hex, b)) + "\n") resp = resp.getvalue() req.httphdr("application/mercurial-0.1", length=len(resp)) req.write(resp) def do_changegroup(self, req): req.httphdr("application/mercurial-0.1") nodes = [] if not self.allowpull: return if req.form.has_key('roots'): nodes = map(bin, req.form['roots'][0].split(" ")) z = zlib.compressobj() f = self.repo.changegroup(nodes, 'serve') while 1: chunk = f.read(4096) if not chunk: break req.write(z.compress(chunk)) req.write(z.flush()) def do_changegroupsubset(self, req): req.httphdr("application/mercurial-0.1") bases = [] heads = [] if not self.allowpull: return if req.form.has_key('bases'): bases = [bin(x) for x in req.form['bases'][0].split(' ')] if req.form.has_key('heads'): heads = [bin(x) for x in req.form['heads'][0].split(' ')] z = zlib.compressobj() f = self.repo.changegroupsubset(bases, heads, 'serve') while 1: chunk = f.read(4096) if not chunk: break req.write(z.compress(chunk)) req.write(z.flush()) def do_archive(self, req): type_ = req.form['type'][0] allowed = self.configlist("web", "allow_archive") if (type_ in self.archives and (type_ in allowed or self.configbool("web", "allow" + type_, False))): self.archive(req, req.form['node'][0], type_) return req.write(self.t("error")) def do_static(self, req): fname = req.form['file'][0] # a repo owner may set web.static in .hg/hgrc to get any file # readable by the user running the CGI script static = self.config("web", "static", os.path.join(self.templatepath, "static"), untrusted=False) req.write(staticfile(static, fname, req) or self.t("error", error="%r not found" % fname)) def do_capabilities(self, req): caps = ['lookup', 'changegroupsubset'] if self.configbool('server', 'uncompressed'): caps.append('stream=%d' % self.repo.changelog.version) # XXX: make configurable and/or share code with do_unbundle: unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN'] if unbundleversions: caps.append('unbundle=%s' % ','.join(unbundleversions)) resp = ' '.join(caps) req.httphdr("application/mercurial-0.1", length=len(resp)) req.write(resp) def check_perm(self, req, op, default): '''check permission for operation based on user auth. return true if op allowed, else false. default is policy to use if no config given.''' user = req.env.get('REMOTE_USER') deny = self.configlist('web', 'deny_' + op) if deny and (not user or deny == ['*'] or user in deny): return False allow = self.configlist('web', 'allow_' + op) return (allow and (allow == ['*'] or user in allow)) or default def do_unbundle(self, req): def bail(response, headers={}): length = int(req.env['CONTENT_LENGTH']) for s in util.filechunkiter(req, limit=length): # drain incoming bundle, else client will not see # response when run outside cgi script pass req.httphdr("application/mercurial-0.1", headers=headers) req.write('0\n') req.write(response) # require ssl by default, auth info cannot be sniffed and # replayed ssl_req = self.configbool('web', 'push_ssl', True) if ssl_req: if not req.env.get('HTTPS'): bail(_('ssl required\n')) return proto = 'https' else: proto = 'http' # do not allow push unless explicitly allowed if not self.check_perm(req, 'push', False): bail(_('push not authorized\n'), headers={'status': '401 Unauthorized'}) return their_heads = req.form['heads'][0].split(' ') def check_heads(): heads = map(hex, self.repo.heads()) return their_heads == [hex('force')] or their_heads == heads # fail early if possible if not check_heads(): bail(_('unsynced changes\n')) return req.httphdr("application/mercurial-0.1") # do not lock repo until all changegroup data is # streamed. save to temporary file. fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') fp = os.fdopen(fd, 'wb+') try: length = int(req.env['CONTENT_LENGTH']) for s in util.filechunkiter(req, limit=length): fp.write(s) try: lock = self.repo.lock() try: if not check_heads(): req.write('0\n') req.write(_('unsynced changes\n')) return fp.seek(0) header = fp.read(6) if not header.startswith("HG"): # old client with uncompressed bundle def generator(f): yield header for chunk in f: yield chunk elif not header.startswith("HG10"): req.write("0\n") req.write(_("unknown bundle version\n")) return elif header == "HG10GZ": def generator(f): zd = zlib.decompressobj() for chunk in f: yield zd.decompress(chunk) elif header == "HG10BZ": def generator(f): zd = bz2.BZ2Decompressor() zd.decompress("BZ") for chunk in f: yield zd.decompress(chunk) elif header == "HG10UN": def generator(f): for chunk in f: yield chunk else: req.write("0\n") req.write(_("unknown bundle compression type\n")) return gen = generator(util.filechunkiter(fp, 4096)) # send addchangegroup output to client old_stdout = sys.stdout sys.stdout = cStringIO.StringIO() try: url = 'remote:%s:%s' % (proto, req.env.get('REMOTE_HOST', '')) try: ret = self.repo.addchangegroup( util.chunkbuffer(gen), 'serve', url) except util.Abort, inst: sys.stdout.write("abort: %s\n" % inst) ret = 0 finally: val = sys.stdout.getvalue() sys.stdout = old_stdout req.write('%d\n' % ret) req.write(val) finally: lock.release() except (OSError, IOError), inst: req.write('0\n') filename = getattr(inst, 'filename', '') # Don't send our filesystem layout to the client if filename.startswith(self.repo.root): filename = filename[len(self.repo.root)+1:] else: filename = '' error = getattr(inst, 'strerror', 'Unknown error') req.write('%s: %s\n' % (error, filename)) finally: fp.close() os.unlink(tempname) def do_stream_out(self, req): req.httphdr("application/mercurial-0.1") streamclone.stream_out(self.repo, req, untrusted=True)