AbstractExecutor.java
/*
* This file is part of Waarp Project (named also Waarp or GG).
*
* Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
* tags. See the COPYRIGHT.txt in the distribution for a full listing of
* individual contributors.
*
* All Waarp Project is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Waarp . If not, see <http://www.gnu.org/licenses/>.
*/
package org.waarp.gateway.ftp.exec;
import org.waarp.common.command.exception.CommandAbstractException;
import org.waarp.common.future.WaarpFuture;
import org.waarp.common.guid.GUID;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.common.utility.WaarpStringUtils;
import org.waarp.gateway.kernel.session.CommandExecutorInterface;
import org.waarp.gateway.kernel.session.HttpAuthInterface;
import java.util.regex.Pattern;
/**
* Abstract Executor class. If the command starts with "REFUSED", the command
* will be refused for execution.
* If "REFUSED" is set, the command "RETR" or "STOR" like operations will be
* stopped at starting of
* command.<br>
* If the command starts with "EXECUTE", the following will be a command to be
* executed.<br>
* If the command starts with "JAVAEXECUTE", the following will be a command
* through Java class to be
* executed.<br>
* If the command starts with "R66PREPARETRANSFER", the following will be a r66
* prepare transfer execution
* (asynchronous operation only).<br>
* <p>
* <p>
* The following replacement are done dynamically before the command is
* executed:<br>
* - #BASEPATH# is replaced by the full path for the root of FTP Directory<br>
* - #FILE# is replaced by the current file path relative to FTP Directory (so
* #BASEPATH##FILE# is the full
* path of the file)<br>
* - #USER# is replaced by the username<br>
* - #ACCOUNT# is replaced by the account<br>
* - #COMMAND# is replaced by the command issued for the file<br>
* - #SPECIALID# is replaced by the FTP id of the transfer (whatever in or
* out)<br>
* - #UUID# is replaced by a special UUID globally unique for the transfer, in
* general to be placed in -info
* part (for instance ##UUID## giving #uuid#)<br>
*/
public abstract class AbstractExecutor {
/**
* Internal Logger
*/
private static final WaarpLogger logger =
WaarpLoggerFactory.getLogger(AbstractExecutor.class);
protected static final Pattern BLANK = WaarpStringUtils.BLANK;
protected static final String USER = "#USER#";
protected static final String ACCOUNT = "#ACCOUNT#";
protected static final String BASEPATH = "#BASEPATH#";
protected static final String FILE = "#FILE#";
protected static final String COMMAND = "#COMMAND#";
protected static final String SPECIALID = "#SPECIALID#";
protected static final String S_UUID = "#UUID#";
protected static final String REFUSED = "REFUSED";
protected static final String NONE = "NONE";
protected static final String EXECUTE = "EXECUTE";
protected static final String JAVAEXECUTE = "JAVAEXECUTE";
protected static final String R66PREPARETRANSFER = "R66PREPARETRANSFER";
protected static final int T_REFUSED = -1;
protected static final int T_NONE = 0;
protected static final int T_EXECUTE = 1;
protected static final int T_R_66_PREPARETRANSFER = 2;
protected static final int T_JAVAEXECUTE = 3;
protected static CommandExecutor commandExecutor;
/**
* For OpenR66 access
*/
public static boolean useDatabase;
/**
* Local Exec Daemon is used or not for execution of external commands
*/
public static boolean useLocalExec;
public static class CommandExecutor implements CommandExecutorInterface {
/**
* Retrieve External Command
*/
public final String pretrCMD;
public final int pretrType;
private boolean pretrRefused;
/**
* Retrieve Delay (0 = unlimited)
*/
private long pretrDelay;
/**
* Store External Command
*/
public final String pstorCMD;
public final int pstorType;
private boolean pstorRefused;
/**
* Store Delay (0 = unlimited)
*/
private long pstorDelay;
/**
* @param retrieve
* @param retrDelay
* @param store
* @param storDelay
*/
public CommandExecutor(final String retrieve, final long retrDelay,
final String store, final long storDelay) {
if (retrieve == null || retrieve.trim().length() == 0) {
pretrCMD = commandExecutor.pretrCMD;
pretrType = commandExecutor.pretrType;
setPretrRefused(commandExecutor.isPretrRefused());
} else if (isRefused(retrieve)) {
pretrCMD = REFUSED;
pretrType = T_REFUSED;
setPretrRefused(true);
} else {
if (isExecute(retrieve)) {
pretrCMD = getExecuteCmd(retrieve);
pretrType = T_EXECUTE;
} else if (isR66PrepareTransfer(retrieve)) {
pretrCMD = getR66PrepareTransferCmd(retrieve);
pretrType = T_R_66_PREPARETRANSFER;
useDatabase = true;
} else if (isJavaExecute(retrieve)) {
pretrCMD = getJavaExecuteCmd(retrieve);
pretrType = T_JAVAEXECUTE;
} else {
// Default NONE
pretrCMD = getNone(retrieve);
pretrType = T_NONE;
}
}
setPretrDelay(retrDelay);
if (store == null || store.trim().length() == 0) {
pstorCMD = commandExecutor.pstorCMD;
setPstorRefused(commandExecutor.isPstorRefused());
pstorType = commandExecutor.pstorType;
} else if (isRefused(store)) {
pstorCMD = REFUSED;
setPstorRefused(true);
pstorType = T_REFUSED;
} else {
if (isExecute(store)) {
pstorCMD = getExecuteCmd(store);
pstorType = T_EXECUTE;
} else if (isR66PrepareTransfer(store)) {
pstorCMD = getR66PrepareTransferCmd(store);
pstorType = T_R_66_PREPARETRANSFER;
useDatabase = true;
} else if (isJavaExecute(store)) {
pstorCMD = getJavaExecuteCmd(store);
pstorType = T_JAVAEXECUTE;
} else {
// Default NONE
pstorCMD = getNone(store);
pstorType = T_NONE;
}
}
setPstorDelay(storDelay);
}
private static String getNone(final String cmd) {
return cmd.substring(NONE.length()).trim();
}
private static String getExecuteCmd(final String cmd) {
return cmd.substring(EXECUTE.length()).trim();
}
private static String getJavaExecuteCmd(final String cmd) {
return cmd.substring(JAVAEXECUTE.length()).trim();
}
private static String getR66PrepareTransferCmd(final String cmd) {
return cmd.substring(R66PREPARETRANSFER.length()).trim();
}
private static boolean isRefused(final String cmd) {
return cmd.startsWith(REFUSED);
}
private static boolean isExecute(final String cmd) {
return cmd.startsWith(EXECUTE);
}
private static boolean isJavaExecute(final String cmd) {
return cmd.startsWith(JAVAEXECUTE);
}
private static boolean isR66PrepareTransfer(final String cmd) {
return cmd.startsWith(R66PREPARETRANSFER);
}
@Override
public final boolean isValidOperation(final boolean isStore) {
if (isStore && isPstorRefused()) {
logger.info("STORe like operations REFUSED");
return false;
} else if (!isStore && isPretrRefused()) {
logger.info("RETRieve operations REFUSED");
return false;
}
return true;
}
@Override
public final String getRetrType() {
switch (pretrType) {
case T_REFUSED:
return REFUSED;
case T_EXECUTE:
return EXECUTE;
case T_R_66_PREPARETRANSFER:
return R66PREPARETRANSFER;
case T_JAVAEXECUTE:
return JAVAEXECUTE;
default:
return NONE;
}
}
@Override
public final String getStorType() {
switch (pstorType) {
case T_REFUSED:
return REFUSED;
case T_EXECUTE:
return EXECUTE;
case T_R_66_PREPARETRANSFER:
return R66PREPARETRANSFER;
case T_JAVAEXECUTE:
return JAVAEXECUTE;
default:
return NONE;
}
}
/**
* @return the pretrRefused
*/
public final boolean isPretrRefused() {
return pretrRefused;
}
/**
* @param pretrRefused the pretrRefused to set
*/
public final void setPretrRefused(final boolean pretrRefused) {
this.pretrRefused = pretrRefused;
}
/**
* @return the pretrDelay
*/
public final long getPretrDelay() {
return pretrDelay;
}
/**
* @param pretrDelay the pretrDelay to set
*/
public final void setPretrDelay(final long pretrDelay) {
this.pretrDelay = pretrDelay;
}
/**
* @return the pstorRefused
*/
public final boolean isPstorRefused() {
return pstorRefused;
}
/**
* @param pstorRefused the pstorRefused to set
*/
public final void setPstorRefused(final boolean pstorRefused) {
this.pstorRefused = pstorRefused;
}
/**
* @return the pstorDelay
*/
public final long getPstorDelay() {
return pstorDelay;
}
/**
* @param pstorDelay the pstorDelay to set
*/
public final void setPstorDelay(final long pstorDelay) {
this.pstorDelay = pstorDelay;
}
}
/**
* Initialize the Executor with the correct command and delay
*
* @param retrieve
* @param retrDelay
* @param store
* @param storDelay
*/
public static void initializeExecutor(final String retrieve,
final long retrDelay,
final String store,
final long storDelay) {
commandExecutor =
new CommandExecutor(retrieve, retrDelay, store, storDelay);
if (logger.isInfoEnabled()) {
logger.info(
"Executor configured as [RETR: " + commandExecutor.getRetrType() +
':' + commandExecutor.pretrCMD + ':' +
commandExecutor.getPretrDelay() + ':' +
commandExecutor.isPretrRefused() + "] [STOR: " +
commandExecutor.getStorType() + ':' + commandExecutor.pstorCMD + ':' +
commandExecutor.getPstorDelay() + ':' +
commandExecutor.isPstorRefused() + ']');
}
}
/**
* Check if the given operation is allowed Globally
*
* @param isStore
*
* @return True if allowed, else False
*/
public static boolean isValidOperation(final boolean isStore) {
return commandExecutor.isValidOperation(isStore);
}
/**
* @param auth the current Authentication
* @param args containing in that order "User Account BaseDir
* FilePath(relative to BaseDir)
* Command"
* @param isStore True for a STORE like operation, else False
* @param futureCompletion
*/
public static AbstractExecutor createAbstractExecutor(
final HttpAuthInterface auth, final String[] args, final boolean isStore,
final WaarpFuture futureCompletion) {
if (isStore) {
CommandExecutor executor = (CommandExecutor) auth.getCommandExecutor();
if (executor == null) {
executor = commandExecutor;
} else if (executor.pstorType == T_NONE) {
final String replaced = getPreparedCommand(executor.pstorCMD, args);
return new NoTaskExecutor(replaced, executor.getPstorDelay(),
futureCompletion);
}
if (executor.isPstorRefused()) {
logger.error("STORe like operation REFUSED");
futureCompletion.cancel();
return null;
}
final String replaced = getPreparedCommand(executor.pstorCMD, args);
switch (executor.pstorType) {
case T_REFUSED:
logger.error("STORe like operation REFUSED");
futureCompletion.cancel();
return null;
case T_EXECUTE:
return new ExecuteExecutor(replaced, executor.getPstorDelay(),
futureCompletion);
case T_JAVAEXECUTE:
return new JavaExecutor(replaced, executor.getPstorDelay(),
futureCompletion);
case T_R_66_PREPARETRANSFER:
return new R66PreparedTransferExecutor(replaced,
executor.getPstorDelay(),
futureCompletion);
default:
return new NoTaskExecutor(replaced, executor.getPstorDelay(),
futureCompletion);
}
} else {
CommandExecutor executor = (CommandExecutor) auth.getCommandExecutor();
if (executor == null) {
executor = commandExecutor;
} else if (executor.pretrType == T_NONE) {
final String replaced = getPreparedCommand(executor.pretrCMD, args);
return new NoTaskExecutor(replaced, executor.getPretrDelay(),
futureCompletion);
}
if (executor.isPretrRefused()) {
logger.error("RETRieve operation REFUSED");
futureCompletion.cancel();
return null;
}
final String replaced = getPreparedCommand(executor.pretrCMD, args);
switch (executor.pretrType) {
case T_REFUSED:
logger.error("RETRieve operation REFUSED");
futureCompletion.cancel();
return null;
case T_EXECUTE:
return new ExecuteExecutor(replaced, executor.getPretrDelay(),
futureCompletion);
case T_JAVAEXECUTE:
return new JavaExecutor(replaced, executor.getPretrDelay(),
futureCompletion);
case T_R_66_PREPARETRANSFER:
return new R66PreparedTransferExecutor(replaced,
executor.getPretrDelay(),
futureCompletion);
default:
return new NoTaskExecutor(replaced, executor.getPretrDelay(),
futureCompletion);
}
}
}
/**
* @param command
* @param args as {User, Account, BaseDir, FilePath(relative to
* BaseDir),
* Command}
*
* @return the prepared command
*/
public static String getPreparedCommand(final String command,
final String[] args) {
final StringBuilder builder = new StringBuilder(command);
logger.debug(
"Will replace value in {} with User={}:Acct={}:Base={}:File={}:Cmd={}",
command, args[0], args[1], args[2], args[3], args[4]);
replaceAll(builder, USER, args[0]);
replaceAll(builder, ACCOUNT, args[1]);
replaceAll(builder, BASEPATH, args[2]);
replaceAll(builder, FILE, args[3]);
replaceAll(builder, COMMAND, args[4]);
replaceAll(builder, SPECIALID, args[5]);
if (builder.indexOf(S_UUID) > 0) {
replaceAll(builder, S_UUID, new GUID().toString());
}
logger.debug("Result: {}", builder);
return builder.toString();
}
/**
* Make a replacement of first "find" string by "replace" string into the
* StringBuilder
*
* @param builder
* @param find
* @param replace
*/
public static boolean replace(final StringBuilder builder, final String find,
final String replace) {
final int start = builder.indexOf(find);
if (start == -1) {
return false;
}
final int end = start + find.length();
builder.replace(start, end, replace);
return true;
}
/**
* Make replacement of all "find" string by "replace" string into the
* StringBuilder
*
* @param builder
* @param find
* @param replace
*/
public static void replaceAll(final StringBuilder builder, final String find,
final String replace) {
while (replace(builder, find, replace)) {
// nothing
}
}
public static CommandExecutor getCommandExecutor() {
return commandExecutor;
}
public abstract void run() throws CommandAbstractException;
}