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.Range;
27  import org.apache.commons.lang3.StringUtils;
28  import org.apache.commons.lang3.time.DurationUtils;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.commons.net.PrintCommandListener;
32  import org.apache.commons.net.ftp.FTPClient;
33  import org.apache.commons.net.ftp.FTPClientConfig;
34  import org.apache.commons.net.ftp.FTPReply;
35  import org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory;
36  import org.apache.commons.vfs2.FileSystemException;
37  import org.apache.commons.vfs2.FileSystemOptions;
38  import org.apache.commons.vfs2.util.UserAuthenticatorUtils;
39  
40  /**
41   * Creates {@link FtpClient} instances.
42   */
43  public final class FtpClientFactory {
44  
45      /**
46       * Abstract Factory, used to configure different FTPClients.
47       *
48       * @param <C> The type of FTPClient.
49       * @param <B> The type of FtpFileSystemConfigBuilder
50       */
51      public abstract static class ConnectionFactory<C extends FTPClient, B extends FtpFileSystemConfigBuilder> {
52  
53          private static final char[] ANON_CHAR_ARRAY = "anonymous".toCharArray();
54          private static final int BUFSZ = 40;
55  
56          /**
57           * My builder.
58           */
59          protected B builder;
60  
61          private final Log log = LogFactory.getLog(getClass());
62  
63          /**
64           * Constructs a new instance.
65           *
66           * @param builder How to build.
67           */
68          protected ConnectionFactory(final B builder) {
69              this.builder = builder;
70          }
71  
72          private void configureClient(final FileSystemOptions fileSystemOptions, final C client) {
73              final String key = builder.getEntryParser(fileSystemOptions);
74              if (key != null) {
75                  final FTPClientConfig config = new FTPClientConfig(key);
76                  final String serverLanguageCode = builder.getServerLanguageCode(fileSystemOptions);
77                  if (serverLanguageCode != null) {
78                      config.setServerLanguageCode(serverLanguageCode);
79                  }
80                  final String defaultDateFormat = builder.getDefaultDateFormat(fileSystemOptions);
81                  if (defaultDateFormat != null) {
82                      config.setDefaultDateFormatStr(defaultDateFormat);
83                  }
84                  final String recentDateFormat = builder.getRecentDateFormat(fileSystemOptions);
85                  if (recentDateFormat != null) {
86                      config.setRecentDateFormatStr(recentDateFormat);
87                  }
88                  final String serverTimeZoneId = builder.getServerTimeZoneId(fileSystemOptions);
89                  if (serverTimeZoneId != null) {
90                      config.setServerTimeZoneId(serverTimeZoneId);
91                  }
92                  final String[] shortMonthNames = builder.getShortMonthNames(fileSystemOptions);
93                  if (shortMonthNames != null) {
94                      final StringBuilder shortMonthNamesStr = new StringBuilder(BUFSZ);
95                      for (final String shortMonthName : shortMonthNames) {
96                          if (!StringUtils.isEmpty(shortMonthNamesStr)) {
97                              shortMonthNamesStr.append("|");
98                          }
99                          shortMonthNamesStr.append(shortMonthName);
100                     }
101                     config.setShortMonthNames(shortMonthNamesStr.toString());
102                 }
103                 client.configure(config);
104             }
105         }
106 
107         /**
108          * Creates a new client.
109          *
110          * @param fileSystemOptions the file system options.
111          * @return a new client.
112          * @throws FileSystemException if a file system error occurs.
113          */
114         protected abstract C createClient(FileSystemOptions fileSystemOptions) throws FileSystemException;
115 
116         /**
117          * Creates a connection.
118          *
119          * @param hostname The host name or IP address.
120          * @param port The host port.
121          * @param username The user name.
122          * @param password The user password.
123          * @param workingDirectory The working directory.
124          * @param fileSystemOptions Options to create the connection.
125          * @return A new connection.
126          * @throws FileSystemException if an error occurs while establishing a connection.
127          */
128         public C createConnection(final String hostname, final int port, char[] username, char[] password,
129                 final String workingDirectory, final FileSystemOptions fileSystemOptions) throws FileSystemException {
130             // Determine the username and password to use
131             if (username == null) {
132                 username = ANON_CHAR_ARRAY;
133             }
134             if (password == null) {
135                 password = ANON_CHAR_ARRAY;
136             }
137             try {
138                 final C client = createClient(fileSystemOptions);
139                 if (log.isDebugEnabled()) {
140                     final Writer writer = new StringWriter(1024) {
141                         @Override
142                         public void flush() {
143                             final StringBuffer buffer = getBuffer();
144                             String message = buffer.toString();
145                             final String prefix = "PASS ";
146                             if (message.toUpperCase().startsWith(prefix) && message.length() > prefix.length()) {
147                                 message = prefix + "***";
148                             }
149                             log.debug(message);
150                             buffer.setLength(0);
151                         }
152                     };
153                     client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(writer)));
154                 }
155                 configureClient(fileSystemOptions, client);
156                 final FTPFileEntryParserFactory myFactory = builder.getEntryParserFactory(fileSystemOptions);
157                 if (myFactory != null) {
158                     client.setParserFactory(myFactory);
159                 }
160                 final Boolean remoteVerification = builder.getRemoteVerification(fileSystemOptions);
161                 if (remoteVerification != null) {
162                     client.setRemoteVerificationEnabled(remoteVerification.booleanValue());
163                 }
164                 try {
165                     final Duration connectTimeout = builder.getConnectTimeoutDuration(fileSystemOptions);
166                     if (connectTimeout != null) {
167                         client.setDefaultTimeout(DurationUtils.toMillisInt(connectTimeout));
168                     }
169                     final String controlEncoding = builder.getControlEncoding(fileSystemOptions);
170                     if (controlEncoding != null) {
171                         client.setControlEncoding(controlEncoding);
172                     }
173                     final Boolean autodetectUTF8 = builder.getAutodetectUtf8(fileSystemOptions);
174                     if (autodetectUTF8 != null) {
175                         client.setAutodetectUTF8(autodetectUTF8);
176                     }
177                     final Proxy proxy = builder.getProxy(fileSystemOptions);
178                     if (proxy != null) {
179                         client.setProxy(proxy);
180                     }
181                     client.connect(hostname, port);
182                     final int reply = client.getReplyCode();
183                     if (!FTPReply.isPositiveCompletion(reply)) {
184                         throw new FileSystemException("vfs.provider.ftp/connect-rejected.error", hostname);
185                     }
186                     // Login
187                     if (!client.login(UserAuthenticatorUtils.toString(username), UserAuthenticatorUtils.toString(password))) {
188                         throw new FileSystemException("vfs.provider.ftp/login.error", hostname, UserAuthenticatorUtils.toString(username));
189                     }
190                     FtpFileType fileType = builder.getFileType(fileSystemOptions);
191                     if (fileType == null) {
192                         fileType = FtpFileType.BINARY;
193                     }
194                     // Set binary mode
195                     if (!client.setFileType(fileType.getValue())) {
196                         throw new FileSystemException("vfs.provider.ftp/set-file-type.error", fileType);
197                     }
198                     // Set dataTimeout value
199                     final Duration dataTimeout = builder.getDataTimeoutDuration(fileSystemOptions);
200                     if (dataTimeout != null) {
201                         client.setDataTimeout(dataTimeout);
202                     }
203                     final Duration socketTimeout = builder.getSoTimeoutDuration(fileSystemOptions);
204                     if (socketTimeout != null) {
205                         client.setSoTimeout(DurationUtils.toMillisInt(socketTimeout));
206                     }
207                     final Duration controlKeepAliveTimeout = builder.getControlKeepAliveTimeout(fileSystemOptions);
208                     if (controlKeepAliveTimeout != null) {
209                         client.setControlKeepAliveTimeout(controlKeepAliveTimeout);
210                     }
211                     final Duration controlKeepAliveReplyTimeout = builder.getControlKeepAliveReplyTimeout(fileSystemOptions);
212                     if (controlKeepAliveReplyTimeout != null) {
213                         client.setControlKeepAliveReplyTimeout(controlKeepAliveReplyTimeout);
214                     }
215                     final Boolean userDirIsRoot = builder.getUserDirIsRoot(fileSystemOptions);
216                     if (workingDirectory != null && (userDirIsRoot == null || !userDirIsRoot.booleanValue())
217                             && !client.changeWorkingDirectory(workingDirectory)) {
218                         throw new FileSystemException("vfs.provider.ftp/change-work-directory.error", workingDirectory);
219                     }
220                     final Boolean passiveMode = builder.getPassiveMode(fileSystemOptions);
221                     if (passiveMode != null && passiveMode.booleanValue()) {
222                         client.enterLocalPassiveMode();
223                     }
224                     final Range<Integer> activePortRange = builder.getActivePortRange(fileSystemOptions);
225                     if (activePortRange != null) {
226                         client.setActivePortRange(activePortRange.getMinimum(), activePortRange.getMaximum());
227                     }
228                     setupOpenConnection(client, fileSystemOptions);
229                 } catch (final IOException e) {
230                     if (client.isConnected()) {
231                         client.disconnect();
232                     }
233                     throw e;
234                 }
235                 return client;
236             } catch (final Exception exc) {
237                 throw new FileSystemException("vfs.provider.ftp/connect.error", exc, hostname);
238             }
239         }
240 
241         /**
242          * Sets up a new client.
243          *
244          * @param client the client.
245          * @param fileSystemOptions the file system options.
246          * @throws IOException if an IO error occurs.
247          */
248         protected abstract void setupOpenConnection(C client, FileSystemOptions fileSystemOptions) throws IOException;
249     }
250 
251     /**
252      * Connection Factory, used to configure the FTPClient.
253      */
254     public static final class FtpConnectionFactory extends ConnectionFactory<FTPClient, FtpFileSystemConfigBuilder> {
255         private FtpConnectionFactory(final FtpFileSystemConfigBuilder builder) {
256             super(builder);
257         }
258 
259         @Override
260         protected FTPClient createClient(final FileSystemOptions fileSystemOptions) {
261             return new FTPClient();
262         }
263 
264         @Override
265         protected void setupOpenConnection(final FTPClient client, final FileSystemOptions fileSystemOptions) {
266             // nothing to do for FTP
267         }
268     }
269 
270     /**
271      * Creates a new connection to the server.
272      *
273      * @param hostname The host name of the server.
274      * @param port The port to connect to.
275      * @param username The name of the user for authentication.
276      * @param password The user's password.
277      * @param workingDirectory The base directory.
278      * @param fileSystemOptions The FileSystemOptions.
279      * @return An FTPClient.
280      * @throws FileSystemException if an error occurs while establishing a connection.
281      */
282     public static FTPClient createConnection(final String hostname, final int port, final char[] username,
283             final char[] password, final String workingDirectory, final FileSystemOptions fileSystemOptions)
284             throws FileSystemException {
285         final FtpConnectionFactory factory = new FtpConnectionFactory(FtpFileSystemConfigBuilder.getInstance());
286         return factory.createConnection(hostname, port, username, password, workingDirectory, fileSystemOptions);
287     }
288 
289     private FtpClientFactory() {
290     }
291 }