##// END OF EJS Templates
hgweb: drop the default argument for get_stat...
Pierre-Yves David -
r25717:46e2c570 default
parent child Browse files
Show More
@@ -1,192 +1,192 b''
1 1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
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 of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import errno, mimetypes, os
10 10
11 11 HTTP_OK = 200
12 12 HTTP_NOT_MODIFIED = 304
13 13 HTTP_BAD_REQUEST = 400
14 14 HTTP_UNAUTHORIZED = 401
15 15 HTTP_FORBIDDEN = 403
16 16 HTTP_NOT_FOUND = 404
17 17 HTTP_METHOD_NOT_ALLOWED = 405
18 18 HTTP_SERVER_ERROR = 500
19 19
20 20
21 21 def ismember(ui, username, userlist):
22 22 """Check if username is a member of userlist.
23 23
24 24 If userlist has a single '*' member, all users are considered members.
25 25 Can be overridden by extensions to provide more complex authorization
26 26 schemes.
27 27 """
28 28 return userlist == ['*'] or username in userlist
29 29
30 30 def checkauthz(hgweb, req, op):
31 31 '''Check permission for operation based on request data (including
32 32 authentication info). Return if op allowed, else raise an ErrorResponse
33 33 exception.'''
34 34
35 35 user = req.env.get('REMOTE_USER')
36 36
37 37 deny_read = hgweb.configlist('web', 'deny_read')
38 38 if deny_read and (not user or ismember(hgweb.repo.ui, user, deny_read)):
39 39 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
40 40
41 41 allow_read = hgweb.configlist('web', 'allow_read')
42 42 if allow_read and (not ismember(hgweb.repo.ui, user, allow_read)):
43 43 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
44 44
45 45 if op == 'pull' and not hgweb.allowpull:
46 46 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
47 47 elif op == 'pull' or op is None: # op is None for interface requests
48 48 return
49 49
50 50 # enforce that you can only push using POST requests
51 51 if req.env['REQUEST_METHOD'] != 'POST':
52 52 msg = 'push requires POST request'
53 53 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
54 54
55 55 # require ssl by default for pushing, auth info cannot be sniffed
56 56 # and replayed
57 57 scheme = req.env.get('wsgi.url_scheme')
58 58 if hgweb.configbool('web', 'push_ssl', True) and scheme != 'https':
59 59 raise ErrorResponse(HTTP_FORBIDDEN, 'ssl required')
60 60
61 61 deny = hgweb.configlist('web', 'deny_push')
62 62 if deny and (not user or ismember(hgweb.repo.ui, user, deny)):
63 63 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
64 64
65 65 allow = hgweb.configlist('web', 'allow_push')
66 66 if not (allow and ismember(hgweb.repo.ui, user, allow)):
67 67 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
68 68
69 69 # Hooks for hgweb permission checks; extensions can add hooks here.
70 70 # Each hook is invoked like this: hook(hgweb, request, operation),
71 71 # where operation is either read, pull or push. Hooks should either
72 72 # raise an ErrorResponse exception, or just return.
73 73 #
74 74 # It is possible to do both authentication and authorization through
75 75 # this.
76 76 permhooks = [checkauthz]
77 77
78 78
79 79 class ErrorResponse(Exception):
80 80 def __init__(self, code, message=None, headers=[]):
81 81 if message is None:
82 82 message = _statusmessage(code)
83 83 Exception.__init__(self)
84 84 self.code = code
85 85 self.message = message
86 86 self.headers = headers
87 87 def __str__(self):
88 88 return self.message
89 89
90 90 class continuereader(object):
91 91 def __init__(self, f, write):
92 92 self.f = f
93 93 self._write = write
94 94 self.continued = False
95 95
96 96 def read(self, amt=-1):
97 97 if not self.continued:
98 98 self.continued = True
99 99 self._write('HTTP/1.1 100 Continue\r\n\r\n')
100 100 return self.f.read(amt)
101 101
102 102 def __getattr__(self, attr):
103 103 if attr in ('close', 'readline', 'readlines', '__iter__'):
104 104 return getattr(self.f, attr)
105 105 raise AttributeError
106 106
107 107 def _statusmessage(code):
108 108 from BaseHTTPServer import BaseHTTPRequestHandler
109 109 responses = BaseHTTPRequestHandler.responses
110 110 return responses.get(code, ('Error', 'Unknown error'))[0]
111 111
112 112 def statusmessage(code, message=None):
113 113 return '%d %s' % (code, message or _statusmessage(code))
114 114
115 def get_stat(spath, fn="00changelog.i"):
116 """stat fn (00changelog.i by default) if it exists, spath otherwise"""
115 def get_stat(spath, fn):
116 """stat fn if it exists, spath otherwise"""
117 117 cl_path = os.path.join(spath, fn)
118 118 if os.path.exists(cl_path):
119 119 return os.stat(cl_path)
120 120 else:
121 121 return os.stat(spath)
122 122
123 123 def get_mtime(spath):
124 return get_stat(spath).st_mtime
124 return get_stat(spath, "00changelog.i").st_mtime
125 125
126 126 def staticfile(directory, fname, req):
127 127 """return a file inside directory with guessed Content-Type header
128 128
129 129 fname always uses '/' as directory separator and isn't allowed to
130 130 contain unusual path components.
131 131 Content-Type is guessed using the mimetypes module.
132 132 Return an empty string if fname is illegal or file not found.
133 133
134 134 """
135 135 parts = fname.split('/')
136 136 for part in parts:
137 137 if (part in ('', os.curdir, os.pardir) or
138 138 os.sep in part or os.altsep is not None and os.altsep in part):
139 139 return
140 140 fpath = os.path.join(*parts)
141 141 if isinstance(directory, str):
142 142 directory = [directory]
143 143 for d in directory:
144 144 path = os.path.join(d, fpath)
145 145 if os.path.exists(path):
146 146 break
147 147 try:
148 148 os.stat(path)
149 149 ct = mimetypes.guess_type(path)[0] or "text/plain"
150 150 fp = open(path, 'rb')
151 151 data = fp.read()
152 152 fp.close()
153 153 req.respond(HTTP_OK, ct, body=data)
154 154 except TypeError:
155 155 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
156 156 except OSError as err:
157 157 if err.errno == errno.ENOENT:
158 158 raise ErrorResponse(HTTP_NOT_FOUND)
159 159 else:
160 160 raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
161 161
162 162 def paritygen(stripecount, offset=0):
163 163 """count parity of horizontal stripes for easier reading"""
164 164 if stripecount and offset:
165 165 # account for offset, e.g. due to building the list in reverse
166 166 count = (stripecount + offset) % stripecount
167 167 parity = (stripecount + offset) / stripecount & 1
168 168 else:
169 169 count = 0
170 170 parity = 0
171 171 while True:
172 172 yield parity
173 173 count += 1
174 174 if stripecount and count >= stripecount:
175 175 parity = 1 - parity
176 176 count = 0
177 177
178 178 def get_contact(config):
179 179 """Return repo contact information or empty string.
180 180
181 181 web.contact is the primary source, but if that is not set, try
182 182 ui.username or $EMAIL as a fallback to display something useful.
183 183 """
184 184 return (config("web", "contact") or
185 185 config("ui", "username") or
186 186 os.environ.get("EMAIL") or "")
187 187
188 188 def caching(web, req):
189 189 tag = str(web.mtime)
190 190 if req.env.get('HTTP_IF_NONE_MATCH') == tag:
191 191 raise ErrorResponse(HTTP_NOT_MODIFIED)
192 192 req.headers.append(('ETag', tag))
@@ -1,412 +1,412 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 of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import os, re
10 10 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
11 11 from mercurial.templatefilters import websub
12 12 from mercurial.i18n import _
13 13 from common import get_stat, ErrorResponse, permhooks, caching
14 14 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
15 15 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
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 'getbundle': 'pull',
23 23 'stream_out': 'pull',
24 24 'listkeys': 'pull',
25 25 'unbundle': 'push',
26 26 'pushkey': 'push',
27 27 }
28 28
29 29 def makebreadcrumb(url, prefix=''):
30 30 '''Return a 'URL breadcrumb' list
31 31
32 32 A 'URL breadcrumb' is a list of URL-name pairs,
33 33 corresponding to each of the path items on a URL.
34 34 This can be used to create path navigation entries.
35 35 '''
36 36 if url.endswith('/'):
37 37 url = url[:-1]
38 38 if prefix:
39 39 url = '/' + prefix + url
40 40 relpath = url
41 41 if relpath.startswith('/'):
42 42 relpath = relpath[1:]
43 43
44 44 breadcrumb = []
45 45 urlel = url
46 46 pathitems = [''] + relpath.split('/')
47 47 for pathel in reversed(pathitems):
48 48 if not pathel or not urlel:
49 49 break
50 50 breadcrumb.append({'url': urlel, 'name': pathel})
51 51 urlel = os.path.dirname(urlel)
52 52 return reversed(breadcrumb)
53 53
54 54
55 55 class hgweb(object):
56 56 def __init__(self, repo, name=None, baseui=None):
57 57 if isinstance(repo, str):
58 58 if baseui:
59 59 u = baseui.copy()
60 60 else:
61 61 u = ui.ui()
62 62 r = hg.repository(u, repo)
63 63 else:
64 64 # we trust caller to give us a private copy
65 65 r = repo
66 66
67 67 r = self._getview(r)
68 68 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
69 69 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
70 70 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
71 71 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
72 72 # displaying bundling progress bar while serving feel wrong and may
73 73 # break some wsgi implementation.
74 74 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
75 75 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
76 76 self.repo = r
77 77 hook.redirect(True)
78 78 self.repostate = ((-1, -1), (-1, -1))
79 79 self.mtime = -1
80 80 self.reponame = name
81 81 self.archives = 'zip', 'gz', 'bz2'
82 82 self.stripecount = 1
83 83 # a repo owner may set web.templates in .hg/hgrc to get any file
84 84 # readable by the user running the CGI script
85 85 self.templatepath = self.config('web', 'templates')
86 86 self.websubtable = self.loadwebsub()
87 87
88 88 # The CGI scripts are often run by a user different from the repo owner.
89 89 # Trust the settings from the .hg/hgrc files by default.
90 90 def config(self, section, name, default=None, untrusted=True):
91 91 return self.repo.ui.config(section, name, default,
92 92 untrusted=untrusted)
93 93
94 94 def configbool(self, section, name, default=False, untrusted=True):
95 95 return self.repo.ui.configbool(section, name, default,
96 96 untrusted=untrusted)
97 97
98 98 def configlist(self, section, name, default=None, untrusted=True):
99 99 return self.repo.ui.configlist(section, name, default,
100 100 untrusted=untrusted)
101 101
102 102 def _getview(self, repo):
103 103 """The 'web.view' config controls changeset filter to hgweb. Possible
104 104 values are ``served``, ``visible`` and ``all``. Default is ``served``.
105 105 The ``served`` filter only shows changesets that can be pulled from the
106 106 hgweb instance. The``visible`` filter includes secret changesets but
107 107 still excludes "hidden" one.
108 108
109 109 See the repoview module for details.
110 110
111 111 The option has been around undocumented since Mercurial 2.5, but no
112 112 user ever asked about it. So we better keep it undocumented for now."""
113 113 viewconfig = repo.ui.config('web', 'view', 'served',
114 114 untrusted=True)
115 115 if viewconfig == 'all':
116 116 return repo.unfiltered()
117 117 elif viewconfig in repoview.filtertable:
118 118 return repo.filtered(viewconfig)
119 119 else:
120 120 return repo.filtered('served')
121 121
122 122 def refresh(self, request=None):
123 st = get_stat(self.repo.spath)
123 st = get_stat(self.repo.spath, '00changelog.i')
124 124 pst = get_stat(self.repo.spath, 'phaseroots')
125 125 # changelog mtime and size, phaseroots mtime and size
126 126 repostate = ((st.st_mtime, st.st_size), (pst.st_mtime, pst.st_size))
127 127 # we need to compare file size in addition to mtime to catch
128 128 # changes made less than a second ago
129 129 if repostate != self.repostate:
130 130 r = hg.repository(self.repo.baseui, self.repo.url())
131 131 self.repo = self._getview(r)
132 132 self.maxchanges = int(self.config("web", "maxchanges", 10))
133 133 self.stripecount = int(self.config("web", "stripes", 1))
134 134 self.maxshortchanges = int(self.config("web", "maxshortchanges",
135 135 60))
136 136 self.maxfiles = int(self.config("web", "maxfiles", 10))
137 137 self.allowpull = self.configbool("web", "allowpull", True)
138 138 encoding.encoding = self.config("web", "encoding",
139 139 encoding.encoding)
140 140 # update these last to avoid threads seeing empty settings
141 141 self.repostate = repostate
142 142 # mtime is needed for ETag
143 143 self.mtime = st.st_mtime
144 144 if request:
145 145 self.repo.ui.environ = request.env
146 146
147 147 def run(self):
148 148 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
149 149 raise RuntimeError("This function is only intended to be "
150 150 "called while running as a CGI script.")
151 151 import mercurial.hgweb.wsgicgi as wsgicgi
152 152 wsgicgi.launch(self)
153 153
154 154 def __call__(self, env, respond):
155 155 req = wsgirequest(env, respond)
156 156 return self.run_wsgi(req)
157 157
158 158 def run_wsgi(self, req):
159 159
160 160 self.refresh(req)
161 161
162 162 # work with CGI variables to create coherent structure
163 163 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
164 164
165 165 req.url = req.env['SCRIPT_NAME']
166 166 if not req.url.endswith('/'):
167 167 req.url += '/'
168 168 if 'REPO_NAME' in req.env:
169 169 req.url += req.env['REPO_NAME'] + '/'
170 170
171 171 if 'PATH_INFO' in req.env:
172 172 parts = req.env['PATH_INFO'].strip('/').split('/')
173 173 repo_parts = req.env.get('REPO_NAME', '').split('/')
174 174 if parts[:len(repo_parts)] == repo_parts:
175 175 parts = parts[len(repo_parts):]
176 176 query = '/'.join(parts)
177 177 else:
178 178 query = req.env['QUERY_STRING'].split('&', 1)[0]
179 179 query = query.split(';', 1)[0]
180 180
181 181 # process this if it's a protocol request
182 182 # protocol bits don't need to create any URLs
183 183 # and the clients always use the old URL structure
184 184
185 185 cmd = req.form.get('cmd', [''])[0]
186 186 if protocol.iscmd(cmd):
187 187 try:
188 188 if query:
189 189 raise ErrorResponse(HTTP_NOT_FOUND)
190 190 if cmd in perms:
191 191 self.check_perm(req, perms[cmd])
192 192 return protocol.call(self.repo, req, cmd)
193 193 except ErrorResponse as inst:
194 194 # A client that sends unbundle without 100-continue will
195 195 # break if we respond early.
196 196 if (cmd == 'unbundle' and
197 197 (req.env.get('HTTP_EXPECT',
198 198 '').lower() != '100-continue') or
199 199 req.env.get('X-HgHttp2', '')):
200 200 req.drain()
201 201 else:
202 202 req.headers.append(('Connection', 'Close'))
203 203 req.respond(inst, protocol.HGTYPE,
204 204 body='0\n%s\n' % inst.message)
205 205 return ''
206 206
207 207 # translate user-visible url structure to internal structure
208 208
209 209 args = query.split('/', 2)
210 210 if 'cmd' not in req.form and args and args[0]:
211 211
212 212 cmd = args.pop(0)
213 213 style = cmd.rfind('-')
214 214 if style != -1:
215 215 req.form['style'] = [cmd[:style]]
216 216 cmd = cmd[style + 1:]
217 217
218 218 # avoid accepting e.g. style parameter as command
219 219 if util.safehasattr(webcommands, cmd):
220 220 req.form['cmd'] = [cmd]
221 221
222 222 if cmd == 'static':
223 223 req.form['file'] = ['/'.join(args)]
224 224 else:
225 225 if args and args[0]:
226 226 node = args.pop(0)
227 227 req.form['node'] = [node]
228 228 if args:
229 229 req.form['file'] = args
230 230
231 231 ua = req.env.get('HTTP_USER_AGENT', '')
232 232 if cmd == 'rev' and 'mercurial' in ua:
233 233 req.form['style'] = ['raw']
234 234
235 235 if cmd == 'archive':
236 236 fn = req.form['node'][0]
237 237 for type_, spec in self.archive_specs.iteritems():
238 238 ext = spec[2]
239 239 if fn.endswith(ext):
240 240 req.form['node'] = [fn[:-len(ext)]]
241 241 req.form['type'] = [type_]
242 242
243 243 # process the web interface request
244 244
245 245 try:
246 246 tmpl = self.templater(req)
247 247 ctype = tmpl('mimetype', encoding=encoding.encoding)
248 248 ctype = templater.stringify(ctype)
249 249
250 250 # check read permissions non-static content
251 251 if cmd != 'static':
252 252 self.check_perm(req, None)
253 253
254 254 if cmd == '':
255 255 req.form['cmd'] = [tmpl.cache['default']]
256 256 cmd = req.form['cmd'][0]
257 257
258 258 if self.configbool('web', 'cache', True):
259 259 caching(self, req) # sets ETag header or raises NOT_MODIFIED
260 260 if cmd not in webcommands.__all__:
261 261 msg = 'no such method: %s' % cmd
262 262 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
263 263 elif cmd == 'file' and 'raw' in req.form.get('style', []):
264 264 self.ctype = ctype
265 265 content = webcommands.rawfile(self, req, tmpl)
266 266 else:
267 267 content = getattr(webcommands, cmd)(self, req, tmpl)
268 268 req.respond(HTTP_OK, ctype)
269 269
270 270 return content
271 271
272 272 except (error.LookupError, error.RepoLookupError) as err:
273 273 req.respond(HTTP_NOT_FOUND, ctype)
274 274 msg = str(err)
275 275 if (util.safehasattr(err, 'name') and
276 276 not isinstance(err, error.ManifestLookupError)):
277 277 msg = 'revision not found: %s' % err.name
278 278 return tmpl('error', error=msg)
279 279 except (error.RepoError, error.RevlogError) as inst:
280 280 req.respond(HTTP_SERVER_ERROR, ctype)
281 281 return tmpl('error', error=str(inst))
282 282 except ErrorResponse as inst:
283 283 req.respond(inst, ctype)
284 284 if inst.code == HTTP_NOT_MODIFIED:
285 285 # Not allowed to return a body on a 304
286 286 return ['']
287 287 return tmpl('error', error=inst.message)
288 288
289 289 def loadwebsub(self):
290 290 websubtable = []
291 291 websubdefs = self.repo.ui.configitems('websub')
292 292 # we must maintain interhg backwards compatibility
293 293 websubdefs += self.repo.ui.configitems('interhg')
294 294 for key, pattern in websubdefs:
295 295 # grab the delimiter from the character after the "s"
296 296 unesc = pattern[1]
297 297 delim = re.escape(unesc)
298 298
299 299 # identify portions of the pattern, taking care to avoid escaped
300 300 # delimiters. the replace format and flags are optional, but
301 301 # delimiters are required.
302 302 match = re.match(
303 303 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
304 304 % (delim, delim, delim), pattern)
305 305 if not match:
306 306 self.repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
307 307 % (key, pattern))
308 308 continue
309 309
310 310 # we need to unescape the delimiter for regexp and format
311 311 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
312 312 regexp = delim_re.sub(unesc, match.group(1))
313 313 format = delim_re.sub(unesc, match.group(2))
314 314
315 315 # the pattern allows for 6 regexp flags, so set them if necessary
316 316 flagin = match.group(3)
317 317 flags = 0
318 318 if flagin:
319 319 for flag in flagin.upper():
320 320 flags |= re.__dict__[flag]
321 321
322 322 try:
323 323 regexp = re.compile(regexp, flags)
324 324 websubtable.append((regexp, format))
325 325 except re.error:
326 326 self.repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
327 327 % (key, regexp))
328 328 return websubtable
329 329
330 330 def templater(self, req):
331 331
332 332 # determine scheme, port and server name
333 333 # this is needed to create absolute urls
334 334
335 335 proto = req.env.get('wsgi.url_scheme')
336 336 if proto == 'https':
337 337 proto = 'https'
338 338 default_port = "443"
339 339 else:
340 340 proto = 'http'
341 341 default_port = "80"
342 342
343 343 port = req.env["SERVER_PORT"]
344 344 port = port != default_port and (":" + port) or ""
345 345 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
346 346 logourl = self.config("web", "logourl", "http://mercurial.selenic.com/")
347 347 logoimg = self.config("web", "logoimg", "hglogo.png")
348 348 staticurl = self.config("web", "staticurl") or req.url + 'static/'
349 349 if not staticurl.endswith('/'):
350 350 staticurl += '/'
351 351
352 352 # some functions for the templater
353 353
354 354 def motd(**map):
355 355 yield self.config("web", "motd", "")
356 356
357 357 # figure out which style to use
358 358
359 359 vars = {}
360 360 styles = (
361 361 req.form.get('style', [None])[0],
362 362 self.config('web', 'style'),
363 363 'paper',
364 364 )
365 365 style, mapfile = templater.stylemap(styles, self.templatepath)
366 366 if style == styles[0]:
367 367 vars['style'] = style
368 368
369 369 start = req.url[-1] == '?' and '&' or '?'
370 370 sessionvars = webutil.sessionvars(vars, start)
371 371
372 372 if not self.reponame:
373 373 self.reponame = (self.config("web", "name")
374 374 or req.env.get('REPO_NAME')
375 375 or req.url.strip('/') or self.repo.root)
376 376
377 377 def websubfilter(text):
378 378 return websub(text, self.websubtable)
379 379
380 380 # create the templater
381 381
382 382 tmpl = templater.templater(mapfile,
383 383 filters={"websub": websubfilter},
384 384 defaults={"url": req.url,
385 385 "logourl": logourl,
386 386 "logoimg": logoimg,
387 387 "staticurl": staticurl,
388 388 "urlbase": urlbase,
389 389 "repo": self.reponame,
390 390 "encoding": encoding.encoding,
391 391 "motd": motd,
392 392 "sessionvars": sessionvars,
393 393 "pathdef": makebreadcrumb(req.url),
394 394 "style": style,
395 395 })
396 396 return tmpl
397 397
398 398 def archivelist(self, nodeid):
399 399 allowed = self.configlist("web", "allow_archive")
400 400 for i, spec in self.archive_specs.iteritems():
401 401 if i in allowed or self.configbool("web", "allow" + i):
402 402 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
403 403
404 404 archive_specs = {
405 405 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
406 406 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
407 407 'zip': ('application/zip', 'zip', '.zip', None),
408 408 }
409 409
410 410 def check_perm(self, req, op):
411 411 for permhook in permhooks:
412 412 permhook(self, req, op)
General Comments 0
You need to be logged in to leave comments. Login now