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