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