##// END OF EJS Templates
hgweb: pass ErrorResponses directly into req.respond()
Dirkjan Ochtman -
r7740:176d3d68 default
parent child Browse files
Show More
@@ -1,313 +1,313 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os, mimetypes
10 10 from mercurial.node import hex, nullid
11 11 from mercurial import ui, hg, util, hook, error
12 12 from mercurial import templater, templatefilters
13 13 from common import get_mtime, style_map, ErrorResponse
14 14 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 15 from common import HTTP_UNAUTHORIZED, HTTP_METHOD_NOT_ALLOWED
16 16 from request import wsgirequest
17 17 import webcommands, protocol, webutil
18 18
19 19 perms = {
20 20 'changegroup': 'pull',
21 21 'changegroupsubset': 'pull',
22 22 'unbundle': 'push',
23 23 'stream_out': 'pull',
24 24 }
25 25
26 26 class hgweb(object):
27 27 def __init__(self, repo, name=None):
28 28 if isinstance(repo, str):
29 29 parentui = ui.ui(report_untrusted=False, interactive=False)
30 30 self.repo = hg.repository(parentui, repo)
31 31 else:
32 32 self.repo = repo
33 33
34 34 hook.redirect(True)
35 35 self.mtime = -1
36 36 self.reponame = name
37 37 self.archives = 'zip', 'gz', 'bz2'
38 38 self.stripecount = 1
39 39 # a repo owner may set web.templates in .hg/hgrc to get any file
40 40 # readable by the user running the CGI script
41 41 self.templatepath = self.config("web", "templates",
42 42 templater.templatepath(),
43 43 untrusted=False)
44 44
45 45 # The CGI scripts are often run by a user different from the repo owner.
46 46 # Trust the settings from the .hg/hgrc files by default.
47 47 def config(self, section, name, default=None, untrusted=True):
48 48 return self.repo.ui.config(section, name, default,
49 49 untrusted=untrusted)
50 50
51 51 def configbool(self, section, name, default=False, untrusted=True):
52 52 return self.repo.ui.configbool(section, name, default,
53 53 untrusted=untrusted)
54 54
55 55 def configlist(self, section, name, default=None, untrusted=True):
56 56 return self.repo.ui.configlist(section, name, default,
57 57 untrusted=untrusted)
58 58
59 59 def refresh(self):
60 60 mtime = get_mtime(self.repo.root)
61 61 if mtime != self.mtime:
62 62 self.mtime = mtime
63 63 self.repo = hg.repository(self.repo.ui, self.repo.root)
64 64 self.maxchanges = int(self.config("web", "maxchanges", 10))
65 65 self.stripecount = int(self.config("web", "stripes", 1))
66 66 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
67 67 self.maxfiles = int(self.config("web", "maxfiles", 10))
68 68 self.allowpull = self.configbool("web", "allowpull", True)
69 69 self.encoding = self.config("web", "encoding", util._encoding)
70 70
71 71 def run(self):
72 72 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
73 73 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
74 74 import mercurial.hgweb.wsgicgi as wsgicgi
75 75 wsgicgi.launch(self)
76 76
77 77 def __call__(self, env, respond):
78 78 req = wsgirequest(env, respond)
79 79 return self.run_wsgi(req)
80 80
81 81 def run_wsgi(self, req):
82 82
83 83 self.refresh()
84 84
85 85 # process this if it's a protocol request
86 86 # protocol bits don't need to create any URLs
87 87 # and the clients always use the old URL structure
88 88
89 89 cmd = req.form.get('cmd', [''])[0]
90 90 if cmd and cmd in protocol.__all__:
91 91 try:
92 92 if cmd in perms:
93 93 try:
94 94 self.check_perm(req, perms[cmd])
95 95 except ErrorResponse, inst:
96 96 if cmd == 'unbundle':
97 97 req.drain()
98 98 raise
99 99 method = getattr(protocol, cmd)
100 100 return method(self.repo, req)
101 101 except ErrorResponse, inst:
102 req.respond(inst.code, protocol.HGTYPE)
102 req.respond(inst, protocol.HGTYPE)
103 103 if not inst.message:
104 104 return []
105 105 return '0\n%s\n' % inst.message,
106 106
107 107 # work with CGI variables to create coherent structure
108 108 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
109 109
110 110 req.url = req.env['SCRIPT_NAME']
111 111 if not req.url.endswith('/'):
112 112 req.url += '/'
113 113 if 'REPO_NAME' in req.env:
114 114 req.url += req.env['REPO_NAME'] + '/'
115 115
116 116 if 'PATH_INFO' in req.env:
117 117 parts = req.env['PATH_INFO'].strip('/').split('/')
118 118 repo_parts = req.env.get('REPO_NAME', '').split('/')
119 119 if parts[:len(repo_parts)] == repo_parts:
120 120 parts = parts[len(repo_parts):]
121 121 query = '/'.join(parts)
122 122 else:
123 123 query = req.env['QUERY_STRING'].split('&', 1)[0]
124 124 query = query.split(';', 1)[0]
125 125
126 126 # translate user-visible url structure to internal structure
127 127
128 128 args = query.split('/', 2)
129 129 if 'cmd' not in req.form and args and args[0]:
130 130
131 131 cmd = args.pop(0)
132 132 style = cmd.rfind('-')
133 133 if style != -1:
134 134 req.form['style'] = [cmd[:style]]
135 135 cmd = cmd[style+1:]
136 136
137 137 # avoid accepting e.g. style parameter as command
138 138 if hasattr(webcommands, cmd):
139 139 req.form['cmd'] = [cmd]
140 140 else:
141 141 cmd = ''
142 142
143 143 if cmd == 'static':
144 144 req.form['file'] = ['/'.join(args)]
145 145 else:
146 146 if args and args[0]:
147 147 node = args.pop(0)
148 148 req.form['node'] = [node]
149 149 if args:
150 150 req.form['file'] = args
151 151
152 152 if cmd == 'archive':
153 153 fn = req.form['node'][0]
154 154 for type_, spec in self.archive_specs.iteritems():
155 155 ext = spec[2]
156 156 if fn.endswith(ext):
157 157 req.form['node'] = [fn[:-len(ext)]]
158 158 req.form['type'] = [type_]
159 159
160 160 # process the web interface request
161 161
162 162 try:
163 163 tmpl = self.templater(req)
164 164 ctype = tmpl('mimetype', encoding=self.encoding)
165 165 ctype = templater.stringify(ctype)
166 166
167 167 # check read permissions non-static content
168 168 if cmd != 'static':
169 169 self.check_perm(req, None)
170 170
171 171 if cmd == '':
172 172 req.form['cmd'] = [tmpl.cache['default']]
173 173 cmd = req.form['cmd'][0]
174 174
175 175 if cmd not in webcommands.__all__:
176 176 msg = 'no such method: %s' % cmd
177 177 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
178 178 elif cmd == 'file' and 'raw' in req.form.get('style', []):
179 179 self.ctype = ctype
180 180 content = webcommands.rawfile(self, req, tmpl)
181 181 else:
182 182 content = getattr(webcommands, cmd)(self, req, tmpl)
183 183 req.respond(HTTP_OK, ctype)
184 184
185 185 return content
186 186
187 187 except error.LookupError, err:
188 188 req.respond(HTTP_NOT_FOUND, ctype)
189 189 msg = str(err)
190 190 if 'manifest' not in msg:
191 191 msg = 'revision not found: %s' % err.name
192 192 return tmpl('error', error=msg)
193 193 except (error.RepoError, error.RevlogError), inst:
194 194 req.respond(HTTP_SERVER_ERROR, ctype)
195 195 return tmpl('error', error=str(inst))
196 196 except ErrorResponse, inst:
197 req.respond(inst.code, ctype)
197 req.respond(inst, ctype)
198 198 return tmpl('error', error=inst.message)
199 199
200 200 def templater(self, req):
201 201
202 202 # determine scheme, port and server name
203 203 # this is needed to create absolute urls
204 204
205 205 proto = req.env.get('wsgi.url_scheme')
206 206 if proto == 'https':
207 207 proto = 'https'
208 208 default_port = "443"
209 209 else:
210 210 proto = 'http'
211 211 default_port = "80"
212 212
213 213 port = req.env["SERVER_PORT"]
214 214 port = port != default_port and (":" + port) or ""
215 215 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
216 216 staticurl = self.config("web", "staticurl") or req.url + 'static/'
217 217 if not staticurl.endswith('/'):
218 218 staticurl += '/'
219 219
220 220 # some functions for the templater
221 221
222 222 def header(**map):
223 223 yield tmpl('header', encoding=self.encoding, **map)
224 224
225 225 def footer(**map):
226 226 yield tmpl("footer", **map)
227 227
228 228 def motd(**map):
229 229 yield self.config("web", "motd", "")
230 230
231 231 # figure out which style to use
232 232
233 233 vars = {}
234 234 style = self.config("web", "style", "paper")
235 235 if 'style' in req.form:
236 236 style = req.form['style'][0]
237 237 vars['style'] = style
238 238
239 239 start = req.url[-1] == '?' and '&' or '?'
240 240 sessionvars = webutil.sessionvars(vars, start)
241 241 mapfile = style_map(self.templatepath, style)
242 242
243 243 if not self.reponame:
244 244 self.reponame = (self.config("web", "name")
245 245 or req.env.get('REPO_NAME')
246 246 or req.url.strip('/') or self.repo.root)
247 247
248 248 # create the templater
249 249
250 250 tmpl = templater.templater(mapfile, templatefilters.filters,
251 251 defaults={"url": req.url,
252 252 "staticurl": staticurl,
253 253 "urlbase": urlbase,
254 254 "repo": self.reponame,
255 255 "header": header,
256 256 "footer": footer,
257 257 "motd": motd,
258 258 "sessionvars": sessionvars
259 259 })
260 260 return tmpl
261 261
262 262 def archivelist(self, nodeid):
263 263 allowed = self.configlist("web", "allow_archive")
264 264 for i, spec in self.archive_specs.iteritems():
265 265 if i in allowed or self.configbool("web", "allow" + i):
266 266 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
267 267
268 268 archive_specs = {
269 269 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
270 270 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
271 271 'zip': ('application/zip', 'zip', '.zip', None),
272 272 }
273 273
274 274 def check_perm(self, req, op):
275 275 '''Check permission for operation based on request data (including
276 276 authentication info). Return if op allowed, else raise an ErrorResponse
277 277 exception.'''
278 278
279 279 user = req.env.get('REMOTE_USER')
280 280
281 281 deny_read = self.configlist('web', 'deny_read')
282 282 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
283 283 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
284 284
285 285 allow_read = self.configlist('web', 'allow_read')
286 286 result = (not allow_read) or (allow_read == ['*'])
287 287 if not result or user in allow_read:
288 288 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
289 289
290 290 if op == 'pull' and not self.allowpull:
291 291 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
292 292 elif op == 'pull' or op is None: # op is None for interface requests
293 293 return
294 294
295 295 # enforce that you can only push using POST requests
296 296 if req.env['REQUEST_METHOD'] != 'POST':
297 297 msg = 'push requires POST request'
298 298 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
299 299
300 300 # require ssl by default for pushing, auth info cannot be sniffed
301 301 # and replayed
302 302 scheme = req.env.get('wsgi.url_scheme')
303 303 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
304 304 raise ErrorResponse(HTTP_OK, 'ssl required')
305 305
306 306 deny = self.configlist('web', 'deny_push')
307 307 if deny and (not user or deny == ['*'] or user in deny):
308 308 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
309 309
310 310 allow = self.configlist('web', 'allow_push')
311 311 result = allow and (allow == ['*'] or user in allow)
312 312 if not result:
313 313 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
@@ -1,327 +1,327 b''
1 1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os
10 10 from mercurial.i18n import _
11 11 from mercurial import ui, hg, util, templater, templatefilters, error
12 12 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen,\
13 13 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 14 from hgweb_mod import hgweb
15 15 from request import wsgirequest
16 16
17 17 # This is a stopgap
18 18 class hgwebdir(object):
19 19 def __init__(self, config, parentui=None):
20 20 def cleannames(items):
21 21 return [(util.pconvert(name).strip('/'), path)
22 22 for name, path in items]
23 23
24 24 self.parentui = parentui or ui.ui(report_untrusted=False,
25 25 interactive = False)
26 26 self.motd = None
27 27 self.style = 'paper'
28 28 self.stripecount = None
29 29 self.repos_sorted = ('name', False)
30 30 self._baseurl = None
31 31 if isinstance(config, (list, tuple)):
32 32 self.repos = cleannames(config)
33 33 self.repos_sorted = ('', False)
34 34 elif isinstance(config, dict):
35 35 self.repos = util.sort(cleannames(config.items()))
36 36 else:
37 37 if isinstance(config, util.configparser):
38 38 cp = config
39 39 else:
40 40 cp = util.configparser()
41 41 cp.read(config)
42 42 self.repos = []
43 43 if cp.has_section('web'):
44 44 if cp.has_option('web', 'motd'):
45 45 self.motd = cp.get('web', 'motd')
46 46 if cp.has_option('web', 'style'):
47 47 self.style = cp.get('web', 'style')
48 48 if cp.has_option('web', 'stripes'):
49 49 self.stripecount = int(cp.get('web', 'stripes'))
50 50 if cp.has_option('web', 'baseurl'):
51 51 self._baseurl = cp.get('web', 'baseurl')
52 52 if cp.has_section('paths'):
53 53 paths = cleannames(cp.items('paths'))
54 54 for prefix, root in paths:
55 55 roothead, roottail = os.path.split(root)
56 56 # "foo = /bar/*" makes every subrepo of /bar/ to be
57 57 # mounted as foo/subrepo
58 58 # and "foo = /bar/**" does even recurse inside the
59 59 # subdirectories, remember to use it without working dir.
60 60 try:
61 61 recurse = {'*': False, '**': True}[roottail]
62 62 except KeyError:
63 63 self.repos.append((prefix, root))
64 64 continue
65 65 roothead = os.path.normpath(roothead)
66 66 for path in util.walkrepos(roothead, followsym=True,
67 67 recurse=recurse):
68 68 path = os.path.normpath(path)
69 69 name = util.pconvert(path[len(roothead):]).strip('/')
70 70 if prefix:
71 71 name = prefix + '/' + name
72 72 self.repos.append((name, path))
73 73 if cp.has_section('collections'):
74 74 for prefix, root in cp.items('collections'):
75 75 for path in util.walkrepos(root, followsym=True):
76 76 repo = os.path.normpath(path)
77 77 name = repo
78 78 if name.startswith(prefix):
79 79 name = name[len(prefix):]
80 80 self.repos.append((name.lstrip(os.sep), repo))
81 81 self.repos.sort()
82 82
83 83 def run(self):
84 84 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
85 85 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
86 86 import mercurial.hgweb.wsgicgi as wsgicgi
87 87 wsgicgi.launch(self)
88 88
89 89 def __call__(self, env, respond):
90 90 req = wsgirequest(env, respond)
91 91 return self.run_wsgi(req)
92 92
93 93 def read_allowed(self, ui, req):
94 94 """Check allow_read and deny_read config options of a repo's ui object
95 95 to determine user permissions. By default, with neither option set (or
96 96 both empty), allow all users to read the repo. There are two ways a
97 97 user can be denied read access: (1) deny_read is not empty, and the
98 98 user is unauthenticated or deny_read contains user (or *), and (2)
99 99 allow_read is not empty and the user is not in allow_read. Return True
100 100 if user is allowed to read the repo, else return False."""
101 101
102 102 user = req.env.get('REMOTE_USER')
103 103
104 104 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
105 105 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
106 106 return False
107 107
108 108 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
109 109 # by default, allow reading if no allow_read option has been set
110 110 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
111 111 return True
112 112
113 113 return False
114 114
115 115 def run_wsgi(self, req):
116 116
117 117 try:
118 118 try:
119 119
120 120 virtual = req.env.get("PATH_INFO", "").strip('/')
121 121 tmpl = self.templater(req)
122 122 ctype = tmpl('mimetype', encoding=util._encoding)
123 123 ctype = templater.stringify(ctype)
124 124
125 125 # a static file
126 126 if virtual.startswith('static/') or 'static' in req.form:
127 127 if virtual.startswith('static/'):
128 128 fname = virtual[7:]
129 129 else:
130 130 fname = req.form['static'][0]
131 131 static = templater.templatepath('static')
132 132 return (staticfile(static, fname, req),)
133 133
134 134 # top-level index
135 135 elif not virtual:
136 136 req.respond(HTTP_OK, ctype)
137 137 return self.makeindex(req, tmpl)
138 138
139 139 # nested indexes and hgwebs
140 140
141 141 repos = dict(self.repos)
142 142 while virtual:
143 143 real = repos.get(virtual)
144 144 if real:
145 145 req.env['REPO_NAME'] = virtual
146 146 try:
147 147 repo = hg.repository(self.parentui, real)
148 148 return hgweb(repo).run_wsgi(req)
149 149 except IOError, inst:
150 150 msg = inst.strerror
151 151 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
152 152 except error.RepoError, inst:
153 153 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
154 154
155 155 # browse subdirectories
156 156 subdir = virtual + '/'
157 157 if [r for r in repos if r.startswith(subdir)]:
158 158 req.respond(HTTP_OK, ctype)
159 159 return self.makeindex(req, tmpl, subdir)
160 160
161 161 up = virtual.rfind('/')
162 162 if up < 0:
163 163 break
164 164 virtual = virtual[:up]
165 165
166 166 # prefixes not found
167 167 req.respond(HTTP_NOT_FOUND, ctype)
168 168 return tmpl("notfound", repo=virtual)
169 169
170 170 except ErrorResponse, err:
171 req.respond(err.code, ctype)
171 req.respond(err, ctype)
172 172 return tmpl('error', error=err.message or '')
173 173 finally:
174 174 tmpl = None
175 175
176 176 def makeindex(self, req, tmpl, subdir=""):
177 177
178 178 def archivelist(ui, nodeid, url):
179 179 allowed = ui.configlist("web", "allow_archive", untrusted=True)
180 180 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
181 181 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
182 182 untrusted=True):
183 183 yield {"type" : i[0], "extension": i[1],
184 184 "node": nodeid, "url": url}
185 185
186 186 def entries(sortcolumn="", descending=False, subdir="", **map):
187 187 def sessionvars(**map):
188 188 fields = []
189 189 if 'style' in req.form:
190 190 style = req.form['style'][0]
191 191 if style != get('web', 'style', ''):
192 192 fields.append(('style', style))
193 193
194 194 separator = url[-1] == '?' and ';' or '?'
195 195 for name, value in fields:
196 196 yield dict(name=name, value=value, separator=separator)
197 197 separator = ';'
198 198
199 199 rows = []
200 200 parity = paritygen(self.stripecount)
201 201 for name, path in self.repos:
202 202 if not name.startswith(subdir):
203 203 continue
204 204 name = name[len(subdir):]
205 205
206 206 u = ui.ui(parentui=self.parentui)
207 207 try:
208 208 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
209 209 except Exception, e:
210 210 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
211 211 continue
212 212 def get(section, name, default=None):
213 213 return u.config(section, name, default, untrusted=True)
214 214
215 215 if u.configbool("web", "hidden", untrusted=True):
216 216 continue
217 217
218 218 if not self.read_allowed(u, req):
219 219 continue
220 220
221 221 parts = [name]
222 222 if 'PATH_INFO' in req.env:
223 223 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
224 224 if req.env['SCRIPT_NAME']:
225 225 parts.insert(0, req.env['SCRIPT_NAME'])
226 226 url = ('/'.join(parts).replace("//", "/")) + '/'
227 227
228 228 # update time with local timezone
229 229 try:
230 230 d = (get_mtime(path), util.makedate()[1])
231 231 except OSError:
232 232 continue
233 233
234 234 contact = get_contact(get)
235 235 description = get("web", "description", "")
236 236 name = get("web", "name", name)
237 237 row = dict(contact=contact or "unknown",
238 238 contact_sort=contact.upper() or "unknown",
239 239 name=name,
240 240 name_sort=name,
241 241 url=url,
242 242 description=description or "unknown",
243 243 description_sort=description.upper() or "unknown",
244 244 lastchange=d,
245 245 lastchange_sort=d[1]-d[0],
246 246 sessionvars=sessionvars,
247 247 archives=archivelist(u, "tip", url))
248 248 if (not sortcolumn
249 249 or (sortcolumn, descending) == self.repos_sorted):
250 250 # fast path for unsorted output
251 251 row['parity'] = parity.next()
252 252 yield row
253 253 else:
254 254 rows.append((row["%s_sort" % sortcolumn], row))
255 255 if rows:
256 256 rows.sort()
257 257 if descending:
258 258 rows.reverse()
259 259 for key, row in rows:
260 260 row['parity'] = parity.next()
261 261 yield row
262 262
263 263 sortable = ["name", "description", "contact", "lastchange"]
264 264 sortcolumn, descending = self.repos_sorted
265 265 if 'sort' in req.form:
266 266 sortcolumn = req.form['sort'][0]
267 267 descending = sortcolumn.startswith('-')
268 268 if descending:
269 269 sortcolumn = sortcolumn[1:]
270 270 if sortcolumn not in sortable:
271 271 sortcolumn = ""
272 272
273 273 sort = [("sort_%s" % column,
274 274 "%s%s" % ((not descending and column == sortcolumn)
275 275 and "-" or "", column))
276 276 for column in sortable]
277 277
278 278 if self._baseurl is not None:
279 279 req.env['SCRIPT_NAME'] = self._baseurl
280 280
281 281 return tmpl("index", entries=entries, subdir=subdir,
282 282 sortcolumn=sortcolumn, descending=descending,
283 283 **dict(sort))
284 284
285 285 def templater(self, req):
286 286
287 287 def header(**map):
288 288 yield tmpl('header', encoding=util._encoding, **map)
289 289
290 290 def footer(**map):
291 291 yield tmpl("footer", **map)
292 292
293 293 def motd(**map):
294 294 if self.motd is not None:
295 295 yield self.motd
296 296 else:
297 297 yield config('web', 'motd', '')
298 298
299 299 def config(section, name, default=None, untrusted=True):
300 300 return self.parentui.config(section, name, default, untrusted)
301 301
302 302 if self._baseurl is not None:
303 303 req.env['SCRIPT_NAME'] = self._baseurl
304 304
305 305 url = req.env.get('SCRIPT_NAME', '')
306 306 if not url.endswith('/'):
307 307 url += '/'
308 308
309 309 staticurl = config('web', 'staticurl') or url + 'static/'
310 310 if not staticurl.endswith('/'):
311 311 staticurl += '/'
312 312
313 313 style = self.style
314 314 if style is None:
315 315 style = config('web', 'style', '')
316 316 if 'style' in req.form:
317 317 style = req.form['style'][0]
318 318 if self.stripecount is None:
319 319 self.stripecount = int(config('web', 'stripes', 1))
320 320 mapfile = style_map(templater.templatepath(), style)
321 321 tmpl = templater.templater(mapfile, templatefilters.filters,
322 322 defaults={"header": header,
323 323 "footer": footer,
324 324 "motd": motd,
325 325 "url": url,
326 326 "staticurl": staticurl})
327 327 return tmpl
General Comments 0
You need to be logged in to leave comments. Login now