##// END OF EJS Templates
hgweb: introduce helper method to update req.env
Yuya Nishihara -
r10673:9848b39a stable
parent child Browse files
Show More
@@ -1,341 +1,343 b''
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
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, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 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, time
9 import os, re, time
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import ui, hg, util, templater
11 from mercurial import ui, hg, util, templater
12 from mercurial import error, encoding
12 from mercurial import error, encoding
13 from common import ErrorResponse, get_mtime, staticfile, paritygen, \
13 from common import ErrorResponse, get_mtime, staticfile, paritygen, \
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from hgweb_mod import hgweb
15 from hgweb_mod import hgweb
16 from request import wsgirequest
16 from request import wsgirequest
17 import webutil
17 import webutil
18
18
19 def cleannames(items):
19 def cleannames(items):
20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
21
21
22 def findrepos(paths):
22 def findrepos(paths):
23 repos = []
23 repos = []
24 for prefix, root in cleannames(paths):
24 for prefix, root in cleannames(paths):
25 roothead, roottail = os.path.split(root)
25 roothead, roottail = os.path.split(root)
26 # "foo = /bar/*" makes every subrepo of /bar/ to be
26 # "foo = /bar/*" makes every subrepo of /bar/ to be
27 # mounted as foo/subrepo
27 # mounted as foo/subrepo
28 # and "foo = /bar/**" also recurses into the subdirectories,
28 # and "foo = /bar/**" also recurses into the subdirectories,
29 # remember to use it without working dir.
29 # remember to use it without working dir.
30 try:
30 try:
31 recurse = {'*': False, '**': True}[roottail]
31 recurse = {'*': False, '**': True}[roottail]
32 except KeyError:
32 except KeyError:
33 repos.append((prefix, root))
33 repos.append((prefix, root))
34 continue
34 continue
35 roothead = os.path.normpath(roothead)
35 roothead = os.path.normpath(roothead)
36 for path in util.walkrepos(roothead, followsym=True, recurse=recurse):
36 for path in util.walkrepos(roothead, followsym=True, recurse=recurse):
37 path = os.path.normpath(path)
37 path = os.path.normpath(path)
38 name = util.pconvert(path[len(roothead):]).strip('/')
38 name = util.pconvert(path[len(roothead):]).strip('/')
39 if prefix:
39 if prefix:
40 name = prefix + '/' + name
40 name = prefix + '/' + name
41 repos.append((name, path))
41 repos.append((name, path))
42 return repos
42 return repos
43
43
44 class hgwebdir(object):
44 class hgwebdir(object):
45 refreshinterval = 20
45 refreshinterval = 20
46
46
47 def __init__(self, conf, baseui=None):
47 def __init__(self, conf, baseui=None):
48 self.conf = conf
48 self.conf = conf
49 self.baseui = baseui
49 self.baseui = baseui
50 self.lastrefresh = 0
50 self.lastrefresh = 0
51 self.motd = None
51 self.motd = None
52 self.refresh()
52 self.refresh()
53
53
54 def refresh(self):
54 def refresh(self):
55 if self.lastrefresh + self.refreshinterval > time.time():
55 if self.lastrefresh + self.refreshinterval > time.time():
56 return
56 return
57
57
58 if self.baseui:
58 if self.baseui:
59 self.ui = self.baseui.copy()
59 self.ui = self.baseui.copy()
60 else:
60 else:
61 self.ui = ui.ui()
61 self.ui = ui.ui()
62 self.ui.setconfig('ui', 'report_untrusted', 'off')
62 self.ui.setconfig('ui', 'report_untrusted', 'off')
63 self.ui.setconfig('ui', 'interactive', 'off')
63 self.ui.setconfig('ui', 'interactive', 'off')
64
64
65 if not isinstance(self.conf, (dict, list, tuple)):
65 if not isinstance(self.conf, (dict, list, tuple)):
66 map = {'paths': 'hgweb-paths'}
66 map = {'paths': 'hgweb-paths'}
67 self.ui.readconfig(self.conf, remap=map, trust=True)
67 self.ui.readconfig(self.conf, remap=map, trust=True)
68 paths = self.ui.configitems('hgweb-paths')
68 paths = self.ui.configitems('hgweb-paths')
69 elif isinstance(self.conf, (list, tuple)):
69 elif isinstance(self.conf, (list, tuple)):
70 paths = self.conf
70 paths = self.conf
71 elif isinstance(self.conf, dict):
71 elif isinstance(self.conf, dict):
72 paths = self.conf.items()
72 paths = self.conf.items()
73
73
74 encoding.encoding = self.ui.config('web', 'encoding',
74 encoding.encoding = self.ui.config('web', 'encoding',
75 encoding.encoding)
75 encoding.encoding)
76 self.style = self.ui.config('web', 'style', 'paper')
76 self.style = self.ui.config('web', 'style', 'paper')
77 self.stripecount = self.ui.config('web', 'stripes', 1)
77 self.stripecount = self.ui.config('web', 'stripes', 1)
78 if self.stripecount:
78 if self.stripecount:
79 self.stripecount = int(self.stripecount)
79 self.stripecount = int(self.stripecount)
80 self._baseurl = self.ui.config('web', 'baseurl')
80 self._baseurl = self.ui.config('web', 'baseurl')
81
81
82 self.repos = findrepos(paths)
82 self.repos = findrepos(paths)
83 for prefix, root in self.ui.configitems('collections'):
83 for prefix, root in self.ui.configitems('collections'):
84 prefix = util.pconvert(prefix)
84 prefix = util.pconvert(prefix)
85 for path in util.walkrepos(root, followsym=True):
85 for path in util.walkrepos(root, followsym=True):
86 repo = os.path.normpath(path)
86 repo = os.path.normpath(path)
87 name = util.pconvert(repo)
87 name = util.pconvert(repo)
88 if name.startswith(prefix):
88 if name.startswith(prefix):
89 name = name[len(prefix):]
89 name = name[len(prefix):]
90 self.repos.append((name.lstrip('/'), repo))
90 self.repos.append((name.lstrip('/'), repo))
91
91
92 self.lastrefresh = time.time()
92 self.lastrefresh = time.time()
93
93
94 def run(self):
94 def run(self):
95 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
95 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
96 raise RuntimeError("This function is only intended to be "
96 raise RuntimeError("This function is only intended to be "
97 "called while running as a CGI script.")
97 "called while running as a CGI script.")
98 import mercurial.hgweb.wsgicgi as wsgicgi
98 import mercurial.hgweb.wsgicgi as wsgicgi
99 wsgicgi.launch(self)
99 wsgicgi.launch(self)
100
100
101 def __call__(self, env, respond):
101 def __call__(self, env, respond):
102 req = wsgirequest(env, respond)
102 req = wsgirequest(env, respond)
103 return self.run_wsgi(req)
103 return self.run_wsgi(req)
104
104
105 def read_allowed(self, ui, req):
105 def read_allowed(self, ui, req):
106 """Check allow_read and deny_read config options of a repo's ui object
106 """Check allow_read and deny_read config options of a repo's ui object
107 to determine user permissions. By default, with neither option set (or
107 to determine user permissions. By default, with neither option set (or
108 both empty), allow all users to read the repo. There are two ways a
108 both empty), allow all users to read the repo. There are two ways a
109 user can be denied read access: (1) deny_read is not empty, and the
109 user can be denied read access: (1) deny_read is not empty, and the
110 user is unauthenticated or deny_read contains user (or *), and (2)
110 user is unauthenticated or deny_read contains user (or *), and (2)
111 allow_read is not empty and the user is not in allow_read. Return True
111 allow_read is not empty and the user is not in allow_read. Return True
112 if user is allowed to read the repo, else return False."""
112 if user is allowed to read the repo, else return False."""
113
113
114 user = req.env.get('REMOTE_USER')
114 user = req.env.get('REMOTE_USER')
115
115
116 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
116 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
117 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
117 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
118 return False
118 return False
119
119
120 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
120 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
121 # by default, allow reading if no allow_read option has been set
121 # by default, allow reading if no allow_read option has been set
122 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
122 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
123 return True
123 return True
124
124
125 return False
125 return False
126
126
127 def run_wsgi(self, req):
127 def run_wsgi(self, req):
128 try:
128 try:
129 try:
129 try:
130 self.refresh()
130 self.refresh()
131
131
132 virtual = req.env.get("PATH_INFO", "").strip('/')
132 virtual = req.env.get("PATH_INFO", "").strip('/')
133 tmpl = self.templater(req)
133 tmpl = self.templater(req)
134 ctype = tmpl('mimetype', encoding=encoding.encoding)
134 ctype = tmpl('mimetype', encoding=encoding.encoding)
135 ctype = templater.stringify(ctype)
135 ctype = templater.stringify(ctype)
136
136
137 # a static file
137 # a static file
138 if virtual.startswith('static/') or 'static' in req.form:
138 if virtual.startswith('static/') or 'static' in req.form:
139 if virtual.startswith('static/'):
139 if virtual.startswith('static/'):
140 fname = virtual[7:]
140 fname = virtual[7:]
141 else:
141 else:
142 fname = req.form['static'][0]
142 fname = req.form['static'][0]
143 static = templater.templatepath('static')
143 static = templater.templatepath('static')
144 return (staticfile(static, fname, req),)
144 return (staticfile(static, fname, req),)
145
145
146 # top-level index
146 # top-level index
147 elif not virtual:
147 elif not virtual:
148 req.respond(HTTP_OK, ctype)
148 req.respond(HTTP_OK, ctype)
149 return self.makeindex(req, tmpl)
149 return self.makeindex(req, tmpl)
150
150
151 # nested indexes and hgwebs
151 # nested indexes and hgwebs
152
152
153 repos = dict(self.repos)
153 repos = dict(self.repos)
154 while virtual:
154 while virtual:
155 real = repos.get(virtual)
155 real = repos.get(virtual)
156 if real:
156 if real:
157 req.env['REPO_NAME'] = virtual
157 req.env['REPO_NAME'] = virtual
158 try:
158 try:
159 repo = hg.repository(self.ui, real)
159 repo = hg.repository(self.ui, real)
160 return hgweb(repo).run_wsgi(req)
160 return hgweb(repo).run_wsgi(req)
161 except IOError, inst:
161 except IOError, inst:
162 msg = inst.strerror
162 msg = inst.strerror
163 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
163 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
164 except error.RepoError, inst:
164 except error.RepoError, inst:
165 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
165 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
166
166
167 # browse subdirectories
167 # browse subdirectories
168 subdir = virtual + '/'
168 subdir = virtual + '/'
169 if [r for r in repos if r.startswith(subdir)]:
169 if [r for r in repos if r.startswith(subdir)]:
170 req.respond(HTTP_OK, ctype)
170 req.respond(HTTP_OK, ctype)
171 return self.makeindex(req, tmpl, subdir)
171 return self.makeindex(req, tmpl, subdir)
172
172
173 up = virtual.rfind('/')
173 up = virtual.rfind('/')
174 if up < 0:
174 if up < 0:
175 break
175 break
176 virtual = virtual[:up]
176 virtual = virtual[:up]
177
177
178 # prefixes not found
178 # prefixes not found
179 req.respond(HTTP_NOT_FOUND, ctype)
179 req.respond(HTTP_NOT_FOUND, ctype)
180 return tmpl("notfound", repo=virtual)
180 return tmpl("notfound", repo=virtual)
181
181
182 except ErrorResponse, err:
182 except ErrorResponse, err:
183 req.respond(err, ctype)
183 req.respond(err, ctype)
184 return tmpl('error', error=err.message or '')
184 return tmpl('error', error=err.message or '')
185 finally:
185 finally:
186 tmpl = None
186 tmpl = None
187
187
188 def makeindex(self, req, tmpl, subdir=""):
188 def makeindex(self, req, tmpl, subdir=""):
189
189
190 def archivelist(ui, nodeid, url):
190 def archivelist(ui, nodeid, url):
191 allowed = ui.configlist("web", "allow_archive", untrusted=True)
191 allowed = ui.configlist("web", "allow_archive", untrusted=True)
192 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
192 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
193 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
193 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
194 untrusted=True):
194 untrusted=True):
195 yield {"type" : i[0], "extension": i[1],
195 yield {"type" : i[0], "extension": i[1],
196 "node": nodeid, "url": url}
196 "node": nodeid, "url": url}
197
197
198 sortdefault = None, False
198 sortdefault = None, False
199 def entries(sortcolumn="", descending=False, subdir="", **map):
199 def entries(sortcolumn="", descending=False, subdir="", **map):
200
200
201 rows = []
201 rows = []
202 parity = paritygen(self.stripecount)
202 parity = paritygen(self.stripecount)
203 descend = self.ui.configbool('web', 'descend', True)
203 descend = self.ui.configbool('web', 'descend', True)
204 for name, path in self.repos:
204 for name, path in self.repos:
205
205
206 if not name.startswith(subdir):
206 if not name.startswith(subdir):
207 continue
207 continue
208 name = name[len(subdir):]
208 name = name[len(subdir):]
209 if not descend and '/' in name:
209 if not descend and '/' in name:
210 continue
210 continue
211
211
212 u = self.ui.copy()
212 u = self.ui.copy()
213 try:
213 try:
214 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
214 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
215 except Exception, e:
215 except Exception, e:
216 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
216 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
217 continue
217 continue
218 def get(section, name, default=None):
218 def get(section, name, default=None):
219 return u.config(section, name, default, untrusted=True)
219 return u.config(section, name, default, untrusted=True)
220
220
221 if u.configbool("web", "hidden", untrusted=True):
221 if u.configbool("web", "hidden", untrusted=True):
222 continue
222 continue
223
223
224 if not self.read_allowed(u, req):
224 if not self.read_allowed(u, req):
225 continue
225 continue
226
226
227 parts = [name]
227 parts = [name]
228 if 'PATH_INFO' in req.env:
228 if 'PATH_INFO' in req.env:
229 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
229 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
230 if req.env['SCRIPT_NAME']:
230 if req.env['SCRIPT_NAME']:
231 parts.insert(0, req.env['SCRIPT_NAME'])
231 parts.insert(0, req.env['SCRIPT_NAME'])
232 m = re.match('((?:https?://)?)(.*)', '/'.join(parts))
232 m = re.match('((?:https?://)?)(.*)', '/'.join(parts))
233 # squish repeated slashes out of the path component
233 # squish repeated slashes out of the path component
234 url = m.group(1) + re.sub('/+', '/', m.group(2)) + '/'
234 url = m.group(1) + re.sub('/+', '/', m.group(2)) + '/'
235
235
236 # update time with local timezone
236 # update time with local timezone
237 try:
237 try:
238 r = hg.repository(self.ui, path)
238 r = hg.repository(self.ui, path)
239 d = (get_mtime(r.spath), util.makedate()[1])
239 d = (get_mtime(r.spath), util.makedate()[1])
240 except OSError:
240 except OSError:
241 continue
241 continue
242
242
243 contact = get_contact(get)
243 contact = get_contact(get)
244 description = get("web", "description", "")
244 description = get("web", "description", "")
245 name = get("web", "name", name)
245 name = get("web", "name", name)
246 row = dict(contact=contact or "unknown",
246 row = dict(contact=contact or "unknown",
247 contact_sort=contact.upper() or "unknown",
247 contact_sort=contact.upper() or "unknown",
248 name=name,
248 name=name,
249 name_sort=name,
249 name_sort=name,
250 url=url,
250 url=url,
251 description=description or "unknown",
251 description=description or "unknown",
252 description_sort=description.upper() or "unknown",
252 description_sort=description.upper() or "unknown",
253 lastchange=d,
253 lastchange=d,
254 lastchange_sort=d[1]-d[0],
254 lastchange_sort=d[1]-d[0],
255 archives=archivelist(u, "tip", url))
255 archives=archivelist(u, "tip", url))
256 if (not sortcolumn or (sortcolumn, descending) == sortdefault):
256 if (not sortcolumn or (sortcolumn, descending) == sortdefault):
257 # fast path for unsorted output
257 # fast path for unsorted output
258 row['parity'] = parity.next()
258 row['parity'] = parity.next()
259 yield row
259 yield row
260 else:
260 else:
261 rows.append((row["%s_sort" % sortcolumn], row))
261 rows.append((row["%s_sort" % sortcolumn], row))
262 if rows:
262 if rows:
263 rows.sort()
263 rows.sort()
264 if descending:
264 if descending:
265 rows.reverse()
265 rows.reverse()
266 for key, row in rows:
266 for key, row in rows:
267 row['parity'] = parity.next()
267 row['parity'] = parity.next()
268 yield row
268 yield row
269
269
270 self.refresh()
270 self.refresh()
271 sortable = ["name", "description", "contact", "lastchange"]
271 sortable = ["name", "description", "contact", "lastchange"]
272 sortcolumn, descending = sortdefault
272 sortcolumn, descending = sortdefault
273 if 'sort' in req.form:
273 if 'sort' in req.form:
274 sortcolumn = req.form['sort'][0]
274 sortcolumn = req.form['sort'][0]
275 descending = sortcolumn.startswith('-')
275 descending = sortcolumn.startswith('-')
276 if descending:
276 if descending:
277 sortcolumn = sortcolumn[1:]
277 sortcolumn = sortcolumn[1:]
278 if sortcolumn not in sortable:
278 if sortcolumn not in sortable:
279 sortcolumn = ""
279 sortcolumn = ""
280
280
281 sort = [("sort_%s" % column,
281 sort = [("sort_%s" % column,
282 "%s%s" % ((not descending and column == sortcolumn)
282 "%s%s" % ((not descending and column == sortcolumn)
283 and "-" or "", column))
283 and "-" or "", column))
284 for column in sortable]
284 for column in sortable]
285
285
286 self.refresh()
286 self.refresh()
287 if self._baseurl is not None:
287 self.updatereqenv(req.env)
288 req.env['SCRIPT_NAME'] = self._baseurl
289
288
290 return tmpl("index", entries=entries, subdir=subdir,
289 return tmpl("index", entries=entries, subdir=subdir,
291 sortcolumn=sortcolumn, descending=descending,
290 sortcolumn=sortcolumn, descending=descending,
292 **dict(sort))
291 **dict(sort))
293
292
294 def templater(self, req):
293 def templater(self, req):
295
294
296 def header(**map):
295 def header(**map):
297 yield tmpl('header', encoding=encoding.encoding, **map)
296 yield tmpl('header', encoding=encoding.encoding, **map)
298
297
299 def footer(**map):
298 def footer(**map):
300 yield tmpl("footer", **map)
299 yield tmpl("footer", **map)
301
300
302 def motd(**map):
301 def motd(**map):
303 if self.motd is not None:
302 if self.motd is not None:
304 yield self.motd
303 yield self.motd
305 else:
304 else:
306 yield config('web', 'motd', '')
305 yield config('web', 'motd', '')
307
306
308 def config(section, name, default=None, untrusted=True):
307 def config(section, name, default=None, untrusted=True):
309 return self.ui.config(section, name, default, untrusted)
308 return self.ui.config(section, name, default, untrusted)
310
309
311 if self._baseurl is not None:
310 self.updatereqenv(req.env)
312 req.env['SCRIPT_NAME'] = self._baseurl
313
311
314 url = req.env.get('SCRIPT_NAME', '')
312 url = req.env.get('SCRIPT_NAME', '')
315 if not url.endswith('/'):
313 if not url.endswith('/'):
316 url += '/'
314 url += '/'
317
315
318 vars = {}
316 vars = {}
319 styles = (
317 styles = (
320 req.form.get('style', [None])[0],
318 req.form.get('style', [None])[0],
321 config('web', 'style'),
319 config('web', 'style'),
322 'paper'
320 'paper'
323 )
321 )
324 style, mapfile = templater.stylemap(styles)
322 style, mapfile = templater.stylemap(styles)
325 if style == styles[0]:
323 if style == styles[0]:
326 vars['style'] = style
324 vars['style'] = style
327
325
328 start = url[-1] == '?' and '&' or '?'
326 start = url[-1] == '?' and '&' or '?'
329 sessionvars = webutil.sessionvars(vars, start)
327 sessionvars = webutil.sessionvars(vars, start)
330 staticurl = config('web', 'staticurl') or url + 'static/'
328 staticurl = config('web', 'staticurl') or url + 'static/'
331 if not staticurl.endswith('/'):
329 if not staticurl.endswith('/'):
332 staticurl += '/'
330 staticurl += '/'
333
331
334 tmpl = templater.templater(mapfile,
332 tmpl = templater.templater(mapfile,
335 defaults={"header": header,
333 defaults={"header": header,
336 "footer": footer,
334 "footer": footer,
337 "motd": motd,
335 "motd": motd,
338 "url": url,
336 "url": url,
339 "staticurl": staticurl,
337 "staticurl": staticurl,
340 "sessionvars": sessionvars})
338 "sessionvars": sessionvars})
341 return tmpl
339 return tmpl
340
341 def updatereqenv(self, env):
342 if self._baseurl is not None:
343 env['SCRIPT_NAME'] = self._baseurl
General Comments 0
You need to be logged in to leave comments. Login now