##// END OF EJS Templates
hgweb: set variables in qsparams...
Gregory Szorc -
r36880:4e06e833 default
parent child Browse files
Show More
@@ -1,439 +1,449 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import contextlib
11 import contextlib
12 import os
12 import os
13
13
14 from .common import (
14 from .common import (
15 ErrorResponse,
15 ErrorResponse,
16 HTTP_BAD_REQUEST,
16 HTTP_BAD_REQUEST,
17 HTTP_NOT_FOUND,
17 HTTP_NOT_FOUND,
18 HTTP_NOT_MODIFIED,
18 HTTP_NOT_MODIFIED,
19 HTTP_OK,
19 HTTP_OK,
20 HTTP_SERVER_ERROR,
20 HTTP_SERVER_ERROR,
21 caching,
21 caching,
22 cspvalues,
22 cspvalues,
23 permhooks,
23 permhooks,
24 )
24 )
25
25
26 from .. import (
26 from .. import (
27 encoding,
27 encoding,
28 error,
28 error,
29 formatter,
29 formatter,
30 hg,
30 hg,
31 hook,
31 hook,
32 profiling,
32 profiling,
33 pycompat,
33 pycompat,
34 repoview,
34 repoview,
35 templatefilters,
35 templatefilters,
36 templater,
36 templater,
37 ui as uimod,
37 ui as uimod,
38 util,
38 util,
39 wireprotoserver,
39 wireprotoserver,
40 )
40 )
41
41
42 from . import (
42 from . import (
43 request as requestmod,
43 request as requestmod,
44 webcommands,
44 webcommands,
45 webutil,
45 webutil,
46 wsgicgi,
46 wsgicgi,
47 )
47 )
48
48
49 archivespecs = util.sortdict((
49 archivespecs = util.sortdict((
50 ('zip', ('application/zip', 'zip', '.zip', None)),
50 ('zip', ('application/zip', 'zip', '.zip', None)),
51 ('gz', ('application/x-gzip', 'tgz', '.tar.gz', None)),
51 ('gz', ('application/x-gzip', 'tgz', '.tar.gz', None)),
52 ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
52 ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
53 ))
53 ))
54
54
55 def getstyle(req, configfn, templatepath):
55 def getstyle(req, configfn, templatepath):
56 styles = (
56 styles = (
57 req.qsparams.get('style', None),
57 req.qsparams.get('style', None),
58 configfn('web', 'style'),
58 configfn('web', 'style'),
59 'paper',
59 'paper',
60 )
60 )
61 return styles, templater.stylemap(styles, templatepath)
61 return styles, templater.stylemap(styles, templatepath)
62
62
63 def makebreadcrumb(url, prefix=''):
63 def makebreadcrumb(url, prefix=''):
64 '''Return a 'URL breadcrumb' list
64 '''Return a 'URL breadcrumb' list
65
65
66 A 'URL breadcrumb' is a list of URL-name pairs,
66 A 'URL breadcrumb' is a list of URL-name pairs,
67 corresponding to each of the path items on a URL.
67 corresponding to each of the path items on a URL.
68 This can be used to create path navigation entries.
68 This can be used to create path navigation entries.
69 '''
69 '''
70 if url.endswith('/'):
70 if url.endswith('/'):
71 url = url[:-1]
71 url = url[:-1]
72 if prefix:
72 if prefix:
73 url = '/' + prefix + url
73 url = '/' + prefix + url
74 relpath = url
74 relpath = url
75 if relpath.startswith('/'):
75 if relpath.startswith('/'):
76 relpath = relpath[1:]
76 relpath = relpath[1:]
77
77
78 breadcrumb = []
78 breadcrumb = []
79 urlel = url
79 urlel = url
80 pathitems = [''] + relpath.split('/')
80 pathitems = [''] + relpath.split('/')
81 for pathel in reversed(pathitems):
81 for pathel in reversed(pathitems):
82 if not pathel or not urlel:
82 if not pathel or not urlel:
83 break
83 break
84 breadcrumb.append({'url': urlel, 'name': pathel})
84 breadcrumb.append({'url': urlel, 'name': pathel})
85 urlel = os.path.dirname(urlel)
85 urlel = os.path.dirname(urlel)
86 return reversed(breadcrumb)
86 return reversed(breadcrumb)
87
87
88 class requestcontext(object):
88 class requestcontext(object):
89 """Holds state/context for an individual request.
89 """Holds state/context for an individual request.
90
90
91 Servers can be multi-threaded. Holding state on the WSGI application
91 Servers can be multi-threaded. Holding state on the WSGI application
92 is prone to race conditions. Instances of this class exist to hold
92 is prone to race conditions. Instances of this class exist to hold
93 mutable and race-free state for requests.
93 mutable and race-free state for requests.
94 """
94 """
95 def __init__(self, app, repo):
95 def __init__(self, app, repo):
96 self.repo = repo
96 self.repo = repo
97 self.reponame = app.reponame
97 self.reponame = app.reponame
98
98
99 self.archivespecs = archivespecs
99 self.archivespecs = archivespecs
100
100
101 self.maxchanges = self.configint('web', 'maxchanges')
101 self.maxchanges = self.configint('web', 'maxchanges')
102 self.stripecount = self.configint('web', 'stripes')
102 self.stripecount = self.configint('web', 'stripes')
103 self.maxshortchanges = self.configint('web', 'maxshortchanges')
103 self.maxshortchanges = self.configint('web', 'maxshortchanges')
104 self.maxfiles = self.configint('web', 'maxfiles')
104 self.maxfiles = self.configint('web', 'maxfiles')
105 self.allowpull = self.configbool('web', 'allow-pull')
105 self.allowpull = self.configbool('web', 'allow-pull')
106
106
107 # we use untrusted=False to prevent a repo owner from using
107 # we use untrusted=False to prevent a repo owner from using
108 # web.templates in .hg/hgrc to get access to any file readable
108 # web.templates in .hg/hgrc to get access to any file readable
109 # by the user running the CGI script
109 # by the user running the CGI script
110 self.templatepath = self.config('web', 'templates', untrusted=False)
110 self.templatepath = self.config('web', 'templates', untrusted=False)
111
111
112 # This object is more expensive to build than simple config values.
112 # This object is more expensive to build than simple config values.
113 # It is shared across requests. The app will replace the object
113 # It is shared across requests. The app will replace the object
114 # if it is updated. Since this is a reference and nothing should
114 # if it is updated. Since this is a reference and nothing should
115 # modify the underlying object, it should be constant for the lifetime
115 # modify the underlying object, it should be constant for the lifetime
116 # of the request.
116 # of the request.
117 self.websubtable = app.websubtable
117 self.websubtable = app.websubtable
118
118
119 self.csp, self.nonce = cspvalues(self.repo.ui)
119 self.csp, self.nonce = cspvalues(self.repo.ui)
120
120
121 # Trust the settings from the .hg/hgrc files by default.
121 # Trust the settings from the .hg/hgrc files by default.
122 def config(self, section, name, default=uimod._unset, untrusted=True):
122 def config(self, section, name, default=uimod._unset, untrusted=True):
123 return self.repo.ui.config(section, name, default,
123 return self.repo.ui.config(section, name, default,
124 untrusted=untrusted)
124 untrusted=untrusted)
125
125
126 def configbool(self, section, name, default=uimod._unset, untrusted=True):
126 def configbool(self, section, name, default=uimod._unset, untrusted=True):
127 return self.repo.ui.configbool(section, name, default,
127 return self.repo.ui.configbool(section, name, default,
128 untrusted=untrusted)
128 untrusted=untrusted)
129
129
130 def configint(self, section, name, default=uimod._unset, untrusted=True):
130 def configint(self, section, name, default=uimod._unset, untrusted=True):
131 return self.repo.ui.configint(section, name, default,
131 return self.repo.ui.configint(section, name, default,
132 untrusted=untrusted)
132 untrusted=untrusted)
133
133
134 def configlist(self, section, name, default=uimod._unset, untrusted=True):
134 def configlist(self, section, name, default=uimod._unset, untrusted=True):
135 return self.repo.ui.configlist(section, name, default,
135 return self.repo.ui.configlist(section, name, default,
136 untrusted=untrusted)
136 untrusted=untrusted)
137
137
138 def archivelist(self, nodeid):
138 def archivelist(self, nodeid):
139 allowed = self.configlist('web', 'allow_archive')
139 allowed = self.configlist('web', 'allow_archive')
140 for typ, spec in self.archivespecs.iteritems():
140 for typ, spec in self.archivespecs.iteritems():
141 if typ in allowed or self.configbool('web', 'allow%s' % typ):
141 if typ in allowed or self.configbool('web', 'allow%s' % typ):
142 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
142 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
143
143
144 def templater(self, wsgireq, req):
144 def templater(self, wsgireq, req):
145 # determine scheme, port and server name
145 # determine scheme, port and server name
146 # this is needed to create absolute urls
146 # this is needed to create absolute urls
147 logourl = self.config('web', 'logourl')
147 logourl = self.config('web', 'logourl')
148 logoimg = self.config('web', 'logoimg')
148 logoimg = self.config('web', 'logoimg')
149 staticurl = (self.config('web', 'staticurl')
149 staticurl = (self.config('web', 'staticurl')
150 or req.apppath + '/static/')
150 or req.apppath + '/static/')
151 if not staticurl.endswith('/'):
151 if not staticurl.endswith('/'):
152 staticurl += '/'
152 staticurl += '/'
153
153
154 # some functions for the templater
154 # some functions for the templater
155
155
156 def motd(**map):
156 def motd(**map):
157 yield self.config('web', 'motd')
157 yield self.config('web', 'motd')
158
158
159 # figure out which style to use
159 # figure out which style to use
160
160
161 vars = {}
161 vars = {}
162 styles, (style, mapfile) = getstyle(wsgireq.req, self.config,
162 styles, (style, mapfile) = getstyle(wsgireq.req, self.config,
163 self.templatepath)
163 self.templatepath)
164 if style == styles[0]:
164 if style == styles[0]:
165 vars['style'] = style
165 vars['style'] = style
166
166
167 sessionvars = webutil.sessionvars(vars, '?')
167 sessionvars = webutil.sessionvars(vars, '?')
168
168
169 if not self.reponame:
169 if not self.reponame:
170 self.reponame = (self.config('web', 'name', '')
170 self.reponame = (self.config('web', 'name', '')
171 or wsgireq.env.get('REPO_NAME')
171 or wsgireq.env.get('REPO_NAME')
172 or req.apppath or self.repo.root)
172 or req.apppath or self.repo.root)
173
173
174 def websubfilter(text):
174 def websubfilter(text):
175 return templatefilters.websub(text, self.websubtable)
175 return templatefilters.websub(text, self.websubtable)
176
176
177 # create the templater
177 # create the templater
178 # TODO: export all keywords: defaults = templatekw.keywords.copy()
178 # TODO: export all keywords: defaults = templatekw.keywords.copy()
179 defaults = {
179 defaults = {
180 'url': req.apppath + '/',
180 'url': req.apppath + '/',
181 'logourl': logourl,
181 'logourl': logourl,
182 'logoimg': logoimg,
182 'logoimg': logoimg,
183 'staticurl': staticurl,
183 'staticurl': staticurl,
184 'urlbase': req.advertisedbaseurl,
184 'urlbase': req.advertisedbaseurl,
185 'repo': self.reponame,
185 'repo': self.reponame,
186 'encoding': encoding.encoding,
186 'encoding': encoding.encoding,
187 'motd': motd,
187 'motd': motd,
188 'sessionvars': sessionvars,
188 'sessionvars': sessionvars,
189 'pathdef': makebreadcrumb(req.apppath),
189 'pathdef': makebreadcrumb(req.apppath),
190 'style': style,
190 'style': style,
191 'nonce': self.nonce,
191 'nonce': self.nonce,
192 }
192 }
193 tres = formatter.templateresources(self.repo.ui, self.repo)
193 tres = formatter.templateresources(self.repo.ui, self.repo)
194 tmpl = templater.templater.frommapfile(mapfile,
194 tmpl = templater.templater.frommapfile(mapfile,
195 filters={'websub': websubfilter},
195 filters={'websub': websubfilter},
196 defaults=defaults,
196 defaults=defaults,
197 resources=tres)
197 resources=tres)
198 return tmpl
198 return tmpl
199
199
200
200
201 class hgweb(object):
201 class hgweb(object):
202 """HTTP server for individual repositories.
202 """HTTP server for individual repositories.
203
203
204 Instances of this class serve HTTP responses for a particular
204 Instances of this class serve HTTP responses for a particular
205 repository.
205 repository.
206
206
207 Instances are typically used as WSGI applications.
207 Instances are typically used as WSGI applications.
208
208
209 Some servers are multi-threaded. On these servers, there may
209 Some servers are multi-threaded. On these servers, there may
210 be multiple active threads inside __call__.
210 be multiple active threads inside __call__.
211 """
211 """
212 def __init__(self, repo, name=None, baseui=None):
212 def __init__(self, repo, name=None, baseui=None):
213 if isinstance(repo, str):
213 if isinstance(repo, str):
214 if baseui:
214 if baseui:
215 u = baseui.copy()
215 u = baseui.copy()
216 else:
216 else:
217 u = uimod.ui.load()
217 u = uimod.ui.load()
218 r = hg.repository(u, repo)
218 r = hg.repository(u, repo)
219 else:
219 else:
220 # we trust caller to give us a private copy
220 # we trust caller to give us a private copy
221 r = repo
221 r = repo
222
222
223 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
223 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
224 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
224 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
225 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
225 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
226 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
226 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
227 # resolve file patterns relative to repo root
227 # resolve file patterns relative to repo root
228 r.ui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
228 r.ui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
229 r.baseui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
229 r.baseui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
230 # displaying bundling progress bar while serving feel wrong and may
230 # displaying bundling progress bar while serving feel wrong and may
231 # break some wsgi implementation.
231 # break some wsgi implementation.
232 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
232 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
233 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
233 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
234 self._repos = [hg.cachedlocalrepo(self._webifyrepo(r))]
234 self._repos = [hg.cachedlocalrepo(self._webifyrepo(r))]
235 self._lastrepo = self._repos[0]
235 self._lastrepo = self._repos[0]
236 hook.redirect(True)
236 hook.redirect(True)
237 self.reponame = name
237 self.reponame = name
238
238
239 def _webifyrepo(self, repo):
239 def _webifyrepo(self, repo):
240 repo = getwebview(repo)
240 repo = getwebview(repo)
241 self.websubtable = webutil.getwebsubs(repo)
241 self.websubtable = webutil.getwebsubs(repo)
242 return repo
242 return repo
243
243
244 @contextlib.contextmanager
244 @contextlib.contextmanager
245 def _obtainrepo(self):
245 def _obtainrepo(self):
246 """Obtain a repo unique to the caller.
246 """Obtain a repo unique to the caller.
247
247
248 Internally we maintain a stack of cachedlocalrepo instances
248 Internally we maintain a stack of cachedlocalrepo instances
249 to be handed out. If one is available, we pop it and return it,
249 to be handed out. If one is available, we pop it and return it,
250 ensuring it is up to date in the process. If one is not available,
250 ensuring it is up to date in the process. If one is not available,
251 we clone the most recently used repo instance and return it.
251 we clone the most recently used repo instance and return it.
252
252
253 It is currently possible for the stack to grow without bounds
253 It is currently possible for the stack to grow without bounds
254 if the server allows infinite threads. However, servers should
254 if the server allows infinite threads. However, servers should
255 have a thread limit, thus establishing our limit.
255 have a thread limit, thus establishing our limit.
256 """
256 """
257 if self._repos:
257 if self._repos:
258 cached = self._repos.pop()
258 cached = self._repos.pop()
259 r, created = cached.fetch()
259 r, created = cached.fetch()
260 else:
260 else:
261 cached = self._lastrepo.copy()
261 cached = self._lastrepo.copy()
262 r, created = cached.fetch()
262 r, created = cached.fetch()
263 if created:
263 if created:
264 r = self._webifyrepo(r)
264 r = self._webifyrepo(r)
265
265
266 self._lastrepo = cached
266 self._lastrepo = cached
267 self.mtime = cached.mtime
267 self.mtime = cached.mtime
268 try:
268 try:
269 yield r
269 yield r
270 finally:
270 finally:
271 self._repos.append(cached)
271 self._repos.append(cached)
272
272
273 def run(self):
273 def run(self):
274 """Start a server from CGI environment.
274 """Start a server from CGI environment.
275
275
276 Modern servers should be using WSGI and should avoid this
276 Modern servers should be using WSGI and should avoid this
277 method, if possible.
277 method, if possible.
278 """
278 """
279 if not encoding.environ.get('GATEWAY_INTERFACE',
279 if not encoding.environ.get('GATEWAY_INTERFACE',
280 '').startswith("CGI/1."):
280 '').startswith("CGI/1."):
281 raise RuntimeError("This function is only intended to be "
281 raise RuntimeError("This function is only intended to be "
282 "called while running as a CGI script.")
282 "called while running as a CGI script.")
283 wsgicgi.launch(self)
283 wsgicgi.launch(self)
284
284
285 def __call__(self, env, respond):
285 def __call__(self, env, respond):
286 """Run the WSGI application.
286 """Run the WSGI application.
287
287
288 This may be called by multiple threads.
288 This may be called by multiple threads.
289 """
289 """
290 req = requestmod.wsgirequest(env, respond)
290 req = requestmod.wsgirequest(env, respond)
291 return self.run_wsgi(req)
291 return self.run_wsgi(req)
292
292
293 def run_wsgi(self, wsgireq):
293 def run_wsgi(self, wsgireq):
294 """Internal method to run the WSGI application.
294 """Internal method to run the WSGI application.
295
295
296 This is typically only called by Mercurial. External consumers
296 This is typically only called by Mercurial. External consumers
297 should be using instances of this class as the WSGI application.
297 should be using instances of this class as the WSGI application.
298 """
298 """
299 with self._obtainrepo() as repo:
299 with self._obtainrepo() as repo:
300 profile = repo.ui.configbool('profiling', 'enabled')
300 profile = repo.ui.configbool('profiling', 'enabled')
301 with profiling.profile(repo.ui, enabled=profile):
301 with profiling.profile(repo.ui, enabled=profile):
302 for r in self._runwsgi(wsgireq, repo):
302 for r in self._runwsgi(wsgireq, repo):
303 yield r
303 yield r
304
304
305 def _runwsgi(self, wsgireq, repo):
305 def _runwsgi(self, wsgireq, repo):
306 req = wsgireq.req
306 req = wsgireq.req
307 res = wsgireq.res
307 res = wsgireq.res
308 rctx = requestcontext(self, repo)
308 rctx = requestcontext(self, repo)
309
309
310 # This state is global across all threads.
310 # This state is global across all threads.
311 encoding.encoding = rctx.config('web', 'encoding')
311 encoding.encoding = rctx.config('web', 'encoding')
312 rctx.repo.ui.environ = wsgireq.env
312 rctx.repo.ui.environ = wsgireq.env
313
313
314 if rctx.csp:
314 if rctx.csp:
315 # hgwebdir may have added CSP header. Since we generate our own,
315 # hgwebdir may have added CSP header. Since we generate our own,
316 # replace it.
316 # replace it.
317 wsgireq.headers = [h for h in wsgireq.headers
317 wsgireq.headers = [h for h in wsgireq.headers
318 if h[0] != 'Content-Security-Policy']
318 if h[0] != 'Content-Security-Policy']
319 wsgireq.headers.append(('Content-Security-Policy', rctx.csp))
319 wsgireq.headers.append(('Content-Security-Policy', rctx.csp))
320 res.headers['Content-Security-Policy'] = rctx.csp
320 res.headers['Content-Security-Policy'] = rctx.csp
321
321
322 handled = wireprotoserver.handlewsgirequest(
322 handled = wireprotoserver.handlewsgirequest(
323 rctx, wsgireq, req, res, self.check_perm)
323 rctx, wsgireq, req, res, self.check_perm)
324 if handled:
324 if handled:
325 return res.sendresponse()
325 return res.sendresponse()
326
326
327 if req.havepathinfo:
327 if req.havepathinfo:
328 query = req.dispatchpath
328 query = req.dispatchpath
329 else:
329 else:
330 query = req.querystring.partition('&')[0].partition(';')[0]
330 query = req.querystring.partition('&')[0].partition(';')[0]
331
331
332 # translate user-visible url structure to internal structure
332 # translate user-visible url structure to internal structure
333
333
334 args = query.split('/', 2)
334 args = query.split('/', 2)
335 if 'cmd' not in wsgireq.form and args and args[0]:
335 if 'cmd' not in wsgireq.form and args and args[0]:
336 cmd = args.pop(0)
336 cmd = args.pop(0)
337 style = cmd.rfind('-')
337 style = cmd.rfind('-')
338 if style != -1:
338 if style != -1:
339 req.qsparams['style'] = cmd[:style]
339 req.qsparams['style'] = cmd[:style]
340 cmd = cmd[style + 1:]
340 cmd = cmd[style + 1:]
341
341
342 # avoid accepting e.g. style parameter as command
342 # avoid accepting e.g. style parameter as command
343 if util.safehasattr(webcommands, cmd):
343 if util.safehasattr(webcommands, cmd):
344 wsgireq.form['cmd'] = [cmd]
344 wsgireq.form['cmd'] = [cmd]
345 req.qsparams['cmd'] = cmd
345
346
346 if cmd == 'static':
347 if cmd == 'static':
347 wsgireq.form['file'] = ['/'.join(args)]
348 wsgireq.form['file'] = ['/'.join(args)]
349 req.qsparams['file'] = '/'.join(args)
348 else:
350 else:
349 if args and args[0]:
351 if args and args[0]:
350 node = args.pop(0).replace('%2F', '/')
352 node = args.pop(0).replace('%2F', '/')
351 wsgireq.form['node'] = [node]
353 wsgireq.form['node'] = [node]
354 req.qsparams['node'] = node
352 if args:
355 if args:
353 wsgireq.form['file'] = args
356 wsgireq.form['file'] = args
357 if 'file' in req.qsparams:
358 del req.qsparams['file']
359 for a in args:
360 req.qsparams.add('file', a)
354
361
355 ua = req.headers.get('User-Agent', '')
362 ua = req.headers.get('User-Agent', '')
356 if cmd == 'rev' and 'mercurial' in ua:
363 if cmd == 'rev' and 'mercurial' in ua:
357 req.qsparams['style'] = 'raw'
364 req.qsparams['style'] = 'raw'
358
365
359 if cmd == 'archive':
366 if cmd == 'archive':
360 fn = wsgireq.form['node'][0]
367 fn = wsgireq.form['node'][0]
361 for type_, spec in rctx.archivespecs.iteritems():
368 for type_, spec in rctx.archivespecs.iteritems():
362 ext = spec[2]
369 ext = spec[2]
363 if fn.endswith(ext):
370 if fn.endswith(ext):
364 wsgireq.form['node'] = [fn[:-len(ext)]]
371 wsgireq.form['node'] = [fn[:-len(ext)]]
372 req.qsparams['node'] = fn[:-len(next)]
365 wsgireq.form['type'] = [type_]
373 wsgireq.form['type'] = [type_]
374 req.qsparams['type'] = type_
366 else:
375 else:
367 cmd = wsgireq.form.get('cmd', [''])[0]
376 cmd = wsgireq.form.get('cmd', [''])[0]
368
377
369 # process the web interface request
378 # process the web interface request
370
379
371 try:
380 try:
372 tmpl = rctx.templater(wsgireq, req)
381 tmpl = rctx.templater(wsgireq, req)
373 ctype = tmpl('mimetype', encoding=encoding.encoding)
382 ctype = tmpl('mimetype', encoding=encoding.encoding)
374 ctype = templater.stringify(ctype)
383 ctype = templater.stringify(ctype)
375
384
376 # check read permissions non-static content
385 # check read permissions non-static content
377 if cmd != 'static':
386 if cmd != 'static':
378 self.check_perm(rctx, wsgireq, None)
387 self.check_perm(rctx, wsgireq, None)
379
388
380 if cmd == '':
389 if cmd == '':
381 wsgireq.form['cmd'] = [tmpl.cache['default']]
390 wsgireq.form['cmd'] = [tmpl.cache['default']]
391 req.qsparams['cmd'] = tmpl.cache['default']
382 cmd = wsgireq.form['cmd'][0]
392 cmd = wsgireq.form['cmd'][0]
383
393
384 # Don't enable caching if using a CSP nonce because then it wouldn't
394 # Don't enable caching if using a CSP nonce because then it wouldn't
385 # be a nonce.
395 # be a nonce.
386 if rctx.configbool('web', 'cache') and not rctx.nonce:
396 if rctx.configbool('web', 'cache') and not rctx.nonce:
387 caching(self, wsgireq) # sets ETag header or raises NOT_MODIFIED
397 caching(self, wsgireq) # sets ETag header or raises NOT_MODIFIED
388 if cmd not in webcommands.__all__:
398 if cmd not in webcommands.__all__:
389 msg = 'no such method: %s' % cmd
399 msg = 'no such method: %s' % cmd
390 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
400 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
391 elif cmd == 'file' and req.qsparams.get('style') == 'raw':
401 elif cmd == 'file' and req.qsparams.get('style') == 'raw':
392 rctx.ctype = ctype
402 rctx.ctype = ctype
393 content = webcommands.rawfile(rctx, wsgireq, tmpl)
403 content = webcommands.rawfile(rctx, wsgireq, tmpl)
394 else:
404 else:
395 content = getattr(webcommands, cmd)(rctx, wsgireq, tmpl)
405 content = getattr(webcommands, cmd)(rctx, wsgireq, tmpl)
396 wsgireq.respond(HTTP_OK, ctype)
406 wsgireq.respond(HTTP_OK, ctype)
397
407
398 return content
408 return content
399
409
400 except (error.LookupError, error.RepoLookupError) as err:
410 except (error.LookupError, error.RepoLookupError) as err:
401 wsgireq.respond(HTTP_NOT_FOUND, ctype)
411 wsgireq.respond(HTTP_NOT_FOUND, ctype)
402 msg = pycompat.bytestr(err)
412 msg = pycompat.bytestr(err)
403 if (util.safehasattr(err, 'name') and
413 if (util.safehasattr(err, 'name') and
404 not isinstance(err, error.ManifestLookupError)):
414 not isinstance(err, error.ManifestLookupError)):
405 msg = 'revision not found: %s' % err.name
415 msg = 'revision not found: %s' % err.name
406 return tmpl('error', error=msg)
416 return tmpl('error', error=msg)
407 except (error.RepoError, error.RevlogError) as inst:
417 except (error.RepoError, error.RevlogError) as inst:
408 wsgireq.respond(HTTP_SERVER_ERROR, ctype)
418 wsgireq.respond(HTTP_SERVER_ERROR, ctype)
409 return tmpl('error', error=pycompat.bytestr(inst))
419 return tmpl('error', error=pycompat.bytestr(inst))
410 except ErrorResponse as inst:
420 except ErrorResponse as inst:
411 wsgireq.respond(inst, ctype)
421 wsgireq.respond(inst, ctype)
412 if inst.code == HTTP_NOT_MODIFIED:
422 if inst.code == HTTP_NOT_MODIFIED:
413 # Not allowed to return a body on a 304
423 # Not allowed to return a body on a 304
414 return ['']
424 return ['']
415 return tmpl('error', error=pycompat.bytestr(inst))
425 return tmpl('error', error=pycompat.bytestr(inst))
416
426
417 def check_perm(self, rctx, req, op):
427 def check_perm(self, rctx, req, op):
418 for permhook in permhooks:
428 for permhook in permhooks:
419 permhook(rctx, req, op)
429 permhook(rctx, req, op)
420
430
421 def getwebview(repo):
431 def getwebview(repo):
422 """The 'web.view' config controls changeset filter to hgweb. Possible
432 """The 'web.view' config controls changeset filter to hgweb. Possible
423 values are ``served``, ``visible`` and ``all``. Default is ``served``.
433 values are ``served``, ``visible`` and ``all``. Default is ``served``.
424 The ``served`` filter only shows changesets that can be pulled from the
434 The ``served`` filter only shows changesets that can be pulled from the
425 hgweb instance. The``visible`` filter includes secret changesets but
435 hgweb instance. The``visible`` filter includes secret changesets but
426 still excludes "hidden" one.
436 still excludes "hidden" one.
427
437
428 See the repoview module for details.
438 See the repoview module for details.
429
439
430 The option has been around undocumented since Mercurial 2.5, but no
440 The option has been around undocumented since Mercurial 2.5, but no
431 user ever asked about it. So we better keep it undocumented for now."""
441 user ever asked about it. So we better keep it undocumented for now."""
432 # experimental config: web.view
442 # experimental config: web.view
433 viewconfig = repo.ui.config('web', 'view', untrusted=True)
443 viewconfig = repo.ui.config('web', 'view', untrusted=True)
434 if viewconfig == 'all':
444 if viewconfig == 'all':
435 return repo.unfiltered()
445 return repo.unfiltered()
436 elif viewconfig in repoview.filtertable:
446 elif viewconfig in repoview.filtertable:
437 return repo.filtered(viewconfig)
447 return repo.filtered(viewconfig)
438 else:
448 else:
439 return repo.filtered('served')
449 return repo.filtered('served')
General Comments 0
You need to be logged in to leave comments. Login now