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