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