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