##// END OF EJS Templates
merge with crew-stable
merge with crew-stable

File last commit:

r7831:b5ed0ab8 default
r7871:cdbe19ab merge default
Show More
hgweb_mod.py
313 lines | 11.9 KiB | text/x-python | PythonLexer
Eric Hopper
Fixing up comment headers for split up code.
r2391 # hgweb/hgweb_mod.py - Web interface for a repository.
Eric Hopper
Final stage of the hgweb split up....
r2356 #
# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
Thomas Arendsen Hein
Updated copyright notices and add "and others" to "hg version"
r4635 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
Eric Hopper
Final stage of the hgweb split up....
r2356 #
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
Dirkjan Ochtman
hgweb: refactor hgweb code
r6393 import os, mimetypes
from mercurial.node import hex, nullid
Matt Mackall
errors: move revlog errors...
r7633 from mercurial import ui, hg, util, hook, error
from mercurial import templater, templatefilters
Dirkjan Ochtman
hgweb: move the diffs() generator into webutil
r7310 from common import get_mtime, style_map, ErrorResponse
Dirkjan Ochtman
hgweb: explicit response status
r5993 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
Dirkjan Ochtman
hgweb: raise ErrorResponses to communicate protocol errors
r6926 from common import HTTP_UNAUTHORIZED, HTTP_METHOD_NOT_ALLOWED
Dirkjan Ochtman
Less indirection in the WSGI web interface. This simplifies some code, and makes it more compliant with WSGI.
r5566 from request import wsgirequest
Dirkjan Ochtman
hgweb: separate out utility functions
r6392 import webcommands, protocol, webutil
Eric Hopper
Final stage of the hgweb split up....
r2356
Dirkjan Ochtman
hgweb: centralize permission checks for protocol commands...
r6779 perms = {
'changegroup': 'pull',
'changegroupsubset': 'pull',
'unbundle': 'push',
'stream_out': 'pull',
}
Eric Hopper
Final stage of the hgweb split up....
r2356 class hgweb(object):
Thomas Arendsen Hein
Backed out changeset b913d3aacddc (see issue971/msg5317)
r6141 def __init__(self, repo, name=None):
Christian Ebert
Use isinstance instead of type == type
r4874 if isinstance(repo, str):
Thomas Arendsen Hein
Backed out changeset b913d3aacddc (see issue971/msg5317)
r6141 parentui = ui.ui(report_untrusted=False, interactive=False)
Dirkjan Ochtman
Prevent WSGI apps from touching sys.stdin by setting ui.interactive to False.
r5289 self.repo = hg.repository(parentui, repo)
Eric Hopper
Final stage of the hgweb split up....
r2356 else:
self.repo = repo
Matt Mackall
hook: redirect stdout to stderr for ssh and http servers
r5833 hook.redirect(True)
Eric Hopper
Final stage of the hgweb split up....
r2356 self.mtime = -1
self.reponame = name
self.archives = 'zip', 'gz', 'bz2'
Frank Kingswood
hgweb: Configurable zebra stripes...
r2666 self.stripecount = 1
Alexis S. L. Carvalho
use untrusted settings in hgweb...
r3555 # 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)
Eric Hopper
Final stage of the hgweb split up....
r2356
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)
Alexis S. L. Carvalho
use untrusted settings in hgweb...
r3555 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)
OHASHI Hideya <ohachige at gmail.com>
Enable to select encoding in hgrc web section...
r4690 self.encoding = self.config("web", "encoding", util._encoding)
Eric Hopper
Final stage of the hgweb split up....
r2356
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591 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
wsgicgi.launch(self)
def __call__(self, env, respond):
req = wsgirequest(env, respond)
Dirkjan Ochtman
hgweb: all protocol functions have become generators...
r6784 return self.run_wsgi(req)
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591
def run_wsgi(self, req):
Dirkjan Ochtman
hgweb: get rid of some nested functions
r5596 self.refresh()
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591
Dirkjan Ochtman
hgweb: protocol requests are processed immediately...
r6777 # process this if it's a protocol request
# protocol bits don't need to create any URLs
# and the clients always use the old URL structure
cmd = req.form.get('cmd', [''])[0]
if cmd and cmd in protocol.__all__:
Dirkjan Ochtman
hgweb: raise ErrorResponses to communicate protocol errors
r6926 try:
if cmd in perms:
Dirkjan Ochtman
hgweb: be sure to drain request data even in early error conditions...
r7180 try:
self.check_perm(req, perms[cmd])
except ErrorResponse, inst:
if cmd == 'unbundle':
req.drain()
raise
Dirkjan Ochtman
hgweb: raise ErrorResponses to communicate protocol errors
r6926 method = getattr(protocol, cmd)
return method(self.repo, req)
except ErrorResponse, inst:
Dirkjan Ochtman
hgweb: pass ErrorResponses directly into req.respond()
r7740 req.respond(inst, protocol.HGTYPE)
Dirkjan Ochtman
hgweb: raise ErrorResponses to communicate protocol errors
r6926 if not inst.message:
return []
return '0\n%s\n' % inst.message,
Dirkjan Ochtman
hgweb: protocol requests are processed immediately...
r6777
Dirkjan Ochtman
hgweb: get rid of some nested functions
r5596 # work with CGI variables to create coherent structure
# use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591
Dirkjan Ochtman
hgweb: get rid of some nested functions
r5596 req.url = req.env['SCRIPT_NAME']
if not req.url.endswith('/'):
req.url += '/'
Christian Ebert
Prefer i in d over d.has_key(i)
r5915 if 'REPO_NAME' in req.env:
Dirkjan Ochtman
hgweb: get rid of some nested functions
r5596 req.url += req.env['REPO_NAME'] + '/'
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591
Dirkjan Ochtman
hgweb: make hgwebdir work in the absence of PATH_INFO...
r6459 if 'PATH_INFO' in req.env:
parts = req.env['PATH_INFO'].strip('/').split('/')
Dirkjan Ochtman
hgweb: get rid of some nested functions
r5596 repo_parts = req.env.get('REPO_NAME', '').split('/')
if parts[:len(repo_parts)] == repo_parts:
parts = parts[len(repo_parts):]
query = '/'.join(parts)
else:
query = req.env['QUERY_STRING'].split('&', 1)[0]
query = query.split(';', 1)[0]
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591
Dirkjan Ochtman
hgweb: get rid of some nested functions
r5596 # translate user-visible url structure to internal structure
args = query.split('/', 2)
if 'cmd' not in req.form and args and args[0]:
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591
cmd = args.pop(0)
style = cmd.rfind('-')
if style != -1:
req.form['style'] = [cmd[:style]]
cmd = cmd[style+1:]
Dirkjan Ochtman
hgweb: get rid of some nested functions
r5596
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591 # avoid accepting e.g. style parameter as command
Dirkjan Ochtman
hgweb: protocol requests are processed immediately...
r6777 if hasattr(webcommands, cmd):
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591 req.form['cmd'] = [cmd]
Dirkjan Ochtman
hgweb: protocol requests are processed immediately...
r6777 else:
cmd = ''
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591
Brendan Cully
hgweb: handle subdirectories within static directory
r7287 if cmd == 'static':
req.form['file'] = ['/'.join(args)]
else:
if args and args[0]:
node = args.pop(0)
req.form['node'] = [node]
if args:
req.form['file'] = args
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591
Brendan Cully
hgweb: handle subdirectories within static directory
r7287 if cmd == 'archive':
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591 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_]
Dirkjan Ochtman
hgweb: separate protocol calls from interface calls (issue996)...
r6149 # process the web interface request
Dirkjan Ochtman
hgweb: split out templater definition
r5599
try:
Dirkjan Ochtman
hgweb: separate protocol calls from interface calls (issue996)...
r6149 tmpl = self.templater(req)
Dirkjan Ochtman
Backed out changeset d2bb66a8a435 (temporary template compatibility)
r6391 ctype = tmpl('mimetype', encoding=self.encoding)
ctype = templater.stringify(ctype)
Dirkjan Ochtman
hgweb: separate protocol calls from interface calls (issue996)...
r6149
Mark Edgington
hgweb: allow static content when deny_read denies access
r7562 # check read permissions non-static content
if cmd != 'static':
self.check_perm(req, None)
Mark Edgington
hgweb: support for deny_read/allow_read options...
r7336
Dirkjan Ochtman
hgweb: separate protocol calls from interface calls (issue996)...
r6149 if cmd == '':
req.form['cmd'] = [tmpl.cache['default']]
cmd = req.form['cmd'][0]
Dirkjan Ochtman
hgweb: fast path for sending raw files
r5890
Dirkjan Ochtman
hgweb: separate protocol calls from interface calls (issue996)...
r6149 if cmd not in webcommands.__all__:
Dirkjan Ochtman
hgweb: better error messages
r6368 msg = 'no such method: %s' % cmd
Dirkjan Ochtman
hgweb: separate protocol calls from interface calls (issue996)...
r6149 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
elif cmd == 'file' and 'raw' in req.form.get('style', []):
self.ctype = ctype
content = webcommands.rawfile(self, req, tmpl)
else:
content = getattr(webcommands, cmd)(self, req, tmpl)
req.respond(HTTP_OK, ctype)
Dirkjan Ochtman
hgweb: fast path for sending raw files
r5890
Brendan Cully
templater: return data in increasing chunk sizes...
r7396 return content
Dirkjan Ochtman
hgweb: explicitly pass around the templater
r5600
Matt Mackall
errors: move revlog errors...
r7633 except error.LookupError, err:
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.respond(HTTP_NOT_FOUND, ctype)
Dirkjan Ochtman
hgweb: fix breakage in python < 2.5 introduced in 2c370f08c486
r6374 msg = str(err)
if 'manifest' not in msg:
Dirkjan Ochtman
hgweb: better error messages
r6368 msg = 'revision not found: %s' % err.name
Brendan Cully
templater: return data in increasing chunk sizes...
r7396 return tmpl('error', error=msg)
Matt Mackall
error: move repo errors...
r7637 except (error.RepoError, error.RevlogError), inst:
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.respond(HTTP_SERVER_ERROR, ctype)
Brendan Cully
templater: return data in increasing chunk sizes...
r7396 return tmpl('error', error=str(inst))
Dirkjan Ochtman
hgweb: explicitly pass around the templater
r5600 except ErrorResponse, inst:
Dirkjan Ochtman
hgweb: pass ErrorResponses directly into req.respond()
r7740 req.respond(inst, ctype)
Brendan Cully
templater: return data in increasing chunk sizes...
r7396 return tmpl('error', error=inst.message)
Dirkjan Ochtman
hgweb: split out templater definition
r5599
def templater(self, req):
Dirkjan Ochtman
hgweb: get rid of some nested functions
r5596 # determine scheme, port and server name
# this is needed to create absolute urls
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591
proto = req.env.get('wsgi.url_scheme')
if proto == 'https':
proto = 'https'
default_port = "443"
else:
proto = 'http'
default_port = "80"
port = req.env["SERVER_PORT"]
port = port != default_port and (":" + port) or ""
urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
staticurl = self.config("web", "staticurl") or req.url + 'static/'
if not staticurl.endswith('/'):
staticurl += '/'
Dirkjan Ochtman
hgweb: get rid of some nested functions
r5596 # some functions for the templater
def header(**map):
Dirkjan Ochtman
Backed out changeset d2bb66a8a435 (temporary template compatibility)
r6391 yield tmpl('header', encoding=self.encoding, **map)
Dirkjan Ochtman
hgweb: get rid of some nested functions
r5596
def footer(**map):
Dirkjan Ochtman
hgweb: explicitly pass around the templater
r5600 yield tmpl("footer", **map)
Dirkjan Ochtman
hgweb: get rid of some nested functions
r5596
def motd(**map):
yield self.config("web", "motd", "")
Dirkjan Ochtman
hgweb: split out templater definition
r5599 # figure out which style to use
Dirkjan Ochtman
hgweb: fix up the less/more links on the graph page...
r7345 vars = {}
Matt Mackall
hgweb: Change default style to paper
r7337 style = self.config("web", "style", "paper")
Christian Ebert
Prefer i in d over d.has_key(i)
r5915 if 'style' in req.form:
Dirkjan Ochtman
hgweb: get rid of some nested functions
r5596 style = req.form['style'][0]
Dirkjan Ochtman
hgweb: fix up the less/more links on the graph page...
r7345 vars['style'] = style
start = req.url[-1] == '?' and '&' or '?'
sessionvars = webutil.sessionvars(vars, start)
Dirkjan Ochtman
hgweb: get rid of some nested functions
r5596 mapfile = style_map(self.templatepath, style)
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591 if not self.reponame:
self.reponame = (self.config("web", "name")
or req.env.get('REPO_NAME')
or req.url.strip('/') or self.repo.root)
Dirkjan Ochtman
hgweb: split out templater definition
r5599 # create the templater
Matt Mackall
templates: move filters to their own module...
r5976 tmpl = templater.templater(mapfile, templatefilters.filters,
Dirkjan Ochtman
hgweb: explicitly pass around the templater
r5600 defaults={"url": req.url,
"staticurl": staticurl,
"urlbase": urlbase,
"repo": self.reponame,
"header": header,
"footer": footer,
"motd": motd,
"sessionvars": sessionvars
})
return tmpl
Dirkjan Ochtman
split out hgweb commands into a separate file, move some code around
r5591
Eric Hopper
Final stage of the hgweb split up....
r2356 def archivelist(self, nodeid):
Alexis S. L. Carvalho
use untrusted settings in hgweb...
r3555 allowed = self.configlist("web", "allow_archive")
Brendan Cully
hgweb: accept NewWebInterface URLs
r3260 for i, spec in self.archive_specs.iteritems():
Alexis S. L. Carvalho
use untrusted settings in hgweb...
r3555 if i in allowed or self.configbool("web", "allow" + i):
Brendan Cully
hgweb: accept NewWebInterface URLs
r3260 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
Eric Hopper
Final stage of the hgweb split up....
r2356
archive_specs = {
Thomas Arendsen Hein
Fix automatic decompression of tarballs with Firefox....
r2361 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
Eric Hopper
Final stage of the hgweb split up....
r2356 'zip': ('application/zip', 'zip', '.zip', None),
}
Dirkjan Ochtman
hgweb: centralize permission checks for protocol commands...
r6779 def check_perm(self, req, op):
'''Check permission for operation based on request data (including
Mark Edgington
hgweb: support for deny_read/allow_read options...
r7336 authentication info). Return if op allowed, else raise an ErrorResponse
exception.'''
user = req.env.get('REMOTE_USER')
deny_read = self.configlist('web', 'deny_read')
if deny_read and (not user or deny_read == ['*'] or user in deny_read):
raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
allow_read = self.configlist('web', 'allow_read')
Dirkjan Ochtman
hgweb: fix long line lengths introduced in 2dc868712dcc
r7575 result = (not allow_read) or (allow_read == ['*'])
Benoit Boissinot
hgweb: fix allow_read permissions when a user is specified...
r7831 if not (result or user in allow_read):
Mark Edgington
hgweb: support for deny_read/allow_read options...
r7336 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
Dirkjan Ochtman
hgweb: centralize permission checks for protocol commands...
r6779
Dirkjan Ochtman
hgweb: raise ErrorResponses to communicate protocol errors
r6926 if op == 'pull' and not self.allowpull:
Mark Edgington
hgweb: send HTTP unauthorized error when denying pull
r7563 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
Dirkjan Ochtman
hgweb: fix long line lengths introduced in 2dc868712dcc
r7575 elif op == 'pull' or op is None: # op is None for interface requests
Dirkjan Ochtman
hgweb: raise ErrorResponses to communicate protocol errors
r6926 return
Dirkjan Ochtman
hgweb: centralize permission checks for protocol commands...
r6779
# enforce that you can only push using POST requests
if req.env['REQUEST_METHOD'] != 'POST':
Dirkjan Ochtman
hgweb: raise ErrorResponses to communicate protocol errors
r6926 msg = 'push requires POST request'
raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
Dirkjan Ochtman
hgweb: centralize permission checks for protocol commands...
r6779
# require ssl by default for pushing, auth info cannot be sniffed
# and replayed
scheme = req.env.get('wsgi.url_scheme')
if self.configbool('web', 'push_ssl', True) and scheme != 'https':
Dirkjan Ochtman
hgweb: raise ErrorResponses to communicate protocol errors
r6926 raise ErrorResponse(HTTP_OK, 'ssl required')
Vadim Gelfer
push over http: server side authorization support....
r2466
Dirkjan Ochtman
hgweb: centralize permission checks for protocol commands...
r6779 deny = self.configlist('web', 'deny_push')
Vadim Gelfer
push over http: server side authorization support....
r2466 if deny and (not user or deny == ['*'] or user in deny):
Dirkjan Ochtman
hgweb: raise ErrorResponses to communicate protocol errors
r6926 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
Vadim Gelfer
push over http: server side authorization support....
r2466
Dirkjan Ochtman
hgweb: centralize permission checks for protocol commands...
r6779 allow = self.configlist('web', 'allow_push')
result = allow and (allow == ['*'] or user in allow)
if not result:
Dirkjan Ochtman
hgweb: raise ErrorResponses to communicate protocol errors
r6926 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')