##// END OF EJS Templates
hgweb: protocol requests are processed immediately...
Dirkjan Ochtman -
r6777:44c51574 default
parent child Browse files
Show More
@@ -1,354 +1,358 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
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-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 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, mimetypes
9 import os, mimetypes
10 from mercurial.node import hex, nullid
10 from mercurial.node import hex, nullid
11 from mercurial.repo import RepoError
11 from mercurial.repo import RepoError
12 from mercurial import mdiff, ui, hg, util, patch, hook
12 from mercurial import mdiff, ui, hg, util, patch, hook
13 from mercurial import revlog, templater, templatefilters, changegroup
13 from mercurial import revlog, templater, templatefilters, changegroup
14 from common import get_mtime, style_map, paritygen, countgen, ErrorResponse
14 from common import get_mtime, style_map, paritygen, countgen, ErrorResponse
15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 from request import wsgirequest
16 from request import wsgirequest
17 import webcommands, protocol, webutil
17 import webcommands, protocol, webutil
18
18
19 class hgweb(object):
19 class hgweb(object):
20 def __init__(self, repo, name=None):
20 def __init__(self, repo, name=None):
21 if isinstance(repo, str):
21 if isinstance(repo, str):
22 parentui = ui.ui(report_untrusted=False, interactive=False)
22 parentui = ui.ui(report_untrusted=False, interactive=False)
23 self.repo = hg.repository(parentui, repo)
23 self.repo = hg.repository(parentui, repo)
24 else:
24 else:
25 self.repo = repo
25 self.repo = repo
26
26
27 hook.redirect(True)
27 hook.redirect(True)
28 self.mtime = -1
28 self.mtime = -1
29 self.reponame = name
29 self.reponame = name
30 self.archives = 'zip', 'gz', 'bz2'
30 self.archives = 'zip', 'gz', 'bz2'
31 self.stripecount = 1
31 self.stripecount = 1
32 self._capabilities = None
32 self._capabilities = None
33 # a repo owner may set web.templates in .hg/hgrc to get any file
33 # a repo owner may set web.templates in .hg/hgrc to get any file
34 # readable by the user running the CGI script
34 # readable by the user running the CGI script
35 self.templatepath = self.config("web", "templates",
35 self.templatepath = self.config("web", "templates",
36 templater.templatepath(),
36 templater.templatepath(),
37 untrusted=False)
37 untrusted=False)
38
38
39 # The CGI scripts are often run by a user different from the repo owner.
39 # The CGI scripts are often run by a user different from the repo owner.
40 # Trust the settings from the .hg/hgrc files by default.
40 # Trust the settings from the .hg/hgrc files by default.
41 def config(self, section, name, default=None, untrusted=True):
41 def config(self, section, name, default=None, untrusted=True):
42 return self.repo.ui.config(section, name, default,
42 return self.repo.ui.config(section, name, default,
43 untrusted=untrusted)
43 untrusted=untrusted)
44
44
45 def configbool(self, section, name, default=False, untrusted=True):
45 def configbool(self, section, name, default=False, untrusted=True):
46 return self.repo.ui.configbool(section, name, default,
46 return self.repo.ui.configbool(section, name, default,
47 untrusted=untrusted)
47 untrusted=untrusted)
48
48
49 def configlist(self, section, name, default=None, untrusted=True):
49 def configlist(self, section, name, default=None, untrusted=True):
50 return self.repo.ui.configlist(section, name, default,
50 return self.repo.ui.configlist(section, name, default,
51 untrusted=untrusted)
51 untrusted=untrusted)
52
52
53 def refresh(self):
53 def refresh(self):
54 mtime = get_mtime(self.repo.root)
54 mtime = get_mtime(self.repo.root)
55 if mtime != self.mtime:
55 if mtime != self.mtime:
56 self.mtime = mtime
56 self.mtime = mtime
57 self.repo = hg.repository(self.repo.ui, self.repo.root)
57 self.repo = hg.repository(self.repo.ui, self.repo.root)
58 self.maxchanges = int(self.config("web", "maxchanges", 10))
58 self.maxchanges = int(self.config("web", "maxchanges", 10))
59 self.stripecount = int(self.config("web", "stripes", 1))
59 self.stripecount = int(self.config("web", "stripes", 1))
60 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
60 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
61 self.maxfiles = int(self.config("web", "maxfiles", 10))
61 self.maxfiles = int(self.config("web", "maxfiles", 10))
62 self.allowpull = self.configbool("web", "allowpull", True)
62 self.allowpull = self.configbool("web", "allowpull", True)
63 self.encoding = self.config("web", "encoding", util._encoding)
63 self.encoding = self.config("web", "encoding", util._encoding)
64 self._capabilities = None
64 self._capabilities = None
65
65
66 def capabilities(self):
66 def capabilities(self):
67 if self._capabilities is not None:
67 if self._capabilities is not None:
68 return self._capabilities
68 return self._capabilities
69 caps = ['lookup', 'changegroupsubset']
69 caps = ['lookup', 'changegroupsubset']
70 if self.configbool('server', 'uncompressed'):
70 if self.configbool('server', 'uncompressed'):
71 caps.append('stream=%d' % self.repo.changelog.version)
71 caps.append('stream=%d' % self.repo.changelog.version)
72 if changegroup.bundlepriority:
72 if changegroup.bundlepriority:
73 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
73 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
74 self._capabilities = caps
74 self._capabilities = caps
75 return caps
75 return caps
76
76
77 def run(self):
77 def run(self):
78 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
78 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
79 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
79 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
80 import mercurial.hgweb.wsgicgi as wsgicgi
80 import mercurial.hgweb.wsgicgi as wsgicgi
81 wsgicgi.launch(self)
81 wsgicgi.launch(self)
82
82
83 def __call__(self, env, respond):
83 def __call__(self, env, respond):
84 req = wsgirequest(env, respond)
84 req = wsgirequest(env, respond)
85 self.run_wsgi(req)
85 self.run_wsgi(req)
86 return req
86 return req
87
87
88 def run_wsgi(self, req):
88 def run_wsgi(self, req):
89
89
90 self.refresh()
90 self.refresh()
91
91
92 # process this if it's a protocol request
93 # protocol bits don't need to create any URLs
94 # and the clients always use the old URL structure
95
96 cmd = req.form.get('cmd', [''])[0]
97 if cmd and cmd in protocol.__all__:
98 method = getattr(protocol, cmd)
99 method(self, req)
100 return
101
92 # work with CGI variables to create coherent structure
102 # work with CGI variables to create coherent structure
93 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
103 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
94
104
95 req.url = req.env['SCRIPT_NAME']
105 req.url = req.env['SCRIPT_NAME']
96 if not req.url.endswith('/'):
106 if not req.url.endswith('/'):
97 req.url += '/'
107 req.url += '/'
98 if 'REPO_NAME' in req.env:
108 if 'REPO_NAME' in req.env:
99 req.url += req.env['REPO_NAME'] + '/'
109 req.url += req.env['REPO_NAME'] + '/'
100
110
101 if 'PATH_INFO' in req.env:
111 if 'PATH_INFO' in req.env:
102 parts = req.env['PATH_INFO'].strip('/').split('/')
112 parts = req.env['PATH_INFO'].strip('/').split('/')
103 repo_parts = req.env.get('REPO_NAME', '').split('/')
113 repo_parts = req.env.get('REPO_NAME', '').split('/')
104 if parts[:len(repo_parts)] == repo_parts:
114 if parts[:len(repo_parts)] == repo_parts:
105 parts = parts[len(repo_parts):]
115 parts = parts[len(repo_parts):]
106 query = '/'.join(parts)
116 query = '/'.join(parts)
107 else:
117 else:
108 query = req.env['QUERY_STRING'].split('&', 1)[0]
118 query = req.env['QUERY_STRING'].split('&', 1)[0]
109 query = query.split(';', 1)[0]
119 query = query.split(';', 1)[0]
110
120
111 # translate user-visible url structure to internal structure
121 # translate user-visible url structure to internal structure
112
122
113 args = query.split('/', 2)
123 args = query.split('/', 2)
114 if 'cmd' not in req.form and args and args[0]:
124 if 'cmd' not in req.form and args and args[0]:
115
125
116 cmd = args.pop(0)
126 cmd = args.pop(0)
117 style = cmd.rfind('-')
127 style = cmd.rfind('-')
118 if style != -1:
128 if style != -1:
119 req.form['style'] = [cmd[:style]]
129 req.form['style'] = [cmd[:style]]
120 cmd = cmd[style+1:]
130 cmd = cmd[style+1:]
121
131
122 # avoid accepting e.g. style parameter as command
132 # avoid accepting e.g. style parameter as command
123 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
133 if hasattr(webcommands, cmd):
124 req.form['cmd'] = [cmd]
134 req.form['cmd'] = [cmd]
135 else:
136 cmd = ''
125
137
126 if args and args[0]:
138 if args and args[0]:
127 node = args.pop(0)
139 node = args.pop(0)
128 req.form['node'] = [node]
140 req.form['node'] = [node]
129 if args:
141 if args:
130 req.form['file'] = args
142 req.form['file'] = args
131
143
132 if cmd == 'static':
144 if cmd == 'static':
133 req.form['file'] = req.form['node']
145 req.form['file'] = req.form['node']
134 elif cmd == 'archive':
146 elif cmd == 'archive':
135 fn = req.form['node'][0]
147 fn = req.form['node'][0]
136 for type_, spec in self.archive_specs.iteritems():
148 for type_, spec in self.archive_specs.iteritems():
137 ext = spec[2]
149 ext = spec[2]
138 if fn.endswith(ext):
150 if fn.endswith(ext):
139 req.form['node'] = [fn[:-len(ext)]]
151 req.form['node'] = [fn[:-len(ext)]]
140 req.form['type'] = [type_]
152 req.form['type'] = [type_]
141
153
142 # process this if it's a protocol request
143
144 cmd = req.form.get('cmd', [''])[0]
145 if cmd in protocol.__all__:
146 method = getattr(protocol, cmd)
147 method(self, req)
148 return
149
150 # process the web interface request
154 # process the web interface request
151
155
152 try:
156 try:
153
157
154 tmpl = self.templater(req)
158 tmpl = self.templater(req)
155 ctype = tmpl('mimetype', encoding=self.encoding)
159 ctype = tmpl('mimetype', encoding=self.encoding)
156 ctype = templater.stringify(ctype)
160 ctype = templater.stringify(ctype)
157
161
158 if cmd == '':
162 if cmd == '':
159 req.form['cmd'] = [tmpl.cache['default']]
163 req.form['cmd'] = [tmpl.cache['default']]
160 cmd = req.form['cmd'][0]
164 cmd = req.form['cmd'][0]
161
165
162 if cmd not in webcommands.__all__:
166 if cmd not in webcommands.__all__:
163 msg = 'no such method: %s' % cmd
167 msg = 'no such method: %s' % cmd
164 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
168 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
165 elif cmd == 'file' and 'raw' in req.form.get('style', []):
169 elif cmd == 'file' and 'raw' in req.form.get('style', []):
166 self.ctype = ctype
170 self.ctype = ctype
167 content = webcommands.rawfile(self, req, tmpl)
171 content = webcommands.rawfile(self, req, tmpl)
168 else:
172 else:
169 content = getattr(webcommands, cmd)(self, req, tmpl)
173 content = getattr(webcommands, cmd)(self, req, tmpl)
170 req.respond(HTTP_OK, ctype)
174 req.respond(HTTP_OK, ctype)
171
175
172 req.write(content)
176 req.write(content)
173 del tmpl
177 del tmpl
174
178
175 except revlog.LookupError, err:
179 except revlog.LookupError, err:
176 req.respond(HTTP_NOT_FOUND, ctype)
180 req.respond(HTTP_NOT_FOUND, ctype)
177 msg = str(err)
181 msg = str(err)
178 if 'manifest' not in msg:
182 if 'manifest' not in msg:
179 msg = 'revision not found: %s' % err.name
183 msg = 'revision not found: %s' % err.name
180 req.write(tmpl('error', error=msg))
184 req.write(tmpl('error', error=msg))
181 except (RepoError, revlog.RevlogError), inst:
185 except (RepoError, revlog.RevlogError), inst:
182 req.respond(HTTP_SERVER_ERROR, ctype)
186 req.respond(HTTP_SERVER_ERROR, ctype)
183 req.write(tmpl('error', error=str(inst)))
187 req.write(tmpl('error', error=str(inst)))
184 except ErrorResponse, inst:
188 except ErrorResponse, inst:
185 req.respond(inst.code, ctype)
189 req.respond(inst.code, ctype)
186 req.write(tmpl('error', error=inst.message))
190 req.write(tmpl('error', error=inst.message))
187
191
188 def templater(self, req):
192 def templater(self, req):
189
193
190 # determine scheme, port and server name
194 # determine scheme, port and server name
191 # this is needed to create absolute urls
195 # this is needed to create absolute urls
192
196
193 proto = req.env.get('wsgi.url_scheme')
197 proto = req.env.get('wsgi.url_scheme')
194 if proto == 'https':
198 if proto == 'https':
195 proto = 'https'
199 proto = 'https'
196 default_port = "443"
200 default_port = "443"
197 else:
201 else:
198 proto = 'http'
202 proto = 'http'
199 default_port = "80"
203 default_port = "80"
200
204
201 port = req.env["SERVER_PORT"]
205 port = req.env["SERVER_PORT"]
202 port = port != default_port and (":" + port) or ""
206 port = port != default_port and (":" + port) or ""
203 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
207 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
204 staticurl = self.config("web", "staticurl") or req.url + 'static/'
208 staticurl = self.config("web", "staticurl") or req.url + 'static/'
205 if not staticurl.endswith('/'):
209 if not staticurl.endswith('/'):
206 staticurl += '/'
210 staticurl += '/'
207
211
208 # some functions for the templater
212 # some functions for the templater
209
213
210 def header(**map):
214 def header(**map):
211 yield tmpl('header', encoding=self.encoding, **map)
215 yield tmpl('header', encoding=self.encoding, **map)
212
216
213 def footer(**map):
217 def footer(**map):
214 yield tmpl("footer", **map)
218 yield tmpl("footer", **map)
215
219
216 def motd(**map):
220 def motd(**map):
217 yield self.config("web", "motd", "")
221 yield self.config("web", "motd", "")
218
222
219 def sessionvars(**map):
223 def sessionvars(**map):
220 fields = []
224 fields = []
221 if 'style' in req.form:
225 if 'style' in req.form:
222 style = req.form['style'][0]
226 style = req.form['style'][0]
223 if style != self.config('web', 'style', ''):
227 if style != self.config('web', 'style', ''):
224 fields.append(('style', style))
228 fields.append(('style', style))
225
229
226 separator = req.url[-1] == '?' and ';' or '?'
230 separator = req.url[-1] == '?' and ';' or '?'
227 for name, value in fields:
231 for name, value in fields:
228 yield dict(name=name, value=value, separator=separator)
232 yield dict(name=name, value=value, separator=separator)
229 separator = ';'
233 separator = ';'
230
234
231 # figure out which style to use
235 # figure out which style to use
232
236
233 style = self.config("web", "style", "")
237 style = self.config("web", "style", "")
234 if 'style' in req.form:
238 if 'style' in req.form:
235 style = req.form['style'][0]
239 style = req.form['style'][0]
236 mapfile = style_map(self.templatepath, style)
240 mapfile = style_map(self.templatepath, style)
237
241
238 if not self.reponame:
242 if not self.reponame:
239 self.reponame = (self.config("web", "name")
243 self.reponame = (self.config("web", "name")
240 or req.env.get('REPO_NAME')
244 or req.env.get('REPO_NAME')
241 or req.url.strip('/') or self.repo.root)
245 or req.url.strip('/') or self.repo.root)
242
246
243 # create the templater
247 # create the templater
244
248
245 tmpl = templater.templater(mapfile, templatefilters.filters,
249 tmpl = templater.templater(mapfile, templatefilters.filters,
246 defaults={"url": req.url,
250 defaults={"url": req.url,
247 "staticurl": staticurl,
251 "staticurl": staticurl,
248 "urlbase": urlbase,
252 "urlbase": urlbase,
249 "repo": self.reponame,
253 "repo": self.reponame,
250 "header": header,
254 "header": header,
251 "footer": footer,
255 "footer": footer,
252 "motd": motd,
256 "motd": motd,
253 "sessionvars": sessionvars
257 "sessionvars": sessionvars
254 })
258 })
255 return tmpl
259 return tmpl
256
260
257 def archivelist(self, nodeid):
261 def archivelist(self, nodeid):
258 allowed = self.configlist("web", "allow_archive")
262 allowed = self.configlist("web", "allow_archive")
259 for i, spec in self.archive_specs.iteritems():
263 for i, spec in self.archive_specs.iteritems():
260 if i in allowed or self.configbool("web", "allow" + i):
264 if i in allowed or self.configbool("web", "allow" + i):
261 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
265 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
262
266
263 def listfilediffs(self, tmpl, files, changeset):
267 def listfilediffs(self, tmpl, files, changeset):
264 for f in files[:self.maxfiles]:
268 for f in files[:self.maxfiles]:
265 yield tmpl("filedifflink", node=hex(changeset), file=f)
269 yield tmpl("filedifflink", node=hex(changeset), file=f)
266 if len(files) > self.maxfiles:
270 if len(files) > self.maxfiles:
267 yield tmpl("fileellipses")
271 yield tmpl("fileellipses")
268
272
269 def diff(self, tmpl, node1, node2, files):
273 def diff(self, tmpl, node1, node2, files):
270 def filterfiles(filters, files):
274 def filterfiles(filters, files):
271 l = [x for x in files if x in filters]
275 l = [x for x in files if x in filters]
272
276
273 for t in filters:
277 for t in filters:
274 if t and t[-1] != os.sep:
278 if t and t[-1] != os.sep:
275 t += os.sep
279 t += os.sep
276 l += [x for x in files if x.startswith(t)]
280 l += [x for x in files if x.startswith(t)]
277 return l
281 return l
278
282
279 parity = paritygen(self.stripecount)
283 parity = paritygen(self.stripecount)
280 def diffblock(diff, f, fn):
284 def diffblock(diff, f, fn):
281 yield tmpl("diffblock",
285 yield tmpl("diffblock",
282 lines=prettyprintlines(diff),
286 lines=prettyprintlines(diff),
283 parity=parity.next(),
287 parity=parity.next(),
284 file=f,
288 file=f,
285 filenode=hex(fn or nullid))
289 filenode=hex(fn or nullid))
286
290
287 blockcount = countgen()
291 blockcount = countgen()
288 def prettyprintlines(diff):
292 def prettyprintlines(diff):
289 blockno = blockcount.next()
293 blockno = blockcount.next()
290 for lineno, l in enumerate(diff.splitlines(1)):
294 for lineno, l in enumerate(diff.splitlines(1)):
291 if blockno == 0:
295 if blockno == 0:
292 lineno = lineno + 1
296 lineno = lineno + 1
293 else:
297 else:
294 lineno = "%d.%d" % (blockno, lineno + 1)
298 lineno = "%d.%d" % (blockno, lineno + 1)
295 if l.startswith('+'):
299 if l.startswith('+'):
296 ltype = "difflineplus"
300 ltype = "difflineplus"
297 elif l.startswith('-'):
301 elif l.startswith('-'):
298 ltype = "difflineminus"
302 ltype = "difflineminus"
299 elif l.startswith('@'):
303 elif l.startswith('@'):
300 ltype = "difflineat"
304 ltype = "difflineat"
301 else:
305 else:
302 ltype = "diffline"
306 ltype = "diffline"
303 yield tmpl(ltype,
307 yield tmpl(ltype,
304 line=l,
308 line=l,
305 lineid="l%s" % lineno,
309 lineid="l%s" % lineno,
306 linenumber="% 8s" % lineno)
310 linenumber="% 8s" % lineno)
307
311
308 r = self.repo
312 r = self.repo
309 c1 = r[node1]
313 c1 = r[node1]
310 c2 = r[node2]
314 c2 = r[node2]
311 date1 = util.datestr(c1.date())
315 date1 = util.datestr(c1.date())
312 date2 = util.datestr(c2.date())
316 date2 = util.datestr(c2.date())
313
317
314 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
318 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
315 if files:
319 if files:
316 modified, added, removed = map(lambda x: filterfiles(files, x),
320 modified, added, removed = map(lambda x: filterfiles(files, x),
317 (modified, added, removed))
321 (modified, added, removed))
318
322
319 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
323 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
320 for f in modified:
324 for f in modified:
321 to = c1.filectx(f).data()
325 to = c1.filectx(f).data()
322 tn = c2.filectx(f).data()
326 tn = c2.filectx(f).data()
323 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
327 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
324 opts=diffopts), f, tn)
328 opts=diffopts), f, tn)
325 for f in added:
329 for f in added:
326 to = None
330 to = None
327 tn = c2.filectx(f).data()
331 tn = c2.filectx(f).data()
328 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
332 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
329 opts=diffopts), f, tn)
333 opts=diffopts), f, tn)
330 for f in removed:
334 for f in removed:
331 to = c1.filectx(f).data()
335 to = c1.filectx(f).data()
332 tn = None
336 tn = None
333 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
337 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
334 opts=diffopts), f, tn)
338 opts=diffopts), f, tn)
335
339
336 archive_specs = {
340 archive_specs = {
337 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
341 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
338 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
342 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
339 'zip': ('application/zip', 'zip', '.zip', None),
343 'zip': ('application/zip', 'zip', '.zip', None),
340 }
344 }
341
345
342 def check_perm(self, req, op, default):
346 def check_perm(self, req, op, default):
343 '''check permission for operation based on user auth.
347 '''check permission for operation based on user auth.
344 return true if op allowed, else false.
348 return true if op allowed, else false.
345 default is policy to use if no config given.'''
349 default is policy to use if no config given.'''
346
350
347 user = req.env.get('REMOTE_USER')
351 user = req.env.get('REMOTE_USER')
348
352
349 deny = self.configlist('web', 'deny_' + op)
353 deny = self.configlist('web', 'deny_' + op)
350 if deny and (not user or deny == ['*'] or user in deny):
354 if deny and (not user or deny == ['*'] or user in deny):
351 return False
355 return False
352
356
353 allow = self.configlist('web', 'allow_' + op)
357 allow = self.configlist('web', 'allow_' + op)
354 return (allow and (allow == ['*'] or user in allow)) or default
358 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,57 +1,57 b''
1 #!/bin/sh
1 #!/bin/sh
2 # An attempt at more fully testing the hgweb web interface.
2 # An attempt at more fully testing the hgweb web interface.
3 # The following things are tested elsewhere and are therefore omitted:
3 # The following things are tested elsewhere and are therefore omitted:
4 # - archive, tested in test-archive
4 # - archive, tested in test-archive
5 # - unbundle, tested in test-push-http
5 # - unbundle, tested in test-push-http
6 # - changegroupsubset, tested in test-pull
6 # - changegroupsubset, tested in test-pull
7
7
8 echo % Set up the repo
8 echo % Set up the repo
9 hg init test
9 hg init test
10 cd test
10 cd test
11 mkdir da
11 mkdir da
12 echo foo > da/foo
12 echo foo > da/foo
13 echo foo > foo
13 echo foo > foo
14 hg ci -d'0 0' -Ambase
14 hg ci -d'0 0' -Ambase
15 hg tag 1.0
15 hg tag 1.0
16 hg serve -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
16 hg serve -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
17 cat hg.pid >> $DAEMON_PIDS
17 cat hg.pid >> $DAEMON_PIDS
18
18
19 echo % Logs and changes
19 echo % Logs and changes
20 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
20 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
21 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
21 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
22 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/foo/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
22 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/foo/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
23 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/shortlog/' | sed "s/[0-9]* years/many years/"
23 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/shortlog/' | sed "s/[0-9]* years/many years/"
24 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/0/' | sed "s/[0-9]* years ago/long ago/g"
24 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/0/' | sed "s/[0-9]* years ago/long ago/g"
25 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/1/?style=raw'
25 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/1/?style=raw'
26
26
27 echo % File-related
27 echo % File-related
28 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo/?style=raw'
28 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo/?style=raw'
29 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/annotate/1/foo/?style=raw'
29 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/annotate/1/foo/?style=raw'
30 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/?style=raw'
30 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/?style=raw'
31 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo' | sed "s/[0-9]* years/many years/"
31 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo' | sed "s/[0-9]* years/many years/"
32 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/filediff/1/foo/?style=raw'
32 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/filediff/1/foo/?style=raw'
33
33
34 echo % Overviews
34 echo % Overviews
35 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/tags/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
35 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/tags/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
36 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/summary/?style=gitweb' | sed "s/[0-9]* years ago/long ago/g"
36 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/summary/?style=gitweb' | sed "s/[0-9]* years ago/long ago/g"
37
37
38 echo % capabilities
38 echo % capabilities
39 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/capabilities'
39 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities'
40 echo % heads
40 echo % heads
41 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/heads'
41 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=heads'
42 echo % lookup
42 echo % lookup
43 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/lookup/1'
43 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=lookup&node=1'
44 echo % branches
44 echo % branches
45 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/branches'
45 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=branches'
46 echo % changegroup
46 echo % changegroup
47 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/changegroup'
47 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=changegroup'
48 echo % stream_out
48 echo % stream_out
49 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/stream_out'
49 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=stream_out'
50 echo % failing unbundle, requires POST request
50 echo % failing unbundle, requires POST request
51 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/unbundle'
51 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=unbundle'
52
52
53 echo % Static files
53 echo % Static files
54 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/static/style.css'
54 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/static/style.css'
55
55
56 echo % ERRORS ENCOUNTERED
56 echo % ERRORS ENCOUNTERED
57 cat errors.log
57 cat errors.log
General Comments 0
You need to be logged in to leave comments. Login now