43 webcommands, |
43 webcommands, |
44 webutil, |
44 webutil, |
45 wsgicgi, |
45 wsgicgi, |
46 ) |
46 ) |
47 |
47 |
|
48 |
48 def getstyle(req, configfn, templatepath): |
49 def getstyle(req, configfn, templatepath): |
49 styles = ( |
50 styles = ( |
50 req.qsparams.get('style', None), |
51 req.qsparams.get('style', None), |
51 configfn('web', 'style'), |
52 configfn('web', 'style'), |
52 'paper', |
53 'paper', |
53 ) |
54 ) |
54 return styles, templater.stylemap(styles, templatepath) |
55 return styles, templater.stylemap(styles, templatepath) |
|
56 |
55 |
57 |
56 def makebreadcrumb(url, prefix=''): |
58 def makebreadcrumb(url, prefix=''): |
57 '''Return a 'URL breadcrumb' list |
59 '''Return a 'URL breadcrumb' list |
58 |
60 |
59 A 'URL breadcrumb' is a list of URL-name pairs, |
61 A 'URL breadcrumb' is a list of URL-name pairs, |
76 break |
78 break |
77 breadcrumb.append({'url': urlel, 'name': pathel}) |
79 breadcrumb.append({'url': urlel, 'name': pathel}) |
78 urlel = os.path.dirname(urlel) |
80 urlel = os.path.dirname(urlel) |
79 return templateutil.mappinglist(reversed(breadcrumb)) |
81 return templateutil.mappinglist(reversed(breadcrumb)) |
80 |
82 |
|
83 |
81 class requestcontext(object): |
84 class requestcontext(object): |
82 """Holds state/context for an individual request. |
85 """Holds state/context for an individual request. |
83 |
86 |
84 Servers can be multi-threaded. Holding state on the WSGI application |
87 Servers can be multi-threaded. Holding state on the WSGI application |
85 is prone to race conditions. Instances of this class exist to hold |
88 is prone to race conditions. Instances of this class exist to hold |
86 mutable and race-free state for requests. |
89 mutable and race-free state for requests. |
87 """ |
90 """ |
|
91 |
88 def __init__(self, app, repo, req, res): |
92 def __init__(self, app, repo, req, res): |
89 self.repo = repo |
93 self.repo = repo |
90 self.reponame = app.reponame |
94 self.reponame = app.reponame |
91 self.req = req |
95 self.req = req |
92 self.res = res |
96 self.res = res |
111 |
115 |
112 self.csp, self.nonce = cspvalues(self.repo.ui) |
116 self.csp, self.nonce = cspvalues(self.repo.ui) |
113 |
117 |
114 # Trust the settings from the .hg/hgrc files by default. |
118 # Trust the settings from the .hg/hgrc files by default. |
115 def config(self, section, name, default=uimod._unset, untrusted=True): |
119 def config(self, section, name, default=uimod._unset, untrusted=True): |
116 return self.repo.ui.config(section, name, default, |
120 return self.repo.ui.config(section, name, default, untrusted=untrusted) |
117 untrusted=untrusted) |
|
118 |
121 |
119 def configbool(self, section, name, default=uimod._unset, untrusted=True): |
122 def configbool(self, section, name, default=uimod._unset, untrusted=True): |
120 return self.repo.ui.configbool(section, name, default, |
123 return self.repo.ui.configbool( |
121 untrusted=untrusted) |
124 section, name, default, untrusted=untrusted |
|
125 ) |
122 |
126 |
123 def configint(self, section, name, default=uimod._unset, untrusted=True): |
127 def configint(self, section, name, default=uimod._unset, untrusted=True): |
124 return self.repo.ui.configint(section, name, default, |
128 return self.repo.ui.configint( |
125 untrusted=untrusted) |
129 section, name, default, untrusted=untrusted |
|
130 ) |
126 |
131 |
127 def configlist(self, section, name, default=uimod._unset, untrusted=True): |
132 def configlist(self, section, name, default=uimod._unset, untrusted=True): |
128 return self.repo.ui.configlist(section, name, default, |
133 return self.repo.ui.configlist( |
129 untrusted=untrusted) |
134 section, name, default, untrusted=untrusted |
|
135 ) |
130 |
136 |
131 def archivelist(self, nodeid): |
137 def archivelist(self, nodeid): |
132 return webutil.archivelist(self.repo.ui, nodeid) |
138 return webutil.archivelist(self.repo.ui, nodeid) |
133 |
139 |
134 def templater(self, req): |
140 def templater(self, req): |
135 # determine scheme, port and server name |
141 # determine scheme, port and server name |
136 # this is needed to create absolute urls |
142 # this is needed to create absolute urls |
137 logourl = self.config('web', 'logourl') |
143 logourl = self.config('web', 'logourl') |
138 logoimg = self.config('web', 'logoimg') |
144 logoimg = self.config('web', 'logoimg') |
139 staticurl = (self.config('web', 'staticurl') |
145 staticurl = ( |
140 or req.apppath.rstrip('/') + '/static/') |
146 self.config('web', 'staticurl') |
|
147 or req.apppath.rstrip('/') + '/static/' |
|
148 ) |
141 if not staticurl.endswith('/'): |
149 if not staticurl.endswith('/'): |
142 staticurl += '/' |
150 staticurl += '/' |
143 |
151 |
144 # figure out which style to use |
152 # figure out which style to use |
145 |
153 |
146 vars = {} |
154 vars = {} |
147 styles, (style, mapfile) = getstyle(req, self.config, |
155 styles, (style, mapfile) = getstyle(req, self.config, self.templatepath) |
148 self.templatepath) |
|
149 if style == styles[0]: |
156 if style == styles[0]: |
150 vars['style'] = style |
157 vars['style'] = style |
151 |
158 |
152 sessionvars = webutil.sessionvars(vars, '?') |
159 sessionvars = webutil.sessionvars(vars, '?') |
153 |
160 |
154 if not self.reponame: |
161 if not self.reponame: |
155 self.reponame = (self.config('web', 'name', '') |
162 self.reponame = ( |
156 or req.reponame |
163 self.config('web', 'name', '') |
157 or req.apppath |
164 or req.reponame |
158 or self.repo.root) |
165 or req.apppath |
|
166 or self.repo.root |
|
167 ) |
159 |
168 |
160 filters = {} |
169 filters = {} |
161 templatefilter = registrar.templatefilter(filters) |
170 templatefilter = registrar.templatefilter(filters) |
|
171 |
162 @templatefilter('websub', intype=bytes) |
172 @templatefilter('websub', intype=bytes) |
163 def websubfilter(text): |
173 def websubfilter(text): |
164 return templatefilters.websub(text, self.websubtable) |
174 return templatefilters.websub(text, self.websubtable) |
165 |
175 |
166 # create the templater |
176 # create the templater |
177 'pathdef': makebreadcrumb(req.apppath), |
187 'pathdef': makebreadcrumb(req.apppath), |
178 'style': style, |
188 'style': style, |
179 'nonce': self.nonce, |
189 'nonce': self.nonce, |
180 } |
190 } |
181 templatekeyword = registrar.templatekeyword(defaults) |
191 templatekeyword = registrar.templatekeyword(defaults) |
|
192 |
182 @templatekeyword('motd', requires=()) |
193 @templatekeyword('motd', requires=()) |
183 def motd(context, mapping): |
194 def motd(context, mapping): |
184 yield self.config('web', 'motd') |
195 yield self.config('web', 'motd') |
185 |
196 |
186 tres = formatter.templateresources(self.repo.ui, self.repo) |
197 tres = formatter.templateresources(self.repo.ui, self.repo) |
187 tmpl = templater.templater.frommapfile(mapfile, |
198 tmpl = templater.templater.frommapfile( |
188 filters=filters, |
199 mapfile, filters=filters, defaults=defaults, resources=tres |
189 defaults=defaults, |
200 ) |
190 resources=tres) |
|
191 return tmpl |
201 return tmpl |
192 |
202 |
193 def sendtemplate(self, name, **kwargs): |
203 def sendtemplate(self, name, **kwargs): |
194 """Helper function to send a response generated from a template.""" |
204 """Helper function to send a response generated from a template.""" |
195 kwargs = pycompat.byteskwargs(kwargs) |
205 kwargs = pycompat.byteskwargs(kwargs) |
196 self.res.setbodygen(self.tmpl.generate(name, kwargs)) |
206 self.res.setbodygen(self.tmpl.generate(name, kwargs)) |
197 return self.res.sendresponse() |
207 return self.res.sendresponse() |
198 |
208 |
|
209 |
199 class hgweb(object): |
210 class hgweb(object): |
200 """HTTP server for individual repositories. |
211 """HTTP server for individual repositories. |
201 |
212 |
202 Instances of this class serve HTTP responses for a particular |
213 Instances of this class serve HTTP responses for a particular |
203 repository. |
214 repository. |
326 # accordingly. But URL paths can conflict with subrepos and virtual |
341 # accordingly. But URL paths can conflict with subrepos and virtual |
327 # repos in hgwebdir. So until we have a workaround for this, only |
342 # repos in hgwebdir. So until we have a workaround for this, only |
328 # expose the URLs if the feature is enabled. |
343 # expose the URLs if the feature is enabled. |
329 apienabled = rctx.repo.ui.configbool('experimental', 'web.apiserver') |
344 apienabled = rctx.repo.ui.configbool('experimental', 'web.apiserver') |
330 if apienabled and req.dispatchparts and req.dispatchparts[0] == b'api': |
345 if apienabled and req.dispatchparts and req.dispatchparts[0] == b'api': |
331 wireprotoserver.handlewsgiapirequest(rctx, req, res, |
346 wireprotoserver.handlewsgiapirequest( |
332 self.check_perm) |
347 rctx, req, res, self.check_perm |
|
348 ) |
333 return res.sendresponse() |
349 return res.sendresponse() |
334 |
350 |
335 handled = wireprotoserver.handlewsgirequest( |
351 handled = wireprotoserver.handlewsgirequest( |
336 rctx, req, res, self.check_perm) |
352 rctx, req, res, self.check_perm |
|
353 ) |
337 if handled: |
354 if handled: |
338 return res.sendresponse() |
355 return res.sendresponse() |
339 |
356 |
340 # Old implementations of hgweb supported dispatching the request via |
357 # Old implementations of hgweb supported dispatching the request via |
341 # the initial query string parameter instead of using PATH_INFO. |
358 # the initial query string parameter instead of using PATH_INFO. |
379 if cmd == 'archive': |
396 if cmd == 'archive': |
380 fn = req.qsparams['node'] |
397 fn = req.qsparams['node'] |
381 for type_, spec in webutil.archivespecs.iteritems(): |
398 for type_, spec in webutil.archivespecs.iteritems(): |
382 ext = spec[2] |
399 ext = spec[2] |
383 if fn.endswith(ext): |
400 if fn.endswith(ext): |
384 req.qsparams['node'] = fn[:-len(ext)] |
401 req.qsparams['node'] = fn[: -len(ext)] |
385 req.qsparams['type'] = type_ |
402 req.qsparams['type'] = type_ |
386 else: |
403 else: |
387 cmd = req.qsparams.get('cmd', '') |
404 cmd = req.qsparams.get('cmd', '') |
388 |
405 |
389 # process the web interface request |
406 # process the web interface request |
390 |
407 |
391 try: |
408 try: |
392 rctx.tmpl = rctx.templater(req) |
409 rctx.tmpl = rctx.templater(req) |
393 ctype = rctx.tmpl.render('mimetype', |
410 ctype = rctx.tmpl.render( |
394 {'encoding': encoding.encoding}) |
411 'mimetype', {'encoding': encoding.encoding} |
|
412 ) |
395 |
413 |
396 # check read permissions non-static content |
414 # check read permissions non-static content |
397 if cmd != 'static': |
415 if cmd != 'static': |
398 self.check_perm(rctx, req, None) |
416 self.check_perm(rctx, req, None) |
399 |
417 |
429 res.headers['Content-Type'] = ctype |
447 res.headers['Content-Type'] = ctype |
430 return getattr(webcommands, cmd)(rctx) |
448 return getattr(webcommands, cmd)(rctx) |
431 |
449 |
432 except (error.LookupError, error.RepoLookupError) as err: |
450 except (error.LookupError, error.RepoLookupError) as err: |
433 msg = pycompat.bytestr(err) |
451 msg = pycompat.bytestr(err) |
434 if (util.safehasattr(err, 'name') and |
452 if util.safehasattr(err, 'name') and not isinstance( |
435 not isinstance(err, error.ManifestLookupError)): |
453 err, error.ManifestLookupError |
|
454 ): |
436 msg = 'revision not found: %s' % err.name |
455 msg = 'revision not found: %s' % err.name |
437 |
456 |
438 res.status = '404 Not Found' |
457 res.status = '404 Not Found' |
439 res.headers['Content-Type'] = ctype |
458 res.headers['Content-Type'] = ctype |
440 return rctx.sendtemplate('error', error=msg) |
459 return rctx.sendtemplate('error', error=msg) |
455 |
474 |
456 def check_perm(self, rctx, req, op): |
475 def check_perm(self, rctx, req, op): |
457 for permhook in permhooks: |
476 for permhook in permhooks: |
458 permhook(rctx, req, op) |
477 permhook(rctx, req, op) |
459 |
478 |
|
479 |
460 def getwebview(repo): |
480 def getwebview(repo): |
461 """The 'web.view' config controls changeset filter to hgweb. Possible |
481 """The 'web.view' config controls changeset filter to hgweb. Possible |
462 values are ``served``, ``visible`` and ``all``. Default is ``served``. |
482 values are ``served``, ``visible`` and ``all``. Default is ``served``. |
463 The ``served`` filter only shows changesets that can be pulled from the |
483 The ``served`` filter only shows changesets that can be pulled from the |
464 hgweb instance. The``visible`` filter includes secret changesets but |
484 hgweb instance. The``visible`` filter includes secret changesets but |