001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.vfs2.provider.ftp; 018 019import java.io.IOException; 020import java.io.PrintWriter; 021import java.io.StringWriter; 022import java.io.Writer; 023import java.net.Proxy; 024import java.time.Duration; 025 026import org.apache.commons.lang3.Range; 027import org.apache.commons.lang3.StringUtils; 028import org.apache.commons.lang3.time.DurationUtils; 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.apache.commons.net.PrintCommandListener; 032import org.apache.commons.net.ftp.FTPClient; 033import org.apache.commons.net.ftp.FTPClientConfig; 034import org.apache.commons.net.ftp.FTPReply; 035import org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory; 036import org.apache.commons.vfs2.FileSystemException; 037import org.apache.commons.vfs2.FileSystemOptions; 038import org.apache.commons.vfs2.util.UserAuthenticatorUtils; 039 040/** 041 * Creates {@link FtpClient} instances. 042 */ 043public final class FtpClientFactory { 044 045 /** 046 * Abstract Factory, used to configure different FTPClients. 047 * 048 * @param <C> The type of FTPClient. 049 * @param <B> The type of FtpFileSystemConfigBuilder 050 */ 051 public abstract static class ConnectionFactory<C extends FTPClient, B extends FtpFileSystemConfigBuilder> { 052 053 private static final char[] ANON_CHAR_ARRAY = "anonymous".toCharArray(); 054 private static final int BUFSZ = 40; 055 056 /** 057 * My builder. 058 */ 059 protected B builder; 060 061 private final Log log = LogFactory.getLog(getClass()); 062 063 /** 064 * Constructs a new instance. 065 * 066 * @param builder How to build. 067 */ 068 protected ConnectionFactory(final B builder) { 069 this.builder = builder; 070 } 071 072 private void configureClient(final FileSystemOptions fileSystemOptions, final C client) { 073 final String key = builder.getEntryParser(fileSystemOptions); 074 if (key != null) { 075 final FTPClientConfig config = new FTPClientConfig(key); 076 077 final String serverLanguageCode = builder.getServerLanguageCode(fileSystemOptions); 078 if (serverLanguageCode != null) { 079 config.setServerLanguageCode(serverLanguageCode); 080 } 081 final String defaultDateFormat = builder.getDefaultDateFormat(fileSystemOptions); 082 if (defaultDateFormat != null) { 083 config.setDefaultDateFormatStr(defaultDateFormat); 084 } 085 final String recentDateFormat = builder.getRecentDateFormat(fileSystemOptions); 086 if (recentDateFormat != null) { 087 config.setRecentDateFormatStr(recentDateFormat); 088 } 089 final String serverTimeZoneId = builder.getServerTimeZoneId(fileSystemOptions); 090 if (serverTimeZoneId != null) { 091 config.setServerTimeZoneId(serverTimeZoneId); 092 } 093 final String[] shortMonthNames = builder.getShortMonthNames(fileSystemOptions); 094 if (shortMonthNames != null) { 095 final StringBuilder shortMonthNamesStr = new StringBuilder(BUFSZ); 096 for (final String shortMonthName : shortMonthNames) { 097 if (!StringUtils.isEmpty(shortMonthNamesStr)) { 098 shortMonthNamesStr.append("|"); 099 } 100 shortMonthNamesStr.append(shortMonthName); 101 } 102 config.setShortMonthNames(shortMonthNamesStr.toString()); 103 } 104 105 client.configure(config); 106 } 107 } 108 109 /** 110 * Creates a new client. 111 * 112 * @param fileSystemOptions the file system options. 113 * @return a new client. 114 * @throws FileSystemException if a file system error occurs. 115 */ 116 protected abstract C createClient(FileSystemOptions fileSystemOptions) throws FileSystemException; 117 118 /** 119 * Creates a connection. 120 * 121 * @param hostname The host name or IP address. 122 * @param port The host port. 123 * @param username The user name. 124 * @param password The user password. 125 * @param workingDirectory The working directory. 126 * @param fileSystemOptions Options to create the connection. 127 * @return A new connection. 128 * @throws FileSystemException if an error occurs while connecting. 129 */ 130 public C createConnection(final String hostname, final int port, char[] username, char[] password, 131 final String workingDirectory, final FileSystemOptions fileSystemOptions) throws FileSystemException { 132 // Determine the username and password to use 133 if (username == null) { 134 username = ANON_CHAR_ARRAY; 135 } 136 137 if (password == null) { 138 password = ANON_CHAR_ARRAY; 139 } 140 141 try { 142 final C client = createClient(fileSystemOptions); 143 144 if (log.isDebugEnabled()) { 145 final Writer writer = new StringWriter(1024) { 146 @Override 147 public void flush() { 148 final StringBuffer buffer = getBuffer(); 149 String message = buffer.toString(); 150 final String prefix = "PASS "; 151 if (message.toUpperCase().startsWith(prefix) && message.length() > prefix.length()) { 152 message = prefix + "***"; 153 } 154 log.debug(message); 155 buffer.setLength(0); 156 } 157 }; 158 client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(writer))); 159 } 160 161 configureClient(fileSystemOptions, client); 162 163 final FTPFileEntryParserFactory myFactory = builder.getEntryParserFactory(fileSystemOptions); 164 if (myFactory != null) { 165 client.setParserFactory(myFactory); 166 } 167 168 final Boolean remoteVerification = builder.getRemoteVerification(fileSystemOptions); 169 if (remoteVerification != null) { 170 client.setRemoteVerificationEnabled(remoteVerification.booleanValue()); 171 } 172 173 try { 174 final Duration connectTimeout = builder.getConnectTimeoutDuration(fileSystemOptions); 175 if (connectTimeout != null) { 176 client.setDefaultTimeout(DurationUtils.toMillisInt(connectTimeout)); 177 } 178 179 final String controlEncoding = builder.getControlEncoding(fileSystemOptions); 180 if (controlEncoding != null) { 181 client.setControlEncoding(controlEncoding); 182 } 183 184 final Boolean autodetectUTF8 = builder.getAutodetectUtf8(fileSystemOptions); 185 if (autodetectUTF8 != null) { 186 client.setAutodetectUTF8(autodetectUTF8); 187 } 188 189 final Proxy proxy = builder.getProxy(fileSystemOptions); 190 if (proxy != null) { 191 client.setProxy(proxy); 192 } 193 194 client.connect(hostname, port); 195 196 final int reply = client.getReplyCode(); 197 if (!FTPReply.isPositiveCompletion(reply)) { 198 throw new FileSystemException("vfs.provider.ftp/connect-rejected.error", hostname); 199 } 200 201 // Login 202 if (!client.login(UserAuthenticatorUtils.toString(username), 203 UserAuthenticatorUtils.toString(password))) { 204 throw new FileSystemException("vfs.provider.ftp/login.error", hostname, 205 UserAuthenticatorUtils.toString(username)); 206 } 207 208 FtpFileType fileType = builder.getFileType(fileSystemOptions); 209 if (fileType == null) { 210 fileType = FtpFileType.BINARY; 211 } 212 // Set binary mode 213 if (!client.setFileType(fileType.getValue())) { 214 throw new FileSystemException("vfs.provider.ftp/set-file-type.error", fileType); 215 } 216 217 // Set dataTimeout value 218 final Duration dataTimeout = builder.getDataTimeoutDuration(fileSystemOptions); 219 if (dataTimeout != null) { 220 client.setDataTimeout(dataTimeout); 221 } 222 223 final Duration socketTimeout = builder.getSoTimeoutDuration(fileSystemOptions); 224 if (socketTimeout != null) { 225 client.setSoTimeout(DurationUtils.toMillisInt(socketTimeout)); 226 } 227 228 final Duration controlKeepAliveTimeout = builder.getControlKeepAliveTimeout(fileSystemOptions); 229 if (controlKeepAliveTimeout != null) { 230 client.setControlKeepAliveTimeout(controlKeepAliveTimeout); 231 } 232 233 final Duration controlKeepAliveReplyTimeout = builder 234 .getControlKeepAliveReplyTimeout(fileSystemOptions); 235 if (controlKeepAliveReplyTimeout != null) { 236 client.setControlKeepAliveReplyTimeout(controlKeepAliveReplyTimeout); 237 } 238 239 final Boolean userDirIsRoot = builder.getUserDirIsRoot(fileSystemOptions); 240 if (workingDirectory != null && (userDirIsRoot == null || !userDirIsRoot.booleanValue()) 241 && !client.changeWorkingDirectory(workingDirectory)) { 242 throw new FileSystemException("vfs.provider.ftp/change-work-directory.error", workingDirectory); 243 } 244 245 final Boolean passiveMode = builder.getPassiveMode(fileSystemOptions); 246 if (passiveMode != null && passiveMode.booleanValue()) { 247 client.enterLocalPassiveMode(); 248 } 249 250 final Range<Integer> activePortRange = builder.getActivePortRange(fileSystemOptions); 251 if (activePortRange != null) { 252 client.setActivePortRange(activePortRange.getMinimum(), activePortRange.getMaximum()); 253 } 254 255 setupOpenConnection(client, fileSystemOptions); 256 } catch (final IOException e) { 257 if (client.isConnected()) { 258 client.disconnect(); 259 } 260 throw e; 261 } 262 263 return client; 264 } catch (final Exception exc) { 265 throw new FileSystemException("vfs.provider.ftp/connect.error", exc, hostname); 266 } 267 } 268 269 /** 270 * Sets up a new client. 271 * 272 * @param client the client. 273 * @param fileSystemOptions the file system options. 274 * @throws IOException if an IO error occurs. 275 */ 276 protected abstract void setupOpenConnection(C client, FileSystemOptions fileSystemOptions) throws IOException; 277 } 278 279 /** 280 * Connection Factory, used to configure the FTPClient. 281 */ 282 public static final class FtpConnectionFactory extends ConnectionFactory<FTPClient, FtpFileSystemConfigBuilder> { 283 private FtpConnectionFactory(final FtpFileSystemConfigBuilder builder) { 284 super(builder); 285 } 286 287 @Override 288 protected FTPClient createClient(final FileSystemOptions fileSystemOptions) { 289 return new FTPClient(); 290 } 291 292 @Override 293 protected void setupOpenConnection(final FTPClient client, final FileSystemOptions fileSystemOptions) { 294 // nothing to do for FTP 295 } 296 } 297 298 /** 299 * Creates a new connection to the server. 300 * 301 * @param hostname The host name of the server. 302 * @param port The port to connect to. 303 * @param username The name of the user for authentication. 304 * @param password The user's password. 305 * @param workingDirectory The base directory. 306 * @param fileSystemOptions The FileSystemOptions. 307 * @return An FTPClient. 308 * @throws FileSystemException if an error occurs while connecting. 309 */ 310 public static FTPClient createConnection(final String hostname, final int port, final char[] username, 311 final char[] password, final String workingDirectory, final FileSystemOptions fileSystemOptions) 312 throws FileSystemException { 313 final FtpConnectionFactory factory = new FtpConnectionFactory(FtpFileSystemConfigBuilder.getInstance()); 314 return factory.createConnection(hostname, port, username, password, workingDirectory, fileSystemOptions); 315 } 316 317 private FtpClientFactory() { 318 } 319}