1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.vfs2.provider.ftp;
18
19 import java.io.FileNotFoundException;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.OutputStream;
23 import java.time.Instant;
24 import java.util.Calendar;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.TimeZone;
29 import java.util.TreeMap;
30 import java.util.concurrent.atomic.AtomicBoolean;
31
32 import org.apache.commons.lang3.ArrayUtils;
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.apache.commons.net.ftp.FTPFile;
36 import org.apache.commons.vfs2.FileName;
37 import org.apache.commons.vfs2.FileNotFolderException;
38 import org.apache.commons.vfs2.FileObject;
39 import org.apache.commons.vfs2.FileSystemException;
40 import org.apache.commons.vfs2.FileType;
41 import org.apache.commons.vfs2.RandomAccessContent;
42 import org.apache.commons.vfs2.provider.AbstractFileName;
43 import org.apache.commons.vfs2.provider.AbstractFileObject;
44 import org.apache.commons.vfs2.provider.UriParser;
45 import org.apache.commons.vfs2.util.FileObjectUtils;
46 import org.apache.commons.vfs2.util.Messages;
47 import org.apache.commons.vfs2.util.MonitorInputStream;
48 import org.apache.commons.vfs2.util.MonitorOutputStream;
49 import org.apache.commons.vfs2.util.RandomAccessMode;
50
51
52
53
54 public class FtpFileObject extends AbstractFileObject<FtpFileSystem> {
55
56
57
58
59 class FtpInputStream extends MonitorInputStream {
60 private final FtpClient client;
61
62 public FtpInputStream(final FtpClient client, final InputStream in) {
63 super(in);
64 this.client = client;
65 }
66
67 public FtpInputStream(final FtpClient client, final InputStream in, final int bufferSize) {
68 super(in, bufferSize);
69 this.client = client;
70 }
71
72 void abort() throws IOException {
73 client.abort();
74 close();
75 }
76
77 private boolean isTransferAbortedOkReplyCode() throws IOException {
78 final List<Integer> transferAbortedOkReplyCodes = FtpFileSystemConfigBuilder
79 .getInstance()
80 .getTransferAbortedOkReplyCodes(getAbstractFileSystem().getFileSystemOptions());
81 return transferAbortedOkReplyCodes != null && transferAbortedOkReplyCodes.contains(client.getReplyCode());
82 }
83
84
85
86
87 @Override
88 protected void onClose() throws IOException {
89 final boolean ok;
90 try {
91 ok = client.completePendingCommand() || isTransferAbortedOkReplyCode();
92 } finally {
93 getAbstractFileSystem().putClient(client);
94 }
95
96 if (!ok) {
97 throw new FileSystemException("vfs.provider.ftp/finish-get.error", getName());
98 }
99 }
100 }
101
102
103
104 private class FtpOutputStream extends MonitorOutputStream {
105 private final FtpClient client;
106
107 public FtpOutputStream(final FtpClient client, final OutputStream outstr) {
108 super(outstr);
109 this.client = client;
110 }
111
112
113
114
115 @Override
116 protected void onClose() throws IOException {
117 final boolean ok;
118 try {
119 ok = client.completePendingCommand();
120 } finally {
121 getAbstractFileSystem().putClient(client);
122 }
123
124 if (!ok) {
125 throw new FileSystemException("vfs.provider.ftp/finish-put.error", getName());
126 }
127 }
128 }
129 private static final long DEFAULT_TIMESTAMP = 0L;
130 private static final Map<String, FTPFile> EMPTY_FTP_FILE_MAP = Collections
131 .unmodifiableMap(new TreeMap<>());
132
133 private static final FTPFile UNKNOWN = new FTPFile();
134
135 private static final Log log = LogFactory.getLog(FtpFileObject.class);
136 private volatile boolean mdtmSet;
137 private final String relPath;
138
139 private volatile FTPFile ftpFile;
140 private volatile Map<String, FTPFile> childMap;
141
142 private volatile FileObject linkDestination;
143
144 private final AtomicBoolean inRefresh = new AtomicBoolean();
145
146 protected FtpFileObject(final AbstractFileName name, final FtpFileSystem fileSystem, final FileName rootName)
147 throws FileSystemException {
148 super(name, fileSystem);
149 final String relPath = UriParser.decode(rootName.getRelativeName(name));
150 if (".".equals(relPath)) {
151
152
153
154
155 this.relPath = null;
156 } else {
157 this.relPath = relPath;
158 }
159 }
160
161
162
163
164 @Override
165 protected void doAttach() throws IOException {
166
167
168 }
169
170
171
172
173 @Override
174 protected void doCreateFolder() throws Exception {
175 final boolean ok;
176 final FtpClient client = getAbstractFileSystem().getClient();
177 try {
178 ok = client.makeDirectory(relPath);
179 } finally {
180 getAbstractFileSystem().putClient(client);
181 }
182
183 if (!ok) {
184 throw new FileSystemException("vfs.provider.ftp/create-folder.error", getName());
185 }
186 }
187
188
189
190
191 @Override
192 protected void doDelete() throws Exception {
193 synchronized (getFileSystem()) {
194 if (this.ftpFile != null) {
195 final boolean ok;
196 final FtpClient ftpClient = getAbstractFileSystem().getClient();
197 try {
198 if (this.ftpFile.isDirectory()) {
199 ok = ftpClient.removeDirectory(relPath);
200 } else {
201 ok = ftpClient.deleteFile(relPath);
202 }
203 } finally {
204 getAbstractFileSystem().putClient(ftpClient);
205 }
206
207 if (!ok) {
208 throw new FileSystemException("vfs.provider.ftp/delete-file.error", getName());
209 }
210 this.ftpFile = null;
211 }
212 this.childMap = EMPTY_FTP_FILE_MAP;
213 }
214 }
215
216
217
218
219 @Override
220 protected void doDetach() {
221 synchronized (getFileSystem()) {
222 this.ftpFile = null;
223 this.childMap = null;
224 }
225 }
226
227
228
229
230 private void doGetChildren() throws IOException {
231 if (childMap != null) {
232 return;
233 }
234
235 final FtpClient client = getAbstractFileSystem().getClient();
236 try {
237 final String path = ftpFile != null && ftpFile.isSymbolicLink()
238 ? getFileSystem().getFileSystemManager().resolveName(getParent().getName(), ftpFile.getLink())
239 .getPath()
240 : relPath;
241 final FTPFile[] tmpChildren = client.listFiles(path);
242 if (ArrayUtils.isEmpty(tmpChildren)) {
243 childMap = EMPTY_FTP_FILE_MAP;
244 } else {
245 childMap = new TreeMap<>();
246
247
248 for (int i = 0; i < tmpChildren.length; i++) {
249 final FTPFile child = tmpChildren[i];
250 if (child == null) {
251 if (log.isDebugEnabled()) {
252 log.debug(Messages.getString("vfs.provider.ftp/invalid-directory-entry.debug",
253 Integer.valueOf(i), relPath));
254 }
255 continue;
256 }
257 if (!".".equals(child.getName()) && !"..".equals(child.getName())) {
258 childMap.put(child.getName(), child);
259 }
260 }
261 }
262 } finally {
263 getAbstractFileSystem().putClient(client);
264 }
265 }
266
267
268
269
270 @Override
271 protected long doGetContentSize() throws Exception {
272 synchronized (getFileSystem()) {
273 if (this.ftpFile == null) {
274 return 0;
275 }
276 if (this.ftpFile.isSymbolicLink()) {
277 final FileObject linkDest = getLinkDestination();
278
279 if (this.isCircular(linkDest)) {
280 return this.ftpFile.getSize();
281 }
282 return linkDest.getContent().getSize();
283 }
284 return this.ftpFile.getSize();
285 }
286 }
287
288
289
290
291 @Override
292 protected InputStream doGetInputStream(final int bufferSize) throws Exception {
293 final FtpClient client = getAbstractFileSystem().getClient();
294 try {
295 final InputStream inputStream = client.retrieveFileStream(relPath, 0);
296
297 if (inputStream == null) {
298 throw new FileNotFoundException(getName().toString());
299 }
300 return new FtpInputStream(client, inputStream, bufferSize);
301 } catch (final Exception e) {
302 getAbstractFileSystem().putClient(client);
303 throw e;
304 }
305 }
306
307
308
309
310
311
312 @Override
313 protected long doGetLastModifiedTime() throws Exception {
314 synchronized (getFileSystem()) {
315 if (this.ftpFile == null) {
316 return DEFAULT_TIMESTAMP;
317 }
318 if (this.ftpFile.isSymbolicLink()) {
319 final FileObject linkDest = getLinkDestination();
320
321 if (this.isCircular(linkDest)) {
322 return getTimestampMillis();
323 }
324 return linkDest.getContent().getLastModifiedTime();
325 }
326 return getTimestampMillis();
327 }
328 }
329
330
331
332
333 @Override
334 protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
335 final FtpClient client = getAbstractFileSystem().getClient();
336 try {
337 final OutputStream out;
338 if (bAppend) {
339 out = client.appendFileStream(relPath);
340 } else {
341 out = client.storeFileStream(relPath);
342 }
343
344 FileSystemException.requireNonNull(out, "vfs.provider.ftp/output-error.debug", this.getName(),
345 client.getReplyString());
346
347 return new FtpOutputStream(client, out);
348 } catch (final Exception e) {
349 getAbstractFileSystem().putClient(client);
350 throw e;
351 }
352 }
353
354 @Override
355 protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
356 return new FtpRandomAccessContent(this, mode);
357 }
358
359
360
361
362 @Override
363 protected FileType doGetType() throws Exception {
364
365 synchronized (getFileSystem()) {
366 if (this.ftpFile == null) {
367 setFTPFile(false);
368 }
369
370 if (this.ftpFile == UNKNOWN) {
371 return FileType.IMAGINARY;
372 }
373 if (this.ftpFile.isDirectory()) {
374 return FileType.FOLDER;
375 }
376 if (this.ftpFile.isFile()) {
377 return FileType.FILE;
378 }
379 if (this.ftpFile.isSymbolicLink()) {
380 final FileObject linkDest = getLinkDestination();
381
382 if (this.isCircular(linkDest)) {
383
384
385
386
387 return FileType.IMAGINARY;
388 }
389 return linkDest.getType();
390
391 }
392 }
393 throw new FileSystemException("vfs.provider.ftp/get-type.error", getName());
394 }
395
396
397
398
399 @Override
400 protected String[] doListChildren() throws Exception {
401
402 doGetChildren();
403
404
405 if (childMap == null) {
406 return null;
407 }
408
409
410 final String[] childNames = childMap.values().stream().map(FTPFile::getName).toArray(String[]::new);
411
412 return UriParser.encode(childNames);
413 }
414
415 @Override
416 protected FileObject[] doListChildrenResolved() throws Exception {
417 synchronized (getFileSystem()) {
418 if (this.ftpFile != null && this.ftpFile.isSymbolicLink()) {
419 final FileObject linkDest = getLinkDestination();
420
421 if (this.isCircular(linkDest)) {
422 return null;
423 }
424 return linkDest.getChildren();
425 }
426 }
427 return null;
428 }
429
430
431
432
433 @Override
434 protected void doRename(final FileObject newFile) throws Exception {
435 synchronized (getFileSystem()) {
436 final boolean ok;
437 final FtpClient ftpClient = getAbstractFileSystem().getClient();
438 try {
439 final String newName = ((FtpFileObject) FileObjectUtils.getAbstractFileObject(newFile)).getRelPath();
440 ok = ftpClient.rename(relPath, newName);
441 } finally {
442 getAbstractFileSystem().putClient(ftpClient);
443 }
444
445 if (!ok) {
446 throw new FileSystemException("vfs.provider.ftp/rename-file.error", getName().toString(), newFile);
447 }
448 this.ftpFile = null;
449 this.childMap = EMPTY_FTP_FILE_MAP;
450 }
451 }
452
453
454
455
456
457
458
459 private FTPFile getChildFile(final String name, final boolean flush) throws IOException {
460
461
462
463
464
465 if (flush && !inRefresh.get()) {
466 childMap = null;
467 }
468
469
470 doGetChildren();
471
472
473
474 return childMap != null ? childMap.get(name) : null;
475 }
476
477
478
479
480
481
482
483
484
485 @Override
486 public FileObject[] getChildren() throws FileSystemException {
487 try {
488 if (doGetType() != FileType.FOLDER) {
489 throw new FileNotFolderException(getName());
490 }
491 } catch (final Exception ex) {
492 throw new FileNotFolderException(getName(), ex);
493 }
494
495 try {
496
497
498
499
500
501
502 this.inRefresh.set(true);
503 return super.getChildren();
504 } finally {
505 this.inRefresh.set(false);
506 }
507 }
508
509 FtpInputStream getInputStream(final long filePointer) throws IOException {
510 final FtpClient client = getAbstractFileSystem().getClient();
511 try {
512 final InputStream instr = client.retrieveFileStream(relPath, filePointer);
513 FileSystemException.requireNonNull(instr, "vfs.provider.ftp/input-error.debug", this.getName(),
514 client.getReplyString());
515 return new FtpInputStream(client, instr);
516 } catch (final IOException e) {
517 getAbstractFileSystem().putClient(client);
518 throw e;
519 }
520 }
521
522 private FileObject getLinkDestination() throws FileSystemException {
523 if (linkDestination == null) {
524 final String path;
525 synchronized (getFileSystem()) {
526 path = this.ftpFile == null ? null : this.ftpFile.getLink();
527 }
528 final FileName parent = getName().getParent();
529 final FileName relativeTo = parent == null ? getName() : parent;
530 final FileName linkDestinationName = getFileSystem().getFileSystemManager().resolveName(relativeTo, path);
531 linkDestination = getFileSystem().resolveFile(linkDestinationName);
532 }
533 return linkDestination;
534 }
535
536 String getRelPath() {
537 return relPath;
538 }
539
540
541
542
543 @SuppressWarnings("resource")
544 private long getTimestampMillis() throws IOException {
545 final FtpFileSystem abstractFileSystem = getAbstractFileSystem();
546 final Boolean mdtmLastModifiedTime = FtpFileSystemConfigBuilder.getInstance()
547 .getMdtmLastModifiedTime(abstractFileSystem.getFileSystemOptions());
548 if (mdtmLastModifiedTime != null && mdtmLastModifiedTime.booleanValue()) {
549 final FtpClient client = abstractFileSystem.getClient();
550 if (!mdtmSet && client.hasFeature("MDTM")) {
551 final Instant mdtmInstant = client.mdtmInstant(relPath);
552 final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
553 final long epochMilli = mdtmInstant.toEpochMilli();
554 calendar.setTimeInMillis(epochMilli);
555 ftpFile.setTimestamp(calendar);
556 mdtmSet = true;
557 }
558 }
559 return ftpFile.getTimestamp().getTime().getTime();
560 }
561
562
563
564
565 private boolean isCircular(final FileObject linkDest) throws FileSystemException {
566 return linkDest.getName().getPathDecoded().equals(this.getName().getPathDecoded());
567 }
568
569
570
571
572 @Override
573 protected void onChange() throws IOException {
574 childMap = null;
575
576 if (getType().equals(FileType.IMAGINARY)) {
577
578 synchronized (getFileSystem()) {
579 this.ftpFile = UNKNOWN;
580 }
581 return;
582 }
583
584 setFTPFile(true);
585 }
586
587
588
589
590 @Override
591 protected void onChildrenChanged(final FileName child, final FileType newType) {
592 if (childMap != null && newType.equals(FileType.IMAGINARY)) {
593 try {
594 childMap.remove(UriParser.decode(child.getBaseName()));
595 } catch (final FileSystemException e) {
596 throw new RuntimeException(e.getMessage());
597 }
598 } else {
599
600
601 childMap = null;
602 }
603 }
604
605
606
607
608 @Override
609 public void refresh() throws FileSystemException {
610 if (inRefresh.compareAndSet(false, true)) {
611 try {
612 super.refresh();
613 synchronized (getFileSystem()) {
614 this.ftpFile = null;
615 }
616
617
618
619
620 } finally {
621 inRefresh.set(false);
622 }
623 }
624 }
625
626
627
628
629 private void setFTPFile(final boolean flush) throws IOException {
630 synchronized (getFileSystem()) {
631 final FtpFileObject./../../../org/apache/commons/vfs2/provider/ftp/FtpFileObject.html#FtpFileObject">FtpFileObject parent = (FtpFileObject) FileObjectUtils.getAbstractFileObject(getParent());
632 final FTPFile newFileInfo;
633 if (parent != null) {
634 newFileInfo = parent.getChildFile(UriParser.decode(getName().getBaseName()), flush);
635 } else {
636
637 newFileInfo = new FTPFile();
638 newFileInfo.setType(FTPFile.DIRECTORY_TYPE);
639 }
640 this.ftpFile = newFileInfo == null ? UNKNOWN : newFileInfo;
641 }}
642 }