package com.cloudhopper.commons.rfs.provider; /* * #%L * ch-commons-rfs * %% * Copyright (C) 2012 - 2013 Cloudhopper by Twitter * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import com.cloudhopper.commons.rfs.*; import com.cloudhopper.commons.util.StringUtil; import java.io.IOException; import java.io.InputStream; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.StringTokenizer; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPReply; import org.apache.commons.net.ftp.FTPSClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * FTP and FTPS remote filesystem. * * @author joelauer */ public class FtpRemoteFileSystem extends BaseRemoteFileSystem { private static final Logger logger = LoggerFactory.getLogger(FtpRemoteFileSystem.class); public enum Mode { PASSIVE, ACTIVE }; // client (will support either FTP or FTPS) private FTPClient ftp; // are we in ssl mode? private boolean ssl; // which mode should we switch to? PASSIVE or ACTIVE? private Mode mode; // should we create the directory path if it doesn't exist? private boolean mkdir; public FtpRemoteFileSystem() { super(); // default ssl mode to false ssl = false; // default mode to passive mode = Mode.PASSIVE; } @Override public void validateURL() throws FileSystemException { // only a hostname needs to be configured if (getURL().getHost() == null) { throw new FileSystemException("The FTP(s) protocol requires a host"); } // "mode" can either be PASSIVE or ACTIVE String tempMode = getURL().getQueryProperty("mode"); if (StringUtil.isEmpty(tempMode)) { mode = Mode.PASSIVE; } else { if (tempMode.equalsIgnoreCase("passive")) { mode = Mode.PASSIVE; } else if (tempMode.equalsIgnoreCase("active")) { mode = Mode.ACTIVE; } else { throw new FileSystemException("Invalid FTP mode parameter value '" + tempMode + "'"); } } logger.info("FTP mode set to " + mode); // "mkdir" can either be true or false String tempMkdir = getURL().getQueryProperty("mkdir"); if (StringUtil.isEmpty(tempMkdir)) { mkdir = false; } else { if (tempMkdir.equalsIgnoreCase("true")) { mkdir = true; } else if (tempMkdir.equalsIgnoreCase("false")) { mkdir = false; } else { throw new FileSystemException("Invalid FTP mkdir parameter value '" + tempMkdir + "'"); } } logger.info("FTP create missing parent directories? " + mkdir); } public void connect() throws FileSystemException { // make sure we don't connect twice if (ftp != null) { throw new FileSystemException("Already connected to FTP(s) server"); } // either create an SSL FTP client or normal one if (getProtocol() == Protocol.FTPS) { try { ftp = new FTPSClient(); } catch (Exception e) { //} catch (NoSuchAlgorithmException e) { throw new FileSystemException("Unable to create FTPS client: " + e.getMessage(), e); } } else { ftp = new FTPClient(); } // // connect to ftp(s) server // try { int reply; // either connect to the default port or an overridden one if (getURL().getPort() == null) { ftp.connect(getURL().getHost()); } else { ftp.connect(getURL().getHost(), getURL().getPort().intValue()); } // After connection attempt, check reply code to verify we're connected reply = ftp.getReplyCode(); if (!FTPReply.isPositiveCompletion(reply)) { // make sure we're definitely disconnected before we throw exception try { ftp.disconnect(); } catch (Exception e) { } ftp = null; throw new FileSystemException("FTP server refused connection (replyCode=" + reply + ")"); } logger.info("Connected to remote FTP server @ " + getURL().getHost() + " (not authenticated yet)"); } catch (IOException e) { if (ftp.isConnected()) { try { ftp.disconnect(); } catch (Exception ex) {} } ftp = null; throw new FileSystemException("Unabled to connect to FTP server @ " + getURL().getHost(), e); } // // login either anonymously or with user/pass combo // try { boolean loggedIn = false; if (getURL().getUsername() == null) { logger.info("Logging in anonymously to FTP server"); loggedIn = ftp.login( "anonymous", "" ); } else { logger.info("Logging in with username and password to FTP server"); loggedIn = ftp.login(getURL().getUsername(), (getURL().getPassword() == null ? "" : getURL().getPassword())); } // did the login work? if (!loggedIn) { throw new FileSystemException("Login failed with FTP server (reply=" + ftp.getReplyString() + ")"); } // // if we're using a secure protocol, encrypt the data channel // if (getProtocol() == Protocol.FTPS) { logger.info("Requesting FTP data channel to also be encrypted with SSL/TLS"); ((FTPSClient)ftp).execPROT("P"); // ignore if this actually worked or not -- file just fail to copy } // // make sure we're using binary files // ftp.setFileType(FTP.BINARY_FILE_TYPE); if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) { throw new FileSystemException("FTP server failed to switch to binary file mode (reply=" + ftp.getReplyString() + ")"); } // should we go into passive or active mode? if (mode == Mode.ACTIVE) { ftp.enterLocalActiveMode(); if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) { throw new FileSystemException("FTP server failed to switch to active mode (reply=" + ftp.getReplyString() + ")"); } } else if (mode == Mode.PASSIVE) { ftp.enterLocalPassiveMode(); if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) { throw new FileSystemException("FTP server failed to switch to passive mode (reply=" + ftp.getReplyString() + ")"); } } // // handle making or changing directories // // if the path is not empty if (!StringUtil.isEmpty(getURL().getPath())) { // recursively iterate thru directory path and attempt to create the path StringTokenizer path = new StringTokenizer(getURL().getPath(), "/"); // create an array list of tokens ArrayList<String> pathParts = new ArrayList<String>(); while (path.hasMoreTokens()) { pathParts.add(path.nextToken()); } // index we'll start searching for int i = 0; // determine path of directories we're going to take if (pathParts.size() > 0 && pathParts.get(i).equals("~")) { // stay in home directory once logged in // just increment what we'll search from i = 1; } else { // change to root directory first if (!ftp.changeWorkingDirectory("/")) { throw new FileSystemException("FTP server failed to change to root directory (reply=" + ftp.getReplyString() + ")"); } } for ( ; i < pathParts.size(); i++) { // try to change to this directory String pathPart = pathParts.get(i); boolean changedDir = ftp.changeWorkingDirectory(pathPart); if (!changedDir) { if (!mkdir) { // now try to change to it again if (!ftp.changeWorkingDirectory(pathPart)) { throw new FileSystemException("Unable to change to directory " + getURL().getPath() + " on FTP server: " + pathPart + " does not exist"); } } else { // try to create it logger.info("Making new directory on FTP server: " + pathPart); if (!ftp.makeDirectory(pathPart)) { throw new FileSystemException("Unable to make directory '" + pathPart + "' on FTP server"); } // now try to change to it again if (!ftp.changeWorkingDirectory(pathPart)) { throw new FileSystemException("Unable to change to new directory '" + pathPart + "' on FTP server"); } } } } // just print out our working directory } else { // staying in whatever directory we were assigned by default // for information purposeds, let's try to print out that dir String currentDir = ftp.printWorkingDirectory(); logger.info("Current FTP working directory: " + currentDir); } } catch (FileSystemException e) { // make sure to disconnect, then rethrow error try { ftp.disconnect(); } catch (Exception ex) { } ftp = null; throw e; } catch (IOException e) { // make sure we're definitely disconnected before we throw exception try { ftp.disconnect(); } catch (Exception ex) { } ftp = null; throw new FileSystemException("Underlying IO exception with FTP server during login and setup process", e); } } public void disconnect() throws FileSystemException { // we can't disconnect twice if (ftp == null) { throw new FileSystemException("Already disconnected from FTP server"); } if (ftp != null) { try { ftp.disconnect(); } catch (Exception e) { logger.warn("", e); } ftp = null; } logger.info("Disconnected to remote FTP server @ " + getURL().getHost()); } public boolean exists(String filename) throws FileSystemException { // we have to be connected if (ftp == null) { throw new FileSystemException("Not yet connected to FTP server"); } try { // check if the file already exists FTPFile[] files = ftp.listFiles(filename); // did this command succeed? if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) { throw new FileSystemException("FTP server failed to get file listing (reply=" + ftp.getReplyString() + ")"); } if (files != null && files.length > 0) { // this file already exists return true; } else { return false; } } catch (IOException e) { throw new FileSystemException("Underlying IO exception with FTP server while checking if file exists", e); } } public void copy(InputStream in, String filename) throws FileSystemException { // does this filename already exist? if (exists(filename)) { throw new FileSystemException("File " + filename + " already exists on FTP server"); } // copy the file try { // this overwrites by default boolean stored = ftp.storeFile(filename, in); if (!stored) { throw new FileSystemException("FTP server failed to store file (reply=" + ftp.getReplyString() + ")"); } } catch (IOException e) { throw new FileSystemException("Failed to copy data during PUT with SFTP server", e); } } }