##// END OF EJS Templates
protocol: command must be checked before passing in
Dirkjan Ochtman -
r11618:83070a9c default
parent child Browse files
Show More
@@ -1,71 +1,71
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import cStringIO, zlib, sys, urllib
9 9 from mercurial import util, wireproto
10 10 from common import HTTP_OK
11 11
12 12 HGTYPE = 'application/mercurial-0.1'
13 13
14 14 class webproto(object):
15 15 def __init__(self, req):
16 16 self.req = req
17 17 self.response = ''
18 18 def getargs(self, args):
19 19 data = {}
20 20 keys = args.split()
21 21 for k in keys:
22 22 if k == '*':
23 23 star = {}
24 24 for key in self.req.form.keys():
25 25 if key not in keys:
26 26 star[key] = self.req.form[key][0]
27 27 data['*'] = star
28 28 else:
29 29 data[k] = self.req.form[k][0]
30 30 return [data[k] for k in keys]
31 31 def sendchangegroup(self, cg):
32 32 self.req.respond(HTTP_OK, HGTYPE)
33 33 z = zlib.compressobj()
34 34 while 1:
35 35 chunk = cg.read(4096)
36 36 if not chunk:
37 37 break
38 38 self.req.write(z.compress(chunk))
39 39 self.req.write(z.flush())
40 40 def sendstream(self, source):
41 41 self.req.respond(HTTP_OK, HGTYPE)
42 42 for chunk in source:
43 43 self.req.write(chunk)
44 44 def respond(self, s):
45 45 self.req.respond(HTTP_OK, HGTYPE, length=len(s))
46 46 self.response = s
47 47 def getfile(self, fp):
48 48 length = int(self.req.env['CONTENT_LENGTH'])
49 49 for s in util.filechunkiter(self.req, limit=length):
50 50 fp.write(s)
51 51 def redirect(self):
52 52 self.oldio = sys.stdout, sys.stderr
53 53 sys.stderr = sys.stdout = cStringIO.StringIO()
54 54 def respondpush(self, ret):
55 55 val = sys.stdout.getvalue()
56 56 sys.stdout, sys.stderr = self.oldio
57 57 self.req.respond(HTTP_OK, HGTYPE)
58 58 self.response = '%d\n%s' % (ret, val)
59 59 def _client(self):
60 60 return 'remote:%s:%s:%s' % (
61 61 self.req.env.get('wsgi.url_scheme') or 'http',
62 62 urllib.quote(self.req.env.get('REMOTE_HOST', '')),
63 63 urllib.quote(self.req.env.get('REMOTE_USER', '')))
64 64
65 65 def iscmd(cmd):
66 66 return cmd in wireproto.commands
67 67
68 68 def call(repo, req, cmd):
69 69 p = webproto(req)
70 r = wireproto.dispatch(repo, p, cmd)
70 wireproto.dispatch(repo, p, cmd)
71 71 yield p.response
@@ -1,133 +1,135
1 1 # sshserver.py - ssh protocol server support for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 from i18n import _
10 10 import util, hook, wireproto
11 11 import os, sys
12 12
13 13 class sshserver(object):
14 14 def __init__(self, ui, repo):
15 15 self.ui = ui
16 16 self.repo = repo
17 17 self.lock = None
18 18 self.fin = sys.stdin
19 19 self.fout = sys.stdout
20 20
21 21 hook.redirect(True)
22 22 sys.stdout = sys.stderr
23 23
24 24 # Prevent insertion/deletion of CRs
25 25 util.set_binary(self.fin)
26 26 util.set_binary(self.fout)
27 27
28 28 def getargs(self, args):
29 29 data = {}
30 30 keys = args.split()
31 31 count = len(keys)
32 32 for n in xrange(len(keys)):
33 33 argline = self.fin.readline()[:-1]
34 34 arg, l = argline.split()
35 35 val = self.fin.read(int(l))
36 36 if arg not in keys:
37 37 raise util.Abort("unexpected parameter %r" % arg)
38 38 if arg == '*':
39 39 star = {}
40 40 for n in xrange(int(l)):
41 41 arg, l = argline.split()
42 42 val = self.fin.read(int(l))
43 43 star[arg] = val
44 44 data['*'] = star
45 45 else:
46 46 data[arg] = val
47 47 return [data[k] for k in keys]
48 48
49 49 def getarg(self, name):
50 50 return self.getargs(name)[0]
51 51
52 52 def respond(self, v):
53 53 self.fout.write("%d\n" % len(v))
54 54 self.fout.write(v)
55 55 self.fout.flush()
56 56
57 57 def sendchangegroup(self, changegroup):
58 58 while True:
59 59 d = changegroup.read(4096)
60 60 if not d:
61 61 break
62 62 self.fout.write(d)
63 63
64 64 self.fout.flush()
65 65
66 66 def sendstream(self, source):
67 67 for chunk in source:
68 68 self.fout.write(chunk)
69 69 self.fout.flush()
70 70
71 71 def getfile(self, fpout):
72 72 self.respond('')
73 73 count = int(self.fin.readline())
74 74 while count:
75 75 fpout.write(self.fin.read(count))
76 76 count = int(self.fin.readline())
77 77
78 78 def redirect(self):
79 79 pass
80 80
81 81 def respondpush(self, ret):
82 82 self.respond('')
83 83 self.respond(str(ret))
84 84
85 85 def serve_forever(self):
86 86 try:
87 87 while self.serve_one():
88 88 pass
89 89 finally:
90 90 if self.lock is not None:
91 91 self.lock.release()
92 92 sys.exit(0)
93 93
94 94 def serve_one(self):
95 95 cmd = self.fin.readline()[:-1]
96 if cmd and not wireproto.dispatch(self.repo, self, cmd):
96 if cmd and cmd in wireproto.commands:
97 wireproto.dispatch(self.repo, self, cmd)
98 elif cmd:
97 99 impl = getattr(self, 'do_' + cmd, None)
98 100 if impl:
99 101 r = impl()
100 102 if r is not None:
101 103 self.respond(r)
102 104 else: self.respond("")
103 105 return cmd != ''
104 106
105 107 def do_lock(self):
106 108 '''DEPRECATED - allowing remote client to lock repo is not safe'''
107 109
108 110 self.lock = self.repo.lock()
109 111 return ""
110 112
111 113 def do_unlock(self):
112 114 '''DEPRECATED'''
113 115
114 116 if self.lock:
115 117 self.lock.release()
116 118 self.lock = None
117 119 return ""
118 120
119 121 def do_addchangegroup(self):
120 122 '''DEPRECATED'''
121 123
122 124 if not self.lock:
123 125 self.respond("not locked")
124 126 return
125 127
126 128 self.respond("")
127 129 r = self.repo.addchangegroup(self.fin, 'serve', self._client(),
128 130 lock=self.lock)
129 131 return str(r)
130 132
131 133 def _client(self):
132 134 client = os.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
133 135 return 'remote:ssh:' + client
@@ -1,290 +1,287
1 1 # wireproto.py - generic wire protocol support functions
2 2 #
3 3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import urllib, tempfile, os
9 9 from i18n import _
10 10 from node import bin, hex
11 11 import changegroup as changegroupmod
12 12 import streamclone, repo, error, encoding, util
13 13 import pushkey as pushkey_
14 14
15 15 # list of nodes encoding / decoding
16 16
17 17 def decodelist(l, sep=' '):
18 18 return map(bin, l.split(sep))
19 19
20 20 def encodelist(l, sep=' '):
21 21 return sep.join(map(hex, l))
22 22
23 23 # client side
24 24
25 25 class wirerepository(repo.repository):
26 26 def lookup(self, key):
27 27 self.requirecap('lookup', _('look up remote revision'))
28 28 d = self._call("lookup", key=key)
29 29 success, data = d[:-1].split(" ", 1)
30 30 if int(success):
31 31 return bin(data)
32 32 self._abort(error.RepoError(data))
33 33
34 34 def heads(self):
35 35 d = self._call("heads")
36 36 try:
37 37 return decodelist(d[:-1])
38 38 except:
39 39 self.abort(error.ResponseError(_("unexpected response:"), d))
40 40
41 41 def branchmap(self):
42 42 d = self._call("branchmap")
43 43 try:
44 44 branchmap = {}
45 45 for branchpart in d.splitlines():
46 46 branchname, branchheads = branchpart.split(' ', 1)
47 47 branchname = urllib.unquote(branchname)
48 48 # Earlier servers (1.3.x) send branch names in (their) local
49 49 # charset. The best we can do is assume it's identical to our
50 50 # own local charset, in case it's not utf-8.
51 51 try:
52 52 branchname.decode('utf-8')
53 53 except UnicodeDecodeError:
54 54 branchname = encoding.fromlocal(branchname)
55 55 branchheads = decodelist(branchheads)
56 56 branchmap[branchname] = branchheads
57 57 return branchmap
58 58 except TypeError:
59 59 self._abort(error.ResponseError(_("unexpected response:"), d))
60 60
61 61 def branches(self, nodes):
62 62 n = encodelist(nodes)
63 63 d = self._call("branches", nodes=n)
64 64 try:
65 65 br = [tuple(decodelist(b)) for b in d.splitlines()]
66 66 return br
67 67 except:
68 68 self._abort(error.ResponseError(_("unexpected response:"), d))
69 69
70 70 def between(self, pairs):
71 71 batch = 8 # avoid giant requests
72 72 r = []
73 73 for i in xrange(0, len(pairs), batch):
74 74 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
75 75 d = self._call("between", pairs=n)
76 76 try:
77 77 r.extend(l and decodelist(l) or [] for l in d.splitlines())
78 78 except:
79 79 self._abort(error.ResponseError(_("unexpected response:"), d))
80 80 return r
81 81
82 82 def pushkey(self, namespace, key, old, new):
83 83 if not self.capable('pushkey'):
84 84 return False
85 85 d = self._call("pushkey",
86 86 namespace=namespace, key=key, old=old, new=new)
87 87 return bool(int(d))
88 88
89 89 def listkeys(self, namespace):
90 90 if not self.capable('pushkey'):
91 91 return {}
92 92 d = self._call("listkeys", namespace=namespace)
93 93 r = {}
94 94 for l in d.splitlines():
95 95 k, v = l.split('\t')
96 96 r[k.decode('string-escape')] = v.decode('string-escape')
97 97 return r
98 98
99 99 def stream_out(self):
100 100 return self._callstream('stream_out')
101 101
102 102 def changegroup(self, nodes, kind):
103 103 n = encodelist(nodes)
104 104 f = self._callstream("changegroup", roots=n)
105 105 return self._decompress(f)
106 106
107 107 def changegroupsubset(self, bases, heads, kind):
108 108 self.requirecap('changegroupsubset', _('look up remote changes'))
109 109 bases = encodelist(bases)
110 110 heads = encodelist(heads)
111 111 return self._decompress(self._callstream("changegroupsubset",
112 112 bases=bases, heads=heads))
113 113
114 114 def unbundle(self, cg, heads, source):
115 115 '''Send cg (a readable file-like object representing the
116 116 changegroup to push, typically a chunkbuffer object) to the
117 117 remote server as a bundle. Return an integer indicating the
118 118 result of the push (see localrepository.addchangegroup()).'''
119 119
120 120 ret, output = self._callpush("unbundle", cg, heads=encodelist(heads))
121 121 if ret == "":
122 122 raise error.ResponseError(
123 123 _('push failed:'), output)
124 124 try:
125 125 ret = int(ret)
126 126 except ValueError, err:
127 127 raise error.ResponseError(
128 128 _('push failed (unexpected response):'), ret)
129 129
130 130 for l in output.splitlines(True):
131 131 self.ui.status(_('remote: '), l)
132 132 return ret
133 133
134 134 # server side
135 135
136 136 def dispatch(repo, proto, command):
137 if command not in commands:
138 return False
139 137 func, spec = commands[command]
140 138 args = proto.getargs(spec)
141 139 r = func(repo, proto, *args)
142 140 if r != None:
143 141 proto.respond(r)
144 return True
145 142
146 143 def between(repo, proto, pairs):
147 144 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
148 145 r = []
149 146 for b in repo.between(pairs):
150 147 r.append(encodelist(b) + "\n")
151 148 return "".join(r)
152 149
153 150 def branchmap(repo, proto):
154 151 branchmap = repo.branchmap()
155 152 heads = []
156 153 for branch, nodes in branchmap.iteritems():
157 154 branchname = urllib.quote(branch)
158 155 branchnodes = encodelist(nodes)
159 156 heads.append('%s %s' % (branchname, branchnodes))
160 157 return '\n'.join(heads)
161 158
162 159 def branches(repo, proto, nodes):
163 160 nodes = decodelist(nodes)
164 161 r = []
165 162 for b in repo.branches(nodes):
166 163 r.append(encodelist(b) + "\n")
167 164 return "".join(r)
168 165
169 166 def capabilities(repo, proto):
170 167 caps = 'lookup changegroupsubset branchmap pushkey'.split()
171 168 if streamclone.allowed(repo.ui):
172 169 caps.append('stream=%d' % repo.changelog.version)
173 170 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
174 171 return ' '.join(caps)
175 172
176 173 def changegroup(repo, proto, roots):
177 174 nodes = decodelist(roots)
178 175 cg = repo.changegroup(nodes, 'serve')
179 176 proto.sendchangegroup(cg)
180 177
181 178 def changegroupsubset(repo, proto, bases, heads):
182 179 bases = decodelist(bases)
183 180 heads = decodelist(heads)
184 181 cg = repo.changegroupsubset(bases, heads, 'serve')
185 182 proto.sendchangegroup(cg)
186 183
187 184 def heads(repo, proto):
188 185 h = repo.heads()
189 186 return encodelist(h) + "\n"
190 187
191 188 def hello(repo, proto):
192 189 '''the hello command returns a set of lines describing various
193 190 interesting things about the server, in an RFC822-like format.
194 191 Currently the only one defined is "capabilities", which
195 192 consists of a line in the form:
196 193
197 194 capabilities: space separated list of tokens
198 195 '''
199 196 return "capabilities: %s\n" % (capabilities(repo, proto))
200 197
201 198 def listkeys(repo, proto, namespace):
202 199 d = pushkey_.list(repo, namespace).items()
203 200 t = '\n'.join(['%s\t%s' % (k.encode('string-escape'),
204 201 v.encode('string-escape')) for k, v in d])
205 202 return t
206 203
207 204 def lookup(repo, proto, key):
208 205 try:
209 206 r = hex(repo.lookup(key))
210 207 success = 1
211 208 except Exception, inst:
212 209 r = str(inst)
213 210 success = 0
214 211 return "%s %s\n" % (success, r)
215 212
216 213 def pushkey(repo, proto, namespace, key, old, new):
217 214 r = pushkey_.push(repo, namespace, key, old, new)
218 215 return '%s\n' % int(r)
219 216
220 217 def stream(repo, proto):
221 218 try:
222 219 proto.sendstream(streamclone.stream_out(repo))
223 220 except streamclone.StreamException, inst:
224 221 return str(inst)
225 222
226 223 def unbundle(repo, proto, heads):
227 224 their_heads = decodelist(heads)
228 225
229 226 def check_heads():
230 227 heads = repo.heads()
231 228 return their_heads == ['force'] or their_heads == heads
232 229
233 230 # fail early if possible
234 231 if not check_heads():
235 232 repo.respond(_('unsynced changes'))
236 233 return
237 234
238 235 # write bundle data to temporary file because it can be big
239 236 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
240 237 fp = os.fdopen(fd, 'wb+')
241 238 r = 0
242 239 proto.redirect()
243 240 try:
244 241 proto.getfile(fp)
245 242 lock = repo.lock()
246 243 try:
247 244 if not check_heads():
248 245 # someone else committed/pushed/unbundled while we
249 246 # were transferring data
250 247 proto.respond(_('unsynced changes'))
251 248 return
252 249
253 250 # push can proceed
254 251 fp.seek(0)
255 252 header = fp.read(6)
256 253 if header.startswith('HG'):
257 254 if not header.startswith('HG10'):
258 255 raise ValueError('unknown bundle version')
259 256 elif header not in changegroupmod.bundletypes:
260 257 raise ValueError('unknown bundle compression type')
261 258 gen = changegroupmod.unbundle(header, fp)
262 259
263 260 try:
264 261 r = repo.addchangegroup(gen, 'serve', proto._client(),
265 262 lock=lock)
266 263 except util.Abort, inst:
267 264 sys.stderr.write("abort: %s\n" % inst)
268 265 finally:
269 266 lock.release()
270 267 proto.respondpush(r)
271 268
272 269 finally:
273 270 fp.close()
274 271 os.unlink(tempname)
275 272
276 273 commands = {
277 274 'between': (between, 'pairs'),
278 275 'branchmap': (branchmap, ''),
279 276 'branches': (branches, 'nodes'),
280 277 'capabilities': (capabilities, ''),
281 278 'changegroup': (changegroup, 'roots'),
282 279 'changegroupsubset': (changegroupsubset, 'bases heads'),
283 280 'heads': (heads, ''),
284 281 'hello': (hello, ''),
285 282 'listkeys': (listkeys, 'namespace'),
286 283 'lookup': (lookup, 'key'),
287 284 'pushkey': (pushkey, 'namespace key old new'),
288 285 'stream_out': (stream, ''),
289 286 'unbundle': (unbundle, 'heads'),
290 287 }
General Comments 0
You need to be logged in to leave comments. Login now