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