eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/hgweb/hgweb_mod.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # hgweb/hgweb_mod.py - Web interface for a repository.
       
     2 #
       
     3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
       
     4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
       
     5 #
       
     6 # This software may be used and distributed according to the terms of the
       
     7 # GNU General Public License version 2 or any later version.
       
     8 
       
     9 import os
       
    10 from mercurial import ui, hg, hook, error, encoding, templater
       
    11 from common import get_mtime, ErrorResponse, permhooks, caching
       
    12 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
       
    13 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
       
    14 from request import wsgirequest
       
    15 import webcommands, protocol, webutil
       
    16 
       
    17 perms = {
       
    18     'changegroup': 'pull',
       
    19     'changegroupsubset': 'pull',
       
    20     'stream_out': 'pull',
       
    21     'listkeys': 'pull',
       
    22     'unbundle': 'push',
       
    23     'pushkey': 'push',
       
    24 }
       
    25 
       
    26 class hgweb(object):
       
    27     def __init__(self, repo, name=None, baseui=None):
       
    28         if isinstance(repo, str):
       
    29             if baseui:
       
    30                 u = baseui.copy()
       
    31             else:
       
    32                 u = ui.ui()
       
    33             self.repo = hg.repository(u, repo)
       
    34         else:
       
    35             self.repo = repo
       
    36 
       
    37         self.repo.ui.setconfig('ui', 'report_untrusted', 'off')
       
    38         self.repo.ui.setconfig('ui', 'interactive', 'off')
       
    39         hook.redirect(True)
       
    40         self.mtime = -1
       
    41         self.reponame = name
       
    42         self.archives = 'zip', 'gz', 'bz2'
       
    43         self.stripecount = 1
       
    44         # a repo owner may set web.templates in .hg/hgrc to get any file
       
    45         # readable by the user running the CGI script
       
    46         self.templatepath = self.config('web', 'templates')
       
    47 
       
    48     # The CGI scripts are often run by a user different from the repo owner.
       
    49     # Trust the settings from the .hg/hgrc files by default.
       
    50     def config(self, section, name, default=None, untrusted=True):
       
    51         return self.repo.ui.config(section, name, default,
       
    52                                    untrusted=untrusted)
       
    53 
       
    54     def configbool(self, section, name, default=False, untrusted=True):
       
    55         return self.repo.ui.configbool(section, name, default,
       
    56                                        untrusted=untrusted)
       
    57 
       
    58     def configlist(self, section, name, default=None, untrusted=True):
       
    59         return self.repo.ui.configlist(section, name, default,
       
    60                                        untrusted=untrusted)
       
    61 
       
    62     def refresh(self, request=None):
       
    63         if request:
       
    64             self.repo.ui.environ = request.env
       
    65         mtime = get_mtime(self.repo.spath)
       
    66         if mtime != self.mtime:
       
    67             self.mtime = mtime
       
    68             self.repo = hg.repository(self.repo.ui, self.repo.root)
       
    69             self.maxchanges = int(self.config("web", "maxchanges", 10))
       
    70             self.stripecount = int(self.config("web", "stripes", 1))
       
    71             self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
       
    72             self.maxfiles = int(self.config("web", "maxfiles", 10))
       
    73             self.allowpull = self.configbool("web", "allowpull", True)
       
    74             encoding.encoding = self.config("web", "encoding",
       
    75                                             encoding.encoding)
       
    76 
       
    77     def run(self):
       
    78         if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
       
    79             raise RuntimeError("This function is only intended to be "
       
    80                                "called while running as a CGI script.")
       
    81         import mercurial.hgweb.wsgicgi as wsgicgi
       
    82         wsgicgi.launch(self)
       
    83 
       
    84     def __call__(self, env, respond):
       
    85         req = wsgirequest(env, respond)
       
    86         return self.run_wsgi(req)
       
    87 
       
    88     def run_wsgi(self, req):
       
    89 
       
    90         self.refresh(req)
       
    91 
       
    92         # work with CGI variables to create coherent structure
       
    93         # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
       
    94 
       
    95         req.url = req.env['SCRIPT_NAME']
       
    96         if not req.url.endswith('/'):
       
    97             req.url += '/'
       
    98         if 'REPO_NAME' in req.env:
       
    99             req.url += req.env['REPO_NAME'] + '/'
       
   100 
       
   101         if 'PATH_INFO' in req.env:
       
   102             parts = req.env['PATH_INFO'].strip('/').split('/')
       
   103             repo_parts = req.env.get('REPO_NAME', '').split('/')
       
   104             if parts[:len(repo_parts)] == repo_parts:
       
   105                 parts = parts[len(repo_parts):]
       
   106             query = '/'.join(parts)
       
   107         else:
       
   108             query = req.env['QUERY_STRING'].split('&', 1)[0]
       
   109             query = query.split(';', 1)[0]
       
   110 
       
   111         # process this if it's a protocol request
       
   112         # protocol bits don't need to create any URLs
       
   113         # and the clients always use the old URL structure
       
   114 
       
   115         cmd = req.form.get('cmd', [''])[0]
       
   116         if protocol.iscmd(cmd):
       
   117             if query:
       
   118                 raise ErrorResponse(HTTP_NOT_FOUND)
       
   119             if cmd in perms:
       
   120                 try:
       
   121                     self.check_perm(req, perms[cmd])
       
   122                 except ErrorResponse, inst:
       
   123                     if cmd == 'unbundle':
       
   124                         req.drain()
       
   125                     req.respond(inst, protocol.HGTYPE)
       
   126                     return '0\n%s\n' % inst.message
       
   127             return protocol.call(self.repo, req, cmd)
       
   128 
       
   129         # translate user-visible url structure to internal structure
       
   130 
       
   131         args = query.split('/', 2)
       
   132         if 'cmd' not in req.form and args and args[0]:
       
   133 
       
   134             cmd = args.pop(0)
       
   135             style = cmd.rfind('-')
       
   136             if style != -1:
       
   137                 req.form['style'] = [cmd[:style]]
       
   138                 cmd = cmd[style + 1:]
       
   139 
       
   140             # avoid accepting e.g. style parameter as command
       
   141             if hasattr(webcommands, cmd):
       
   142                 req.form['cmd'] = [cmd]
       
   143             else:
       
   144                 cmd = ''
       
   145 
       
   146             if cmd == 'static':
       
   147                 req.form['file'] = ['/'.join(args)]
       
   148             else:
       
   149                 if args and args[0]:
       
   150                     node = args.pop(0)
       
   151                     req.form['node'] = [node]
       
   152                 if args:
       
   153                     req.form['file'] = args
       
   154 
       
   155             ua = req.env.get('HTTP_USER_AGENT', '')
       
   156             if cmd == 'rev' and 'mercurial' in ua:
       
   157                 req.form['style'] = ['raw']
       
   158 
       
   159             if cmd == 'archive':
       
   160                 fn = req.form['node'][0]
       
   161                 for type_, spec in self.archive_specs.iteritems():
       
   162                     ext = spec[2]
       
   163                     if fn.endswith(ext):
       
   164                         req.form['node'] = [fn[:-len(ext)]]
       
   165                         req.form['type'] = [type_]
       
   166 
       
   167         # process the web interface request
       
   168 
       
   169         try:
       
   170             tmpl = self.templater(req)
       
   171             ctype = tmpl('mimetype', encoding=encoding.encoding)
       
   172             ctype = templater.stringify(ctype)
       
   173 
       
   174             # check read permissions non-static content
       
   175             if cmd != 'static':
       
   176                 self.check_perm(req, None)
       
   177 
       
   178             if cmd == '':
       
   179                 req.form['cmd'] = [tmpl.cache['default']]
       
   180                 cmd = req.form['cmd'][0]
       
   181 
       
   182             caching(self, req) # sets ETag header or raises NOT_MODIFIED
       
   183             if cmd not in webcommands.__all__:
       
   184                 msg = 'no such method: %s' % cmd
       
   185                 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
       
   186             elif cmd == 'file' and 'raw' in req.form.get('style', []):
       
   187                 self.ctype = ctype
       
   188                 content = webcommands.rawfile(self, req, tmpl)
       
   189             else:
       
   190                 content = getattr(webcommands, cmd)(self, req, tmpl)
       
   191                 req.respond(HTTP_OK, ctype)
       
   192 
       
   193             return content
       
   194 
       
   195         except error.LookupError, err:
       
   196             req.respond(HTTP_NOT_FOUND, ctype)
       
   197             msg = str(err)
       
   198             if 'manifest' not in msg:
       
   199                 msg = 'revision not found: %s' % err.name
       
   200             return tmpl('error', error=msg)
       
   201         except (error.RepoError, error.RevlogError), inst:
       
   202             req.respond(HTTP_SERVER_ERROR, ctype)
       
   203             return tmpl('error', error=str(inst))
       
   204         except ErrorResponse, inst:
       
   205             req.respond(inst, ctype)
       
   206             if inst.code == HTTP_NOT_MODIFIED:
       
   207                 # Not allowed to return a body on a 304
       
   208                 return ['']
       
   209             return tmpl('error', error=inst.message)
       
   210 
       
   211     def templater(self, req):
       
   212 
       
   213         # determine scheme, port and server name
       
   214         # this is needed to create absolute urls
       
   215 
       
   216         proto = req.env.get('wsgi.url_scheme')
       
   217         if proto == 'https':
       
   218             proto = 'https'
       
   219             default_port = "443"
       
   220         else:
       
   221             proto = 'http'
       
   222             default_port = "80"
       
   223 
       
   224         port = req.env["SERVER_PORT"]
       
   225         port = port != default_port and (":" + port) or ""
       
   226         urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
       
   227         staticurl = self.config("web", "staticurl") or req.url + 'static/'
       
   228         if not staticurl.endswith('/'):
       
   229             staticurl += '/'
       
   230 
       
   231         # some functions for the templater
       
   232 
       
   233         def header(**map):
       
   234             yield tmpl('header', encoding=encoding.encoding, **map)
       
   235 
       
   236         def footer(**map):
       
   237             yield tmpl("footer", **map)
       
   238 
       
   239         def motd(**map):
       
   240             yield self.config("web", "motd", "")
       
   241 
       
   242         # figure out which style to use
       
   243 
       
   244         vars = {}
       
   245         styles = (
       
   246             req.form.get('style', [None])[0],
       
   247             self.config('web', 'style'),
       
   248             'paper',
       
   249         )
       
   250         style, mapfile = templater.stylemap(styles, self.templatepath)
       
   251         if style == styles[0]:
       
   252             vars['style'] = style
       
   253 
       
   254         start = req.url[-1] == '?' and '&' or '?'
       
   255         sessionvars = webutil.sessionvars(vars, start)
       
   256 
       
   257         if not self.reponame:
       
   258             self.reponame = (self.config("web", "name")
       
   259                              or req.env.get('REPO_NAME')
       
   260                              or req.url.strip('/') or self.repo.root)
       
   261 
       
   262         # create the templater
       
   263 
       
   264         tmpl = templater.templater(mapfile,
       
   265                                    defaults={"url": req.url,
       
   266                                              "staticurl": staticurl,
       
   267                                              "urlbase": urlbase,
       
   268                                              "repo": self.reponame,
       
   269                                              "header": header,
       
   270                                              "footer": footer,
       
   271                                              "motd": motd,
       
   272                                              "sessionvars": sessionvars
       
   273                                             })
       
   274         return tmpl
       
   275 
       
   276     def archivelist(self, nodeid):
       
   277         allowed = self.configlist("web", "allow_archive")
       
   278         for i, spec in self.archive_specs.iteritems():
       
   279             if i in allowed or self.configbool("web", "allow" + i):
       
   280                 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
       
   281 
       
   282     archive_specs = {
       
   283         'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
       
   284         'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
       
   285         'zip': ('application/zip', 'zip', '.zip', None),
       
   286         }
       
   287 
       
   288     def check_perm(self, req, op):
       
   289         for hook in permhooks:
       
   290             hook(self, req, op)