mercurial/hgweb/hgwebdir_mod.py
changeset 36812 b9b968e21f78
parent 36636 c6061cadb400
child 36813 ec46415ed826
equal deleted inserted replaced
36811:8e1556ac01bb 36812:b9b968e21f78
    24     get_mtime,
    24     get_mtime,
    25     ismember,
    25     ismember,
    26     paritygen,
    26     paritygen,
    27     staticfile,
    27     staticfile,
    28 )
    28 )
    29 from .request import wsgirequest
       
    30 
    29 
    31 from .. import (
    30 from .. import (
    32     configitems,
    31     configitems,
    33     encoding,
    32     encoding,
    34     error,
    33     error,
    41     util,
    40     util,
    42 )
    41 )
    43 
    42 
    44 from . import (
    43 from . import (
    45     hgweb_mod,
    44     hgweb_mod,
       
    45     request as requestmod,
    46     webutil,
    46     webutil,
    47     wsgicgi,
    47     wsgicgi,
    48 )
    48 )
    49 from ..utils import dateutil
    49 from ..utils import dateutil
    50 
    50 
   195             raise RuntimeError("This function is only intended to be "
   195             raise RuntimeError("This function is only intended to be "
   196                                "called while running as a CGI script.")
   196                                "called while running as a CGI script.")
   197         wsgicgi.launch(self)
   197         wsgicgi.launch(self)
   198 
   198 
   199     def __call__(self, env, respond):
   199     def __call__(self, env, respond):
   200         req = wsgirequest(env, respond)
   200         wsgireq = requestmod.wsgirequest(env, respond)
   201         return self.run_wsgi(req)
   201         return self.run_wsgi(wsgireq)
   202 
   202 
   203     def read_allowed(self, ui, req):
   203     def read_allowed(self, ui, wsgireq):
   204         """Check allow_read and deny_read config options of a repo's ui object
   204         """Check allow_read and deny_read config options of a repo's ui object
   205         to determine user permissions.  By default, with neither option set (or
   205         to determine user permissions.  By default, with neither option set (or
   206         both empty), allow all users to read the repo.  There are two ways a
   206         both empty), allow all users to read the repo.  There are two ways a
   207         user can be denied read access:  (1) deny_read is not empty, and the
   207         user can be denied read access:  (1) deny_read is not empty, and the
   208         user is unauthenticated or deny_read contains user (or *), and (2)
   208         user is unauthenticated or deny_read contains user (or *), and (2)
   209         allow_read is not empty and the user is not in allow_read.  Return True
   209         allow_read is not empty and the user is not in allow_read.  Return True
   210         if user is allowed to read the repo, else return False."""
   210         if user is allowed to read the repo, else return False."""
   211 
   211 
   212         user = req.env.get('REMOTE_USER')
   212         user = wsgireq.env.get('REMOTE_USER')
   213 
   213 
   214         deny_read = ui.configlist('web', 'deny_read', untrusted=True)
   214         deny_read = ui.configlist('web', 'deny_read', untrusted=True)
   215         if deny_read and (not user or ismember(ui, user, deny_read)):
   215         if deny_read and (not user or ismember(ui, user, deny_read)):
   216             return False
   216             return False
   217 
   217 
   220         if (not allow_read) or ismember(ui, user, allow_read):
   220         if (not allow_read) or ismember(ui, user, allow_read):
   221             return True
   221             return True
   222 
   222 
   223         return False
   223         return False
   224 
   224 
   225     def run_wsgi(self, req):
   225     def run_wsgi(self, wsgireq):
   226         profile = self.ui.configbool('profiling', 'enabled')
   226         profile = self.ui.configbool('profiling', 'enabled')
   227         with profiling.profile(self.ui, enabled=profile):
   227         with profiling.profile(self.ui, enabled=profile):
   228             for r in self._runwsgi(req):
   228             for r in self._runwsgi(wsgireq):
   229                 yield r
   229                 yield r
   230 
   230 
   231     def _runwsgi(self, req):
   231     def _runwsgi(self, wsgireq):
   232         try:
   232         try:
   233             self.refresh()
   233             self.refresh()
   234 
   234 
   235             csp, nonce = cspvalues(self.ui)
   235             csp, nonce = cspvalues(self.ui)
   236             if csp:
   236             if csp:
   237                 req.headers.append(('Content-Security-Policy', csp))
   237                 wsgireq.headers.append(('Content-Security-Policy', csp))
   238 
   238 
   239             virtual = req.env.get("PATH_INFO", "").strip('/')
   239             virtual = wsgireq.env.get("PATH_INFO", "").strip('/')
   240             tmpl = self.templater(req, nonce)
   240             tmpl = self.templater(wsgireq, nonce)
   241             ctype = tmpl('mimetype', encoding=encoding.encoding)
   241             ctype = tmpl('mimetype', encoding=encoding.encoding)
   242             ctype = templater.stringify(ctype)
   242             ctype = templater.stringify(ctype)
   243 
   243 
   244             # a static file
   244             # a static file
   245             if virtual.startswith('static/') or 'static' in req.form:
   245             if virtual.startswith('static/') or 'static' in wsgireq.form:
   246                 if virtual.startswith('static/'):
   246                 if virtual.startswith('static/'):
   247                     fname = virtual[7:]
   247                     fname = virtual[7:]
   248                 else:
   248                 else:
   249                     fname = req.form['static'][0]
   249                     fname = wsgireq.form['static'][0]
   250                 static = self.ui.config("web", "static", None,
   250                 static = self.ui.config("web", "static", None,
   251                                         untrusted=False)
   251                                         untrusted=False)
   252                 if not static:
   252                 if not static:
   253                     tp = self.templatepath or templater.templatepaths()
   253                     tp = self.templatepath or templater.templatepaths()
   254                     if isinstance(tp, str):
   254                     if isinstance(tp, str):
   255                         tp = [tp]
   255                         tp = [tp]
   256                     static = [os.path.join(p, 'static') for p in tp]
   256                     static = [os.path.join(p, 'static') for p in tp]
   257                 staticfile(static, fname, req)
   257                 staticfile(static, fname, wsgireq)
   258                 return []
   258                 return []
   259 
   259 
   260             # top-level index
   260             # top-level index
   261 
   261 
   262             repos = dict(self.repos)
   262             repos = dict(self.repos)
   263 
   263 
   264             if (not virtual or virtual == 'index') and virtual not in repos:
   264             if (not virtual or virtual == 'index') and virtual not in repos:
   265                 req.respond(HTTP_OK, ctype)
   265                 wsgireq.respond(HTTP_OK, ctype)
   266                 return self.makeindex(req, tmpl)
   266                 return self.makeindex(wsgireq, tmpl)
   267 
   267 
   268             # nested indexes and hgwebs
   268             # nested indexes and hgwebs
   269 
   269 
   270             if virtual.endswith('/index') and virtual not in repos:
   270             if virtual.endswith('/index') and virtual not in repos:
   271                 subdir = virtual[:-len('index')]
   271                 subdir = virtual[:-len('index')]
   272                 if any(r.startswith(subdir) for r in repos):
   272                 if any(r.startswith(subdir) for r in repos):
   273                     req.respond(HTTP_OK, ctype)
   273                     wsgireq.respond(HTTP_OK, ctype)
   274                     return self.makeindex(req, tmpl, subdir)
   274                     return self.makeindex(wsgireq, tmpl, subdir)
   275 
   275 
   276             def _virtualdirs():
   276             def _virtualdirs():
   277                 # Check the full virtual path, each parent, and the root ('')
   277                 # Check the full virtual path, each parent, and the root ('')
   278                 if virtual != '':
   278                 if virtual != '':
   279                     yield virtual
   279                     yield virtual
   284                 yield ''
   284                 yield ''
   285 
   285 
   286             for virtualrepo in _virtualdirs():
   286             for virtualrepo in _virtualdirs():
   287                 real = repos.get(virtualrepo)
   287                 real = repos.get(virtualrepo)
   288                 if real:
   288                 if real:
   289                     req.env['REPO_NAME'] = virtualrepo
   289                     wsgireq.env['REPO_NAME'] = virtualrepo
   290                     try:
   290                     try:
   291                         # ensure caller gets private copy of ui
   291                         # ensure caller gets private copy of ui
   292                         repo = hg.repository(self.ui.copy(), real)
   292                         repo = hg.repository(self.ui.copy(), real)
   293                         return hgweb_mod.hgweb(repo).run_wsgi(req)
   293                         return hgweb_mod.hgweb(repo).run_wsgi(wsgireq)
   294                     except IOError as inst:
   294                     except IOError as inst:
   295                         msg = encoding.strtolocal(inst.strerror)
   295                         msg = encoding.strtolocal(inst.strerror)
   296                         raise ErrorResponse(HTTP_SERVER_ERROR, msg)
   296                         raise ErrorResponse(HTTP_SERVER_ERROR, msg)
   297                     except error.RepoError as inst:
   297                     except error.RepoError as inst:
   298                         raise ErrorResponse(HTTP_SERVER_ERROR, bytes(inst))
   298                         raise ErrorResponse(HTTP_SERVER_ERROR, bytes(inst))
   299 
   299 
   300             # browse subdirectories
   300             # browse subdirectories
   301             subdir = virtual + '/'
   301             subdir = virtual + '/'
   302             if [r for r in repos if r.startswith(subdir)]:
   302             if [r for r in repos if r.startswith(subdir)]:
   303                 req.respond(HTTP_OK, ctype)
   303                 wsgireq.respond(HTTP_OK, ctype)
   304                 return self.makeindex(req, tmpl, subdir)
   304                 return self.makeindex(wsgireq, tmpl, subdir)
   305 
   305 
   306             # prefixes not found
   306             # prefixes not found
   307             req.respond(HTTP_NOT_FOUND, ctype)
   307             wsgireq.respond(HTTP_NOT_FOUND, ctype)
   308             return tmpl("notfound", repo=virtual)
   308             return tmpl("notfound", repo=virtual)
   309 
   309 
   310         except ErrorResponse as err:
   310         except ErrorResponse as err:
   311             req.respond(err, ctype)
   311             wsgireq.respond(err, ctype)
   312             return tmpl('error', error=err.message or '')
   312             return tmpl('error', error=err.message or '')
   313         finally:
   313         finally:
   314             tmpl = None
   314             tmpl = None
   315 
   315 
   316     def makeindex(self, req, tmpl, subdir=""):
   316     def makeindex(self, wsgireq, tmpl, subdir=""):
   317 
   317 
   318         def archivelist(ui, nodeid, url):
   318         def archivelist(ui, nodeid, url):
   319             allowed = ui.configlist("web", "allow_archive", untrusted=True)
   319             allowed = ui.configlist("web", "allow_archive", untrusted=True)
   320             archives = []
   320             archives = []
   321             for typ, spec in hgweb_mod.archivespecs.iteritems():
   321             for typ, spec in hgweb_mod.archivespecs.iteritems():
   367                         except (IOError, error.RepoError):
   367                         except (IOError, error.RepoError):
   368                             pass
   368                             pass
   369 
   369 
   370                 parts = [name]
   370                 parts = [name]
   371                 parts.insert(0, '/' + subdir.rstrip('/'))
   371                 parts.insert(0, '/' + subdir.rstrip('/'))
   372                 if req.env['SCRIPT_NAME']:
   372                 if wsgireq.env['SCRIPT_NAME']:
   373                     parts.insert(0, req.env['SCRIPT_NAME'])
   373                     parts.insert(0, wsgireq.env['SCRIPT_NAME'])
   374                 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
   374                 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
   375 
   375 
   376                 # show either a directory entry or a repository
   376                 # show either a directory entry or a repository
   377                 if directory:
   377                 if directory:
   378                     # get the directory's time information
   378                     # get the directory's time information
   411                     return u.config(section, name, default, untrusted=True)
   411                     return u.config(section, name, default, untrusted=True)
   412 
   412 
   413                 if u.configbool("web", "hidden", untrusted=True):
   413                 if u.configbool("web", "hidden", untrusted=True):
   414                     continue
   414                     continue
   415 
   415 
   416                 if not self.read_allowed(u, req):
   416                 if not self.read_allowed(u, wsgireq):
   417                     continue
   417                     continue
   418 
   418 
   419                 # update time with local timezone
   419                 # update time with local timezone
   420                 try:
   420                 try:
   421                     r = hg.repository(self.ui, path)
   421                     r = hg.repository(self.ui, path)
   463                 yield row
   463                 yield row
   464 
   464 
   465         self.refresh()
   465         self.refresh()
   466         sortable = ["name", "description", "contact", "lastchange"]
   466         sortable = ["name", "description", "contact", "lastchange"]
   467         sortcolumn, descending = sortdefault
   467         sortcolumn, descending = sortdefault
   468         if 'sort' in req.form:
   468         if 'sort' in wsgireq.form:
   469             sortcolumn = req.form['sort'][0]
   469             sortcolumn = wsgireq.form['sort'][0]
   470             descending = sortcolumn.startswith('-')
   470             descending = sortcolumn.startswith('-')
   471             if descending:
   471             if descending:
   472                 sortcolumn = sortcolumn[1:]
   472                 sortcolumn = sortcolumn[1:]
   473             if sortcolumn not in sortable:
   473             if sortcolumn not in sortable:
   474                 sortcolumn = ""
   474                 sortcolumn = ""
   477                  "%s%s" % ((not descending and column == sortcolumn)
   477                  "%s%s" % ((not descending and column == sortcolumn)
   478                             and "-" or "", column))
   478                             and "-" or "", column))
   479                 for column in sortable]
   479                 for column in sortable]
   480 
   480 
   481         self.refresh()
   481         self.refresh()
   482         self.updatereqenv(req.env)
   482         self.updatereqenv(wsgireq.env)
   483 
   483 
   484         return tmpl("index", entries=entries, subdir=subdir,
   484         return tmpl("index", entries=entries, subdir=subdir,
   485                     pathdef=hgweb_mod.makebreadcrumb('/' + subdir, self.prefix),
   485                     pathdef=hgweb_mod.makebreadcrumb('/' + subdir, self.prefix),
   486                     sortcolumn=sortcolumn, descending=descending,
   486                     sortcolumn=sortcolumn, descending=descending,
   487                     **dict(sort))
   487                     **dict(sort))
   488 
   488 
   489     def templater(self, req, nonce):
   489     def templater(self, wsgireq, nonce):
   490 
   490 
   491         def motd(**map):
   491         def motd(**map):
   492             if self.motd is not None:
   492             if self.motd is not None:
   493                 yield self.motd
   493                 yield self.motd
   494             else:
   494             else:
   495                 yield config('web', 'motd')
   495                 yield config('web', 'motd')
   496 
   496 
   497         def config(section, name, default=uimod._unset, untrusted=True):
   497         def config(section, name, default=uimod._unset, untrusted=True):
   498             return self.ui.config(section, name, default, untrusted)
   498             return self.ui.config(section, name, default, untrusted)
   499 
   499 
   500         self.updatereqenv(req.env)
   500         self.updatereqenv(wsgireq.env)
   501 
   501 
   502         url = req.env.get('SCRIPT_NAME', '')
   502         url = wsgireq.env.get('SCRIPT_NAME', '')
   503         if not url.endswith('/'):
   503         if not url.endswith('/'):
   504             url += '/'
   504             url += '/'
   505 
   505 
   506         vars = {}
   506         vars = {}
   507         styles, (style, mapfile) = hgweb_mod.getstyle(req, config,
   507         styles, (style, mapfile) = hgweb_mod.getstyle(wsgireq, config,
   508                                                       self.templatepath)
   508                                                       self.templatepath)
   509         if style == styles[0]:
   509         if style == styles[0]:
   510             vars['style'] = style
   510             vars['style'] = style
   511 
   511 
   512         start = r'&' if url[-1] == r'?' else r'?'
   512         start = r'&' if url[-1] == r'?' else r'?'