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