##// END OF EJS Templates
hgweb: correctly validate permissions with streamclone pulling
Benoit Boissinot -
r6630:8542fac2 default
parent child Browse files
Show More
@@ -1,228 +1,230 b''
1 #
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import cStringIO, zlib, tempfile, errno, os, sys
8 import cStringIO, zlib, tempfile, errno, os, sys
9 from mercurial import util, streamclone
9 from mercurial import util, streamclone
10 from mercurial.node import bin, hex
10 from mercurial.node import bin, hex
11 from mercurial import changegroup as changegroupmod
11 from mercurial import changegroup as changegroupmod
12 from common import HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
12 from common import HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
13
13
14 # __all__ is populated with the allowed commands. Be sure to add to it if
14 # __all__ is populated with the allowed commands. Be sure to add to it if
15 # you're adding a new command, or the new command won't work.
15 # you're adding a new command, or the new command won't work.
16
16
17 __all__ = [
17 __all__ = [
18 'lookup', 'heads', 'branches', 'between', 'changegroup',
18 'lookup', 'heads', 'branches', 'between', 'changegroup',
19 'changegroupsubset', 'capabilities', 'unbundle', 'stream_out',
19 'changegroupsubset', 'capabilities', 'unbundle', 'stream_out',
20 ]
20 ]
21
21
22 HGTYPE = 'application/mercurial-0.1'
22 HGTYPE = 'application/mercurial-0.1'
23
23
24 def lookup(web, req):
24 def lookup(web, req):
25 try:
25 try:
26 r = hex(web.repo.lookup(req.form['key'][0]))
26 r = hex(web.repo.lookup(req.form['key'][0]))
27 success = 1
27 success = 1
28 except Exception,inst:
28 except Exception,inst:
29 r = str(inst)
29 r = str(inst)
30 success = 0
30 success = 0
31 resp = "%s %s\n" % (success, r)
31 resp = "%s %s\n" % (success, r)
32 req.respond(HTTP_OK, HGTYPE, length=len(resp))
32 req.respond(HTTP_OK, HGTYPE, length=len(resp))
33 req.write(resp)
33 req.write(resp)
34
34
35 def heads(web, req):
35 def heads(web, req):
36 resp = " ".join(map(hex, web.repo.heads())) + "\n"
36 resp = " ".join(map(hex, web.repo.heads())) + "\n"
37 req.respond(HTTP_OK, HGTYPE, length=len(resp))
37 req.respond(HTTP_OK, HGTYPE, length=len(resp))
38 req.write(resp)
38 req.write(resp)
39
39
40 def branches(web, req):
40 def branches(web, req):
41 nodes = []
41 nodes = []
42 if 'nodes' in req.form:
42 if 'nodes' in req.form:
43 nodes = map(bin, req.form['nodes'][0].split(" "))
43 nodes = map(bin, req.form['nodes'][0].split(" "))
44 resp = cStringIO.StringIO()
44 resp = cStringIO.StringIO()
45 for b in web.repo.branches(nodes):
45 for b in web.repo.branches(nodes):
46 resp.write(" ".join(map(hex, b)) + "\n")
46 resp.write(" ".join(map(hex, b)) + "\n")
47 resp = resp.getvalue()
47 resp = resp.getvalue()
48 req.respond(HTTP_OK, HGTYPE, length=len(resp))
48 req.respond(HTTP_OK, HGTYPE, length=len(resp))
49 req.write(resp)
49 req.write(resp)
50
50
51 def between(web, req):
51 def between(web, req):
52 if 'pairs' in req.form:
52 if 'pairs' in req.form:
53 pairs = [map(bin, p.split("-"))
53 pairs = [map(bin, p.split("-"))
54 for p in req.form['pairs'][0].split(" ")]
54 for p in req.form['pairs'][0].split(" ")]
55 resp = cStringIO.StringIO()
55 resp = cStringIO.StringIO()
56 for b in web.repo.between(pairs):
56 for b in web.repo.between(pairs):
57 resp.write(" ".join(map(hex, b)) + "\n")
57 resp.write(" ".join(map(hex, b)) + "\n")
58 resp = resp.getvalue()
58 resp = resp.getvalue()
59 req.respond(HTTP_OK, HGTYPE, length=len(resp))
59 req.respond(HTTP_OK, HGTYPE, length=len(resp))
60 req.write(resp)
60 req.write(resp)
61
61
62 def changegroup(web, req):
62 def changegroup(web, req):
63 req.respond(HTTP_OK, HGTYPE)
63 req.respond(HTTP_OK, HGTYPE)
64 nodes = []
64 nodes = []
65 if not web.allowpull:
65 if not web.allowpull:
66 return
66 return
67
67
68 if 'roots' in req.form:
68 if 'roots' in req.form:
69 nodes = map(bin, req.form['roots'][0].split(" "))
69 nodes = map(bin, req.form['roots'][0].split(" "))
70
70
71 z = zlib.compressobj()
71 z = zlib.compressobj()
72 f = web.repo.changegroup(nodes, 'serve')
72 f = web.repo.changegroup(nodes, 'serve')
73 while 1:
73 while 1:
74 chunk = f.read(4096)
74 chunk = f.read(4096)
75 if not chunk:
75 if not chunk:
76 break
76 break
77 req.write(z.compress(chunk))
77 req.write(z.compress(chunk))
78
78
79 req.write(z.flush())
79 req.write(z.flush())
80
80
81 def changegroupsubset(web, req):
81 def changegroupsubset(web, req):
82 req.respond(HTTP_OK, HGTYPE)
82 req.respond(HTTP_OK, HGTYPE)
83 bases = []
83 bases = []
84 heads = []
84 heads = []
85 if not web.allowpull:
85 if not web.allowpull:
86 return
86 return
87
87
88 if 'bases' in req.form:
88 if 'bases' in req.form:
89 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
89 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
90 if 'heads' in req.form:
90 if 'heads' in req.form:
91 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
91 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
92
92
93 z = zlib.compressobj()
93 z = zlib.compressobj()
94 f = web.repo.changegroupsubset(bases, heads, 'serve')
94 f = web.repo.changegroupsubset(bases, heads, 'serve')
95 while 1:
95 while 1:
96 chunk = f.read(4096)
96 chunk = f.read(4096)
97 if not chunk:
97 if not chunk:
98 break
98 break
99 req.write(z.compress(chunk))
99 req.write(z.compress(chunk))
100
100
101 req.write(z.flush())
101 req.write(z.flush())
102
102
103 def capabilities(web, req):
103 def capabilities(web, req):
104 resp = ' '.join(web.capabilities())
104 resp = ' '.join(web.capabilities())
105 req.respond(HTTP_OK, HGTYPE, length=len(resp))
105 req.respond(HTTP_OK, HGTYPE, length=len(resp))
106 req.write(resp)
106 req.write(resp)
107
107
108 def unbundle(web, req):
108 def unbundle(web, req):
109
109
110 def bail(response, headers={}):
110 def bail(response, headers={}):
111 length = int(req.env.get('CONTENT_LENGTH', 0))
111 length = int(req.env.get('CONTENT_LENGTH', 0))
112 for s in util.filechunkiter(req, limit=length):
112 for s in util.filechunkiter(req, limit=length):
113 # drain incoming bundle, else client will not see
113 # drain incoming bundle, else client will not see
114 # response when run outside cgi script
114 # response when run outside cgi script
115 pass
115 pass
116
116
117 status = headers.pop('status', HTTP_OK)
117 status = headers.pop('status', HTTP_OK)
118 req.header(headers.items())
118 req.header(headers.items())
119 req.respond(status, HGTYPE)
119 req.respond(status, HGTYPE)
120 req.write('0\n')
120 req.write('0\n')
121 req.write(response)
121 req.write(response)
122
122
123 # enforce that you can only unbundle with POST requests
123 # enforce that you can only unbundle with POST requests
124 if req.env['REQUEST_METHOD'] != 'POST':
124 if req.env['REQUEST_METHOD'] != 'POST':
125 headers = {'status': '405 Method Not Allowed'}
125 headers = {'status': '405 Method Not Allowed'}
126 bail('unbundle requires POST request\n', headers)
126 bail('unbundle requires POST request\n', headers)
127 return
127 return
128
128
129 # require ssl by default, auth info cannot be sniffed and
129 # require ssl by default, auth info cannot be sniffed and
130 # replayed
130 # replayed
131 ssl_req = web.configbool('web', 'push_ssl', True)
131 ssl_req = web.configbool('web', 'push_ssl', True)
132 if ssl_req:
132 if ssl_req:
133 if req.env.get('wsgi.url_scheme') != 'https':
133 if req.env.get('wsgi.url_scheme') != 'https':
134 bail('ssl required\n')
134 bail('ssl required\n')
135 return
135 return
136 proto = 'https'
136 proto = 'https'
137 else:
137 else:
138 proto = 'http'
138 proto = 'http'
139
139
140 # do not allow push unless explicitly allowed
140 # do not allow push unless explicitly allowed
141 if not web.check_perm(req, 'push', False):
141 if not web.check_perm(req, 'push', False):
142 bail('push not authorized\n', headers={'status': '401 Unauthorized'})
142 bail('push not authorized\n', headers={'status': '401 Unauthorized'})
143 return
143 return
144
144
145 their_heads = req.form['heads'][0].split(' ')
145 their_heads = req.form['heads'][0].split(' ')
146
146
147 def check_heads():
147 def check_heads():
148 heads = map(hex, web.repo.heads())
148 heads = map(hex, web.repo.heads())
149 return their_heads == [hex('force')] or their_heads == heads
149 return their_heads == [hex('force')] or their_heads == heads
150
150
151 # fail early if possible
151 # fail early if possible
152 if not check_heads():
152 if not check_heads():
153 bail('unsynced changes\n')
153 bail('unsynced changes\n')
154 return
154 return
155
155
156 req.respond(HTTP_OK, HGTYPE)
156 req.respond(HTTP_OK, HGTYPE)
157
157
158 # do not lock repo until all changegroup data is
158 # do not lock repo until all changegroup data is
159 # streamed. save to temporary file.
159 # streamed. save to temporary file.
160
160
161 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
161 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
162 fp = os.fdopen(fd, 'wb+')
162 fp = os.fdopen(fd, 'wb+')
163 try:
163 try:
164 length = int(req.env['CONTENT_LENGTH'])
164 length = int(req.env['CONTENT_LENGTH'])
165 for s in util.filechunkiter(req, limit=length):
165 for s in util.filechunkiter(req, limit=length):
166 fp.write(s)
166 fp.write(s)
167
167
168 try:
168 try:
169 lock = web.repo.lock()
169 lock = web.repo.lock()
170 try:
170 try:
171 if not check_heads():
171 if not check_heads():
172 req.write('0\n')
172 req.write('0\n')
173 req.write('unsynced changes\n')
173 req.write('unsynced changes\n')
174 return
174 return
175
175
176 fp.seek(0)
176 fp.seek(0)
177 header = fp.read(6)
177 header = fp.read(6)
178 if header.startswith('HG') and not header.startswith('HG10'):
178 if header.startswith('HG') and not header.startswith('HG10'):
179 raise ValueError('unknown bundle version')
179 raise ValueError('unknown bundle version')
180 elif header not in changegroupmod.bundletypes:
180 elif header not in changegroupmod.bundletypes:
181 raise ValueError('unknown bundle compression type')
181 raise ValueError('unknown bundle compression type')
182 gen = changegroupmod.unbundle(header, fp)
182 gen = changegroupmod.unbundle(header, fp)
183
183
184 # send addchangegroup output to client
184 # send addchangegroup output to client
185
185
186 oldio = sys.stdout, sys.stderr
186 oldio = sys.stdout, sys.stderr
187 sys.stderr = sys.stdout = cStringIO.StringIO()
187 sys.stderr = sys.stdout = cStringIO.StringIO()
188
188
189 try:
189 try:
190 url = 'remote:%s:%s' % (proto,
190 url = 'remote:%s:%s' % (proto,
191 req.env.get('REMOTE_HOST', ''))
191 req.env.get('REMOTE_HOST', ''))
192 try:
192 try:
193 ret = web.repo.addchangegroup(gen, 'serve', url)
193 ret = web.repo.addchangegroup(gen, 'serve', url)
194 except util.Abort, inst:
194 except util.Abort, inst:
195 sys.stdout.write("abort: %s\n" % inst)
195 sys.stdout.write("abort: %s\n" % inst)
196 ret = 0
196 ret = 0
197 finally:
197 finally:
198 val = sys.stdout.getvalue()
198 val = sys.stdout.getvalue()
199 sys.stdout, sys.stderr = oldio
199 sys.stdout, sys.stderr = oldio
200 req.write('%d\n' % ret)
200 req.write('%d\n' % ret)
201 req.write(val)
201 req.write(val)
202 finally:
202 finally:
203 del lock
203 del lock
204 except ValueError, inst:
204 except ValueError, inst:
205 req.write('0\n')
205 req.write('0\n')
206 req.write(str(inst) + '\n')
206 req.write(str(inst) + '\n')
207 except (OSError, IOError), inst:
207 except (OSError, IOError), inst:
208 req.write('0\n')
208 req.write('0\n')
209 filename = getattr(inst, 'filename', '')
209 filename = getattr(inst, 'filename', '')
210 # Don't send our filesystem layout to the client
210 # Don't send our filesystem layout to the client
211 if filename.startswith(web.repo.root):
211 if filename.startswith(web.repo.root):
212 filename = filename[len(web.repo.root)+1:]
212 filename = filename[len(web.repo.root)+1:]
213 else:
213 else:
214 filename = ''
214 filename = ''
215 error = getattr(inst, 'strerror', 'Unknown error')
215 error = getattr(inst, 'strerror', 'Unknown error')
216 if inst.errno == errno.ENOENT:
216 if inst.errno == errno.ENOENT:
217 code = HTTP_NOT_FOUND
217 code = HTTP_NOT_FOUND
218 else:
218 else:
219 code = HTTP_SERVER_ERROR
219 code = HTTP_SERVER_ERROR
220 req.respond(code)
220 req.respond(code)
221 req.write('%s: %s\n' % (error, filename))
221 req.write('%s: %s\n' % (error, filename))
222 finally:
222 finally:
223 fp.close()
223 fp.close()
224 os.unlink(tempname)
224 os.unlink(tempname)
225
225
226 def stream_out(web, req):
226 def stream_out(web, req):
227 if not web.allowpull:
228 return
227 req.respond(HTTP_OK, HGTYPE)
229 req.respond(HTTP_OK, HGTYPE)
228 streamclone.stream_out(web.repo, req, untrusted=True)
230 streamclone.stream_out(web.repo, req, untrusted=True)
General Comments 0
You need to be logged in to leave comments. Login now