1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.transport;
45
46 import static org.eclipse.jgit.lib.Constants.INFO_ALTERNATES;
47 import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
48 import static org.eclipse.jgit.lib.Constants.OBJECTS;
49
50 import java.io.BufferedReader;
51 import java.io.FileNotFoundException;
52 import java.io.IOException;
53 import java.io.OutputStream;
54 import java.text.MessageFormat;
55 import java.util.ArrayList;
56 import java.util.Collection;
57 import java.util.Collections;
58 import java.util.EnumSet;
59 import java.util.HashMap;
60 import java.util.List;
61 import java.util.Map;
62 import java.util.Set;
63 import java.util.TreeMap;
64 import java.util.concurrent.TimeUnit;
65 import java.util.stream.Collectors;
66
67 import org.eclipse.jgit.errors.NotSupportedException;
68 import org.eclipse.jgit.errors.TransportException;
69 import org.eclipse.jgit.internal.JGitText;
70 import org.eclipse.jgit.lib.Constants;
71 import org.eclipse.jgit.lib.ObjectId;
72 import org.eclipse.jgit.lib.ObjectIdRef;
73 import org.eclipse.jgit.lib.ProgressMonitor;
74 import org.eclipse.jgit.lib.Ref;
75 import org.eclipse.jgit.lib.Ref.Storage;
76 import org.eclipse.jgit.lib.Repository;
77 import org.eclipse.jgit.lib.SymbolicRef;
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 public class TransportSftp extends SshTransport implements WalkTransport {
101 static final TransportProtocoll.html#TransportProtocol">TransportProtocol PROTO_SFTP = new TransportProtocol() {
102 @Override
103 public String getName() {
104 return JGitText.get().transportProtoSFTP;
105 }
106
107 @Override
108 public Set<String> getSchemes() {
109 return Collections.singleton("sftp");
110 }
111
112 @Override
113 public Set<URIishField> getRequiredFields() {
114 return Collections.unmodifiableSet(EnumSet.of(URIishField.HOST,
115 URIishField.PATH));
116 }
117
118 @Override
119 public Set<URIishField> getOptionalFields() {
120 return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
121 URIishField.PASS, URIishField.PORT));
122 }
123
124 @Override
125 public int getDefaultPort() {
126 return 22;
127 }
128
129 @Override
130 public Transport open(URIish uri, Repository local, String remoteName)
131 throws NotSupportedException {
132 return new TransportSftp(local, uri);
133 }
134 };
135
136 TransportSftp(Repository local, URIish uri) {
137 super(local, uri);
138 }
139
140
141 @Override
142 public FetchConnection openFetch() throws TransportException {
143 final SftpObjectDB c = new SftpObjectDB(uri.getPath());
144 final WalkFetchConnectionConnection.html#WalkFetchConnection">WalkFetchConnection r = new WalkFetchConnection(this, c);
145 r.available(c.readAdvertisedRefs());
146 return r;
147 }
148
149
150 @Override
151 public PushConnection openPush() throws TransportException {
152 final SftpObjectDB c = new SftpObjectDB(uri.getPath());
153 final WalkPushConnectionConnection.html#WalkPushConnection">WalkPushConnection r = new WalkPushConnection(this, c);
154 r.available(c.readAdvertisedRefs());
155 return r;
156 }
157
158 FtpChannel newSftp() throws IOException {
159 FtpChannel channel = getSession().getFtpChannel();
160 channel.connect(getTimeout(), TimeUnit.SECONDS);
161 return channel;
162 }
163
164 class SftpObjectDB extends WalkRemoteObjectDatabase {
165 private final String objectsPath;
166
167 private FtpChannel ftp;
168
169 SftpObjectDB(String path) throws TransportException {
170 if (path.startsWith("/~"))
171 path = path.substring(1);
172 if (path.startsWith("~/"))
173 path = path.substring(2);
174 try {
175 ftp = newSftp();
176 ftp.cd(path);
177 ftp.cd(OBJECTS);
178 objectsPath = ftp.pwd();
179 } catch (FtpChannel.FtpException f) {
180 throw new TransportException(MessageFormat.format(
181 JGitText.get().cannotEnterObjectsPath, path,
182 f.getMessage()), f);
183 } catch (IOException ioe) {
184 close();
185 throw new TransportException(uri, ioe.getMessage(), ioe);
186 }
187 }
188
189 SftpObjectDB(SftpObjectDB parent, String p)
190 throws TransportException {
191 try {
192 ftp = newSftp();
193 ftp.cd(parent.objectsPath);
194 ftp.cd(p);
195 objectsPath = ftp.pwd();
196 } catch (FtpChannel.FtpException f) {
197 throw new TransportException(MessageFormat.format(
198 JGitText.get().cannotEnterPathFromParent, p,
199 parent.objectsPath, f.getMessage()), f);
200 } catch (IOException ioe) {
201 close();
202 throw new TransportException(uri, ioe.getMessage(), ioe);
203 }
204 }
205
206 @Override
207 URIish getURI() {
208 return uri.setPath(objectsPath);
209 }
210
211 @Override
212 Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
213 try {
214 return readAlternates(INFO_ALTERNATES);
215 } catch (FileNotFoundException err) {
216 return null;
217 }
218 }
219
220 @Override
221 WalkRemoteObjectDatabase openAlternate(String location)
222 throws IOException {
223 return new SftpObjectDB(this, location);
224 }
225
226 @Override
227 Collection<String> getPackNames() throws IOException {
228 final List<String> packs = new ArrayList<>();
229 try {
230 Collection<FtpChannel.DirEntry> list = ftp.ls("pack");
231 Set<String> files = list.stream()
232 .map(FtpChannel.DirEntry::getFilename)
233 .collect(Collectors.toSet());
234 HashMap<String, Long> mtimes = new HashMap<>();
235
236 for (FtpChannel.DirEntry ent : list) {
237 String n = ent.getFilename();
238 if (!n.startsWith("pack-") || !n.endsWith(".pack")) {
239 continue;
240 }
241 String in = n.substring(0, n.length() - 5) + ".idx";
242 if (!files.contains(in)) {
243 continue;
244 }
245 mtimes.put(n, Long.valueOf(ent.getModifiedTime()));
246 packs.add(n);
247 }
248
249 Collections.sort(packs,
250 (o1, o2) -> mtimes.get(o2).compareTo(mtimes.get(o1)));
251 } catch (FtpChannel.FtpException f) {
252 throw new TransportException(
253 MessageFormat.format(JGitText.get().cannotListPackPath,
254 objectsPath, f.getMessage()),
255 f);
256 }
257 return packs;
258 }
259
260 @Override
261 FileStream open(String path) throws IOException {
262 try {
263 return new FileStream(ftp.get(path));
264 } catch (FtpChannel.FtpException f) {
265 if (f.getStatus() == FtpChannel.FtpException.NO_SUCH_FILE) {
266 throw new FileNotFoundException(path);
267 }
268 throw new TransportException(MessageFormat.format(
269 JGitText.get().cannotGetObjectsPath, objectsPath, path,
270 f.getMessage()), f);
271 }
272 }
273
274 @Override
275 void deleteFile(String path) throws IOException {
276 try {
277 ftp.delete(path);
278 } catch (FtpChannel.FtpException f) {
279 throw new TransportException(MessageFormat.format(
280 JGitText.get().cannotDeleteObjectsPath, objectsPath,
281 path, f.getMessage()), f);
282 }
283
284
285
286 String dir = path;
287 int s = dir.lastIndexOf('/');
288 while (s > 0) {
289 try {
290 dir = dir.substring(0, s);
291 ftp.rmdir(dir);
292 s = dir.lastIndexOf('/');
293 } catch (IOException je) {
294
295
296
297
298 break;
299 }
300 }
301 }
302
303 @Override
304 OutputStream writeFile(String path, ProgressMonitor monitor,
305 String monitorTask) throws IOException {
306 Throwable err = null;
307 try {
308 return ftp.put(path);
309 } catch (FileNotFoundException e) {
310 mkdir_p(path);
311 } catch (FtpChannel.FtpException je) {
312 if (je.getStatus() == FtpChannel.FtpException.NO_SUCH_FILE) {
313 mkdir_p(path);
314 } else {
315 err = je;
316 }
317 }
318 if (err == null) {
319 try {
320 return ftp.put(path);
321 } catch (IOException e) {
322 err = e;
323 }
324 }
325 throw new TransportException(
326 MessageFormat.format(JGitText.get().cannotWriteObjectsPath,
327 objectsPath, path, err.getMessage()),
328 err);
329 }
330
331 @Override
332 void writeFile(String path, byte[] data) throws IOException {
333 final String lock = path + LOCK_SUFFIX;
334 try {
335 super.writeFile(lock, data);
336 try {
337 ftp.rename(lock, path);
338 } catch (IOException e) {
339 throw new TransportException(MessageFormat.format(
340 JGitText.get().cannotWriteObjectsPath, objectsPath,
341 path, e.getMessage()), e);
342 }
343 } catch (IOException err) {
344 try {
345 ftp.rm(lock);
346 } catch (IOException e) {
347
348
349 }
350 throw err;
351 }
352 }
353
354 private void mkdir_p(String path) throws IOException {
355 final int s = path.lastIndexOf('/');
356 if (s <= 0)
357 return;
358
359 path = path.substring(0, s);
360 Throwable err = null;
361 try {
362 ftp.mkdir(path);
363 return;
364 } catch (FileNotFoundException f) {
365 mkdir_p(path);
366 } catch (FtpChannel.FtpException je) {
367 if (je.getStatus() == FtpChannel.FtpException.NO_SUCH_FILE) {
368 mkdir_p(path);
369 } else {
370 err = je;
371 }
372 }
373 if (err == null) {
374 try {
375 ftp.mkdir(path);
376 return;
377 } catch (IOException e) {
378 err = e;
379 }
380 }
381 throw new TransportException(MessageFormat.format(
382 JGitText.get().cannotMkdirObjectPath, objectsPath, path,
383 err.getMessage()), err);
384 }
385
386 Map<String, Ref> readAdvertisedRefs() throws TransportException {
387 final TreeMap<String, Ref> avail = new TreeMap<>();
388 readPackedRefs(avail);
389 readRef(avail, ROOT_DIR + Constants.HEAD, Constants.HEAD);
390 readLooseRefs(avail, ROOT_DIR + "refs", "refs/");
391 return avail;
392 }
393
394 private void readLooseRefs(TreeMap<String, Ref> avail, String dir,
395 String prefix) throws TransportException {
396 final Collection<FtpChannel.DirEntry> list;
397 try {
398 list = ftp.ls(dir);
399 } catch (IOException e) {
400 throw new TransportException(MessageFormat.format(
401 JGitText.get().cannotListObjectsPath, objectsPath, dir,
402 e.getMessage()), e);
403 }
404
405 for (FtpChannel.DirEntry ent : list) {
406 String n = ent.getFilename();
407 if (".".equals(n) || "..".equals(n))
408 continue;
409
410 String nPath = dir + "/" + n;
411 if (ent.isDirectory()) {
412 readLooseRefs(avail, nPath, prefix + n + "/");
413 } else {
414 readRef(avail, nPath, prefix + n);
415 }
416 }
417 }
418
419 private Ref readRef(TreeMap<String, Ref> avail, String path,
420 String name) throws TransportException {
421 final String line;
422 try (BufferedReader br = openReader(path)) {
423 line = br.readLine();
424 } catch (FileNotFoundException noRef) {
425 return null;
426 } catch (IOException err) {
427 throw new TransportException(MessageFormat.format(
428 JGitText.get().cannotReadObjectsPath, objectsPath, path,
429 err.getMessage()), err);
430 }
431
432 if (line == null) {
433 throw new TransportException(
434 MessageFormat.format(JGitText.get().emptyRef, name));
435 }
436 if (line.startsWith("ref: ")) {
437 final String target = line.substring("ref: ".length());
438 Ref r = avail.get(target);
439 if (r == null)
440 r = readRef(avail, ROOT_DIR + target, target);
441 if (r == null)
442 r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
443 r = new SymbolicRef(name, r);
444 avail.put(r.getName(), r);
445 return r;
446 }
447
448 if (ObjectId.isId(line)) {
449 final Ref r = new ObjectIdRef.Unpeeled(loose(avail.get(name)),
450 name, ObjectId.fromString(line));
451 avail.put(r.getName(), r);
452 return r;
453 }
454
455 throw new TransportException(
456 MessageFormat.format(JGitText.get().badRef, name, line));
457 }
458
459 private Storage loose(Ref r) {
460 if (r != null && r.getStorage() == Storage.PACKED) {
461 return Storage.LOOSE_PACKED;
462 }
463 return Storage.LOOSE;
464 }
465
466 @Override
467 void close() {
468 if (ftp != null) {
469 try {
470 if (ftp.isConnected()) {
471 ftp.disconnect();
472 }
473 } finally {
474 ftp = null;
475 }
476 }
477 }
478 }
479 }