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 |
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'?' |