##// END OF EJS Templates
hgewb: disable progress when serving (issue4582)...
Pierre-Yves David -
r25488:89ce95f9 default
parent child Browse files
Show More
@@ -1,398 +1,402
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 # displaying bundling progress bar while serving feel wrong and may
73 # break some wsgi implementation.
74 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
75 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
72 76 self.repo = r
73 77 hook.redirect(True)
74 78 self.repostate = ((-1, -1), (-1, -1))
75 79 self.mtime = -1
76 80 self.reponame = name
77 81 self.archives = 'zip', 'gz', 'bz2'
78 82 self.stripecount = 1
79 83 # a repo owner may set web.templates in .hg/hgrc to get any file
80 84 # readable by the user running the CGI script
81 85 self.templatepath = self.config('web', 'templates')
82 86 self.websubtable = self.loadwebsub()
83 87
84 88 # The CGI scripts are often run by a user different from the repo owner.
85 89 # Trust the settings from the .hg/hgrc files by default.
86 90 def config(self, section, name, default=None, untrusted=True):
87 91 return self.repo.ui.config(section, name, default,
88 92 untrusted=untrusted)
89 93
90 94 def configbool(self, section, name, default=False, untrusted=True):
91 95 return self.repo.ui.configbool(section, name, default,
92 96 untrusted=untrusted)
93 97
94 98 def configlist(self, section, name, default=None, untrusted=True):
95 99 return self.repo.ui.configlist(section, name, default,
96 100 untrusted=untrusted)
97 101
98 102 def _getview(self, repo):
99 103 viewconfig = repo.ui.config('web', 'view', 'served',
100 104 untrusted=True)
101 105 if viewconfig == 'all':
102 106 return repo.unfiltered()
103 107 elif viewconfig in repoview.filtertable:
104 108 return repo.filtered(viewconfig)
105 109 else:
106 110 return repo.filtered('served')
107 111
108 112 def refresh(self, request=None):
109 113 st = get_stat(self.repo.spath)
110 114 pst = get_stat(self.repo.spath, 'phaseroots')
111 115 # changelog mtime and size, phaseroots mtime and size
112 116 repostate = ((st.st_mtime, st.st_size), (pst.st_mtime, pst.st_size))
113 117 # we need to compare file size in addition to mtime to catch
114 118 # changes made less than a second ago
115 119 if repostate != self.repostate:
116 120 r = hg.repository(self.repo.baseui, self.repo.url())
117 121 self.repo = self._getview(r)
118 122 self.maxchanges = int(self.config("web", "maxchanges", 10))
119 123 self.stripecount = int(self.config("web", "stripes", 1))
120 124 self.maxshortchanges = int(self.config("web", "maxshortchanges",
121 125 60))
122 126 self.maxfiles = int(self.config("web", "maxfiles", 10))
123 127 self.allowpull = self.configbool("web", "allowpull", True)
124 128 encoding.encoding = self.config("web", "encoding",
125 129 encoding.encoding)
126 130 # update these last to avoid threads seeing empty settings
127 131 self.repostate = repostate
128 132 # mtime is needed for ETag
129 133 self.mtime = st.st_mtime
130 134 if request:
131 135 self.repo.ui.environ = request.env
132 136
133 137 def run(self):
134 138 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
135 139 raise RuntimeError("This function is only intended to be "
136 140 "called while running as a CGI script.")
137 141 import mercurial.hgweb.wsgicgi as wsgicgi
138 142 wsgicgi.launch(self)
139 143
140 144 def __call__(self, env, respond):
141 145 req = wsgirequest(env, respond)
142 146 return self.run_wsgi(req)
143 147
144 148 def run_wsgi(self, req):
145 149
146 150 self.refresh(req)
147 151
148 152 # work with CGI variables to create coherent structure
149 153 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
150 154
151 155 req.url = req.env['SCRIPT_NAME']
152 156 if not req.url.endswith('/'):
153 157 req.url += '/'
154 158 if 'REPO_NAME' in req.env:
155 159 req.url += req.env['REPO_NAME'] + '/'
156 160
157 161 if 'PATH_INFO' in req.env:
158 162 parts = req.env['PATH_INFO'].strip('/').split('/')
159 163 repo_parts = req.env.get('REPO_NAME', '').split('/')
160 164 if parts[:len(repo_parts)] == repo_parts:
161 165 parts = parts[len(repo_parts):]
162 166 query = '/'.join(parts)
163 167 else:
164 168 query = req.env['QUERY_STRING'].split('&', 1)[0]
165 169 query = query.split(';', 1)[0]
166 170
167 171 # process this if it's a protocol request
168 172 # protocol bits don't need to create any URLs
169 173 # and the clients always use the old URL structure
170 174
171 175 cmd = req.form.get('cmd', [''])[0]
172 176 if protocol.iscmd(cmd):
173 177 try:
174 178 if query:
175 179 raise ErrorResponse(HTTP_NOT_FOUND)
176 180 if cmd in perms:
177 181 self.check_perm(req, perms[cmd])
178 182 return protocol.call(self.repo, req, cmd)
179 183 except ErrorResponse, inst:
180 184 # A client that sends unbundle without 100-continue will
181 185 # break if we respond early.
182 186 if (cmd == 'unbundle' and
183 187 (req.env.get('HTTP_EXPECT',
184 188 '').lower() != '100-continue') or
185 189 req.env.get('X-HgHttp2', '')):
186 190 req.drain()
187 191 else:
188 192 req.headers.append(('Connection', 'Close'))
189 193 req.respond(inst, protocol.HGTYPE,
190 194 body='0\n%s\n' % inst.message)
191 195 return ''
192 196
193 197 # translate user-visible url structure to internal structure
194 198
195 199 args = query.split('/', 2)
196 200 if 'cmd' not in req.form and args and args[0]:
197 201
198 202 cmd = args.pop(0)
199 203 style = cmd.rfind('-')
200 204 if style != -1:
201 205 req.form['style'] = [cmd[:style]]
202 206 cmd = cmd[style + 1:]
203 207
204 208 # avoid accepting e.g. style parameter as command
205 209 if util.safehasattr(webcommands, cmd):
206 210 req.form['cmd'] = [cmd]
207 211
208 212 if cmd == 'static':
209 213 req.form['file'] = ['/'.join(args)]
210 214 else:
211 215 if args and args[0]:
212 216 node = args.pop(0)
213 217 req.form['node'] = [node]
214 218 if args:
215 219 req.form['file'] = args
216 220
217 221 ua = req.env.get('HTTP_USER_AGENT', '')
218 222 if cmd == 'rev' and 'mercurial' in ua:
219 223 req.form['style'] = ['raw']
220 224
221 225 if cmd == 'archive':
222 226 fn = req.form['node'][0]
223 227 for type_, spec in self.archive_specs.iteritems():
224 228 ext = spec[2]
225 229 if fn.endswith(ext):
226 230 req.form['node'] = [fn[:-len(ext)]]
227 231 req.form['type'] = [type_]
228 232
229 233 # process the web interface request
230 234
231 235 try:
232 236 tmpl = self.templater(req)
233 237 ctype = tmpl('mimetype', encoding=encoding.encoding)
234 238 ctype = templater.stringify(ctype)
235 239
236 240 # check read permissions non-static content
237 241 if cmd != 'static':
238 242 self.check_perm(req, None)
239 243
240 244 if cmd == '':
241 245 req.form['cmd'] = [tmpl.cache['default']]
242 246 cmd = req.form['cmd'][0]
243 247
244 248 if self.configbool('web', 'cache', True):
245 249 caching(self, req) # sets ETag header or raises NOT_MODIFIED
246 250 if cmd not in webcommands.__all__:
247 251 msg = 'no such method: %s' % cmd
248 252 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
249 253 elif cmd == 'file' and 'raw' in req.form.get('style', []):
250 254 self.ctype = ctype
251 255 content = webcommands.rawfile(self, req, tmpl)
252 256 else:
253 257 content = getattr(webcommands, cmd)(self, req, tmpl)
254 258 req.respond(HTTP_OK, ctype)
255 259
256 260 return content
257 261
258 262 except (error.LookupError, error.RepoLookupError), err:
259 263 req.respond(HTTP_NOT_FOUND, ctype)
260 264 msg = str(err)
261 265 if (util.safehasattr(err, 'name') and
262 266 not isinstance(err, error.ManifestLookupError)):
263 267 msg = 'revision not found: %s' % err.name
264 268 return tmpl('error', error=msg)
265 269 except (error.RepoError, error.RevlogError), inst:
266 270 req.respond(HTTP_SERVER_ERROR, ctype)
267 271 return tmpl('error', error=str(inst))
268 272 except ErrorResponse, inst:
269 273 req.respond(inst, ctype)
270 274 if inst.code == HTTP_NOT_MODIFIED:
271 275 # Not allowed to return a body on a 304
272 276 return ['']
273 277 return tmpl('error', error=inst.message)
274 278
275 279 def loadwebsub(self):
276 280 websubtable = []
277 281 websubdefs = self.repo.ui.configitems('websub')
278 282 # we must maintain interhg backwards compatibility
279 283 websubdefs += self.repo.ui.configitems('interhg')
280 284 for key, pattern in websubdefs:
281 285 # grab the delimiter from the character after the "s"
282 286 unesc = pattern[1]
283 287 delim = re.escape(unesc)
284 288
285 289 # identify portions of the pattern, taking care to avoid escaped
286 290 # delimiters. the replace format and flags are optional, but
287 291 # delimiters are required.
288 292 match = re.match(
289 293 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
290 294 % (delim, delim, delim), pattern)
291 295 if not match:
292 296 self.repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
293 297 % (key, pattern))
294 298 continue
295 299
296 300 # we need to unescape the delimiter for regexp and format
297 301 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
298 302 regexp = delim_re.sub(unesc, match.group(1))
299 303 format = delim_re.sub(unesc, match.group(2))
300 304
301 305 # the pattern allows for 6 regexp flags, so set them if necessary
302 306 flagin = match.group(3)
303 307 flags = 0
304 308 if flagin:
305 309 for flag in flagin.upper():
306 310 flags |= re.__dict__[flag]
307 311
308 312 try:
309 313 regexp = re.compile(regexp, flags)
310 314 websubtable.append((regexp, format))
311 315 except re.error:
312 316 self.repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
313 317 % (key, regexp))
314 318 return websubtable
315 319
316 320 def templater(self, req):
317 321
318 322 # determine scheme, port and server name
319 323 # this is needed to create absolute urls
320 324
321 325 proto = req.env.get('wsgi.url_scheme')
322 326 if proto == 'https':
323 327 proto = 'https'
324 328 default_port = "443"
325 329 else:
326 330 proto = 'http'
327 331 default_port = "80"
328 332
329 333 port = req.env["SERVER_PORT"]
330 334 port = port != default_port and (":" + port) or ""
331 335 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
332 336 logourl = self.config("web", "logourl", "http://mercurial.selenic.com/")
333 337 logoimg = self.config("web", "logoimg", "hglogo.png")
334 338 staticurl = self.config("web", "staticurl") or req.url + 'static/'
335 339 if not staticurl.endswith('/'):
336 340 staticurl += '/'
337 341
338 342 # some functions for the templater
339 343
340 344 def motd(**map):
341 345 yield self.config("web", "motd", "")
342 346
343 347 # figure out which style to use
344 348
345 349 vars = {}
346 350 styles = (
347 351 req.form.get('style', [None])[0],
348 352 self.config('web', 'style'),
349 353 'paper',
350 354 )
351 355 style, mapfile = templater.stylemap(styles, self.templatepath)
352 356 if style == styles[0]:
353 357 vars['style'] = style
354 358
355 359 start = req.url[-1] == '?' and '&' or '?'
356 360 sessionvars = webutil.sessionvars(vars, start)
357 361
358 362 if not self.reponame:
359 363 self.reponame = (self.config("web", "name")
360 364 or req.env.get('REPO_NAME')
361 365 or req.url.strip('/') or self.repo.root)
362 366
363 367 def websubfilter(text):
364 368 return websub(text, self.websubtable)
365 369
366 370 # create the templater
367 371
368 372 tmpl = templater.templater(mapfile,
369 373 filters={"websub": websubfilter},
370 374 defaults={"url": req.url,
371 375 "logourl": logourl,
372 376 "logoimg": logoimg,
373 377 "staticurl": staticurl,
374 378 "urlbase": urlbase,
375 379 "repo": self.reponame,
376 380 "encoding": encoding.encoding,
377 381 "motd": motd,
378 382 "sessionvars": sessionvars,
379 383 "pathdef": makebreadcrumb(req.url),
380 384 "style": style,
381 385 })
382 386 return tmpl
383 387
384 388 def archivelist(self, nodeid):
385 389 allowed = self.configlist("web", "allow_archive")
386 390 for i, spec in self.archive_specs.iteritems():
387 391 if i in allowed or self.configbool("web", "allow" + i):
388 392 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
389 393
390 394 archive_specs = {
391 395 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
392 396 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
393 397 'zip': ('application/zip', 'zip', '.zip', None),
394 398 }
395 399
396 400 def check_perm(self, req, op):
397 401 for permhook in permhooks:
398 402 permhook(self, req, op)
@@ -1,469 +1,472
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 of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import os, re, time
10 10 from mercurial.i18n import _
11 11 from mercurial import ui, hg, scmutil, util, templater
12 12 from mercurial import error, encoding
13 13 from common import ErrorResponse, get_mtime, staticfile, paritygen, ismember, \
14 14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 15 from hgweb_mod import hgweb, makebreadcrumb
16 16 from request import wsgirequest
17 17 import webutil
18 18
19 19 def cleannames(items):
20 20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
21 21
22 22 def findrepos(paths):
23 23 repos = []
24 24 for prefix, root in cleannames(paths):
25 25 roothead, roottail = os.path.split(root)
26 26 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
27 27 # /bar/ be served as as foo/N .
28 28 # '*' will not search inside dirs with .hg (except .hg/patches),
29 29 # '**' will search inside dirs with .hg (and thus also find subrepos).
30 30 try:
31 31 recurse = {'*': False, '**': True}[roottail]
32 32 except KeyError:
33 33 repos.append((prefix, root))
34 34 continue
35 35 roothead = os.path.normpath(os.path.abspath(roothead))
36 36 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
37 37 repos.extend(urlrepos(prefix, roothead, paths))
38 38 return repos
39 39
40 40 def urlrepos(prefix, roothead, paths):
41 41 """yield url paths and filesystem paths from a list of repo paths
42 42
43 43 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
44 44 >>> conv(urlrepos('hg', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
45 45 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
46 46 >>> conv(urlrepos('', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
47 47 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
48 48 """
49 49 for path in paths:
50 50 path = os.path.normpath(path)
51 51 yield (prefix + '/' +
52 52 util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path
53 53
54 54 def geturlcgivars(baseurl, port):
55 55 """
56 56 Extract CGI variables from baseurl
57 57
58 58 >>> geturlcgivars("http://host.org/base", "80")
59 59 ('host.org', '80', '/base')
60 60 >>> geturlcgivars("http://host.org:8000/base", "80")
61 61 ('host.org', '8000', '/base')
62 62 >>> geturlcgivars('/base', 8000)
63 63 ('', '8000', '/base')
64 64 >>> geturlcgivars("base", '8000')
65 65 ('', '8000', '/base')
66 66 >>> geturlcgivars("http://host", '8000')
67 67 ('host', '8000', '/')
68 68 >>> geturlcgivars("http://host/", '8000')
69 69 ('host', '8000', '/')
70 70 """
71 71 u = util.url(baseurl)
72 72 name = u.host or ''
73 73 if u.port:
74 74 port = u.port
75 75 path = u.path or ""
76 76 if not path.startswith('/'):
77 77 path = '/' + path
78 78
79 79 return name, str(port), path
80 80
81 81 class hgwebdir(object):
82 82 refreshinterval = 20
83 83
84 84 def __init__(self, conf, baseui=None):
85 85 self.conf = conf
86 86 self.baseui = baseui
87 87 self.lastrefresh = 0
88 88 self.motd = None
89 89 self.refresh()
90 90
91 91 def refresh(self):
92 92 if self.lastrefresh + self.refreshinterval > time.time():
93 93 return
94 94
95 95 if self.baseui:
96 96 u = self.baseui.copy()
97 97 else:
98 98 u = ui.ui()
99 99 u.setconfig('ui', 'report_untrusted', 'off', 'hgwebdir')
100 100 u.setconfig('ui', 'nontty', 'true', 'hgwebdir')
101 # displaying bundling progress bar while serving feels wrong and may
102 # break some wsgi implementations.
103 u.setconfig('progress', 'disable', 'true', 'hgweb')
101 104
102 105 if not isinstance(self.conf, (dict, list, tuple)):
103 106 map = {'paths': 'hgweb-paths'}
104 107 if not os.path.exists(self.conf):
105 108 raise util.Abort(_('config file %s not found!') % self.conf)
106 109 u.readconfig(self.conf, remap=map, trust=True)
107 110 paths = []
108 111 for name, ignored in u.configitems('hgweb-paths'):
109 112 for path in u.configlist('hgweb-paths', name):
110 113 paths.append((name, path))
111 114 elif isinstance(self.conf, (list, tuple)):
112 115 paths = self.conf
113 116 elif isinstance(self.conf, dict):
114 117 paths = self.conf.items()
115 118
116 119 repos = findrepos(paths)
117 120 for prefix, root in u.configitems('collections'):
118 121 prefix = util.pconvert(prefix)
119 122 for path in scmutil.walkrepos(root, followsym=True):
120 123 repo = os.path.normpath(path)
121 124 name = util.pconvert(repo)
122 125 if name.startswith(prefix):
123 126 name = name[len(prefix):]
124 127 repos.append((name.lstrip('/'), repo))
125 128
126 129 self.repos = repos
127 130 self.ui = u
128 131 encoding.encoding = self.ui.config('web', 'encoding',
129 132 encoding.encoding)
130 133 self.style = self.ui.config('web', 'style', 'paper')
131 134 self.templatepath = self.ui.config('web', 'templates', None)
132 135 self.stripecount = self.ui.config('web', 'stripes', 1)
133 136 if self.stripecount:
134 137 self.stripecount = int(self.stripecount)
135 138 self._baseurl = self.ui.config('web', 'baseurl')
136 139 prefix = self.ui.config('web', 'prefix', '')
137 140 if prefix.startswith('/'):
138 141 prefix = prefix[1:]
139 142 if prefix.endswith('/'):
140 143 prefix = prefix[:-1]
141 144 self.prefix = prefix
142 145 self.lastrefresh = time.time()
143 146
144 147 def run(self):
145 148 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
146 149 raise RuntimeError("This function is only intended to be "
147 150 "called while running as a CGI script.")
148 151 import mercurial.hgweb.wsgicgi as wsgicgi
149 152 wsgicgi.launch(self)
150 153
151 154 def __call__(self, env, respond):
152 155 req = wsgirequest(env, respond)
153 156 return self.run_wsgi(req)
154 157
155 158 def read_allowed(self, ui, req):
156 159 """Check allow_read and deny_read config options of a repo's ui object
157 160 to determine user permissions. By default, with neither option set (or
158 161 both empty), allow all users to read the repo. There are two ways a
159 162 user can be denied read access: (1) deny_read is not empty, and the
160 163 user is unauthenticated or deny_read contains user (or *), and (2)
161 164 allow_read is not empty and the user is not in allow_read. Return True
162 165 if user is allowed to read the repo, else return False."""
163 166
164 167 user = req.env.get('REMOTE_USER')
165 168
166 169 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
167 170 if deny_read and (not user or ismember(ui, user, deny_read)):
168 171 return False
169 172
170 173 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
171 174 # by default, allow reading if no allow_read option has been set
172 175 if (not allow_read) or ismember(ui, user, allow_read):
173 176 return True
174 177
175 178 return False
176 179
177 180 def run_wsgi(self, req):
178 181 try:
179 182 self.refresh()
180 183
181 184 virtual = req.env.get("PATH_INFO", "").strip('/')
182 185 tmpl = self.templater(req)
183 186 ctype = tmpl('mimetype', encoding=encoding.encoding)
184 187 ctype = templater.stringify(ctype)
185 188
186 189 # a static file
187 190 if virtual.startswith('static/') or 'static' in req.form:
188 191 if virtual.startswith('static/'):
189 192 fname = virtual[7:]
190 193 else:
191 194 fname = req.form['static'][0]
192 195 static = self.ui.config("web", "static", None,
193 196 untrusted=False)
194 197 if not static:
195 198 tp = self.templatepath or templater.templatepaths()
196 199 if isinstance(tp, str):
197 200 tp = [tp]
198 201 static = [os.path.join(p, 'static') for p in tp]
199 202 staticfile(static, fname, req)
200 203 return []
201 204
202 205 # top-level index
203 206 elif not virtual:
204 207 req.respond(HTTP_OK, ctype)
205 208 return self.makeindex(req, tmpl)
206 209
207 210 # nested indexes and hgwebs
208 211
209 212 repos = dict(self.repos)
210 213 virtualrepo = virtual
211 214 while virtualrepo:
212 215 real = repos.get(virtualrepo)
213 216 if real:
214 217 req.env['REPO_NAME'] = virtualrepo
215 218 try:
216 219 # ensure caller gets private copy of ui
217 220 repo = hg.repository(self.ui.copy(), real)
218 221 return hgweb(repo).run_wsgi(req)
219 222 except IOError, inst:
220 223 msg = inst.strerror
221 224 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
222 225 except error.RepoError, inst:
223 226 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
224 227
225 228 up = virtualrepo.rfind('/')
226 229 if up < 0:
227 230 break
228 231 virtualrepo = virtualrepo[:up]
229 232
230 233 # browse subdirectories
231 234 subdir = virtual + '/'
232 235 if [r for r in repos if r.startswith(subdir)]:
233 236 req.respond(HTTP_OK, ctype)
234 237 return self.makeindex(req, tmpl, subdir)
235 238
236 239 # prefixes not found
237 240 req.respond(HTTP_NOT_FOUND, ctype)
238 241 return tmpl("notfound", repo=virtual)
239 242
240 243 except ErrorResponse, err:
241 244 req.respond(err, ctype)
242 245 return tmpl('error', error=err.message or '')
243 246 finally:
244 247 tmpl = None
245 248
246 249 def makeindex(self, req, tmpl, subdir=""):
247 250
248 251 def archivelist(ui, nodeid, url):
249 252 allowed = ui.configlist("web", "allow_archive", untrusted=True)
250 253 archives = []
251 254 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
252 255 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
253 256 untrusted=True):
254 257 archives.append({"type" : i[0], "extension": i[1],
255 258 "node": nodeid, "url": url})
256 259 return archives
257 260
258 261 def rawentries(subdir="", **map):
259 262
260 263 descend = self.ui.configbool('web', 'descend', True)
261 264 collapse = self.ui.configbool('web', 'collapse', False)
262 265 seenrepos = set()
263 266 seendirs = set()
264 267 for name, path in self.repos:
265 268
266 269 if not name.startswith(subdir):
267 270 continue
268 271 name = name[len(subdir):]
269 272 directory = False
270 273
271 274 if '/' in name:
272 275 if not descend:
273 276 continue
274 277
275 278 nameparts = name.split('/')
276 279 rootname = nameparts[0]
277 280
278 281 if not collapse:
279 282 pass
280 283 elif rootname in seendirs:
281 284 continue
282 285 elif rootname in seenrepos:
283 286 pass
284 287 else:
285 288 directory = True
286 289 name = rootname
287 290
288 291 # redefine the path to refer to the directory
289 292 discarded = '/'.join(nameparts[1:])
290 293
291 294 # remove name parts plus accompanying slash
292 295 path = path[:-len(discarded) - 1]
293 296
294 297 try:
295 298 r = hg.repository(self.ui, path)
296 299 directory = False
297 300 except (IOError, error.RepoError):
298 301 pass
299 302
300 303 parts = [name]
301 304 if 'PATH_INFO' in req.env:
302 305 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
303 306 if req.env['SCRIPT_NAME']:
304 307 parts.insert(0, req.env['SCRIPT_NAME'])
305 308 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
306 309
307 310 # show either a directory entry or a repository
308 311 if directory:
309 312 # get the directory's time information
310 313 try:
311 314 d = (get_mtime(path), util.makedate()[1])
312 315 except OSError:
313 316 continue
314 317
315 318 # add '/' to the name to make it obvious that
316 319 # the entry is a directory, not a regular repository
317 320 row = {'contact': "",
318 321 'contact_sort': "",
319 322 'name': name + '/',
320 323 'name_sort': name,
321 324 'url': url,
322 325 'description': "",
323 326 'description_sort': "",
324 327 'lastchange': d,
325 328 'lastchange_sort': d[1]-d[0],
326 329 'archives': [],
327 330 'isdirectory': True}
328 331
329 332 seendirs.add(name)
330 333 yield row
331 334 continue
332 335
333 336 u = self.ui.copy()
334 337 try:
335 338 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
336 339 except Exception, e:
337 340 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
338 341 continue
339 342 def get(section, name, default=None):
340 343 return u.config(section, name, default, untrusted=True)
341 344
342 345 if u.configbool("web", "hidden", untrusted=True):
343 346 continue
344 347
345 348 if not self.read_allowed(u, req):
346 349 continue
347 350
348 351 # update time with local timezone
349 352 try:
350 353 r = hg.repository(self.ui, path)
351 354 except IOError:
352 355 u.warn(_('error accessing repository at %s\n') % path)
353 356 continue
354 357 except error.RepoError:
355 358 u.warn(_('error accessing repository at %s\n') % path)
356 359 continue
357 360 try:
358 361 d = (get_mtime(r.spath), util.makedate()[1])
359 362 except OSError:
360 363 continue
361 364
362 365 contact = get_contact(get)
363 366 description = get("web", "description", "")
364 367 seenrepos.add(name)
365 368 name = get("web", "name", name)
366 369 row = {'contact': contact or "unknown",
367 370 'contact_sort': contact.upper() or "unknown",
368 371 'name': name,
369 372 'name_sort': name,
370 373 'url': url,
371 374 'description': description or "unknown",
372 375 'description_sort': description.upper() or "unknown",
373 376 'lastchange': d,
374 377 'lastchange_sort': d[1]-d[0],
375 378 'archives': archivelist(u, "tip", url),
376 379 'isdirectory': None,
377 380 }
378 381
379 382 yield row
380 383
381 384 sortdefault = None, False
382 385 def entries(sortcolumn="", descending=False, subdir="", **map):
383 386 rows = rawentries(subdir=subdir, **map)
384 387
385 388 if sortcolumn and sortdefault != (sortcolumn, descending):
386 389 sortkey = '%s_sort' % sortcolumn
387 390 rows = sorted(rows, key=lambda x: x[sortkey],
388 391 reverse=descending)
389 392 for row, parity in zip(rows, paritygen(self.stripecount)):
390 393 row['parity'] = parity
391 394 yield row
392 395
393 396 self.refresh()
394 397 sortable = ["name", "description", "contact", "lastchange"]
395 398 sortcolumn, descending = sortdefault
396 399 if 'sort' in req.form:
397 400 sortcolumn = req.form['sort'][0]
398 401 descending = sortcolumn.startswith('-')
399 402 if descending:
400 403 sortcolumn = sortcolumn[1:]
401 404 if sortcolumn not in sortable:
402 405 sortcolumn = ""
403 406
404 407 sort = [("sort_%s" % column,
405 408 "%s%s" % ((not descending and column == sortcolumn)
406 409 and "-" or "", column))
407 410 for column in sortable]
408 411
409 412 self.refresh()
410 413 self.updatereqenv(req.env)
411 414
412 415 return tmpl("index", entries=entries, subdir=subdir,
413 416 pathdef=makebreadcrumb('/' + subdir, self.prefix),
414 417 sortcolumn=sortcolumn, descending=descending,
415 418 **dict(sort))
416 419
417 420 def templater(self, req):
418 421
419 422 def motd(**map):
420 423 if self.motd is not None:
421 424 yield self.motd
422 425 else:
423 426 yield config('web', 'motd', '')
424 427
425 428 def config(section, name, default=None, untrusted=True):
426 429 return self.ui.config(section, name, default, untrusted)
427 430
428 431 self.updatereqenv(req.env)
429 432
430 433 url = req.env.get('SCRIPT_NAME', '')
431 434 if not url.endswith('/'):
432 435 url += '/'
433 436
434 437 vars = {}
435 438 styles = (
436 439 req.form.get('style', [None])[0],
437 440 config('web', 'style'),
438 441 'paper'
439 442 )
440 443 style, mapfile = templater.stylemap(styles, self.templatepath)
441 444 if style == styles[0]:
442 445 vars['style'] = style
443 446
444 447 start = url[-1] == '?' and '&' or '?'
445 448 sessionvars = webutil.sessionvars(vars, start)
446 449 logourl = config('web', 'logourl', 'http://mercurial.selenic.com/')
447 450 logoimg = config('web', 'logoimg', 'hglogo.png')
448 451 staticurl = config('web', 'staticurl') or url + 'static/'
449 452 if not staticurl.endswith('/'):
450 453 staticurl += '/'
451 454
452 455 tmpl = templater.templater(mapfile,
453 456 defaults={"encoding": encoding.encoding,
454 457 "motd": motd,
455 458 "url": url,
456 459 "logourl": logourl,
457 460 "logoimg": logoimg,
458 461 "staticurl": staticurl,
459 462 "sessionvars": sessionvars,
460 463 "style": style,
461 464 })
462 465 return tmpl
463 466
464 467 def updatereqenv(self, env):
465 468 if self._baseurl is not None:
466 469 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
467 470 env['SERVER_NAME'] = name
468 471 env['SERVER_PORT'] = port
469 472 env['SCRIPT_NAME'] = path
General Comments 0
You need to be logged in to leave comments. Login now