##// END OF EJS Templates
sshrepo: add more safe characters (issue2983)...
Thomas Arendsen Hein -
r15624:be43234a default
parent child Browse files
Show More
@@ -1,232 +1,232
1 # sshrepo.py - ssh repository proxy class for mercurial
1 # sshrepo.py - ssh repository proxy class for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re
8 import re
9 from i18n import _
9 from i18n import _
10 import util, error, wireproto
10 import util, error, wireproto
11
11
12 class remotelock(object):
12 class remotelock(object):
13 def __init__(self, repo):
13 def __init__(self, repo):
14 self.repo = repo
14 self.repo = repo
15 def release(self):
15 def release(self):
16 self.repo.unlock()
16 self.repo.unlock()
17 self.repo = None
17 self.repo = None
18 def __del__(self):
18 def __del__(self):
19 if self.repo:
19 if self.repo:
20 self.release()
20 self.release()
21
21
22 def _serverquote(s):
22 def _serverquote(s):
23 '''quote a string for the remote shell ... which we assume is sh'''
23 '''quote a string for the remote shell ... which we assume is sh'''
24 if re.match(r'[a-zA-Z0-9._\-/]*$', s):
24 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
25 return s
25 return s
26 return "'%s'" % s.replace("'", "'\\''")
26 return "'%s'" % s.replace("'", "'\\''")
27
27
28 class sshrepository(wireproto.wirerepository):
28 class sshrepository(wireproto.wirerepository):
29 def __init__(self, ui, path, create=False):
29 def __init__(self, ui, path, create=False):
30 self._url = path
30 self._url = path
31 self.ui = ui
31 self.ui = ui
32
32
33 u = util.url(path, parsequery=False, parsefragment=False)
33 u = util.url(path, parsequery=False, parsefragment=False)
34 if u.scheme != 'ssh' or not u.host or u.path is None:
34 if u.scheme != 'ssh' or not u.host or u.path is None:
35 self._abort(error.RepoError(_("couldn't parse location %s") % path))
35 self._abort(error.RepoError(_("couldn't parse location %s") % path))
36
36
37 self.user = u.user
37 self.user = u.user
38 if u.passwd is not None:
38 if u.passwd is not None:
39 self._abort(error.RepoError(_("password in URL not supported")))
39 self._abort(error.RepoError(_("password in URL not supported")))
40 self.host = u.host
40 self.host = u.host
41 self.port = u.port
41 self.port = u.port
42 self.path = u.path or "."
42 self.path = u.path or "."
43
43
44 sshcmd = self.ui.config("ui", "ssh", "ssh")
44 sshcmd = self.ui.config("ui", "ssh", "ssh")
45 remotecmd = self.ui.config("ui", "remotecmd", "hg")
45 remotecmd = self.ui.config("ui", "remotecmd", "hg")
46
46
47 args = util.sshargs(sshcmd, self.host, self.user, self.port)
47 args = util.sshargs(sshcmd, self.host, self.user, self.port)
48
48
49 if create:
49 if create:
50 cmd = '%s %s %s' % (sshcmd, args,
50 cmd = '%s %s %s' % (sshcmd, args,
51 util.shellquote("%s init %s" %
51 util.shellquote("%s init %s" %
52 (_serverquote(remotecmd), _serverquote(self.path))))
52 (_serverquote(remotecmd), _serverquote(self.path))))
53 ui.note(_('running %s\n') % cmd)
53 ui.note(_('running %s\n') % cmd)
54 res = util.system(cmd)
54 res = util.system(cmd)
55 if res != 0:
55 if res != 0:
56 self._abort(error.RepoError(_("could not create remote repo")))
56 self._abort(error.RepoError(_("could not create remote repo")))
57
57
58 self.validate_repo(ui, sshcmd, args, remotecmd)
58 self.validate_repo(ui, sshcmd, args, remotecmd)
59
59
60 def url(self):
60 def url(self):
61 return self._url
61 return self._url
62
62
63 def validate_repo(self, ui, sshcmd, args, remotecmd):
63 def validate_repo(self, ui, sshcmd, args, remotecmd):
64 # cleanup up previous run
64 # cleanup up previous run
65 self.cleanup()
65 self.cleanup()
66
66
67 cmd = '%s %s %s' % (sshcmd, args,
67 cmd = '%s %s %s' % (sshcmd, args,
68 util.shellquote("%s -R %s serve --stdio" %
68 util.shellquote("%s -R %s serve --stdio" %
69 (_serverquote(remotecmd), _serverquote(self.path))))
69 (_serverquote(remotecmd), _serverquote(self.path))))
70 ui.note(_('running %s\n') % cmd)
70 ui.note(_('running %s\n') % cmd)
71 cmd = util.quotecommand(cmd)
71 cmd = util.quotecommand(cmd)
72 self.pipeo, self.pipei, self.pipee = util.popen3(cmd)
72 self.pipeo, self.pipei, self.pipee = util.popen3(cmd)
73
73
74 # skip any noise generated by remote shell
74 # skip any noise generated by remote shell
75 self._callstream("hello")
75 self._callstream("hello")
76 r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
76 r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
77 lines = ["", "dummy"]
77 lines = ["", "dummy"]
78 max_noise = 500
78 max_noise = 500
79 while lines[-1] and max_noise:
79 while lines[-1] and max_noise:
80 l = r.readline()
80 l = r.readline()
81 self.readerr()
81 self.readerr()
82 if lines[-1] == "1\n" and l == "\n":
82 if lines[-1] == "1\n" and l == "\n":
83 break
83 break
84 if l:
84 if l:
85 ui.debug("remote: ", l)
85 ui.debug("remote: ", l)
86 lines.append(l)
86 lines.append(l)
87 max_noise -= 1
87 max_noise -= 1
88 else:
88 else:
89 self._abort(error.RepoError(_("no suitable response from remote hg")))
89 self._abort(error.RepoError(_("no suitable response from remote hg")))
90
90
91 self.capabilities = set()
91 self.capabilities = set()
92 for l in reversed(lines):
92 for l in reversed(lines):
93 if l.startswith("capabilities:"):
93 if l.startswith("capabilities:"):
94 self.capabilities.update(l[:-1].split(":")[1].split())
94 self.capabilities.update(l[:-1].split(":")[1].split())
95 break
95 break
96
96
97 def readerr(self):
97 def readerr(self):
98 while True:
98 while True:
99 size = util.fstat(self.pipee).st_size
99 size = util.fstat(self.pipee).st_size
100 if size == 0:
100 if size == 0:
101 break
101 break
102 s = self.pipee.read(size)
102 s = self.pipee.read(size)
103 if not s:
103 if not s:
104 break
104 break
105 for l in s.splitlines():
105 for l in s.splitlines():
106 self.ui.status(_("remote: "), l, '\n')
106 self.ui.status(_("remote: "), l, '\n')
107
107
108 def _abort(self, exception):
108 def _abort(self, exception):
109 self.cleanup()
109 self.cleanup()
110 raise exception
110 raise exception
111
111
112 def cleanup(self):
112 def cleanup(self):
113 try:
113 try:
114 self.pipeo.close()
114 self.pipeo.close()
115 self.pipei.close()
115 self.pipei.close()
116 # read the error descriptor until EOF
116 # read the error descriptor until EOF
117 for l in self.pipee:
117 for l in self.pipee:
118 self.ui.status(_("remote: "), l)
118 self.ui.status(_("remote: "), l)
119 self.pipee.close()
119 self.pipee.close()
120 except:
120 except:
121 pass
121 pass
122
122
123 __del__ = cleanup
123 __del__ = cleanup
124
124
125 def _callstream(self, cmd, **args):
125 def _callstream(self, cmd, **args):
126 self.ui.debug("sending %s command\n" % cmd)
126 self.ui.debug("sending %s command\n" % cmd)
127 self.pipeo.write("%s\n" % cmd)
127 self.pipeo.write("%s\n" % cmd)
128 _func, names = wireproto.commands[cmd]
128 _func, names = wireproto.commands[cmd]
129 keys = names.split()
129 keys = names.split()
130 wireargs = {}
130 wireargs = {}
131 for k in keys:
131 for k in keys:
132 if k == '*':
132 if k == '*':
133 wireargs['*'] = args
133 wireargs['*'] = args
134 break
134 break
135 else:
135 else:
136 wireargs[k] = args[k]
136 wireargs[k] = args[k]
137 del args[k]
137 del args[k]
138 for k, v in sorted(wireargs.iteritems()):
138 for k, v in sorted(wireargs.iteritems()):
139 self.pipeo.write("%s %d\n" % (k, len(v)))
139 self.pipeo.write("%s %d\n" % (k, len(v)))
140 if isinstance(v, dict):
140 if isinstance(v, dict):
141 for dk, dv in v.iteritems():
141 for dk, dv in v.iteritems():
142 self.pipeo.write("%s %d\n" % (dk, len(dv)))
142 self.pipeo.write("%s %d\n" % (dk, len(dv)))
143 self.pipeo.write(dv)
143 self.pipeo.write(dv)
144 else:
144 else:
145 self.pipeo.write(v)
145 self.pipeo.write(v)
146 self.pipeo.flush()
146 self.pipeo.flush()
147
147
148 return self.pipei
148 return self.pipei
149
149
150 def _call(self, cmd, **args):
150 def _call(self, cmd, **args):
151 self._callstream(cmd, **args)
151 self._callstream(cmd, **args)
152 return self._recv()
152 return self._recv()
153
153
154 def _callpush(self, cmd, fp, **args):
154 def _callpush(self, cmd, fp, **args):
155 r = self._call(cmd, **args)
155 r = self._call(cmd, **args)
156 if r:
156 if r:
157 return '', r
157 return '', r
158 while True:
158 while True:
159 d = fp.read(4096)
159 d = fp.read(4096)
160 if not d:
160 if not d:
161 break
161 break
162 self._send(d)
162 self._send(d)
163 self._send("", flush=True)
163 self._send("", flush=True)
164 r = self._recv()
164 r = self._recv()
165 if r:
165 if r:
166 return '', r
166 return '', r
167 return self._recv(), ''
167 return self._recv(), ''
168
168
169 def _decompress(self, stream):
169 def _decompress(self, stream):
170 return stream
170 return stream
171
171
172 def _recv(self):
172 def _recv(self):
173 l = self.pipei.readline()
173 l = self.pipei.readline()
174 if l == '\n':
174 if l == '\n':
175 err = []
175 err = []
176 while True:
176 while True:
177 line = self.pipee.readline()
177 line = self.pipee.readline()
178 if line == '-\n':
178 if line == '-\n':
179 break
179 break
180 err.extend([line])
180 err.extend([line])
181 if len(err) > 0:
181 if len(err) > 0:
182 # strip the trailing newline added to the last line server-side
182 # strip the trailing newline added to the last line server-side
183 err[-1] = err[-1][:-1]
183 err[-1] = err[-1][:-1]
184 self._abort(error.OutOfBandError(*err))
184 self._abort(error.OutOfBandError(*err))
185 self.readerr()
185 self.readerr()
186 try:
186 try:
187 l = int(l)
187 l = int(l)
188 except ValueError:
188 except ValueError:
189 self._abort(error.ResponseError(_("unexpected response:"), l))
189 self._abort(error.ResponseError(_("unexpected response:"), l))
190 return self.pipei.read(l)
190 return self.pipei.read(l)
191
191
192 def _send(self, data, flush=False):
192 def _send(self, data, flush=False):
193 self.pipeo.write("%d\n" % len(data))
193 self.pipeo.write("%d\n" % len(data))
194 if data:
194 if data:
195 self.pipeo.write(data)
195 self.pipeo.write(data)
196 if flush:
196 if flush:
197 self.pipeo.flush()
197 self.pipeo.flush()
198 self.readerr()
198 self.readerr()
199
199
200 def lock(self):
200 def lock(self):
201 self._call("lock")
201 self._call("lock")
202 return remotelock(self)
202 return remotelock(self)
203
203
204 def unlock(self):
204 def unlock(self):
205 self._call("unlock")
205 self._call("unlock")
206
206
207 def addchangegroup(self, cg, source, url, lock=None):
207 def addchangegroup(self, cg, source, url, lock=None):
208 '''Send a changegroup to the remote server. Return an integer
208 '''Send a changegroup to the remote server. Return an integer
209 similar to unbundle(). DEPRECATED, since it requires locking the
209 similar to unbundle(). DEPRECATED, since it requires locking the
210 remote.'''
210 remote.'''
211 d = self._call("addchangegroup")
211 d = self._call("addchangegroup")
212 if d:
212 if d:
213 self._abort(error.RepoError(_("push refused: %s") % d))
213 self._abort(error.RepoError(_("push refused: %s") % d))
214 while True:
214 while True:
215 d = cg.read(4096)
215 d = cg.read(4096)
216 if not d:
216 if not d:
217 break
217 break
218 self.pipeo.write(d)
218 self.pipeo.write(d)
219 self.readerr()
219 self.readerr()
220
220
221 self.pipeo.flush()
221 self.pipeo.flush()
222
222
223 self.readerr()
223 self.readerr()
224 r = self._recv()
224 r = self._recv()
225 if not r:
225 if not r:
226 return 1
226 return 1
227 try:
227 try:
228 return int(r)
228 return int(r)
229 except ValueError:
229 except ValueError:
230 self._abort(error.ResponseError(_("unexpected response:"), r))
230 self._abort(error.ResponseError(_("unexpected response:"), r))
231
231
232 instance = sshrepository
232 instance = sshrepository
General Comments 0
You need to be logged in to leave comments. Login now