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