1 package ch
.cyberduck
.core
.ftp
;
4 * Copyright (c) 2002-2013 David Kocher. All rights reserved.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * Bug fixes, suggestions and comments should be sent to feedback@cyberduck.ch
20 import ch
.cyberduck
.core
.AttributedList
;
21 import ch
.cyberduck
.core
.ListProgressListener
;
22 import ch
.cyberduck
.core
.Path
;
23 import ch
.cyberduck
.core
.PathNormalizer
;
24 import ch
.cyberduck
.core
.Permission
;
25 import ch
.cyberduck
.core
.exception
.ConnectionCanceledException
;
26 import ch
.cyberduck
.core
.ftp
.parser
.FTPExtendedFile
;
28 import org
.apache
.commons
.net
.ftp
.FTPFile
;
29 import org
.apache
.commons
.net
.ftp
.FTPFileEntryParser
;
30 import org
.apache
.log4j
.Logger
;
32 import java
.io
.IOException
;
33 import java
.util
.Calendar
;
34 import java
.util
.EnumSet
;
35 import java
.util
.List
;
40 public class FTPListResponseReader
implements FTPDataResponseReader
{
41 private static final Logger log
= Logger
.getLogger(FTPListResponseReader
.class);
43 private FTPFileEntryParser parser
;
45 private boolean lenient
;
47 public FTPListResponseReader(final FTPFileEntryParser parser
) {
51 public FTPListResponseReader(final FTPFileEntryParser parser
, final boolean lenient
) {
53 this.lenient
= lenient
;
57 public AttributedList
<Path
> read(final Path directory
, final List
<String
> replies
, final ListProgressListener listener
)
58 throws IOException
, FTPInvalidListException
, ConnectionCanceledException
{
59 final AttributedList
<Path
> children
= new AttributedList
<Path
>();
60 // At least one entry successfully parsed
61 boolean success
= false;
62 // Call hook for those implementors which need to perform some action upon the list after it has been created
63 // from the server stream, but before any clients see the list
64 parser
.preParse(replies
);
65 for(String line
: replies
) {
66 final FTPFile f
= parser
.parseFTPEntry(line
);
70 final String name
= f
.getName();
73 // Workaround for #2410. STAT only returns ls of directory itself
74 // Workaround for #2434. STAT of symbolic link directory only lists the directory itself.
75 if(directory
.getName().equals(name
)) {
76 log
.warn(String
.format("Skip %s matching parent directory name", f
.getName()));
79 if(name
.contains(String
.valueOf(Path
.DELIMITER
))) {
80 if(!name
.startsWith(directory
.getAbsolute() + Path
.DELIMITER
)) {
81 // Workaround for #2434.
82 log
.warn(String
.format("Skip %s with delimiter in name", name
));
89 if(name
.equals(".") || name
.equals("..")) {
90 if(log
.isDebugEnabled()) {
91 log
.debug(String
.format("Skip %s", f
.getName()));
95 final Path parsed
= new Path(directory
, PathNormalizer
.name(name
), f
.getType() == FTPFile
.DIRECTORY_TYPE ? EnumSet
.of(Path
.Type
.directory
) : EnumSet
.of(Path
.Type
.file
));
97 case FTPFile
.SYMBOLIC_LINK_TYPE
:
98 parsed
.setType(EnumSet
.of(Path
.Type
.file
, Path
.Type
.symboliclink
));
99 // Symbolic link target may be an absolute or relative path
100 final String target
= f
.getLink();
101 if(target
.startsWith(String
.valueOf(Path
.DELIMITER
))) {
102 parsed
.setSymlinkTarget(new Path(target
, EnumSet
.of(Path
.Type
.file
)));
105 parsed
.setSymlinkTarget(new Path(String
.format("%s/%s", directory
.getAbsolute(), target
),
106 EnumSet
.of(Path
.Type
.file
)));
110 if(parsed
.isFile()) {
111 parsed
.attributes().setSize(f
.getSize());
113 parsed
.attributes().setOwner(f
.getUser());
114 parsed
.attributes().setGroup(f
.getGroup());
115 Permission
.Action u
= Permission
.Action
.none
;
116 if(f
.hasPermission(FTPFile
.USER_ACCESS
, FTPFile
.READ_PERMISSION
)) {
117 u
= u
.or(Permission
.Action
.read
);
119 if(f
.hasPermission(FTPFile
.USER_ACCESS
, FTPFile
.WRITE_PERMISSION
)) {
120 u
= u
.or(Permission
.Action
.write
);
122 if(f
.hasPermission(FTPFile
.USER_ACCESS
, FTPFile
.EXECUTE_PERMISSION
)) {
123 u
= u
.or(Permission
.Action
.execute
);
125 Permission
.Action g
= Permission
.Action
.none
;
126 if(f
.hasPermission(FTPFile
.GROUP_ACCESS
, FTPFile
.READ_PERMISSION
)) {
127 g
= g
.or(Permission
.Action
.read
);
129 if(f
.hasPermission(FTPFile
.GROUP_ACCESS
, FTPFile
.WRITE_PERMISSION
)) {
130 g
= g
.or(Permission
.Action
.write
);
132 if(f
.hasPermission(FTPFile
.GROUP_ACCESS
, FTPFile
.EXECUTE_PERMISSION
)) {
133 g
= g
.or(Permission
.Action
.execute
);
135 Permission
.Action o
= Permission
.Action
.none
;
136 if(f
.hasPermission(FTPFile
.WORLD_ACCESS
, FTPFile
.READ_PERMISSION
)) {
137 o
= o
.or(Permission
.Action
.read
);
139 if(f
.hasPermission(FTPFile
.WORLD_ACCESS
, FTPFile
.WRITE_PERMISSION
)) {
140 o
= o
.or(Permission
.Action
.write
);
142 if(f
.hasPermission(FTPFile
.WORLD_ACCESS
, FTPFile
.EXECUTE_PERMISSION
)) {
143 o
= o
.or(Permission
.Action
.execute
);
145 final Permission permission
= new Permission(u
, g
, o
);
146 if(f
instanceof FTPExtendedFile
) {
147 permission
.setSetuid(((FTPExtendedFile
) f
).isSetuid());
148 permission
.setSetgid(((FTPExtendedFile
) f
).isSetgid());
149 permission
.setSticky(((FTPExtendedFile
) f
).isSticky());
151 parsed
.attributes().setPermission(permission
);
152 final Calendar timestamp
= f
.getTimestamp();
153 if(timestamp
!= null) {
154 parsed
.attributes().setModificationDate(timestamp
.getTimeInMillis());
156 children
.add(parsed
);
157 listener
.chunk(directory
, children
);
160 throw new FTPInvalidListException(children
);