1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jgit.transport;
15
16 import java.io.File;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.text.MessageFormat;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.EnumSet;
25 import java.util.LinkedHashSet;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Map;
29 import java.util.Set;
30
31 import org.eclipse.jgit.errors.NoRemoteRepositoryException;
32 import org.eclipse.jgit.errors.NotSupportedException;
33 import org.eclipse.jgit.errors.TransportException;
34 import org.eclipse.jgit.internal.JGitText;
35 import org.eclipse.jgit.lib.Constants;
36 import org.eclipse.jgit.lib.Repository;
37 import org.eclipse.jgit.util.FS;
38 import org.eclipse.jgit.util.QuotedString;
39 import org.eclipse.jgit.util.SystemReader;
40 import org.eclipse.jgit.util.io.MessageWriter;
41 import org.eclipse.jgit.util.io.StreamCopyThread;
42
43
44
45
46
47
48
49
50
51
52
53
54 public class TransportGitSsh extends SshTransport implements PackTransport {
55 private static final String EXT = "ext";
56
57 static final TransportProtocol PROTO_SSH = new TransportProtocol() {
58 private final String[] schemeNames = { "ssh", "ssh+git", "git+ssh" };
59
60 private final Set<String> schemeSet = Collections
61 .unmodifiableSet(new LinkedHashSet<>(Arrays
62 .asList(schemeNames)));
63
64 @Override
65 public String getName() {
66 return JGitText.get().transportProtoSSH;
67 }
68
69 @Override
70 public Set<String> getSchemes() {
71 return schemeSet;
72 }
73
74 @Override
75 public Set<URIishField> getRequiredFields() {
76 return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
77 URIishField.PATH));
78 }
79
80 @Override
81 public Set<URIishField> getOptionalFields() {
82 return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
83 URIishField.PASS, URIishField.PORT));
84 }
85
86 @Override
87 public int getDefaultPort() {
88 return 22;
89 }
90
91 @Override
92 public boolean canHandle(URIish uri, Repository local, String remoteName) {
93 if (uri.getScheme() == null) {
94
95 return uri.getHost() != null
96 && uri.getPath() != null
97 && uri.getHost().length() != 0
98 && uri.getPath().length() != 0;
99 }
100 return super.canHandle(uri, local, remoteName);
101 }
102
103 @Override
104 public Transport open(URIish uri, Repository local, String remoteName)
105 throws NotSupportedException {
106 return new TransportGitSsh(local, uri);
107 }
108
109 @Override
110 public Transport open(URIish uri) throws NotSupportedException, TransportException {
111 return new TransportGitSsh(uri);
112 }
113 };
114
115 TransportGitSsh(Repository local, URIish uri) {
116 super(local, uri);
117 initSshSessionFactory();
118 }
119
120 TransportGitSsh(URIish uri) {
121 super(uri);
122 initSshSessionFactory();
123 }
124
125 private void initSshSessionFactory() {
126 if (useExtSession()) {
127 setSshSessionFactory(new SshSessionFactory() {
128 @Override
129 public RemoteSession getSession(URIish uri2,
130 CredentialsProvider credentialsProvider, FS fs, int tms)
131 throws TransportException {
132 return new ExtSession();
133 }
134
135 @Override
136 public String getType() {
137 return EXT;
138 }
139 });
140 }
141 }
142
143
144 @Override
145 public FetchConnection openFetch() throws TransportException {
146 return new SshFetchConnection();
147 }
148
149 @Override
150 public FetchConnection openFetch(Collection<RefSpec> refSpecs,
151 String... additionalPatterns)
152 throws NotSupportedException, TransportException {
153 return new SshFetchConnection(refSpecs, additionalPatterns);
154 }
155
156
157 @Override
158 public PushConnection openPush() throws TransportException {
159 return new SshPushConnection();
160 }
161
162 String commandFor(String exe) {
163 String path = uri.getPath();
164 if (uri.getScheme() != null && uri.getPath().startsWith("/~"))
165 path = (uri.getPath().substring(1));
166
167 final StringBuilder cmd = new StringBuilder();
168 cmd.append(exe);
169 cmd.append(' ');
170 cmd.append(QuotedString.BOURNE.quote(path));
171 return cmd.toString();
172 }
173
174 void checkExecFailure(int status, String exe, String why)
175 throws TransportException {
176 if (status == 127) {
177 IOException cause = null;
178 if (why != null && why.length() > 0)
179 cause = new IOException(why);
180 throw new TransportException(uri, MessageFormat.format(
181 JGitText.get().cannotExecute, commandFor(exe)), cause);
182 }
183 }
184
185 NoRemoteRepositoryException cleanNotFound(NoRemoteRepositoryException nf,
186 String why) {
187 if (why == null || why.length() == 0)
188 return nf;
189
190 String path = uri.getPath();
191 if (uri.getScheme() != null && uri.getPath().startsWith("/~"))
192 path = uri.getPath().substring(1);
193
194 final StringBuilder pfx = new StringBuilder();
195 pfx.append("fatal: ");
196 pfx.append(QuotedString.BOURNE.quote(path));
197 pfx.append(": ");
198 if (why.startsWith(pfx.toString()))
199 why = why.substring(pfx.length());
200
201 return new NoRemoteRepositoryException(uri, why);
202 }
203
204 private static boolean useExtSession() {
205 return SystemReader.getInstance().getenv("GIT_SSH") != null;
206 }
207
208 private class ExtSession implements RemoteSession2 {
209
210 @Override
211 public Process exec(String command, int timeout)
212 throws TransportException {
213 return exec(command, null, timeout);
214 }
215
216 @Override
217 public Process exec(String command, Map<String, String> environment,
218 int timeout) throws TransportException {
219 String ssh = SystemReader.getInstance().getenv("GIT_SSH");
220 boolean putty = ssh.toLowerCase(Locale.ROOT).contains("plink");
221
222 List<String> args = new ArrayList<>();
223 args.add(ssh);
224 if (putty && !ssh.toLowerCase(Locale.ROOT)
225 .contains("tortoiseplink")) {
226 args.add("-batch");
227 }
228 if (0 < getURI().getPort()) {
229 args.add(putty ? "-P" : "-p");
230 args.add(String.valueOf(getURI().getPort()));
231 }
232 if (getURI().getUser() != null) {
233 args.add(getURI().getUser() + "@" + getURI().getHost());
234 } else {
235 args.add(getURI().getHost());
236 }
237 args.add(command);
238
239 ProcessBuilder pb = createProcess(args, environment);
240 try {
241 return pb.start();
242 } catch (IOException err) {
243 throw new TransportException(err.getMessage(), err);
244 }
245 }
246
247 private ProcessBuilder createProcess(List<String> args,
248 Map<String, String> environment) {
249 ProcessBuilder pb = new ProcessBuilder();
250 pb.command(args);
251 if (environment != null) {
252 pb.environment().putAll(environment);
253 }
254 File directory = local != null ? local.getDirectory() : null;
255 if (directory != null) {
256 pb.environment().put(Constants.GIT_DIR_KEY,
257 directory.getPath());
258 }
259 return pb;
260 }
261
262 @Override
263 public void disconnect() {
264
265 }
266 }
267
268 class SshFetchConnection extends BasePackFetchConnection {
269 private final Process process;
270
271 private StreamCopyThread errorThread;
272
273 SshFetchConnection() throws TransportException {
274 this(Collections.emptyList());
275 }
276
277 SshFetchConnection(Collection<RefSpec> refSpecs,
278 String... additionalPatterns) throws TransportException {
279 super(TransportGitSsh.this);
280 try {
281 RemoteSession session = getSession();
282 TransferConfig.ProtocolVersion gitProtocol = protocol;
283 if (gitProtocol == null) {
284 gitProtocol = TransferConfig.ProtocolVersion.V2;
285 }
286 if (session instanceof RemoteSession2
287 && TransferConfig.ProtocolVersion.V2
288 .equals(gitProtocol)) {
289 process = ((RemoteSession2) session).exec(
290 commandFor(getOptionUploadPack()), Collections
291 .singletonMap(
292 GitProtocolConstants.PROTOCOL_ENVIRONMENT_VARIABLE,
293 GitProtocolConstants.VERSION_2_REQUEST),
294 getTimeout());
295 } else {
296 process = session.exec(commandFor(getOptionUploadPack()),
297 getTimeout());
298 }
299 final MessageWriter msg = new MessageWriter();
300 setMessageWriter(msg);
301
302 final InputStream upErr = process.getErrorStream();
303 errorThread = new StreamCopyThread(upErr, msg.getRawStream());
304 errorThread.start();
305
306 init(process.getInputStream(), process.getOutputStream());
307
308 } catch (TransportException err) {
309 close();
310 throw err;
311 } catch (Throwable err) {
312 close();
313 throw new TransportException(uri,
314 JGitText.get().remoteHungUpUnexpectedly, err);
315 }
316
317 try {
318 if (!readAdvertisedRefs()) {
319 lsRefs(refSpecs, additionalPatterns);
320 }
321 } catch (NoRemoteRepositoryException notFound) {
322 final String msgs = getMessages();
323 checkExecFailure(process.exitValue(), getOptionUploadPack(),
324 msgs);
325 throw cleanNotFound(notFound, msgs);
326 }
327 }
328
329 @Override
330 public void close() {
331 endOut();
332
333 if (process != null) {
334 process.destroy();
335 }
336 if (errorThread != null) {
337 try {
338 errorThread.halt();
339 } catch (InterruptedException e) {
340
341 } finally {
342 errorThread = null;
343 }
344 }
345
346 super.close();
347 }
348 }
349
350 class SshPushConnection extends BasePackPushConnection {
351 private final Process process;
352
353 private StreamCopyThread errorThread;
354
355 SshPushConnection() throws TransportException {
356 super(TransportGitSsh.this);
357 try {
358 process = getSession().exec(commandFor(getOptionReceivePack()),
359 getTimeout());
360 final MessageWriter msg = new MessageWriter();
361 setMessageWriter(msg);
362
363 final InputStream rpErr = process.getErrorStream();
364 errorThread = new StreamCopyThread(rpErr, msg.getRawStream());
365 errorThread.start();
366
367 init(process.getInputStream(), process.getOutputStream());
368
369 } catch (TransportException err) {
370 try {
371 close();
372 } catch (Exception e) {
373
374 }
375 throw err;
376 } catch (Throwable err) {
377 try {
378 close();
379 } catch (Exception e) {
380
381 }
382 throw new TransportException(uri,
383 JGitText.get().remoteHungUpUnexpectedly, err);
384 }
385
386 try {
387 readAdvertisedRefs();
388 } catch (NoRemoteRepositoryException notFound) {
389 final String msgs = getMessages();
390 checkExecFailure(process.exitValue(), getOptionReceivePack(),
391 msgs);
392 throw cleanNotFound(notFound, msgs);
393 }
394 }
395
396 @Override
397 public void close() {
398 endOut();
399
400 if (process != null) {
401 process.destroy();
402 }
403 if (errorThread != null) {
404 try {
405 errorThread.halt();
406 } catch (InterruptedException e) {
407
408 } finally {
409 errorThread = null;
410 }
411 }
412
413 super.close();
414 }
415 }
416 }