blob: 6d2e739233b5c936c3599bf351157630c1a6195c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* https://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.
*
*/
package org.apache.tools.ant.taskdefs.optional.net;
import java.io.File;
import java.lang.reflect.Constructor;
import java.util.Locale;
import java.util.Vector;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.EnumeratedAttribute;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.util.LoaderUtils;
import org.apache.tools.ant.util.Retryable;
import org.apache.tools.ant.util.SplitClassLoader;
/**
* Basic FTP client. Performs the following actions:
* <ul>
* <li> <strong>send</strong> - send files to a remote server. This is the
* default action.</li>
* <li> <strong>get</strong> - retrieve files from a remote server.</li>
* <li> <strong>del</strong> - delete files from a remote server.</li>
* <li> <strong>list</strong> - create a file listing.</li>
* <li> <strong>chmod</strong> - change unix file permissions.</li>
* <li> <strong>rmdir</strong> - remove directories, if empty, from a
* remote server.</li>
* </ul>
* <strong>Note:</strong> Some FTP servers - notably the Solaris server - seem
* to hold data ports open after a "retr" operation, allowing them to timeout
* instead of shutting them down cleanly. This happens in active or passive
* mode, and the ports will remain open even after ending the FTP session. FTP
* "send" operations seem to close ports immediately. This behavior may cause
* problems on some systems when downloading large sets of files.
*
* @since Ant 1.3
*/
public class FTPTask extends Task implements FTPTaskConfig {
public static final int SEND_FILES = 0;
public static final int GET_FILES = 1;
public static final int DEL_FILES = 2;
public static final int LIST_FILES = 3;
public static final int MK_DIR = 4;
public static final int CHMOD = 5;
public static final int RM_DIR = 6;
public static final int SITE_CMD = 7;
/** adjust uptodate calculations where server timestamps are HH:mm and client's
* are HH:mm:ss */
private static final long GRANULARITY_MINUTE = 60000L;
/** Default port for FTP */
public static final int DEFAULT_FTP_PORT = 21;
private String remotedir;
private String server;
private String userid;
private String password;
private String account;
private File listing;
private boolean binary = true;
private boolean passive = false;
private boolean verbose = false;
private boolean newerOnly = false;
private long timeDiffMillis = 0;
private long granularityMillis = 0L;
private boolean timeDiffAuto = false;
private int action = SEND_FILES;
private Vector<FileSet> filesets = new Vector<>();
private String remoteFileSep = "/";
private int port = DEFAULT_FTP_PORT;
private boolean skipFailedTransfers = false;
private boolean ignoreNoncriticalErrors = false;
private boolean preserveLastModified = false;
private String chmod = null;
private String umask = null;
private FTPSystemType systemTypeKey = FTPSystemType.getDefault();
private String defaultDateFormatConfig = null;
private String recentDateFormatConfig = null;
private String serverLanguageCodeConfig = null;
private String serverTimeZoneConfig = null;
private String shortMonthNamesConfig = null;
private Granularity timestampGranularity = Granularity.getDefault();
private boolean isConfigurationSet = false;
private int retriesAllowed = 0;
private String siteCommand = null;
private String initialSiteCommand = null;
private boolean enableRemoteVerification = true;
private Path classpath;
private ClassLoader mirrorLoader;
private FTPTaskMirror delegate = null;
public static final String[] ACTION_STRS = { //NOSONAR
"sending",
"getting",
"deleting",
"listing",
"making directory",
"chmod",
"removing",
"site"
};
public static final String[] COMPLETED_ACTION_STRS = { //NOSONAR
"sent",
"retrieved",
"deleted",
"listed",
"created directory",
"mode changed",
"removed",
"site command executed"
};
public static final String[] ACTION_TARGET_STRS = { //NOSONAR
"files",
"files",
"files",
"files",
"directory",
"files",
"directories",
"site command"
};
/**
* Sets the remote directory where files will be placed. This may be a
* relative or absolute path, and must be in the path syntax expected by
* the remote server. No correction of path syntax will be performed.
*
* @param dir the remote directory name.
*/
public void setRemotedir(String dir) {
this.remotedir = dir;
}
public String getRemotedir() {
return remotedir;
}
/**
* Sets the FTP server to send files to.
*
* @param server the remote server name.
*/
public void setServer(String server) {
this.server = server;
}
public String getServer() {
return server;
}
/**
* Sets the FTP port used by the remote server.
*
* @param port the port on which the remote server is listening.
*/
public void setPort(int port) {
this.port = port;
}
public int getPort() {
return port;
}
/**
* Sets the login user id to use on the specified server.
*
* @param userid remote system userid.
*/
public void setUserid(String userid) {
this.userid = userid;
}
public String getUserid() {
return userid;
}
/**
* Sets the login password for the given user id.
*
* @param password the password on the remote system.
*/
public void setPassword(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
/**
* Sets the login account to use on the specified server.
*
* @param pAccount the account name on remote system
* @since Ant 1.7
*/
public void setAccount(String pAccount) {
this.account = pAccount;
}
public String getAccount() {
return account;
}
/**
* If true, uses binary mode, otherwise text mode (default is binary).
*
* @param binary if true use binary mode in transfers.
*/
public void setBinary(boolean binary) {
this.binary = binary;
}
public boolean isBinary() {
return binary;
}
/**
* Specifies whether to use passive mode. Set to true if you are behind a
* firewall and cannot connect without it. Passive mode is disabled by
* default.
*
* @param passive true is passive mode should be used.
*/
public void setPassive(boolean passive) {
this.passive = passive;
}
public boolean isPassive() {
return passive;
}
/**
* Set to true to receive notification about each file as it is
* transferred.
*
* @param verbose true if verbose notifications are required.
*/
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
public boolean isVerbose() {
return verbose;
}
/**
* A synonym for <code>depends</code>. Set to true to transmit only new
* or changed files.
*
* See the related attributes timediffmillis and timediffauto.
*
* @param newer if true only transfer newer files.
*/
public void setNewer(boolean newer) {
this.newerOnly = newer;
}
public boolean isNewer() {
return newerOnly;
}
/**
* number of milliseconds to add to the time on the remote machine
* to get the time on the local machine.
*
* use in conjunction with <code>newer</code>
*
* @param timeDiffMillis number of milliseconds
*
* @since ant 1.6
*/
public void setTimeDiffMillis(long timeDiffMillis) {
this.timeDiffMillis = timeDiffMillis;
}
public long getTimeDiffMillis() {
return timeDiffMillis;
}
/**
* &quot;true&quot; to find out automatically the time difference
* between local and remote machine.
*
* This requires right to create
* and delete a temporary file in the remote directory.
*
* @param timeDiffAuto true = find automatically the time diff
*
* @since ant 1.6
*/
public void setTimeDiffAuto(boolean timeDiffAuto) {
this.timeDiffAuto = timeDiffAuto;
}
public boolean isTimeDiffAuto() {
return timeDiffAuto;
}
/**
* Set to true to preserve modification times for "gotten" files.
*
* @param preserveLastModified if true preserver modification times.
*/
public void setPreserveLastModified(boolean preserveLastModified) {
this.preserveLastModified = preserveLastModified;
}
public boolean isPreserveLastModified() {
return preserveLastModified;
}
/**
* Set to true to transmit only files that are new or changed from their
* remote counterparts. The default is to transmit all files.
*
* @param depends if true only transfer newer files.
*/
public void setDepends(boolean depends) {
this.newerOnly = depends;
}
/**
* Sets the remote file separator character. This normally defaults to the
* Unix standard forward slash, but can be manually overridden using this
* call if the remote server requires some other separator. Only the first
* character of the string is used.
*
* @param separator the file separator on the remote system.
*/
public void setSeparator(String separator) {
remoteFileSep = separator;
}
public String getSeparator() {
return remoteFileSep;
}
/**
* Sets the file permission mode (Unix only) for files sent to the
* server.
*
* @param theMode unix style file mode for the files sent to the remote
* system.
*/
public void setChmod(String theMode) {
this.chmod = theMode;
}
public String getChmod() {
return chmod;
}
/**
* Sets the default mask for file creation on a unix server.
*
* @param theUmask unix style umask for files created on the remote server.
*/
public void setUmask(String theUmask) {
this.umask = theUmask;
}
public String getUmask() {
return umask;
}
/**
* A set of files to upload or download
*
* @param set the set of files to be added to the list of files to be
* transferred.
*/
public void addFileset(FileSet set) {
filesets.addElement(set);
}
public Vector<FileSet> getFilesets() {
return filesets;
}
/**
* Sets the FTP action to be taken. Currently accepts "put", "get", "del",
* "mkdir", "chmod", "list", and "site".
*
* @deprecated since 1.5.x.
* setAction(String) is deprecated and is replaced with
* setAction(FTP.Action) to make Ant's Introspection mechanism do the
* work and also to encapsulate operations on the type in its own
* class.
* @ant.attribute ignore="true"
*
* @param action the FTP action to be performed.
*
* @throws BuildException if the action is not a valid action.
*/
@Deprecated
public void setAction(String action) throws BuildException {
log("DEPRECATED - The setAction(String) method has been deprecated."
+ " Use setAction(FTP.Action) instead.");
Action a = new Action();
a.setValue(action);
this.action = a.getAction();
}
/**
* Sets the FTP action to be taken. Currently accepts "put", "get", "del",
* "mkdir", "chmod", "list", and "site".
*
* @param action the FTP action to be performed.
*
* @throws BuildException if the action is not a valid action.
*/
public void setAction(Action action) throws BuildException {
this.action = action.getAction();
}
public int getAction() {
return this.action;
}
/**
* The output file for the "list" action. This attribute is ignored for
* any other actions.
*
* @param listing file in which to store the listing.
*/
public void setListing(File listing) {
this.listing = listing;
}
public File getListing() {
return listing;
}
/**
* If true, enables unsuccessful file put, delete and get
* operations to be skipped with a warning and the remainder
* of the files still transferred.
*
* @param skipFailedTransfers true if failures in transfers are ignored.
*/
public void setSkipFailedTransfers(boolean skipFailedTransfers) {
this.skipFailedTransfers = skipFailedTransfers;
}
public boolean isSkipFailedTransfers() {
return skipFailedTransfers;
}
/**
* set the flag to skip errors on directory creation.
* (and maybe later other server specific errors)
*
* @param ignoreNoncriticalErrors true if non-critical errors should not
* cause a failure.
*/
public void setIgnoreNoncriticalErrors(boolean ignoreNoncriticalErrors) {
this.ignoreNoncriticalErrors = ignoreNoncriticalErrors;
}
public boolean isIgnoreNoncriticalErrors() {
return ignoreNoncriticalErrors;
}
private void configurationHasBeenSet() {
this.isConfigurationSet = true;
}
public boolean isConfigurationSet() {
return this.isConfigurationSet;
}
/**
* Sets the systemTypeKey attribute.
* Method for setting <code>FTPClientConfig</code> remote system key.
*
* @param systemKey the key to be set - BUT if blank
* the default value of null (which signifies "autodetect") will be kept.
* @see org.apache.commons.net.ftp.FTPClientConfig
*/
public void setSystemTypeKey(FTPSystemType systemKey) {
if (systemKey != null && !systemKey.getValue().isEmpty()) {
this.systemTypeKey = systemKey;
configurationHasBeenSet();
}
}
/**
* Sets the defaultDateFormatConfig attribute.
* @param defaultDateFormat configuration to be set, unless it is
* null or empty string, in which case ignored.
* @see org.apache.commons.net.ftp.FTPClientConfig
*/
public void setDefaultDateFormatConfig(String defaultDateFormat) {
if (defaultDateFormat != null && !defaultDateFormat.isEmpty()) {
this.defaultDateFormatConfig = defaultDateFormat;
configurationHasBeenSet();
}
}
/**
* Sets the recentDateFormatConfig attribute.
* @param recentDateFormat configuration to be set, unless it is
* null or empty string, in which case ignored.
* @see org.apache.commons.net.ftp.FTPClientConfig
*/
public void setRecentDateFormatConfig(String recentDateFormat) {
if (recentDateFormat != null && !recentDateFormat.isEmpty()) {
this.recentDateFormatConfig = recentDateFormat;
configurationHasBeenSet();
}
}
/**
* Sets the serverLanguageCode attribute.
* @param serverLanguageCode configuration to be set, unless it is
* null or empty string, in which case ignored.
* @see org.apache.commons.net.ftp.FTPClientConfig
*/
public void setServerLanguageCodeConfig(String serverLanguageCode) {
if (serverLanguageCode != null && !serverLanguageCode.isEmpty()) {
this.serverLanguageCodeConfig = serverLanguageCode;
configurationHasBeenSet();
}
}
/**
* Sets the serverTimeZoneConfig attribute.
* @param serverTimeZoneId configuration to be set, unless it is
* null or empty string, in which case ignored.
* @see org.apache.commons.net.ftp.FTPClientConfig
*/
public void setServerTimeZoneConfig(String serverTimeZoneId) {
if (serverTimeZoneId != null && !serverTimeZoneId.isEmpty()) {
this.serverTimeZoneConfig = serverTimeZoneId;
configurationHasBeenSet();
}
}
/**
* Sets the shortMonthNamesConfig attribute
*
* @param shortMonthNames configuration to be set, unless it is
* null or empty string, in which case ignored.
* @see org.apache.commons.net.ftp.FTPClientConfig
*/
public void setShortMonthNamesConfig(String shortMonthNames) {
if (shortMonthNames != null && !shortMonthNames.isEmpty()) {
this.shortMonthNamesConfig = shortMonthNames;
configurationHasBeenSet();
}
}
/**
* Defines how many times to retry executing FTP command before giving up.
* Default is 0 - try once and if failure then give up.
*
* @param retriesAllowed number of retries to allow. -1 means
* keep trying forever. "forever" may also be specified as a
* synonym for -1.
*/
public void setRetriesAllowed(String retriesAllowed) {
if ("FOREVER".equalsIgnoreCase(retriesAllowed)) {
this.retriesAllowed = Retryable.RETRY_FOREVER;
} else {
try {
int retries = Integer.parseInt(retriesAllowed);
if (retries < Retryable.RETRY_FOREVER) {
throw new BuildException(
"Invalid value for retriesAllowed attribute: %s",
retriesAllowed);
}
this.retriesAllowed = retries;
} catch (NumberFormatException px) {
throw new BuildException(
"Invalid value for retriesAllowed attribute: %s",
retriesAllowed);
}
}
}
public int getRetriesAllowed() {
return retriesAllowed;
}
/**
* @return Returns the systemTypeKey.
*/
@Override
public String getSystemTypeKey() {
return systemTypeKey.getValue();
}
/**
* @return Returns the defaultDateFormatConfig.
*/
@Override
public String getDefaultDateFormatConfig() {
return defaultDateFormatConfig;
}
/**
* @return Returns the recentDateFormatConfig.
*/
@Override
public String getRecentDateFormatConfig() {
return recentDateFormatConfig;
}
/**
* @return Returns the serverLanguageCodeConfig.
*/
@Override
public String getServerLanguageCodeConfig() {
return serverLanguageCodeConfig;
}
/**
* @return Returns the serverTimeZoneConfig.
*/
@Override
public String getServerTimeZoneConfig() {
return serverTimeZoneConfig;
}
/**
* @return Returns the shortMonthNamesConfig.
*/
@Override
public String getShortMonthNamesConfig() {
return shortMonthNamesConfig;
}
/**
* @return Returns the timestampGranularity.
*/
public Granularity getTimestampGranularity() {
return timestampGranularity;
}
/**
* Sets the timestampGranularity attribute
* @param timestampGranularity The timestampGranularity to set.
*/
public void setTimestampGranularity(Granularity timestampGranularity) {
if (null == timestampGranularity || timestampGranularity.getValue().isEmpty()) {
return;
}
this.timestampGranularity = timestampGranularity;
}
/**
* Sets the siteCommand attribute. This attribute
* names the command that will be executed if the action
* is "site".
* @param siteCommand The siteCommand to set.
*/
public void setSiteCommand(String siteCommand) {
this.siteCommand = siteCommand;
}
public String getSiteCommand() {
return siteCommand;
}
/**
* Sets the initialSiteCommand attribute. This attribute
* names a site command that will be executed immediately
* after connection.
* @param initialCommand The initialSiteCommand to set.
*/
public void setInitialSiteCommand(String initialCommand) {
this.initialSiteCommand = initialCommand;
}
public String getInitialSiteCommand() {
return initialSiteCommand;
}
public long getGranularityMillis() {
return this.granularityMillis;
}
public void setGranularityMillis(long granularity) {
this.granularityMillis = granularity;
}
/**
* Whether to verify that data and control connections are
* connected to the same remote host.
*
* @param b boolean
* @since Ant 1.8.0
*/
public void setEnableRemoteVerification(boolean b) {
enableRemoteVerification = b;
}
public boolean getEnableRemoteVerification() {
return enableRemoteVerification;
}
/**
* Checks to see that all required parameters are set.
*
* @throws BuildException if the configuration is not valid.
*/
protected void checkAttributes() throws BuildException {
if (server == null) {
throw new BuildException("server attribute must be set!");
}
if (userid == null) {
throw new BuildException("userid attribute must be set!");
}
if (password == null) {
throw new BuildException("password attribute must be set!");
}
if (action == LIST_FILES && listing == null) {
throw new BuildException(
"listing attribute must be set for list action!");
}
if (action == MK_DIR && remotedir == null) {
throw new BuildException(
"remotedir attribute must be set for mkdir action!");
}
if (action == CHMOD && chmod == null) {
throw new BuildException(
"chmod attribute must be set for chmod action!");
}
if (action == SITE_CMD && siteCommand == null) {
throw new BuildException(
"sitecommand attribute must be set for site action!");
}
if (this.isConfigurationSet) {
try {
Class.forName("org.apache.commons.net.ftp.FTPClientConfig");
} catch (ClassNotFoundException e) {
throw new BuildException(
"commons-net.jar >= 1.4.0 is required for at least one of the attributes specified.");
}
}
}
/**
* Runs the task.
*
* @throws BuildException if the task fails or is not configured
* correctly.
*/
@Override
public void execute() throws BuildException {
checkAttributes();
try {
setupFTPDelegate();
delegate.doFTP();
} finally {
if (mirrorLoader instanceof SplitClassLoader) {
((SplitClassLoader) mirrorLoader).cleanup();
}
mirrorLoader = null;
}
}
public Path createClasspath() {
if (classpath == null) {
classpath = new Path(getProject());
}
return classpath;
}
protected void setupFTPDelegate() {
ClassLoader myLoader = FTPTask.class.getClassLoader();
if (mustSplit()) {
mirrorLoader =
new SplitClassLoader(myLoader, classpath, getProject(),
new String[] {
"FTPTaskMirrorImpl",
"FTPConfigurator"
});
} else {
mirrorLoader = myLoader;
}
delegate = createMirror(this, mirrorLoader);
}
private static boolean mustSplit() {
return LoaderUtils.getResourceSource(FTPTask.class.getClassLoader(),
"/org/apache/commons/net/"
+ "ftp/FTP.class")
== null;
}
private static FTPTaskMirror createMirror(FTPTask task,
ClassLoader loader) {
try {
loader.loadClass("org.apache.commons.net.ftp.FTP"); // sanity check
} catch (ClassNotFoundException e) {
throw new BuildException(
"The <classpath> for <ftp> must include commons-net.jar if not in Ant's own classpath",
e, task.getLocation());
}
try {
Class<? extends FTPTaskMirror> c =
loader.loadClass(FTPTaskMirror.class.getName() + "Impl")
.asSubclass(FTPTaskMirror.class);
if (c.getClassLoader() != loader) {
throw new BuildException("Overdelegating loader",
task.getLocation());
}
Constructor<? extends FTPTaskMirror> cons =
c.getConstructor(FTPTask.class);
return cons.newInstance(task);
} catch (Exception e) {
throw new BuildException(e, task.getLocation());
}
}
/**
* an action to perform, one of
* "send", "put", "recv", "get", "del", "delete", "list", "mkdir", "chmod",
* "rmdir"
*/
public static class Action extends EnumeratedAttribute {
private static final String[] VALID_ACTIONS = {
"send", "put", "recv", "get", "del", "delete", "list", "mkdir",
"chmod", "rmdir", "site"
};
/**
* Get the valid values
*
* @return an array of the valid FTP actions.
*/
@Override
public String[] getValues() {
return VALID_ACTIONS;
}
/**
* Get the symbolic equivalent of the action value.
*
* @return the SYMBOL representing the given action.
*/
public int getAction() {
String actionL = getValue().toLowerCase(Locale.ENGLISH);
switch (actionL) {
case "send":
case "put":
return SEND_FILES;
case "recv":
case "get":
return GET_FILES;
case "del":
case "delete":
return DEL_FILES;
case "list":
return LIST_FILES;
case "chmod":
return CHMOD;
case "mkdir":
return MK_DIR;
case "rmdir":
return RM_DIR;
case "site":
return SITE_CMD;
}
return SEND_FILES;
}
}
/**
* represents one of the valid timestamp adjustment values
* recognized by the <code>timestampGranularity</code> attribute.<p>
* A timestamp adjustment may be used in file transfers for checking
* uptodateness. MINUTE means to add one minute to the server
* timestamp. This is done because FTP servers typically list
* timestamps HH:mm and client FileSystems typically use HH:mm:ss.
*
* The default is to use MINUTE for PUT actions and NONE for GET
* actions, since GETs have the <code>preserveLastModified</code>
* option, which takes care of the problem in most use cases where
* this level of granularity is an issue.
*
*/
public static class Granularity extends EnumeratedAttribute {
private static final String[] VALID_GRANULARITIES = {
"", "MINUTE", "NONE"
};
/**
* Get the valid values.
* @return the list of valid Granularity values
*/
@Override
public String[] getValues() {
return VALID_GRANULARITIES;
}
/**
* returns the number of milliseconds associated with
* the attribute, which can vary in some cases depending
* on the value of the action parameter.
* @param action SEND_FILES or GET_FILES
* @return the number of milliseconds associated with
* the attribute, in the context of the supplied action
*/
public long getMilliseconds(int action) {
String granularityU = getValue().toUpperCase(Locale.ENGLISH);
if (granularityU.isEmpty()) {
if (action == SEND_FILES) {
return GRANULARITY_MINUTE;
}
} else if ("MINUTE".equals(granularityU)) {
return GRANULARITY_MINUTE;
}
return 0L;
}
static final Granularity getDefault() {
Granularity g = new Granularity();
g.setValue("");
return g;
}
}
/**
* one of the valid system type keys recognized by the systemTypeKey
* attribute.
*/
public static class FTPSystemType extends EnumeratedAttribute {
private static final String[] VALID_SYSTEM_TYPES = {
"", "UNIX", "VMS", "WINDOWS", "OS/2", "OS/400",
"MVS"
};
/**
* Get the valid values.
* @return the list of valid system types.
*/
@Override
public String[] getValues() {
return VALID_SYSTEM_TYPES;
}
static final FTPSystemType getDefault() {
FTPSystemType ftpst = new FTPSystemType();
ftpst.setValue("");
return ftpst;
}
}
}