Mercurial > public > mercurial-scm > hg
comparison mercurial/hgweb/hgweb_mod.py @ 36804:b9b968e21f78
hgweb: rename req to wsgireq
We will soon introduce a parsed WSGI request object so we don't
have to concern ourselves with low-level WSGI matters. Prepare
for multiple request objects by renaming the existing one so it
is clear it deals with WSGI.
We also remove a symbol import to avoid even more naming confusion.
# no-check-commit because of some new foo_bar naming that's required
Differential Revision: https://phab.mercurial-scm.org/D2732
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Thu, 08 Mar 2018 15:15:59 -0800 |
parents | 7fc80c982656 |
children | ec46415ed826 |
comparison
equal
deleted
inserted
replaced
36803:8e1556ac01bb | 36804:b9b968e21f78 |
---|---|
20 HTTP_SERVER_ERROR, | 20 HTTP_SERVER_ERROR, |
21 caching, | 21 caching, |
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, |
29 error, | 28 error, |
30 formatter, | 29 formatter, |
39 util, | 38 util, |
40 wireprotoserver, | 39 wireprotoserver, |
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, |
47 ) | 47 ) |
48 | 48 |
140 allowed = self.configlist('web', 'allow_archive') | 140 allowed = self.configlist('web', 'allow_archive') |
141 for typ, spec in self.archivespecs.iteritems(): | 141 for typ, spec in self.archivespecs.iteritems(): |
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' |
153 else: | 153 else: |
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 |
167 # some functions for the templater | 167 # some functions for the templater |
168 | 168 |
170 yield self.config('web', 'motd') | 170 yield self.config('web', 'motd') |
171 | 171 |
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) |
190 | 190 |
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, |
198 'urlbase': urlbase, | 198 'urlbase': urlbase, |
199 'repo': self.reponame, | 199 'repo': self.reponame, |
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 } |
207 tres = formatter.templateresources(self.repo.ui, self.repo) | 207 tres = formatter.templateresources(self.repo.ui, self.repo) |
208 tmpl = templater.templater.frommapfile(mapfile, | 208 tmpl = templater.templater.frommapfile(mapfile, |
299 def __call__(self, env, respond): | 299 def __call__(self, env, respond): |
300 """Run the WSGI application. | 300 """Run the WSGI application. |
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 |
311 should be using instances of this class as the WSGI application. | 311 should be using instances of this class as the WSGI application. |
312 """ | 312 """ |
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: |
358 try: | 358 try: |
359 if query: | 359 if query: |
364 return protohandler['handleerror'](inst) | 364 return protohandler['handleerror'](inst) |
365 | 365 |
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 [''] |
449 return tmpl('error', error=pycompat.bytestr(inst)) | 449 return tmpl('error', error=pycompat.bytestr(inst)) |
450 | 450 |