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