View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.provider.ftp;
18  
19  import java.io.IOException;
20  import java.io.PrintWriter;
21  import java.io.StringWriter;
22  import java.io.Writer;
23  import java.net.Proxy;
24  import java.time.Duration;
25  
26  import org.apache.commons.lang3.StringUtils;
27  import org.apache.commons.lang3.time.DurationUtils;
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.commons.net.PrintCommandListener;
31  import org.apache.commons.net.ftp.FTPClient;
32  import org.apache.commons.net.ftp.FTPClientConfig;
33  import org.apache.commons.net.ftp.FTPReply;
34  import org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory;
35  import org.apache.commons.vfs2.FileSystemException;
36  import org.apache.commons.vfs2.FileSystemOptions;
37  import org.apache.commons.vfs2.util.UserAuthenticatorUtils;
38  
39  /**
40   * Create a FtpClient instance.
41   */
42  public final class FtpClientFactory {
43  
44      /**
45       * Abstract Factory, used to configure different FTPClients.
46       *
47       * @param <C>
48       *            The type of FTPClient.
49       * @param <B>
50       *            The type of FtpFileSystemConfigBuilder
51       */
52      public abstract static class ConnectionFactory<C extends FTPClient, B extends FtpFileSystemConfigBuilder> {
53          private static final char[] ANON_CHAR_ARRAY = "anonymous".toCharArray();
54          private static final int BUFSZ = 40;
55          private final Log log = LogFactory.getLog(getClass());
56  
57          protected B builder;
58  
59          protected ConnectionFactory(final B builder) {
60              this.builder = builder;
61          }
62  
63          private void configureClient(final FileSystemOptions fileSystemOptions, final C client) {
64              final String key = builder.getEntryParser(fileSystemOptions);
65              if (key != null) {
66                  final FTPClientConfig config = new FTPClientConfig(key);
67  
68                  final String serverLanguageCode = builder.getServerLanguageCode(fileSystemOptions);
69                  if (serverLanguageCode != null) {
70                      config.setServerLanguageCode(serverLanguageCode);
71                  }
72                  final String defaultDateFormat = builder.getDefaultDateFormat(fileSystemOptions);
73                  if (defaultDateFormat != null) {
74                      config.setDefaultDateFormatStr(defaultDateFormat);
75                  }
76                  final String recentDateFormat = builder.getRecentDateFormat(fileSystemOptions);
77                  if (recentDateFormat != null) {
78                      config.setRecentDateFormatStr(recentDateFormat);
79                  }
80                  final String serverTimeZoneId = builder.getServerTimeZoneId(fileSystemOptions);
81                  if (serverTimeZoneId != null) {
82                      config.setServerTimeZoneId(serverTimeZoneId);
83                  }
84                  final String[] shortMonthNames = builder.getShortMonthNames(fileSystemOptions);
85                  if (shortMonthNames != null) {
86                      final StringBuilder shortMonthNamesStr = new StringBuilder(BUFSZ);
87                      for (final String shortMonthName : shortMonthNames) {
88                          if (!StringUtils.isEmpty(shortMonthNamesStr)) {
89                              shortMonthNamesStr.append("|");
90                          }
91                          shortMonthNamesStr.append(shortMonthName);
92                      }
93                      config.setShortMonthNames(shortMonthNamesStr.toString());
94                  }
95  
96                  client.configure(config);
97              }
98          }
99  
100         protected abstract C createClient(FileSystemOptions fileSystemOptions) throws FileSystemException;
101 
102         public C createConnection(final String hostname, final int port, char[] username, char[] password,
103                 final String workingDirectory, final FileSystemOptions fileSystemOptions) throws FileSystemException {
104             // Determine the username and password to use
105             if (username == null) {
106                 username = ANON_CHAR_ARRAY;
107             }
108 
109             if (password == null) {
110                 password = ANON_CHAR_ARRAY;
111             }
112 
113             try {
114                 final C client = createClient(fileSystemOptions);
115 
116                 if (log.isDebugEnabled()) {
117                     final Writer writer = new StringWriter(1024) {
118                         @Override
119                         public void flush() {
120                             final StringBuffer buffer = getBuffer();
121                             String message = buffer.toString();
122                             if (message.toUpperCase().startsWith("PASS ") && message.length() > 5) {
123                                 message = "PASS ***";
124                             }
125                             log.debug(message);
126                             buffer.setLength(0);
127                         }
128                     };
129                     client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(writer)));
130                 }
131 
132                 configureClient(fileSystemOptions, client);
133 
134                 final FTPFileEntryParserFactory myFactory = builder.getEntryParserFactory(fileSystemOptions);
135                 if (myFactory != null) {
136                     client.setParserFactory(myFactory);
137                 }
138 
139                 final Boolean remoteVerification = builder.getRemoteVerification(fileSystemOptions);
140                 if (remoteVerification != null) {
141                     client.setRemoteVerificationEnabled(remoteVerification.booleanValue());
142                 }
143 
144                 try {
145                     final Duration connectTimeout = builder.getConnectTimeoutDuration(fileSystemOptions);
146                     if (connectTimeout != null) {
147                         client.setDefaultTimeout(DurationUtils.toMillisInt(connectTimeout));
148                     }
149 
150                     final String controlEncoding = builder.getControlEncoding(fileSystemOptions);
151                     if (controlEncoding != null) {
152                         client.setControlEncoding(controlEncoding);
153                     }
154 
155                     final Boolean autodetectUTF8 = builder.getAutodetectUtf8(fileSystemOptions);
156                     if (autodetectUTF8 != null) {
157                         client.setAutodetectUTF8(autodetectUTF8);
158                     }
159 
160                     final Proxy proxy = builder.getProxy(fileSystemOptions);
161                     if (proxy != null) {
162                         client.setProxy(proxy);
163                     }
164 
165                     client.connect(hostname, port);
166 
167                     final int reply = client.getReplyCode();
168                     if (!FTPReply.isPositiveCompletion(reply)) {
169                         throw new FileSystemException("vfs.provider.ftp/connect-rejected.error", hostname);
170                     }
171 
172                     // Login
173                     if (!client.login(UserAuthenticatorUtils.toString(username),
174                         UserAuthenticatorUtils.toString(password))) {
175                         throw new FileSystemException("vfs.provider.ftp/login.error", hostname,
176                             UserAuthenticatorUtils.toString(username));
177                     }
178 
179                     FtpFileType fileType = builder.getFileType(fileSystemOptions);
180                     if (fileType == null) {
181                         fileType = FtpFileType.BINARY;
182                     }
183                     // Set binary mode
184                     if (!client.setFileType(fileType.getValue())) {
185                         throw new FileSystemException("vfs.provider.ftp/set-file-type.error", fileType);
186                     }
187 
188                     // Set dataTimeout value
189                     final Duration dataTimeout = builder.getDataTimeoutDuration(fileSystemOptions);
190                     if (dataTimeout != null) {
191                         client.setDataTimeout(DurationUtils.toMillisInt(dataTimeout));
192                     }
193 
194                     final Duration socketTimeout = builder.getSoTimeoutDuration(fileSystemOptions);
195                     if (socketTimeout != null) {
196                         client.setSoTimeout(DurationUtils.toMillisInt(socketTimeout));
197                     }
198 
199                     final Duration controlKeepAliveTimeout = builder.getControlKeepAliveTimeout(fileSystemOptions);
200                     if (controlKeepAliveTimeout != null) {
201                         // yes, in seconds.
202                         client.setControlKeepAliveTimeout(controlKeepAliveTimeout.getSeconds());
203                     }
204 
205                     final Duration controlKeepAliveReplyTimeout = builder
206                         .getControlKeepAliveReplyTimeout(fileSystemOptions);
207                     if (controlKeepAliveReplyTimeout != null) {
208                         client.setControlKeepAliveReplyTimeout((int) controlKeepAliveReplyTimeout.toMillis());
209                     }
210 
211                     final Boolean userDirIsRoot = builder.getUserDirIsRoot(fileSystemOptions);
212                     if ((workingDirectory != null && (userDirIsRoot == null || !userDirIsRoot.booleanValue())) && !client.changeWorkingDirectory(workingDirectory)) {
213                         throw new FileSystemException("vfs.provider.ftp/change-work-directory.error",
214                             workingDirectory);
215                     }
216 
217                     final Boolean passiveMode = builder.getPassiveMode(fileSystemOptions);
218                     if (passiveMode != null && passiveMode.booleanValue()) {
219                         client.enterLocalPassiveMode();
220                     }
221 
222                     setupOpenConnection(client, fileSystemOptions);
223                 } catch (final IOException e) {
224                     if (client.isConnected()) {
225                         client.disconnect();
226                     }
227                     throw e;
228                 }
229 
230                 return client;
231             } catch (final Exception exc) {
232                 throw new FileSystemException("vfs.provider.ftp/connect.error", exc, hostname);
233             }
234         }
235 
236         protected abstract void setupOpenConnection(C client, FileSystemOptions fileSystemOptions) throws IOException;
237     }
238 
239     /** Connection Factory, used to configure the FTPClient. */
240     public static final class FtpConnectionFactory extends ConnectionFactory<FTPClient, FtpFileSystemConfigBuilder> {
241         private FtpConnectionFactory(final FtpFileSystemConfigBuilder builder) {
242             super(builder);
243         }
244 
245         @Override
246         protected FTPClient createClient(final FileSystemOptions fileSystemOptions) {
247             return new FTPClient();
248         }
249 
250         @Override
251         protected void setupOpenConnection(final FTPClient client, final FileSystemOptions fileSystemOptions) {
252             // nothing to do for FTP
253         }
254     }
255 
256     /**
257      * Creates a new connection to the server.
258      *
259      * @param hostname The host name of the server.
260      * @param port The port to connect to.
261      * @param username The name of the user for authentication.
262      * @param password The user's password.
263      * @param workingDirectory The base directory.
264      * @param fileSystemOptions The FileSystemOptions.
265      * @return An FTPClient.
266      * @throws FileSystemException if an error occurs while connecting.
267      */
268     public static FTPClient createConnection(final String hostname, final int port, final char[] username,
269             final char[] password, final String workingDirectory, final FileSystemOptions fileSystemOptions)
270             throws FileSystemException {
271         final FtpConnectionFactory factory = new FtpConnectionFactory(FtpFileSystemConfigBuilder.getInstance());
272         return factory.createConnection(hostname, port, username, password, workingDirectory, fileSystemOptions);
273     }
274 
275     private FtpClientFactory() {
276     }
277 }