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 final String serverLanguageCode = builder.getServerLanguageCode(fileSystemOptions); 077 if (serverLanguageCode != null) { 078 config.setServerLanguageCode(serverLanguageCode); 079 } 080 final String defaultDateFormat = builder.getDefaultDateFormat(fileSystemOptions); 081 if (defaultDateFormat != null) { 082 config.setDefaultDateFormatStr(defaultDateFormat); 083 } 084 final String recentDateFormat = builder.getRecentDateFormat(fileSystemOptions); 085 if (recentDateFormat != null) { 086 config.setRecentDateFormatStr(recentDateFormat); 087 } 088 final String serverTimeZoneId = builder.getServerTimeZoneId(fileSystemOptions); 089 if (serverTimeZoneId != null) { 090 config.setServerTimeZoneId(serverTimeZoneId); 091 } 092 final String[] shortMonthNames = builder.getShortMonthNames(fileSystemOptions); 093 if (shortMonthNames != null) { 094 final StringBuilder shortMonthNamesStr = new StringBuilder(BUFSZ); 095 for (final String shortMonthName : shortMonthNames) { 096 if (!StringUtils.isEmpty(shortMonthNamesStr)) { 097 shortMonthNamesStr.append("|"); 098 } 099 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}