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;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026import org.apache.commons.net.PrintCommandListener;
027import org.apache.commons.net.ftp.FTPClient;
028import org.apache.commons.net.ftp.FTPClientConfig;
029import org.apache.commons.net.ftp.FTPReply;
030import org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory;
031import org.apache.commons.vfs2.FileSystemException;
032import org.apache.commons.vfs2.FileSystemOptions;
033import org.apache.commons.vfs2.util.UserAuthenticatorUtils;
034
035/**
036 * Create a FtpClient instance.
037 */
038public final class FtpClientFactory
039{
040    private FtpClientFactory()
041    {
042    }
043
044    /**
045     * Creates a new connection to the server.
046     *
047     * @param hostname          The host name of the server.
048     * @param port              The port to connect to.
049     * @param username          The name of the user for authentication.
050     * @param password          The user's password.
051     * @param workingDirectory  The base directory.
052     * @param fileSystemOptions The FileSystemOptions.
053     * @return An FTPClient.
054     * @throws FileSystemException if an error occurs while connecting.
055     */
056    public static FTPClient createConnection(final String hostname, final int port, final char[] username, final char[] password,
057                                             final String workingDirectory, final FileSystemOptions fileSystemOptions)
058        throws FileSystemException
059    {
060        final FtpConnectionFactory factory = new FtpConnectionFactory(FtpFileSystemConfigBuilder.getInstance());
061        return factory.createConnection(hostname, port, username, password, workingDirectory, fileSystemOptions);
062    }
063
064    public static class FtpConnectionFactory extends ConnectionFactory<FTPClient, FtpFileSystemConfigBuilder> {
065        private FtpConnectionFactory(final FtpFileSystemConfigBuilder builder)
066        {
067            super(builder);
068        }
069
070        @Override
071        protected FTPClient createClient(final FileSystemOptions fileSystemOptions)
072        {
073            return new FTPClient();
074        }
075
076        @Override
077        protected void setupOpenConnection(final FTPClient client, final FileSystemOptions fileSystemOptions)
078        {
079            // nothing to do for FTP
080        }
081    }
082
083    public static abstract class ConnectionFactory<C extends FTPClient, B extends FtpFileSystemConfigBuilder> {
084        private static final char[] ANON_CHAR_ARRAY = "anonymous".toCharArray();
085        private static final int BUFSZ = 40;
086
087        protected Log log = LogFactory.getLog(getClass());
088        protected B builder;
089
090        protected ConnectionFactory(final B builder)
091        {
092            this.builder = builder;
093        }
094
095        public C createConnection(final String hostname, final int port, char[] username, char[] password,
096            final String workingDirectory, final FileSystemOptions fileSystemOptions) throws FileSystemException
097        {
098            // Determine the username and password to use
099            if (username == null)
100            {
101                username = ANON_CHAR_ARRAY;
102            }
103
104            if (password == null)
105            {
106                password = ANON_CHAR_ARRAY;
107            }
108
109            try
110            {
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                        {
118                            final StringBuffer buffer = getBuffer();
119                            String message = buffer.toString();
120                            if (message.toUpperCase().startsWith("PASS ") && message.length() > 5) {
121                                message = "PASS ***";
122                            }
123                            log.debug(message);
124                            buffer.setLength(0);
125                        }
126                    };
127                    client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(writer)));
128                }
129
130                configureClient(fileSystemOptions, client);
131
132                final FTPFileEntryParserFactory myFactory =
133                    builder.getEntryParserFactory(fileSystemOptions);
134                if (myFactory != null)
135                {
136                    client.setParserFactory(myFactory);
137                }
138
139                try
140                {
141                    // Set connect timeout
142                    final Integer connectTimeout = builder.getConnectTimeout(fileSystemOptions);
143                    if (connectTimeout != null)
144                    {
145                        client.setDefaultTimeout(connectTimeout.intValue());
146                    }
147
148                    final String controlEncoding = builder.getControlEncoding(fileSystemOptions);
149                    if (controlEncoding != null)
150                    {
151                        client.setControlEncoding(controlEncoding);
152                    }
153
154                    client.connect(hostname, port);
155
156                    final int reply = client.getReplyCode();
157                    if (!FTPReply.isPositiveCompletion(reply))
158                    {
159                        throw new FileSystemException("vfs.provider.ftp/connect-rejected.error", hostname);
160                    }
161
162                    // Login
163                    if (!client.login(
164                        UserAuthenticatorUtils.toString(username),
165                        UserAuthenticatorUtils.toString(password)))
166                    {
167                        throw new FileSystemException("vfs.provider.ftp/login.error",
168                            hostname, UserAuthenticatorUtils.toString(username));
169                    }
170
171                    FtpFileType fileType = builder.getFileType(fileSystemOptions);
172                    if (fileType == null)
173                    {
174                        fileType = FtpFileType.BINARY;
175                    }
176                    // Set binary mode
177                    if (!client.setFileType(fileType.getValue()))
178                    {
179                        throw new FileSystemException("vfs.provider.ftp/set-file-type.error", fileType);
180                    }
181
182                    // Set dataTimeout value
183                    final Integer dataTimeout = builder.getDataTimeout(fileSystemOptions);
184                    if (dataTimeout != null)
185                    {
186                        client.setDataTimeout(dataTimeout.intValue());
187                    }
188
189                    final Integer socketTimeout = builder.getSoTimeout(fileSystemOptions);
190                    if (socketTimeout != null)
191                    {
192                        client.setSoTimeout(socketTimeout.intValue());
193                    }
194
195                    // Change to root by default
196                    // All file operations a relative to the filesystem-root
197                    // String root = getRoot().getName().getPath();
198
199                    final Boolean userDirIsRoot = builder.getUserDirIsRoot(fileSystemOptions);
200                    if (workingDirectory != null && (userDirIsRoot == null || !userDirIsRoot.booleanValue()))
201                    {
202                        if (!client.changeWorkingDirectory(workingDirectory))
203                        {
204                            throw new FileSystemException("vfs.provider.ftp/change-work-directory.error", workingDirectory);
205                        }
206                    }
207
208                    final Boolean passiveMode = builder.getPassiveMode(fileSystemOptions);
209                    if (passiveMode != null && passiveMode.booleanValue())
210                    {
211                        client.enterLocalPassiveMode();
212                    }
213
214                    setupOpenConnection(client, fileSystemOptions);
215                }
216                catch (final IOException e)
217                {
218                    if (client.isConnected())
219                    {
220                        client.disconnect();
221                    }
222                    throw e;
223                }
224
225                return client;
226            }
227            catch (final Exception exc)
228            {
229                throw new FileSystemException("vfs.provider.ftp/connect.error", exc, hostname);
230            }
231        }
232
233        protected abstract C createClient(FileSystemOptions fileSystemOptions) throws FileSystemException;
234        protected abstract void setupOpenConnection(C client, FileSystemOptions fileSystemOptions) throws IOException;
235
236        private void configureClient(final FileSystemOptions fileSystemOptions, final C client)
237        {
238            final String key = builder.getEntryParser(fileSystemOptions);
239            if (key != null)
240            {
241                final FTPClientConfig config = new FTPClientConfig(key);
242
243                final String serverLanguageCode =
244                    builder.getServerLanguageCode(fileSystemOptions);
245                if (serverLanguageCode != null)
246                {
247                    config.setServerLanguageCode(serverLanguageCode);
248                }
249                final String defaultDateFormat =
250                    builder.getDefaultDateFormat(fileSystemOptions);
251                if (defaultDateFormat != null)
252                {
253                    config.setDefaultDateFormatStr(defaultDateFormat);
254                }
255                final String recentDateFormat =
256                    builder.getRecentDateFormat(fileSystemOptions);
257                if (recentDateFormat != null)
258                {
259                    config.setRecentDateFormatStr(recentDateFormat);
260                }
261                final String serverTimeZoneId =
262                    builder.getServerTimeZoneId(fileSystemOptions);
263                if (serverTimeZoneId != null)
264                {
265                    config.setServerTimeZoneId(serverTimeZoneId);
266                }
267                final String[] shortMonthNames =
268                    builder.getShortMonthNames(fileSystemOptions);
269                if (shortMonthNames != null)
270                {
271                    final StringBuilder shortMonthNamesStr = new StringBuilder(BUFSZ);
272                    for (final String shortMonthName : shortMonthNames)
273                    {
274                        if (shortMonthNamesStr.length() > 0)
275                        {
276                            shortMonthNamesStr.append("|");
277                        }
278                        shortMonthNamesStr.append(shortMonthName);
279                    }
280                    config.setShortMonthNames(shortMonthNamesStr.toString());
281                }
282
283                client.configure(config);
284            }
285        }
286    }
287}