package lucee.runtime.net.ftp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Vector;
import lucee.commons.lang.StringUtil;
import lucee.runtime.op.Caster;
import lucee.runtime.type.Collection.Key;
import lucee.runtime.type.KeyImpl;
import org.apache.commons.net.ftp.FTPFile;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.UserInfo;
public class SFTPClientImpl extends AFTPClient {
private static final Key IS_DIRECTORY = new KeyImpl("isDirectory");
private JSch jsch;
private int timeout=60000;
private Session session;
private ChannelSftp channelSftp;
private InetAddress host;
private int port;
private String username;
private String password;
private boolean stopOnError;
private String fingerprint;
private String replyString;
private int replyCode;
private boolean positiveCompletion;
SFTPClientImpl(){
jsch = new JSch();
}
@Override
public void init(InetAddress host, int port, String username, String password, String fingerprint, boolean stopOnError) throws SocketException, IOException {
if(port<1)port=22;
this.host=host;
this.port=port;
this.username=username;
this.password=password;
this.fingerprint=fingerprint==null?null:fingerprint.trim();
this.stopOnError=stopOnError;
}
@Override
public void connect() throws SocketException, IOException {
try {
session = jsch.getSession(username,host.getHostAddress() , port);
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
UserInfo ui=new UserInfoImpl(password,null);
session.setUserInfo(ui);
if(timeout>0) session.setTimeout(timeout);
session.connect();
Channel channel=session.openChannel("sftp");
channel.connect();
channelSftp = (ChannelSftp)channel;
// check fingerprint
if(!StringUtil.isEmpty(fingerprint)) {
if(!fingerprint.equalsIgnoreCase(fingerprint())) {
disconnect();
throw new IOException("given fingerprint is not a match.");
}
}
handleSucess();
}
catch(JSchException e){
handleFail(e, stopOnError);
}
}
private String fingerprint() {
return session.getHostKey().getFingerPrint(jsch);
}
@Override
public boolean rename(String from, String to) throws IOException {
try {
channelSftp.rename(from, to);
handleSucess();
return true;
}
catch (SftpException e) {
handleFail(e, stopOnError);
}
return false;
}
@Override
public boolean removeDirectory(String pathname) throws IOException {
try {
channelSftp.rmdir(pathname);
handleSucess();
return true;
}
catch(SftpException ioe) {
handleFail(ioe, stopOnError);
}
return false;
}
@Override
public boolean makeDirectory(String pathname) throws IOException {
try {
channelSftp.mkdir(pathname);
handleSucess();
return true;
}
catch(SftpException ioe) {
handleFail(ioe, stopOnError);
}
return false;
}
@Override
public boolean directoryExists(String pathname) throws IOException {
try {
String pwd=channelSftp.pwd();
channelSftp.cd(pathname);
channelSftp.cd(pwd); // we change it back to what it was
handleSucess();
return true;
}
catch(SftpException e) {/*do nothing*/}
return false;
}
@Override
public boolean changeWorkingDirectory(String pathname) throws IOException {
try {
channelSftp.cd(pathname);
handleSucess();
return true;
}
catch(SftpException ioe) {
handleFail(ioe, stopOnError);
}
return false;
}
@Override
public String printWorkingDirectory() throws IOException {
try {
String pwd = channelSftp.pwd();
handleSucess();
return pwd;
}
catch(SftpException ioe) {
handleFail(ioe, stopOnError);
}
return null;
}
@Override
public boolean deleteFile(String pathname) throws IOException {
try {
channelSftp.rm(pathname);
handleSucess();
return true;
}
catch(SftpException ioe) {
handleFail(ioe, stopOnError);
}
return false;
}
@Override
public boolean retrieveFile(String remote, OutputStream local) throws IOException {
boolean success=false;
try {
channelSftp.get(remote, local);
handleSucess();
success = true;
}
catch(SftpException ioe) {
handleFail(ioe, stopOnError);
}
return success;
}
@Override
public boolean storeFile(String remote, InputStream local) throws IOException {
try {
this.channelSftp.put(local, remote); // TODO add progress monitor?
handleSucess();
return true;
}
catch(SftpException ioe) {
handleFail(ioe, stopOnError);
}
return false;
}
@Override
public int getReplyCode() {
return replyCode;
}
@Override
public String getReplyString() {
return replyString;
}
@Override
public FTPFile[] listFiles(String pathname) throws IOException {
pathname=cleanPath(pathname);
List<FTPFile> files=new ArrayList<FTPFile>();
try {
Vector list = channelSftp.ls(pathname);
Iterator<ChannelSftp.LsEntry> it = list.iterator();
ChannelSftp.LsEntry entry;
SftpATTRS attrs;
FTPFile file;
String fileName;
while(it.hasNext()){
entry=it.next();
attrs = entry.getAttrs();
fileName=entry.getFilename();
if(fileName.equals(".") || fileName.equals("..")) continue;
file=new FTPFile();
files.add(file);
// is dir
file.setType(attrs.isDir()?FTPFile.DIRECTORY_TYPE:FTPFile.FILE_TYPE);
file.setTimestamp(Caster.toCalendar(attrs.getMTime()*1000L, null, Locale.ENGLISH));
file.setSize(attrs.isDir()?0:attrs.getSize());
FTPConstant.setPermission(file, attrs.getPermissions());
file.setName(fileName);
}
handleSucess();
}
catch (SftpException e) {
handleFail(e, stopOnError);
}
return files.toArray(new FTPFile[files.size()]);
}
private String cleanPath(String pathname) {
if(!pathname.endsWith("/"))
pathname = pathname + "/";
return pathname;
}
@Override
public boolean setFileType(int fileType) throws IOException {
// not used
return true;
}
@Override
public String getPrefix() {
return "sftp";
}
@Override
public InetAddress getRemoteAddress() {
return host;
}
@Override
public boolean isConnected() {
return channelSftp.isConnected();
}
@Override
public int quit() throws IOException {
// do nothing
return 0;
}
@Override
public void disconnect() throws IOException {
if(session!=null && session.isConnected()) {
session.disconnect();
session=null;
}
}
@Override
public void setTimeout(int timeout) {
this.timeout=timeout;
if(session!=null) {
try {
session.setTimeout(timeout);
}
catch (JSchException e) {}
}
}
@Override
public int getDataConnectionMode() {
// not used
return -1;
}
@Override
public void enterLocalPassiveMode() {
// not used
}
@Override
public void enterLocalActiveMode() {
// not used
}
@Override
public boolean isPositiveCompletion() {
return positiveCompletion;
}
private void handleSucess() {
replyCode=0;
replyString="SSH_FX_OK successful completion of the operation";
positiveCompletion=true;
}
private void handleFail(Exception e, boolean stopOnError) throws IOException {
String msg = e.getMessage()==null?"":e.getMessage();
if (StringUtil.indexOfIgnoreCase(msg, "AUTHENTICATION") != -1 || StringUtil.indexOfIgnoreCase(msg, "PRIVATEKEY") != -1) {
replyCode = 51;
}
else replyCode = 82;
replyString=msg;
positiveCompletion=false;
if (stopOnError) {
disconnect();
if(e instanceof IOException) throw (IOException)e;
throw new IOException(e);
}
}
}