##// END OF EJS Templates
hgweb: rename req to wsgireq...
Gregory Szorc -
r36822:b9b968e2 default
parent child Browse files
Show More
@@ -22,7 +22,6 b' from .common import ('
22 cspvalues,
22 cspvalues,
23 permhooks,
23 permhooks,
24 )
24 )
25 from .request import wsgirequest
26
25
27 from .. import (
26 from .. import (
28 encoding,
27 encoding,
@@ -41,6 +40,7 b' from .. import ('
41 )
40 )
42
41
43 from . import (
42 from . import (
43 request as requestmod,
44 webcommands,
44 webcommands,
45 webutil,
45 webutil,
46 wsgicgi,
46 wsgicgi,
@@ -142,11 +142,11 b' class requestcontext(object):'
142 if typ in allowed or self.configbool('web', 'allow%s' % typ):
142 if typ in allowed or self.configbool('web', 'allow%s' % typ):
143 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
143 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
144
144
145 def templater(self, req):
145 def templater(self, wsgireq):
146 # determine scheme, port and server name
146 # determine scheme, port and server name
147 # this is needed to create absolute urls
147 # this is needed to create absolute urls
148
148
149 proto = req.env.get('wsgi.url_scheme')
149 proto = wsgireq.env.get('wsgi.url_scheme')
150 if proto == 'https':
150 if proto == 'https':
151 proto = 'https'
151 proto = 'https'
152 default_port = '443'
152 default_port = '443'
@@ -154,13 +154,13 b' class requestcontext(object):'
154 proto = 'http'
154 proto = 'http'
155 default_port = '80'
155 default_port = '80'
156
156
157 port = req.env[r'SERVER_PORT']
157 port = wsgireq.env[r'SERVER_PORT']
158 port = port != default_port and (r':' + port) or r''
158 port = port != default_port and (r':' + port) or r''
159 urlbase = r'%s://%s%s' % (proto, req.env[r'SERVER_NAME'], port)
159 urlbase = r'%s://%s%s' % (proto, wsgireq.env[r'SERVER_NAME'], port)
160 logourl = self.config('web', 'logourl')
160 logourl = self.config('web', 'logourl')
161 logoimg = self.config('web', 'logoimg')
161 logoimg = self.config('web', 'logoimg')
162 staticurl = (self.config('web', 'staticurl')
162 staticurl = (self.config('web', 'staticurl')
163 or pycompat.sysbytes(req.url) + 'static/')
163 or pycompat.sysbytes(wsgireq.url) + 'static/')
164 if not staticurl.endswith('/'):
164 if not staticurl.endswith('/'):
165 staticurl += '/'
165 staticurl += '/'
166
166
@@ -172,18 +172,18 b' class requestcontext(object):'
172 # figure out which style to use
172 # figure out which style to use
173
173
174 vars = {}
174 vars = {}
175 styles, (style, mapfile) = getstyle(req, self.config,
175 styles, (style, mapfile) = getstyle(wsgireq, self.config,
176 self.templatepath)
176 self.templatepath)
177 if style == styles[0]:
177 if style == styles[0]:
178 vars['style'] = style
178 vars['style'] = style
179
179
180 start = '&' if req.url[-1] == r'?' else '?'
180 start = '&' if wsgireq.url[-1] == r'?' else '?'
181 sessionvars = webutil.sessionvars(vars, start)
181 sessionvars = webutil.sessionvars(vars, start)
182
182
183 if not self.reponame:
183 if not self.reponame:
184 self.reponame = (self.config('web', 'name', '')
184 self.reponame = (self.config('web', 'name', '')
185 or req.env.get('REPO_NAME')
185 or wsgireq.env.get('REPO_NAME')
186 or req.url.strip(r'/') or self.repo.root)
186 or wsgireq.url.strip(r'/') or self.repo.root)
187
187
188 def websubfilter(text):
188 def websubfilter(text):
189 return templatefilters.websub(text, self.websubtable)
189 return templatefilters.websub(text, self.websubtable)
@@ -191,7 +191,7 b' class requestcontext(object):'
191 # create the templater
191 # create the templater
192 # TODO: export all keywords: defaults = templatekw.keywords.copy()
192 # TODO: export all keywords: defaults = templatekw.keywords.copy()
193 defaults = {
193 defaults = {
194 'url': pycompat.sysbytes(req.url),
194 'url': pycompat.sysbytes(wsgireq.url),
195 'logourl': logourl,
195 'logourl': logourl,
196 'logoimg': logoimg,
196 'logoimg': logoimg,
197 'staticurl': staticurl,
197 'staticurl': staticurl,
@@ -200,7 +200,7 b' class requestcontext(object):'
200 'encoding': encoding.encoding,
200 'encoding': encoding.encoding,
201 'motd': motd,
201 'motd': motd,
202 'sessionvars': sessionvars,
202 'sessionvars': sessionvars,
203 'pathdef': makebreadcrumb(pycompat.sysbytes(req.url)),
203 'pathdef': makebreadcrumb(pycompat.sysbytes(wsgireq.url)),
204 'style': style,
204 'style': style,
205 'nonce': self.nonce,
205 'nonce': self.nonce,
206 }
206 }
@@ -301,10 +301,10 b' class hgweb(object):'
301
301
302 This may be called by multiple threads.
302 This may be called by multiple threads.
303 """
303 """
304 req = wsgirequest(env, respond)
304 req = requestmod.wsgirequest(env, respond)
305 return self.run_wsgi(req)
305 return self.run_wsgi(req)
306
306
307 def run_wsgi(self, req):
307 def run_wsgi(self, wsgireq):
308 """Internal method to run the WSGI application.
308 """Internal method to run the WSGI application.
309
309
310 This is typically only called by Mercurial. External consumers
310 This is typically only called by Mercurial. External consumers
@@ -313,45 +313,45 b' class hgweb(object):'
313 with self._obtainrepo() as repo:
313 with self._obtainrepo() as repo:
314 profile = repo.ui.configbool('profiling', 'enabled')
314 profile = repo.ui.configbool('profiling', 'enabled')
315 with profiling.profile(repo.ui, enabled=profile):
315 with profiling.profile(repo.ui, enabled=profile):
316 for r in self._runwsgi(req, repo):
316 for r in self._runwsgi(wsgireq, repo):
317 yield r
317 yield r
318
318
319 def _runwsgi(self, req, repo):
319 def _runwsgi(self, wsgireq, repo):
320 rctx = requestcontext(self, repo)
320 rctx = requestcontext(self, repo)
321
321
322 # This state is global across all threads.
322 # This state is global across all threads.
323 encoding.encoding = rctx.config('web', 'encoding')
323 encoding.encoding = rctx.config('web', 'encoding')
324 rctx.repo.ui.environ = req.env
324 rctx.repo.ui.environ = wsgireq.env
325
325
326 if rctx.csp:
326 if rctx.csp:
327 # hgwebdir may have added CSP header. Since we generate our own,
327 # hgwebdir may have added CSP header. Since we generate our own,
328 # replace it.
328 # replace it.
329 req.headers = [h for h in req.headers
329 wsgireq.headers = [h for h in wsgireq.headers
330 if h[0] != 'Content-Security-Policy']
330 if h[0] != 'Content-Security-Policy']
331 req.headers.append(('Content-Security-Policy', rctx.csp))
331 wsgireq.headers.append(('Content-Security-Policy', rctx.csp))
332
332
333 # work with CGI variables to create coherent structure
333 # work with CGI variables to create coherent structure
334 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
334 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
335
335
336 req.url = req.env[r'SCRIPT_NAME']
336 wsgireq.url = wsgireq.env[r'SCRIPT_NAME']
337 if not req.url.endswith(r'/'):
337 if not wsgireq.url.endswith(r'/'):
338 req.url += r'/'
338 wsgireq.url += r'/'
339 if req.env.get('REPO_NAME'):
339 if wsgireq.env.get('REPO_NAME'):
340 req.url += req.env[r'REPO_NAME'] + r'/'
340 wsgireq.url += wsgireq.env[r'REPO_NAME'] + r'/'
341
341
342 if r'PATH_INFO' in req.env:
342 if r'PATH_INFO' in wsgireq.env:
343 parts = req.env[r'PATH_INFO'].strip(r'/').split(r'/')
343 parts = wsgireq.env[r'PATH_INFO'].strip(r'/').split(r'/')
344 repo_parts = req.env.get(r'REPO_NAME', r'').split(r'/')
344 repo_parts = wsgireq.env.get(r'REPO_NAME', r'').split(r'/')
345 if parts[:len(repo_parts)] == repo_parts:
345 if parts[:len(repo_parts)] == repo_parts:
346 parts = parts[len(repo_parts):]
346 parts = parts[len(repo_parts):]
347 query = r'/'.join(parts)
347 query = r'/'.join(parts)
348 else:
348 else:
349 query = req.env[r'QUERY_STRING'].partition(r'&')[0]
349 query = wsgireq.env[r'QUERY_STRING'].partition(r'&')[0]
350 query = query.partition(r';')[0]
350 query = query.partition(r';')[0]
351
351
352 # Route it to a wire protocol handler if it looks like a wire protocol
352 # Route it to a wire protocol handler if it looks like a wire protocol
353 # request.
353 # request.
354 protohandler = wireprotoserver.parsehttprequest(rctx, req, query,
354 protohandler = wireprotoserver.parsehttprequest(rctx, wsgireq, query,
355 self.check_perm)
355 self.check_perm)
356
356
357 if protohandler:
357 if protohandler:
@@ -366,83 +366,83 b' class hgweb(object):'
366 # translate user-visible url structure to internal structure
366 # translate user-visible url structure to internal structure
367
367
368 args = query.split(r'/', 2)
368 args = query.split(r'/', 2)
369 if 'cmd' not in req.form and args and args[0]:
369 if 'cmd' not in wsgireq.form and args and args[0]:
370 cmd = args.pop(0)
370 cmd = args.pop(0)
371 style = cmd.rfind('-')
371 style = cmd.rfind('-')
372 if style != -1:
372 if style != -1:
373 req.form['style'] = [cmd[:style]]
373 wsgireq.form['style'] = [cmd[:style]]
374 cmd = cmd[style + 1:]
374 cmd = cmd[style + 1:]
375
375
376 # avoid accepting e.g. style parameter as command
376 # avoid accepting e.g. style parameter as command
377 if util.safehasattr(webcommands, cmd):
377 if util.safehasattr(webcommands, cmd):
378 req.form['cmd'] = [cmd]
378 wsgireq.form['cmd'] = [cmd]
379
379
380 if cmd == 'static':
380 if cmd == 'static':
381 req.form['file'] = ['/'.join(args)]
381 wsgireq.form['file'] = ['/'.join(args)]
382 else:
382 else:
383 if args and args[0]:
383 if args and args[0]:
384 node = args.pop(0).replace('%2F', '/')
384 node = args.pop(0).replace('%2F', '/')
385 req.form['node'] = [node]
385 wsgireq.form['node'] = [node]
386 if args:
386 if args:
387 req.form['file'] = args
387 wsgireq.form['file'] = args
388
388
389 ua = req.env.get('HTTP_USER_AGENT', '')
389 ua = wsgireq.env.get('HTTP_USER_AGENT', '')
390 if cmd == 'rev' and 'mercurial' in ua:
390 if cmd == 'rev' and 'mercurial' in ua:
391 req.form['style'] = ['raw']
391 wsgireq.form['style'] = ['raw']
392
392
393 if cmd == 'archive':
393 if cmd == 'archive':
394 fn = req.form['node'][0]
394 fn = wsgireq.form['node'][0]
395 for type_, spec in rctx.archivespecs.iteritems():
395 for type_, spec in rctx.archivespecs.iteritems():
396 ext = spec[2]
396 ext = spec[2]
397 if fn.endswith(ext):
397 if fn.endswith(ext):
398 req.form['node'] = [fn[:-len(ext)]]
398 wsgireq.form['node'] = [fn[:-len(ext)]]
399 req.form['type'] = [type_]
399 wsgireq.form['type'] = [type_]
400 else:
400 else:
401 cmd = req.form.get('cmd', [''])[0]
401 cmd = wsgireq.form.get('cmd', [''])[0]
402
402
403 # process the web interface request
403 # process the web interface request
404
404
405 try:
405 try:
406 tmpl = rctx.templater(req)
406 tmpl = rctx.templater(wsgireq)
407 ctype = tmpl('mimetype', encoding=encoding.encoding)
407 ctype = tmpl('mimetype', encoding=encoding.encoding)
408 ctype = templater.stringify(ctype)
408 ctype = templater.stringify(ctype)
409
409
410 # check read permissions non-static content
410 # check read permissions non-static content
411 if cmd != 'static':
411 if cmd != 'static':
412 self.check_perm(rctx, req, None)
412 self.check_perm(rctx, wsgireq, None)
413
413
414 if cmd == '':
414 if cmd == '':
415 req.form['cmd'] = [tmpl.cache['default']]
415 wsgireq.form['cmd'] = [tmpl.cache['default']]
416 cmd = req.form['cmd'][0]
416 cmd = wsgireq.form['cmd'][0]
417
417
418 # Don't enable caching if using a CSP nonce because then it wouldn't
418 # Don't enable caching if using a CSP nonce because then it wouldn't
419 # be a nonce.
419 # be a nonce.
420 if rctx.configbool('web', 'cache') and not rctx.nonce:
420 if rctx.configbool('web', 'cache') and not rctx.nonce:
421 caching(self, req) # sets ETag header or raises NOT_MODIFIED
421 caching(self, wsgireq) # sets ETag header or raises NOT_MODIFIED
422 if cmd not in webcommands.__all__:
422 if cmd not in webcommands.__all__:
423 msg = 'no such method: %s' % cmd
423 msg = 'no such method: %s' % cmd
424 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
424 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
425 elif cmd == 'file' and 'raw' in req.form.get('style', []):
425 elif cmd == 'file' and 'raw' in wsgireq.form.get('style', []):
426 rctx.ctype = ctype
426 rctx.ctype = ctype
427 content = webcommands.rawfile(rctx, req, tmpl)
427 content = webcommands.rawfile(rctx, wsgireq, tmpl)
428 else:
428 else:
429 content = getattr(webcommands, cmd)(rctx, req, tmpl)
429 content = getattr(webcommands, cmd)(rctx, wsgireq, tmpl)
430 req.respond(HTTP_OK, ctype)
430 wsgireq.respond(HTTP_OK, ctype)
431
431
432 return content
432 return content
433
433
434 except (error.LookupError, error.RepoLookupError) as err:
434 except (error.LookupError, error.RepoLookupError) as err:
435 req.respond(HTTP_NOT_FOUND, ctype)
435 wsgireq.respond(HTTP_NOT_FOUND, ctype)
436 msg = pycompat.bytestr(err)
436 msg = pycompat.bytestr(err)
437 if (util.safehasattr(err, 'name') and
437 if (util.safehasattr(err, 'name') and
438 not isinstance(err, error.ManifestLookupError)):
438 not isinstance(err, error.ManifestLookupError)):
439 msg = 'revision not found: %s' % err.name
439 msg = 'revision not found: %s' % err.name
440 return tmpl('error', error=msg)
440 return tmpl('error', error=msg)
441 except (error.RepoError, error.RevlogError) as inst:
441 except (error.RepoError, error.RevlogError) as inst:
442 req.respond(HTTP_SERVER_ERROR, ctype)
442 wsgireq.respond(HTTP_SERVER_ERROR, ctype)
443 return tmpl('error', error=pycompat.bytestr(inst))
443 return tmpl('error', error=pycompat.bytestr(inst))
444 except ErrorResponse as inst:
444 except ErrorResponse as inst:
445 req.respond(inst, ctype)
445 wsgireq.respond(inst, ctype)
446 if inst.code == HTTP_NOT_MODIFIED:
446 if inst.code == HTTP_NOT_MODIFIED:
447 # Not allowed to return a body on a 304
447 # Not allowed to return a body on a 304
448 return ['']
448 return ['']
@@ -26,7 +26,6 b' from .common import ('
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,
@@ -43,6 +42,7 b' from .. import ('
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 )
@@ -197,10 +197,10 b' class hgwebdir(object):'
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
@@ -209,7 +209,7 b' class hgwebdir(object):'
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)):
@@ -222,31 +222,31 b' class hgwebdir(object):'
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:
@@ -254,7 +254,7 b' class hgwebdir(object):'
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
@@ -262,16 +262,16 b' class hgwebdir(object):'
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 ('')
@@ -286,11 +286,11 b' class hgwebdir(object):'
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)
@@ -300,20 +300,20 b' class hgwebdir(object):'
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)
@@ -369,8 +369,8 b' class hgwebdir(object):'
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
@@ -413,7 +413,7 b' class hgwebdir(object):'
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
@@ -465,8 +465,8 b' class hgwebdir(object):'
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:]
@@ -479,14 +479,14 b' class hgwebdir(object):'
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:
@@ -497,14 +497,14 b' class hgwebdir(object):'
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
@@ -36,7 +36,7 b" HGERRTYPE = 'application/hg-error'"
36 SSHV1 = wireprototypes.SSHV1
36 SSHV1 = wireprototypes.SSHV1
37 SSHV2 = wireprototypes.SSHV2
37 SSHV2 = wireprototypes.SSHV2
38
38
39 def decodevaluefromheaders(req, headerprefix):
39 def decodevaluefromheaders(wsgireq, headerprefix):
40 """Decode a long value from multiple HTTP request headers.
40 """Decode a long value from multiple HTTP request headers.
41
41
42 Returns the value as a bytes, not a str.
42 Returns the value as a bytes, not a str.
@@ -45,7 +45,7 b' def decodevaluefromheaders(req, headerpr'
45 i = 1
45 i = 1
46 prefix = headerprefix.upper().replace(r'-', r'_')
46 prefix = headerprefix.upper().replace(r'-', r'_')
47 while True:
47 while True:
48 v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
48 v = wsgireq.env.get(r'HTTP_%s_%d' % (prefix, i))
49 if v is None:
49 if v is None:
50 break
50 break
51 chunks.append(pycompat.bytesurl(v))
51 chunks.append(pycompat.bytesurl(v))
@@ -54,8 +54,8 b' def decodevaluefromheaders(req, headerpr'
54 return ''.join(chunks)
54 return ''.join(chunks)
55
55
56 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
56 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
57 def __init__(self, req, ui, checkperm):
57 def __init__(self, wsgireq, ui, checkperm):
58 self._req = req
58 self._wsgireq = wsgireq
59 self._ui = ui
59 self._ui = ui
60 self._checkperm = checkperm
60 self._checkperm = checkperm
61
61
@@ -79,26 +79,26 b' class httpv1protocolhandler(wireprototyp'
79 return [data[k] for k in keys]
79 return [data[k] for k in keys]
80
80
81 def _args(self):
81 def _args(self):
82 args = util.rapply(pycompat.bytesurl, self._req.form.copy())
82 args = util.rapply(pycompat.bytesurl, self._wsgireq.form.copy())
83 postlen = int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
83 postlen = int(self._wsgireq.env.get(r'HTTP_X_HGARGS_POST', 0))
84 if postlen:
84 if postlen:
85 args.update(urlreq.parseqs(
85 args.update(urlreq.parseqs(
86 self._req.read(postlen), keep_blank_values=True))
86 self._wsgireq.read(postlen), keep_blank_values=True))
87 return args
87 return args
88
88
89 argvalue = decodevaluefromheaders(self._req, r'X-HgArg')
89 argvalue = decodevaluefromheaders(self._wsgireq, r'X-HgArg')
90 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
90 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
91 return args
91 return args
92
92
93 def forwardpayload(self, fp):
93 def forwardpayload(self, fp):
94 if r'HTTP_CONTENT_LENGTH' in self._req.env:
94 if r'HTTP_CONTENT_LENGTH' in self._wsgireq.env:
95 length = int(self._req.env[r'HTTP_CONTENT_LENGTH'])
95 length = int(self._wsgireq.env[r'HTTP_CONTENT_LENGTH'])
96 else:
96 else:
97 length = int(self._req.env[r'CONTENT_LENGTH'])
97 length = int(self._wsgireq.env[r'CONTENT_LENGTH'])
98 # If httppostargs is used, we need to read Content-Length
98 # If httppostargs is used, we need to read Content-Length
99 # minus the amount that was consumed by args.
99 # minus the amount that was consumed by args.
100 length -= int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
100 length -= int(self._wsgireq.env.get(r'HTTP_X_HGARGS_POST', 0))
101 for s in util.filechunkiter(self._req, limit=length):
101 for s in util.filechunkiter(self._wsgireq, limit=length):
102 fp.write(s)
102 fp.write(s)
103
103
104 @contextlib.contextmanager
104 @contextlib.contextmanager
@@ -118,9 +118,9 b' class httpv1protocolhandler(wireprototyp'
118
118
119 def client(self):
119 def client(self):
120 return 'remote:%s:%s:%s' % (
120 return 'remote:%s:%s:%s' % (
121 self._req.env.get('wsgi.url_scheme') or 'http',
121 self._wsgireq.env.get('wsgi.url_scheme') or 'http',
122 urlreq.quote(self._req.env.get('REMOTE_HOST', '')),
122 urlreq.quote(self._wsgireq.env.get('REMOTE_HOST', '')),
123 urlreq.quote(self._req.env.get('REMOTE_USER', '')))
123 urlreq.quote(self._wsgireq.env.get('REMOTE_USER', '')))
124
124
125 def addcapabilities(self, repo, caps):
125 def addcapabilities(self, repo, caps):
126 caps.append('httpheader=%d' %
126 caps.append('httpheader=%d' %
@@ -150,7 +150,7 b' class httpv1protocolhandler(wireprototyp'
150 def iscmd(cmd):
150 def iscmd(cmd):
151 return cmd in wireproto.commands
151 return cmd in wireproto.commands
152
152
153 def parsehttprequest(rctx, req, query, checkperm):
153 def parsehttprequest(rctx, wsgireq, query, checkperm):
154 """Parse the HTTP request for a wire protocol request.
154 """Parse the HTTP request for a wire protocol request.
155
155
156 If the current request appears to be a wire protocol request, this
156 If the current request appears to be a wire protocol request, this
@@ -158,17 +158,17 b' def parsehttprequest(rctx, req, query, c'
158 an ``abstractprotocolserver`` instance suitable for handling the
158 an ``abstractprotocolserver`` instance suitable for handling the
159 request. Otherwise, ``None`` is returned.
159 request. Otherwise, ``None`` is returned.
160
160
161 ``req`` is a ``wsgirequest`` instance.
161 ``wsgireq`` is a ``wsgirequest`` instance.
162 """
162 """
163 repo = rctx.repo
163 repo = rctx.repo
164
164
165 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
165 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
166 # string parameter. If it isn't present, this isn't a wire protocol
166 # string parameter. If it isn't present, this isn't a wire protocol
167 # request.
167 # request.
168 if 'cmd' not in req.form:
168 if 'cmd' not in wsgireq.form:
169 return None
169 return None
170
170
171 cmd = req.form['cmd'][0]
171 cmd = wsgireq.form['cmd'][0]
172
172
173 # The "cmd" request parameter is used by both the wire protocol and hgweb.
173 # The "cmd" request parameter is used by both the wire protocol and hgweb.
174 # While not all wire protocol commands are available for all transports,
174 # While not all wire protocol commands are available for all transports,
@@ -180,24 +180,24 b' def parsehttprequest(rctx, req, query, c'
180 if not iscmd(cmd):
180 if not iscmd(cmd):
181 return None
181 return None
182
182
183 proto = httpv1protocolhandler(req, repo.ui,
183 proto = httpv1protocolhandler(wsgireq, repo.ui,
184 lambda perm: checkperm(rctx, req, perm))
184 lambda perm: checkperm(rctx, wsgireq, perm))
185
185
186 return {
186 return {
187 'cmd': cmd,
187 'cmd': cmd,
188 'proto': proto,
188 'proto': proto,
189 'dispatch': lambda: _callhttp(repo, req, proto, cmd),
189 'dispatch': lambda: _callhttp(repo, wsgireq, proto, cmd),
190 'handleerror': lambda ex: _handlehttperror(ex, req, cmd),
190 'handleerror': lambda ex: _handlehttperror(ex, wsgireq, cmd),
191 }
191 }
192
192
193 def _httpresponsetype(ui, req, prefer_uncompressed):
193 def _httpresponsetype(ui, wsgireq, prefer_uncompressed):
194 """Determine the appropriate response type and compression settings.
194 """Determine the appropriate response type and compression settings.
195
195
196 Returns a tuple of (mediatype, compengine, engineopts).
196 Returns a tuple of (mediatype, compengine, engineopts).
197 """
197 """
198 # Determine the response media type and compression engine based
198 # Determine the response media type and compression engine based
199 # on the request parameters.
199 # on the request parameters.
200 protocaps = decodevaluefromheaders(req, r'X-HgProto').split(' ')
200 protocaps = decodevaluefromheaders(wsgireq, r'X-HgProto').split(' ')
201
201
202 if '0.2' in protocaps:
202 if '0.2' in protocaps:
203 # All clients are expected to support uncompressed data.
203 # All clients are expected to support uncompressed data.
@@ -230,7 +230,7 b' def _httpresponsetype(ui, req, prefer_un'
230 opts = {'level': ui.configint('server', 'zliblevel')}
230 opts = {'level': ui.configint('server', 'zliblevel')}
231 return HGTYPE, util.compengines['zlib'], opts
231 return HGTYPE, util.compengines['zlib'], opts
232
232
233 def _callhttp(repo, req, proto, cmd):
233 def _callhttp(repo, wsgireq, proto, cmd):
234 def genversion2(gen, engine, engineopts):
234 def genversion2(gen, engine, engineopts):
235 # application/mercurial-0.2 always sends a payload header
235 # application/mercurial-0.2 always sends a payload header
236 # identifying the compression engine.
236 # identifying the compression engine.
@@ -243,9 +243,9 b' def _callhttp(repo, req, proto, cmd):'
243 yield chunk
243 yield chunk
244
244
245 if not wireproto.commands.commandavailable(cmd, proto):
245 if not wireproto.commands.commandavailable(cmd, proto):
246 req.respond(HTTP_OK, HGERRTYPE,
246 wsgireq.respond(HTTP_OK, HGERRTYPE,
247 body=_('requested wire protocol command is not available '
247 body=_('requested wire protocol command is not '
248 'over HTTP'))
248 'available over HTTP'))
249 return []
249 return []
250
250
251 proto.checkperm(wireproto.commands[cmd].permission)
251 proto.checkperm(wireproto.commands[cmd].permission)
@@ -253,14 +253,14 b' def _callhttp(repo, req, proto, cmd):'
253 rsp = wireproto.dispatch(repo, proto, cmd)
253 rsp = wireproto.dispatch(repo, proto, cmd)
254
254
255 if isinstance(rsp, bytes):
255 if isinstance(rsp, bytes):
256 req.respond(HTTP_OK, HGTYPE, body=rsp)
256 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp)
257 return []
257 return []
258 elif isinstance(rsp, wireprototypes.bytesresponse):
258 elif isinstance(rsp, wireprototypes.bytesresponse):
259 req.respond(HTTP_OK, HGTYPE, body=rsp.data)
259 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp.data)
260 return []
260 return []
261 elif isinstance(rsp, wireprototypes.streamreslegacy):
261 elif isinstance(rsp, wireprototypes.streamreslegacy):
262 gen = rsp.gen
262 gen = rsp.gen
263 req.respond(HTTP_OK, HGTYPE)
263 wsgireq.respond(HTTP_OK, HGTYPE)
264 return gen
264 return gen
265 elif isinstance(rsp, wireprototypes.streamres):
265 elif isinstance(rsp, wireprototypes.streamres):
266 gen = rsp.gen
266 gen = rsp.gen
@@ -268,32 +268,32 b' def _callhttp(repo, req, proto, cmd):'
268 # This code for compression should not be streamres specific. It
268 # This code for compression should not be streamres specific. It
269 # is here because we only compress streamres at the moment.
269 # is here because we only compress streamres at the moment.
270 mediatype, engine, engineopts = _httpresponsetype(
270 mediatype, engine, engineopts = _httpresponsetype(
271 repo.ui, req, rsp.prefer_uncompressed)
271 repo.ui, wsgireq, rsp.prefer_uncompressed)
272 gen = engine.compressstream(gen, engineopts)
272 gen = engine.compressstream(gen, engineopts)
273
273
274 if mediatype == HGTYPE2:
274 if mediatype == HGTYPE2:
275 gen = genversion2(gen, engine, engineopts)
275 gen = genversion2(gen, engine, engineopts)
276
276
277 req.respond(HTTP_OK, mediatype)
277 wsgireq.respond(HTTP_OK, mediatype)
278 return gen
278 return gen
279 elif isinstance(rsp, wireprototypes.pushres):
279 elif isinstance(rsp, wireprototypes.pushres):
280 rsp = '%d\n%s' % (rsp.res, rsp.output)
280 rsp = '%d\n%s' % (rsp.res, rsp.output)
281 req.respond(HTTP_OK, HGTYPE, body=rsp)
281 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp)
282 return []
282 return []
283 elif isinstance(rsp, wireprototypes.pusherr):
283 elif isinstance(rsp, wireprototypes.pusherr):
284 # This is the httplib workaround documented in _handlehttperror().
284 # This is the httplib workaround documented in _handlehttperror().
285 req.drain()
285 wsgireq.drain()
286
286
287 rsp = '0\n%s\n' % rsp.res
287 rsp = '0\n%s\n' % rsp.res
288 req.respond(HTTP_OK, HGTYPE, body=rsp)
288 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp)
289 return []
289 return []
290 elif isinstance(rsp, wireprototypes.ooberror):
290 elif isinstance(rsp, wireprototypes.ooberror):
291 rsp = rsp.message
291 rsp = rsp.message
292 req.respond(HTTP_OK, HGERRTYPE, body=rsp)
292 wsgireq.respond(HTTP_OK, HGERRTYPE, body=rsp)
293 return []
293 return []
294 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
294 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
295
295
296 def _handlehttperror(e, req, cmd):
296 def _handlehttperror(e, wsgireq, cmd):
297 """Called when an ErrorResponse is raised during HTTP request processing."""
297 """Called when an ErrorResponse is raised during HTTP request processing."""
298
298
299 # Clients using Python's httplib are stateful: the HTTP client
299 # Clients using Python's httplib are stateful: the HTTP client
@@ -304,20 +304,20 b' def _handlehttperror(e, req, cmd):'
304 # the HTTP response. In other words, it helps prevent deadlocks
304 # the HTTP response. In other words, it helps prevent deadlocks
305 # on clients using httplib.
305 # on clients using httplib.
306
306
307 if (req.env[r'REQUEST_METHOD'] == r'POST' and
307 if (wsgireq.env[r'REQUEST_METHOD'] == r'POST' and
308 # But not if Expect: 100-continue is being used.
308 # But not if Expect: 100-continue is being used.
309 (req.env.get('HTTP_EXPECT',
309 (wsgireq.env.get('HTTP_EXPECT',
310 '').lower() != '100-continue') or
310 '').lower() != '100-continue') or
311 # Or the non-httplib HTTP library is being advertised by
311 # Or the non-httplib HTTP library is being advertised by
312 # the client.
312 # the client.
313 req.env.get('X-HgHttp2', '')):
313 wsgireq.env.get('X-HgHttp2', '')):
314 req.drain()
314 wsgireq.drain()
315 else:
315 else:
316 req.headers.append((r'Connection', r'Close'))
316 wsgireq.headers.append((r'Connection', r'Close'))
317
317
318 # TODO This response body assumes the failed command was
318 # TODO This response body assumes the failed command was
319 # "unbundle." That assumption is not always valid.
319 # "unbundle." That assumption is not always valid.
320 req.respond(e, HGTYPE, body='0\n%s\n' % pycompat.bytestr(e))
320 wsgireq.respond(e, HGTYPE, body='0\n%s\n' % pycompat.bytestr(e))
321
321
322 return ''
322 return ''
323
323
General Comments 0
You need to be logged in to leave comments. Login now