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