##// END OF EJS Templates
hgweb: move templatepath to requestcontext...
Gregory Szorc -
r26163:84511b1d default
parent child Browse files
Show More
@@ -1,441 +1,443 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 import os
9 import os
10 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
10 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
11 from mercurial.templatefilters import websub
11 from mercurial.templatefilters import websub
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from common import get_stat, ErrorResponse, permhooks, caching
13 from common import get_stat, ErrorResponse, permhooks, caching
14 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
14 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
15 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 from request import wsgirequest
16 from request import wsgirequest
17 import webcommands, protocol, webutil
17 import webcommands, protocol, webutil
18
18
19 perms = {
19 perms = {
20 'changegroup': 'pull',
20 'changegroup': 'pull',
21 'changegroupsubset': 'pull',
21 'changegroupsubset': 'pull',
22 'getbundle': 'pull',
22 'getbundle': 'pull',
23 'stream_out': 'pull',
23 'stream_out': 'pull',
24 'listkeys': 'pull',
24 'listkeys': 'pull',
25 'unbundle': 'push',
25 'unbundle': 'push',
26 'pushkey': 'push',
26 'pushkey': 'push',
27 }
27 }
28
28
29 ## Files of interest
29 ## Files of interest
30 # Used to check if the repository has changed looking at mtime and size of
30 # Used to check if the repository has changed looking at mtime and size of
31 # theses files. This should probably be relocated a bit higher in core.
31 # theses files. This should probably be relocated a bit higher in core.
32 foi = [('spath', '00changelog.i'),
32 foi = [('spath', '00changelog.i'),
33 ('spath', 'phaseroots'), # ! phase can change content at the same size
33 ('spath', 'phaseroots'), # ! phase can change content at the same size
34 ('spath', 'obsstore'),
34 ('spath', 'obsstore'),
35 ('path', 'bookmarks'), # ! bookmark can change content at the same size
35 ('path', 'bookmarks'), # ! bookmark can change content at the same size
36 ]
36 ]
37
37
38 def makebreadcrumb(url, prefix=''):
38 def makebreadcrumb(url, prefix=''):
39 '''Return a 'URL breadcrumb' list
39 '''Return a 'URL breadcrumb' list
40
40
41 A 'URL breadcrumb' is a list of URL-name pairs,
41 A 'URL breadcrumb' is a list of URL-name pairs,
42 corresponding to each of the path items on a URL.
42 corresponding to each of the path items on a URL.
43 This can be used to create path navigation entries.
43 This can be used to create path navigation entries.
44 '''
44 '''
45 if url.endswith('/'):
45 if url.endswith('/'):
46 url = url[:-1]
46 url = url[:-1]
47 if prefix:
47 if prefix:
48 url = '/' + prefix + url
48 url = '/' + prefix + url
49 relpath = url
49 relpath = url
50 if relpath.startswith('/'):
50 if relpath.startswith('/'):
51 relpath = relpath[1:]
51 relpath = relpath[1:]
52
52
53 breadcrumb = []
53 breadcrumb = []
54 urlel = url
54 urlel = url
55 pathitems = [''] + relpath.split('/')
55 pathitems = [''] + relpath.split('/')
56 for pathel in reversed(pathitems):
56 for pathel in reversed(pathitems):
57 if not pathel or not urlel:
57 if not pathel or not urlel:
58 break
58 break
59 breadcrumb.append({'url': urlel, 'name': pathel})
59 breadcrumb.append({'url': urlel, 'name': pathel})
60 urlel = os.path.dirname(urlel)
60 urlel = os.path.dirname(urlel)
61 return reversed(breadcrumb)
61 return reversed(breadcrumb)
62
62
63 class requestcontext(object):
63 class requestcontext(object):
64 """Holds state/context for an individual request.
64 """Holds state/context for an individual request.
65
65
66 Servers can be multi-threaded. Holding state on the WSGI application
66 Servers can be multi-threaded. Holding state on the WSGI application
67 is prone to race conditions. Instances of this class exist to hold
67 is prone to race conditions. Instances of this class exist to hold
68 mutable and race-free state for requests.
68 mutable and race-free state for requests.
69 """
69 """
70 def __init__(self, app):
70 def __init__(self, app):
71 object.__setattr__(self, 'app', app)
71 object.__setattr__(self, 'app', app)
72 object.__setattr__(self, 'repo', app.repo)
72 object.__setattr__(self, 'repo', app.repo)
73
73
74 object.__setattr__(self, 'archives', ('zip', 'gz', 'bz2'))
74 object.__setattr__(self, 'archives', ('zip', 'gz', 'bz2'))
75
75
76 object.__setattr__(self, 'maxchanges',
76 object.__setattr__(self, 'maxchanges',
77 self.configint('web', 'maxchanges', 10))
77 self.configint('web', 'maxchanges', 10))
78 object.__setattr__(self, 'stripecount',
78 object.__setattr__(self, 'stripecount',
79 self.configint('web', 'stripes', 1))
79 self.configint('web', 'stripes', 1))
80 object.__setattr__(self, 'maxshortchanges',
80 object.__setattr__(self, 'maxshortchanges',
81 self.configint('web', 'maxshortchanges', 60))
81 self.configint('web', 'maxshortchanges', 60))
82 object.__setattr__(self, 'maxfiles',
82 object.__setattr__(self, 'maxfiles',
83 self.configint('web', 'maxfiles', 10))
83 self.configint('web', 'maxfiles', 10))
84 object.__setattr__(self, 'allowpull',
84 object.__setattr__(self, 'allowpull',
85 self.configbool('web', 'allowpull', True))
85 self.configbool('web', 'allowpull', True))
86
86
87 # we use untrusted=False to prevent a repo owner from using
88 # web.templates in .hg/hgrc to get access to any file readable
89 # by the user running the CGI script
90 object.__setattr__(self, 'templatepath',
91 self.config('web', 'templates', untrusted=False))
92
87 # Proxy unknown reads and writes to the application instance
93 # Proxy unknown reads and writes to the application instance
88 # until everything is moved to us.
94 # until everything is moved to us.
89 def __getattr__(self, name):
95 def __getattr__(self, name):
90 return getattr(self.app, name)
96 return getattr(self.app, name)
91
97
92 def __setattr__(self, name, value):
98 def __setattr__(self, name, value):
93 return setattr(self.app, name, value)
99 return setattr(self.app, name, value)
94
100
95 # Servers are often run by a user different from the repo owner.
101 # Servers are often run by a user different from the repo owner.
96 # Trust the settings from the .hg/hgrc files by default.
102 # Trust the settings from the .hg/hgrc files by default.
97 def config(self, section, name, default=None, untrusted=True):
103 def config(self, section, name, default=None, untrusted=True):
98 return self.repo.ui.config(section, name, default,
104 return self.repo.ui.config(section, name, default,
99 untrusted=untrusted)
105 untrusted=untrusted)
100
106
101 def configbool(self, section, name, default=False, untrusted=True):
107 def configbool(self, section, name, default=False, untrusted=True):
102 return self.repo.ui.configbool(section, name, default,
108 return self.repo.ui.configbool(section, name, default,
103 untrusted=untrusted)
109 untrusted=untrusted)
104
110
105 def configint(self, section, name, default=None, untrusted=True):
111 def configint(self, section, name, default=None, untrusted=True):
106 return self.repo.ui.configint(section, name, default,
112 return self.repo.ui.configint(section, name, default,
107 untrusted=untrusted)
113 untrusted=untrusted)
108
114
109 def configlist(self, section, name, default=None, untrusted=True):
115 def configlist(self, section, name, default=None, untrusted=True):
110 return self.repo.ui.configlist(section, name, default,
116 return self.repo.ui.configlist(section, name, default,
111 untrusted=untrusted)
117 untrusted=untrusted)
112
118
113 archivespecs = {
119 archivespecs = {
114 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
120 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
115 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
121 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
116 'zip': ('application/zip', 'zip', '.zip', None),
122 'zip': ('application/zip', 'zip', '.zip', None),
117 }
123 }
118
124
119 def archivelist(self, nodeid):
125 def archivelist(self, nodeid):
120 allowed = self.configlist('web', 'allow_archive')
126 allowed = self.configlist('web', 'allow_archive')
121 for typ, spec in self.archivespecs.iteritems():
127 for typ, spec in self.archivespecs.iteritems():
122 if typ in allowed or self.configbool('web', 'allow%s' % typ):
128 if typ in allowed or self.configbool('web', 'allow%s' % typ):
123 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
129 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
124
130
125 class hgweb(object):
131 class hgweb(object):
126 """HTTP server for individual repositories.
132 """HTTP server for individual repositories.
127
133
128 Instances of this class serve HTTP responses for a particular
134 Instances of this class serve HTTP responses for a particular
129 repository.
135 repository.
130
136
131 Instances are typically used as WSGI applications.
137 Instances are typically used as WSGI applications.
132
138
133 Some servers are multi-threaded. On these servers, there may
139 Some servers are multi-threaded. On these servers, there may
134 be multiple active threads inside __call__.
140 be multiple active threads inside __call__.
135 """
141 """
136 def __init__(self, repo, name=None, baseui=None):
142 def __init__(self, repo, name=None, baseui=None):
137 if isinstance(repo, str):
143 if isinstance(repo, str):
138 if baseui:
144 if baseui:
139 u = baseui.copy()
145 u = baseui.copy()
140 else:
146 else:
141 u = ui.ui()
147 u = ui.ui()
142 r = hg.repository(u, repo)
148 r = hg.repository(u, repo)
143 else:
149 else:
144 # we trust caller to give us a private copy
150 # we trust caller to give us a private copy
145 r = repo
151 r = repo
146
152
147 r = self._getview(r)
153 r = self._getview(r)
148 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
154 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
149 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
155 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
150 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
156 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
151 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
157 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
152 # displaying bundling progress bar while serving feel wrong and may
158 # displaying bundling progress bar while serving feel wrong and may
153 # break some wsgi implementation.
159 # break some wsgi implementation.
154 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
160 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
155 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
161 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
156 self.repo = r
162 self.repo = r
157 hook.redirect(True)
163 hook.redirect(True)
158 self.repostate = None
164 self.repostate = None
159 self.mtime = -1
165 self.mtime = -1
160 self.reponame = name
166 self.reponame = name
161 # we use untrusted=False to prevent a repo owner from using
162 # web.templates in .hg/hgrc to get access to any file readable
163 # by the user running the CGI script
164 self.templatepath = self.config('web', 'templates', untrusted=False)
165 self.websubtable = webutil.getwebsubs(r)
167 self.websubtable = webutil.getwebsubs(r)
166
168
167 # The CGI scripts are often run by a user different from the repo owner.
169 # The CGI scripts are often run by a user different from the repo owner.
168 # Trust the settings from the .hg/hgrc files by default.
170 # Trust the settings from the .hg/hgrc files by default.
169 def config(self, section, name, default=None, untrusted=True):
171 def config(self, section, name, default=None, untrusted=True):
170 return self.repo.ui.config(section, name, default,
172 return self.repo.ui.config(section, name, default,
171 untrusted=untrusted)
173 untrusted=untrusted)
172
174
173 def _getview(self, repo):
175 def _getview(self, repo):
174 """The 'web.view' config controls changeset filter to hgweb. Possible
176 """The 'web.view' config controls changeset filter to hgweb. Possible
175 values are ``served``, ``visible`` and ``all``. Default is ``served``.
177 values are ``served``, ``visible`` and ``all``. Default is ``served``.
176 The ``served`` filter only shows changesets that can be pulled from the
178 The ``served`` filter only shows changesets that can be pulled from the
177 hgweb instance. The``visible`` filter includes secret changesets but
179 hgweb instance. The``visible`` filter includes secret changesets but
178 still excludes "hidden" one.
180 still excludes "hidden" one.
179
181
180 See the repoview module for details.
182 See the repoview module for details.
181
183
182 The option has been around undocumented since Mercurial 2.5, but no
184 The option has been around undocumented since Mercurial 2.5, but no
183 user ever asked about it. So we better keep it undocumented for now."""
185 user ever asked about it. So we better keep it undocumented for now."""
184 viewconfig = repo.ui.config('web', 'view', 'served',
186 viewconfig = repo.ui.config('web', 'view', 'served',
185 untrusted=True)
187 untrusted=True)
186 if viewconfig == 'all':
188 if viewconfig == 'all':
187 return repo.unfiltered()
189 return repo.unfiltered()
188 elif viewconfig in repoview.filtertable:
190 elif viewconfig in repoview.filtertable:
189 return repo.filtered(viewconfig)
191 return repo.filtered(viewconfig)
190 else:
192 else:
191 return repo.filtered('served')
193 return repo.filtered('served')
192
194
193 def refresh(self):
195 def refresh(self):
194 repostate = []
196 repostate = []
195 # file of interrests mtime and size
197 # file of interrests mtime and size
196 for meth, fname in foi:
198 for meth, fname in foi:
197 prefix = getattr(self.repo, meth)
199 prefix = getattr(self.repo, meth)
198 st = get_stat(prefix, fname)
200 st = get_stat(prefix, fname)
199 repostate.append((st.st_mtime, st.st_size))
201 repostate.append((st.st_mtime, st.st_size))
200 repostate = tuple(repostate)
202 repostate = tuple(repostate)
201 # we need to compare file size in addition to mtime to catch
203 # we need to compare file size in addition to mtime to catch
202 # changes made less than a second ago
204 # changes made less than a second ago
203 if repostate != self.repostate:
205 if repostate != self.repostate:
204 r = hg.repository(self.repo.baseui, self.repo.url())
206 r = hg.repository(self.repo.baseui, self.repo.url())
205 self.repo = self._getview(r)
207 self.repo = self._getview(r)
206 # update these last to avoid threads seeing empty settings
208 # update these last to avoid threads seeing empty settings
207 self.repostate = repostate
209 self.repostate = repostate
208 # mtime is needed for ETag
210 # mtime is needed for ETag
209 self.mtime = st.st_mtime
211 self.mtime = st.st_mtime
210
212
211 def run(self):
213 def run(self):
212 """Start a server from CGI environment.
214 """Start a server from CGI environment.
213
215
214 Modern servers should be using WSGI and should avoid this
216 Modern servers should be using WSGI and should avoid this
215 method, if possible.
217 method, if possible.
216 """
218 """
217 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
219 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
218 raise RuntimeError("This function is only intended to be "
220 raise RuntimeError("This function is only intended to be "
219 "called while running as a CGI script.")
221 "called while running as a CGI script.")
220 import mercurial.hgweb.wsgicgi as wsgicgi
222 import mercurial.hgweb.wsgicgi as wsgicgi
221 wsgicgi.launch(self)
223 wsgicgi.launch(self)
222
224
223 def __call__(self, env, respond):
225 def __call__(self, env, respond):
224 """Run the WSGI application.
226 """Run the WSGI application.
225
227
226 This may be called by multiple threads.
228 This may be called by multiple threads.
227 """
229 """
228 req = wsgirequest(env, respond)
230 req = wsgirequest(env, respond)
229 return self.run_wsgi(req)
231 return self.run_wsgi(req)
230
232
231 def run_wsgi(self, req):
233 def run_wsgi(self, req):
232 """Internal method to run the WSGI application.
234 """Internal method to run the WSGI application.
233
235
234 This is typically only called by Mercurial. External consumers
236 This is typically only called by Mercurial. External consumers
235 should be using instances of this class as the WSGI application.
237 should be using instances of this class as the WSGI application.
236 """
238 """
237 self.refresh()
239 self.refresh()
238 rctx = requestcontext(self)
240 rctx = requestcontext(self)
239
241
240 # This state is global across all threads.
242 # This state is global across all threads.
241 encoding.encoding = rctx.config('web', 'encoding', encoding.encoding)
243 encoding.encoding = rctx.config('web', 'encoding', encoding.encoding)
242 rctx.repo.ui.environ = req.env
244 rctx.repo.ui.environ = req.env
243
245
244 # work with CGI variables to create coherent structure
246 # work with CGI variables to create coherent structure
245 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
247 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
246
248
247 req.url = req.env['SCRIPT_NAME']
249 req.url = req.env['SCRIPT_NAME']
248 if not req.url.endswith('/'):
250 if not req.url.endswith('/'):
249 req.url += '/'
251 req.url += '/'
250 if 'REPO_NAME' in req.env:
252 if 'REPO_NAME' in req.env:
251 req.url += req.env['REPO_NAME'] + '/'
253 req.url += req.env['REPO_NAME'] + '/'
252
254
253 if 'PATH_INFO' in req.env:
255 if 'PATH_INFO' in req.env:
254 parts = req.env['PATH_INFO'].strip('/').split('/')
256 parts = req.env['PATH_INFO'].strip('/').split('/')
255 repo_parts = req.env.get('REPO_NAME', '').split('/')
257 repo_parts = req.env.get('REPO_NAME', '').split('/')
256 if parts[:len(repo_parts)] == repo_parts:
258 if parts[:len(repo_parts)] == repo_parts:
257 parts = parts[len(repo_parts):]
259 parts = parts[len(repo_parts):]
258 query = '/'.join(parts)
260 query = '/'.join(parts)
259 else:
261 else:
260 query = req.env['QUERY_STRING'].split('&', 1)[0]
262 query = req.env['QUERY_STRING'].split('&', 1)[0]
261 query = query.split(';', 1)[0]
263 query = query.split(';', 1)[0]
262
264
263 # process this if it's a protocol request
265 # process this if it's a protocol request
264 # protocol bits don't need to create any URLs
266 # protocol bits don't need to create any URLs
265 # and the clients always use the old URL structure
267 # and the clients always use the old URL structure
266
268
267 cmd = req.form.get('cmd', [''])[0]
269 cmd = req.form.get('cmd', [''])[0]
268 if protocol.iscmd(cmd):
270 if protocol.iscmd(cmd):
269 try:
271 try:
270 if query:
272 if query:
271 raise ErrorResponse(HTTP_NOT_FOUND)
273 raise ErrorResponse(HTTP_NOT_FOUND)
272 if cmd in perms:
274 if cmd in perms:
273 self.check_perm(rctx, req, perms[cmd])
275 self.check_perm(rctx, req, perms[cmd])
274 return protocol.call(self.repo, req, cmd)
276 return protocol.call(self.repo, req, cmd)
275 except ErrorResponse as inst:
277 except ErrorResponse as inst:
276 # A client that sends unbundle without 100-continue will
278 # A client that sends unbundle without 100-continue will
277 # break if we respond early.
279 # break if we respond early.
278 if (cmd == 'unbundle' and
280 if (cmd == 'unbundle' and
279 (req.env.get('HTTP_EXPECT',
281 (req.env.get('HTTP_EXPECT',
280 '').lower() != '100-continue') or
282 '').lower() != '100-continue') or
281 req.env.get('X-HgHttp2', '')):
283 req.env.get('X-HgHttp2', '')):
282 req.drain()
284 req.drain()
283 else:
285 else:
284 req.headers.append(('Connection', 'Close'))
286 req.headers.append(('Connection', 'Close'))
285 req.respond(inst, protocol.HGTYPE,
287 req.respond(inst, protocol.HGTYPE,
286 body='0\n%s\n' % inst.message)
288 body='0\n%s\n' % inst.message)
287 return ''
289 return ''
288
290
289 # translate user-visible url structure to internal structure
291 # translate user-visible url structure to internal structure
290
292
291 args = query.split('/', 2)
293 args = query.split('/', 2)
292 if 'cmd' not in req.form and args and args[0]:
294 if 'cmd' not in req.form and args and args[0]:
293
295
294 cmd = args.pop(0)
296 cmd = args.pop(0)
295 style = cmd.rfind('-')
297 style = cmd.rfind('-')
296 if style != -1:
298 if style != -1:
297 req.form['style'] = [cmd[:style]]
299 req.form['style'] = [cmd[:style]]
298 cmd = cmd[style + 1:]
300 cmd = cmd[style + 1:]
299
301
300 # avoid accepting e.g. style parameter as command
302 # avoid accepting e.g. style parameter as command
301 if util.safehasattr(webcommands, cmd):
303 if util.safehasattr(webcommands, cmd):
302 req.form['cmd'] = [cmd]
304 req.form['cmd'] = [cmd]
303
305
304 if cmd == 'static':
306 if cmd == 'static':
305 req.form['file'] = ['/'.join(args)]
307 req.form['file'] = ['/'.join(args)]
306 else:
308 else:
307 if args and args[0]:
309 if args and args[0]:
308 node = args.pop(0).replace('%2F', '/')
310 node = args.pop(0).replace('%2F', '/')
309 req.form['node'] = [node]
311 req.form['node'] = [node]
310 if args:
312 if args:
311 req.form['file'] = args
313 req.form['file'] = args
312
314
313 ua = req.env.get('HTTP_USER_AGENT', '')
315 ua = req.env.get('HTTP_USER_AGENT', '')
314 if cmd == 'rev' and 'mercurial' in ua:
316 if cmd == 'rev' and 'mercurial' in ua:
315 req.form['style'] = ['raw']
317 req.form['style'] = ['raw']
316
318
317 if cmd == 'archive':
319 if cmd == 'archive':
318 fn = req.form['node'][0]
320 fn = req.form['node'][0]
319 for type_, spec in rctx.archivespecs.iteritems():
321 for type_, spec in rctx.archivespecs.iteritems():
320 ext = spec[2]
322 ext = spec[2]
321 if fn.endswith(ext):
323 if fn.endswith(ext):
322 req.form['node'] = [fn[:-len(ext)]]
324 req.form['node'] = [fn[:-len(ext)]]
323 req.form['type'] = [type_]
325 req.form['type'] = [type_]
324
326
325 # process the web interface request
327 # process the web interface request
326
328
327 try:
329 try:
328 tmpl = self.templater(req)
330 tmpl = self.templater(req)
329 ctype = tmpl('mimetype', encoding=encoding.encoding)
331 ctype = tmpl('mimetype', encoding=encoding.encoding)
330 ctype = templater.stringify(ctype)
332 ctype = templater.stringify(ctype)
331
333
332 # check read permissions non-static content
334 # check read permissions non-static content
333 if cmd != 'static':
335 if cmd != 'static':
334 self.check_perm(rctx, req, None)
336 self.check_perm(rctx, req, None)
335
337
336 if cmd == '':
338 if cmd == '':
337 req.form['cmd'] = [tmpl.cache['default']]
339 req.form['cmd'] = [tmpl.cache['default']]
338 cmd = req.form['cmd'][0]
340 cmd = req.form['cmd'][0]
339
341
340 if rctx.configbool('web', 'cache', True):
342 if rctx.configbool('web', 'cache', True):
341 caching(self, req) # sets ETag header or raises NOT_MODIFIED
343 caching(self, req) # sets ETag header or raises NOT_MODIFIED
342 if cmd not in webcommands.__all__:
344 if cmd not in webcommands.__all__:
343 msg = 'no such method: %s' % cmd
345 msg = 'no such method: %s' % cmd
344 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
346 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
345 elif cmd == 'file' and 'raw' in req.form.get('style', []):
347 elif cmd == 'file' and 'raw' in req.form.get('style', []):
346 self.ctype = ctype
348 self.ctype = ctype
347 content = webcommands.rawfile(rctx, req, tmpl)
349 content = webcommands.rawfile(rctx, req, tmpl)
348 else:
350 else:
349 content = getattr(webcommands, cmd)(rctx, req, tmpl)
351 content = getattr(webcommands, cmd)(rctx, req, tmpl)
350 req.respond(HTTP_OK, ctype)
352 req.respond(HTTP_OK, ctype)
351
353
352 return content
354 return content
353
355
354 except (error.LookupError, error.RepoLookupError) as err:
356 except (error.LookupError, error.RepoLookupError) as err:
355 req.respond(HTTP_NOT_FOUND, ctype)
357 req.respond(HTTP_NOT_FOUND, ctype)
356 msg = str(err)
358 msg = str(err)
357 if (util.safehasattr(err, 'name') and
359 if (util.safehasattr(err, 'name') and
358 not isinstance(err, error.ManifestLookupError)):
360 not isinstance(err, error.ManifestLookupError)):
359 msg = 'revision not found: %s' % err.name
361 msg = 'revision not found: %s' % err.name
360 return tmpl('error', error=msg)
362 return tmpl('error', error=msg)
361 except (error.RepoError, error.RevlogError) as inst:
363 except (error.RepoError, error.RevlogError) as inst:
362 req.respond(HTTP_SERVER_ERROR, ctype)
364 req.respond(HTTP_SERVER_ERROR, ctype)
363 return tmpl('error', error=str(inst))
365 return tmpl('error', error=str(inst))
364 except ErrorResponse as inst:
366 except ErrorResponse as inst:
365 req.respond(inst, ctype)
367 req.respond(inst, ctype)
366 if inst.code == HTTP_NOT_MODIFIED:
368 if inst.code == HTTP_NOT_MODIFIED:
367 # Not allowed to return a body on a 304
369 # Not allowed to return a body on a 304
368 return ['']
370 return ['']
369 return tmpl('error', error=inst.message)
371 return tmpl('error', error=inst.message)
370
372
371 def templater(self, req):
373 def templater(self, req):
372
374
373 # determine scheme, port and server name
375 # determine scheme, port and server name
374 # this is needed to create absolute urls
376 # this is needed to create absolute urls
375
377
376 proto = req.env.get('wsgi.url_scheme')
378 proto = req.env.get('wsgi.url_scheme')
377 if proto == 'https':
379 if proto == 'https':
378 proto = 'https'
380 proto = 'https'
379 default_port = "443"
381 default_port = "443"
380 else:
382 else:
381 proto = 'http'
383 proto = 'http'
382 default_port = "80"
384 default_port = "80"
383
385
384 port = req.env["SERVER_PORT"]
386 port = req.env["SERVER_PORT"]
385 port = port != default_port and (":" + port) or ""
387 port = port != default_port and (":" + port) or ""
386 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
388 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
387 logourl = self.config("web", "logourl", "http://mercurial.selenic.com/")
389 logourl = self.config("web", "logourl", "http://mercurial.selenic.com/")
388 logoimg = self.config("web", "logoimg", "hglogo.png")
390 logoimg = self.config("web", "logoimg", "hglogo.png")
389 staticurl = self.config("web", "staticurl") or req.url + 'static/'
391 staticurl = self.config("web", "staticurl") or req.url + 'static/'
390 if not staticurl.endswith('/'):
392 if not staticurl.endswith('/'):
391 staticurl += '/'
393 staticurl += '/'
392
394
393 # some functions for the templater
395 # some functions for the templater
394
396
395 def motd(**map):
397 def motd(**map):
396 yield self.config("web", "motd", "")
398 yield self.config("web", "motd", "")
397
399
398 # figure out which style to use
400 # figure out which style to use
399
401
400 vars = {}
402 vars = {}
401 styles = (
403 styles = (
402 req.form.get('style', [None])[0],
404 req.form.get('style', [None])[0],
403 self.config('web', 'style'),
405 self.config('web', 'style'),
404 'paper',
406 'paper',
405 )
407 )
406 style, mapfile = templater.stylemap(styles, self.templatepath)
408 style, mapfile = templater.stylemap(styles, self.templatepath)
407 if style == styles[0]:
409 if style == styles[0]:
408 vars['style'] = style
410 vars['style'] = style
409
411
410 start = req.url[-1] == '?' and '&' or '?'
412 start = req.url[-1] == '?' and '&' or '?'
411 sessionvars = webutil.sessionvars(vars, start)
413 sessionvars = webutil.sessionvars(vars, start)
412
414
413 if not self.reponame:
415 if not self.reponame:
414 self.reponame = (self.config("web", "name")
416 self.reponame = (self.config("web", "name")
415 or req.env.get('REPO_NAME')
417 or req.env.get('REPO_NAME')
416 or req.url.strip('/') or self.repo.root)
418 or req.url.strip('/') or self.repo.root)
417
419
418 def websubfilter(text):
420 def websubfilter(text):
419 return websub(text, self.websubtable)
421 return websub(text, self.websubtable)
420
422
421 # create the templater
423 # create the templater
422
424
423 tmpl = templater.templater(mapfile,
425 tmpl = templater.templater(mapfile,
424 filters={"websub": websubfilter},
426 filters={"websub": websubfilter},
425 defaults={"url": req.url,
427 defaults={"url": req.url,
426 "logourl": logourl,
428 "logourl": logourl,
427 "logoimg": logoimg,
429 "logoimg": logoimg,
428 "staticurl": staticurl,
430 "staticurl": staticurl,
429 "urlbase": urlbase,
431 "urlbase": urlbase,
430 "repo": self.reponame,
432 "repo": self.reponame,
431 "encoding": encoding.encoding,
433 "encoding": encoding.encoding,
432 "motd": motd,
434 "motd": motd,
433 "sessionvars": sessionvars,
435 "sessionvars": sessionvars,
434 "pathdef": makebreadcrumb(req.url),
436 "pathdef": makebreadcrumb(req.url),
435 "style": style,
437 "style": style,
436 })
438 })
437 return tmpl
439 return tmpl
438
440
439 def check_perm(self, rctx, req, op):
441 def check_perm(self, rctx, req, op):
440 for permhook in permhooks:
442 for permhook in permhooks:
441 permhook(rctx, req, op)
443 permhook(rctx, req, op)
General Comments 0
You need to be logged in to leave comments. Login now