/** * * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * **/ package lucee.runtime.tag; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import lucee.commons.io.IOUtil; import lucee.commons.io.res.Resource; import lucee.commons.io.res.util.ResourceUtil; import lucee.runtime.PageContextImpl; import lucee.runtime.exp.ApplicationException; import lucee.runtime.exp.PageException; import lucee.runtime.ext.tag.TagImpl; import lucee.runtime.net.ftp.AFTPClient; import lucee.runtime.net.ftp.FTPConnection; import lucee.runtime.net.ftp.FTPConnectionImpl; import lucee.runtime.net.ftp.FTPConstant; import lucee.runtime.net.ftp.FTPPath; import lucee.runtime.net.ftp.FTPPoolImpl; import lucee.runtime.op.Caster; import lucee.runtime.type.Collection.Key; import lucee.runtime.type.KeyImpl; import lucee.runtime.type.QueryImpl; import lucee.runtime.type.Struct; import lucee.runtime.type.StructImpl; import lucee.runtime.type.dt.DateTimeImpl; import lucee.runtime.type.util.ListUtil; import org.apache.commons.net.ftp.FTPFile; /** * * Lets users implement File Transfer Protocol (FTP) operations. * * * **/ public final class Ftp extends TagImpl { private static final String ASCCI_EXT_LIST="txt;htm;html;cfm;cfml;shtm;shtml;css;asp;asa"; private static final int PORT_FTP=21; private static final int PORT_SFTP=22; private static final Key SUCCEEDED = KeyImpl.intern("succeeded"); private static final Key ERROR_CODE = KeyImpl.intern("errorCode"); private static final Key ERROR_TEXT = KeyImpl.intern("errorText"); private static final Key RETURN_VALUE = KeyImpl.intern("returnValue"); private static final Key CFFTP = KeyImpl.intern("cfftp"); /*private static final Key = KeyImpl.getInstance(); private static final Key = KeyImpl.getInstance(); private static final Key = KeyImpl.getInstance(); private static final Key = KeyImpl.getInstance(); private static final Key = KeyImpl.getInstance(); private static final Key = KeyImpl.getInstance();*/ private FTPPoolImpl pool; private String action; private String username; private String password; private String server; private int timeout=30; private int port=-1; private String connectionName; private int retrycount=1; private int count=0; private boolean stoponerror=true; private boolean passive; private String name; private String directory; private String ASCIIExtensionList=ASCCI_EXT_LIST; private short transferMode=FTPConstant.TRANSFER_MODE_AUTO; private String remotefile; private String localfile; private boolean failifexists=true; private String existing; private String _new; private String item; private String result; private String proxyserver; private int proxyport=80; private String proxyuser; private String proxypassword=""; private String fingerprint; private boolean secure; private boolean recursive; //private Struct cfftp=new StructImpl(); @Override public void release() { super.release(); this.pool=null; this.action=null; this.username=null; this.password=null; this.server=null; this.timeout=30; this.port=-1; this.connectionName=null; this.proxyserver=null; this.proxyport=80; this.proxyuser=null; this.proxypassword=""; this.retrycount=1; this.count=0; this.stoponerror=true; this.passive=false; this.name=null; this.directory=null; this.ASCIIExtensionList=ASCCI_EXT_LIST; this.transferMode=FTPConstant.TRANSFER_MODE_AUTO; this.remotefile=null; this.localfile=null; this.failifexists=true; this.existing=null; this._new=null; this.item=null; this.result=null; fingerprint=null; secure=false; recursive=false; } public void setAction(String action) { this.action=action.trim().toLowerCase(); } /** * sets the attribute action * @param action */ public void setSecure(boolean secure) { this.secure=secure; } @Override public int doStartTag() { return SKIP_BODY; } @Override public int doEndTag() throws PageException { pool=((PageContextImpl)pageContext).getFTPPool(); AFTPClient client = null; // retries do { try { if(action.equals("open")) client=actionOpen(); else if(action.equals("close")) client=actionClose(); else if(action.equals("changedir")) client=actionChangeDir(); else if(action.equals("createdir")) client=actionCreateDir(); else if(action.equals("listdir")) client=actionListDir(); else if(action.equals("removedir")) client=actionRemoveDir(); else if(action.equals("getfile")) client=actionGetFile(); else if(action.equals("putfile")) client=actionPutFile(); else if(action.equals("rename")) client=actionRename(); else if(action.equals("remove")) client=actionRemove(); else if(action.equals("getcurrentdir")) client=actionGetCurrentDir(); else if(action.equals("getcurrenturl")) client=actionGetCurrentURL(); else if(action.equals("existsdir")) client=actionExistsDir(); else if(action.equals("existsfile")) client=actionExistsFile(); else if(action.equals("exists")) client=actionExists(); //else if(action.equals("copy")) client=actionCopy(); else throw new ApplicationException( "attribute action has an invalid value ["+action+"]", "valid values are [open,close,listDir,createDir,removeDir,changeDir,getCurrentDir," + "getCurrentURL,existsFile,existsDir,exists,getFile,putFile,rename,remove]"); } catch(IOException ioe) { if(count++<retrycount)continue; throw Caster.toPageException(ioe); } if(client==null || !checkCompletion(client))break; }while(true); return EVAL_PAGE; } /** * check if a directory exists or not * @return FTPCLient * @throws PageException * @throws IOException */ private AFTPClient actionExistsDir() throws PageException, IOException { required("directory",directory); AFTPClient client = getClient(); boolean res = existsDir(client,directory); Struct cfftp = writeCfftp(client); cfftp.setEL(RETURN_VALUE,Caster.toBoolean(res)); cfftp.setEL(SUCCEEDED,Boolean.TRUE); stoponerror=false; return client; } /** * check if a file exists or not * @return FTPCLient * @throws IOException * @throws PageException */ private AFTPClient actionExistsFile() throws PageException, IOException { required("remotefile",remotefile); AFTPClient client = getClient(); FTPFile file=existsFile(client,remotefile,true); Struct cfftp = writeCfftp(client); cfftp.setEL(RETURN_VALUE,Caster.toBoolean(file!=null && file.isFile())); cfftp.setEL(SUCCEEDED,Boolean.TRUE); stoponerror=false; return client; } /** * check if a file or directory exists * @return FTPCLient * @throws PageException * @throws IOException */ private AFTPClient actionExists() throws PageException, IOException { required("item",item); AFTPClient client = getClient(); FTPFile file=existsFile(client,item,false); Struct cfftp = writeCfftp(client); cfftp.setEL(RETURN_VALUE,Caster.toBoolean(file!=null)); cfftp.setEL(SUCCEEDED,Boolean.TRUE); return client; } /* * * check if file or directory exists if it exists return FTPFile otherwise null * @param client * @param strPath * @return FTPFile or null * @throws IOException * @throws PageException * / private FTPFile exists(FTPClient client, String strPath) throws PageException, IOException { strPath=strPath.trim(); // get parent path FTPPath path=new FTPPath(client.printWorkingDirectory(),strPath); String name=path.getName(); print.out("path:"+name); // when directory FTPFile[] files=null; try { files = client.listFiles(path.getPath()); } catch (IOException e) {} if(files!=null) { for(int i=0;i<files.length;i++) { if(files[i].getName().equalsIgnoreCase(name)) { return files[i]; } } } return null; }*/ private FTPFile existsFile(AFTPClient client, String strPath,boolean isFile) throws PageException, IOException { strPath=strPath.trim(); if(strPath.equals("/")) { FTPFile file= new FTPFile(); file.setName("/"); file.setType(FTPFile.DIRECTORY_TYPE); return file; } // get parent path FTPPath path=new FTPPath(client,strPath); String p=path.getPath(); String n=path.getName(); strPath=p; if("//".equals(p))strPath="/"; if(isFile)strPath+=n; // when directory FTPFile[] files=null; try { files = client.listFiles(p); } catch (IOException e) {} if(files!=null) { for(int i=0;i<files.length;i++) { if(files[i].getName().equalsIgnoreCase(n)) { return files[i]; } } } return null; } private boolean existsDir(AFTPClient client, String strPath) throws PageException, IOException { strPath=strPath.trim(); // get parent path FTPPath path=new FTPPath(client,strPath); String p=path.getPath(); String n=path.getName(); strPath=p+""+n; if("//".equals(p))strPath="/"+n; if(!strPath.endsWith("/"))strPath+="/"; return client.directoryExists(directory); } /** * removes a file on the server * @return FTPCLient * @throws IOException * @throws PageException */ private AFTPClient actionRemove() throws IOException, PageException { required("item",item); AFTPClient client = getClient(); client.deleteFile(item); writeCfftp(client); return client; } /** * rename a file on the server * @return FTPCLient * @throws PageException * @throws IOException */ private AFTPClient actionRename() throws PageException, IOException { required("existing",existing); required("new",_new); AFTPClient client = getClient(); client.rename(existing,_new); writeCfftp(client); return client; } /** * copy a local file to server * @return FTPClient * @throws IOException * @throws PageException */ private AFTPClient actionPutFile() throws IOException, PageException { required("remotefile",remotefile); required("localfile",localfile); AFTPClient client = getClient(); Resource local=ResourceUtil.toResourceExisting(pageContext ,localfile);//new File(localfile); // if(failifexists && local.exists()) throw new ApplicationException("File ["+local+"] already exist, if you want to overwrite, set attribute failIfExists to false"); InputStream is=null; try { is=IOUtil.toBufferedInputStream(local.getInputStream()); client.setFileType(getType(local)); client.storeFile(remotefile,is); } finally { IOUtil.closeEL(is); } writeCfftp(client); return client; } /** * gets a file from server and copy it local * @return FTPCLient * @throws PageException * @throws IOException */ private AFTPClient actionGetFile() throws PageException, IOException { required("remotefile",remotefile); required("localfile",localfile); AFTPClient client = getClient(); Resource local=ResourceUtil.toResourceExistingParent(pageContext ,localfile);//new File(localfile); pageContext.getConfig().getSecurityManager().checkFileLocation(local); if(failifexists && local.exists()) throw new ApplicationException("File ["+local+"] already exist, if you want to overwrite, set attribute failIfExists to false"); OutputStream fos=null; client.setFileType(getType(local)); boolean success=false; try { fos=IOUtil.toBufferedOutputStream(local.getOutputStream()); success=client.retrieveFile(remotefile,fos); } finally { IOUtil.closeEL(fos); if(!success) local.delete(); } writeCfftp(client); return client; } /** * get url of the working directory * @return FTPCLient * @throws IOException * @throws PageException */ private AFTPClient actionGetCurrentURL() throws PageException, IOException { AFTPClient client = getClient(); String pwd=client.printWorkingDirectory(); Struct cfftp = writeCfftp(client); cfftp.setEL("returnValue",client.getPrefix()+"://"+client.getRemoteAddress().getHostName()+pwd); return client; } /** * get path from the working directory * @return FTPCLient * @throws IOException * @throws PageException */ private AFTPClient actionGetCurrentDir() throws PageException, IOException { AFTPClient client = getClient(); String pwd=client.printWorkingDirectory(); Struct cfftp = writeCfftp(client); cfftp.setEL("returnValue",pwd); return client; } /** * change working directory * @return FTPCLient * @throws IOException * @throws PageException */ private AFTPClient actionChangeDir() throws IOException, PageException { required("directory",directory); AFTPClient client = getClient(); client.changeWorkingDirectory(directory); writeCfftp(client); return client; } private AFTPClient getClient() throws PageException, IOException { return pool.get(_createConnection()); } /** * removes a remote directory on server * @return FTPCLient * @throws IOException * @throws PageException */ private AFTPClient actionRemoveDir() throws IOException, PageException { required("directory",directory); AFTPClient client = getClient(); if(recursive) { removeRecursive(client,directory,FTPFile.DIRECTORY_TYPE); } else client.removeDirectory(directory); writeCfftp(client); return client; } private static void removeRecursive(AFTPClient client, String path, int type) throws IOException { // directory if(FTPFile.DIRECTORY_TYPE==type) { if(!path.endsWith("/")) path+="/"; // first we remove the children FTPFile[] children = client.listFiles(path); for(FTPFile child:children){ if(child.getName().equals(".") || child.getName().equals("..")) continue; removeRecursive(client, path+child.getName(), child.getType()); } // then the directory itself client.removeDirectory(path); } // file else if(FTPFile.FILE_TYPE==type) { client.deleteFile(path); } } /** * create a remote directory * @return FTPCLient * @throws IOException * @throws PageException */ private AFTPClient actionCreateDir() throws IOException, PageException { required("directory",directory); AFTPClient client = getClient(); client.makeDirectory(directory); writeCfftp(client); return client; } /** * List data of a ftp connection * @return FTPCLient * @throws PageException * @throws IOException */ private AFTPClient actionListDir() throws PageException, IOException { required("name",name); required("directory",directory); AFTPClient client = getClient(); FTPFile[] files = client.listFiles(directory); if(files==null)files=new FTPFile[0]; pageContext.setVariable(name,toQuery(files,"ftp",directory,client.getRemoteAddress().getHostName())); writeCfftp(client); return client; } public static lucee.runtime.type.Query toQuery(FTPFile[] files, String prefix, String directory, String hostName) throws PageException { String[] cols = new String[]{"name","isdirectory","lastmodified","length","mode", "path","url","type","raw","attributes"}; String[] types = new String[]{"VARCHAR","BOOLEAN","DATE","DOUBLE","VARCHAR","VARCHAR", "VARCHAR","VARCHAR","VARCHAR","VARCHAR"}; lucee.runtime.type.Query query=new QueryImpl(cols,types,0,"query"); // translate directory path for display if(directory.length()==0)directory="/"; else if(directory.startsWith("./"))directory=directory.substring(1); else if(directory.charAt(0)!='/')directory='/'+directory; if(directory.charAt(directory.length()-1)!='/')directory=directory+'/'; int row; for(int i=0;i<files.length;i++) { FTPFile file = files[i]; if(file.getName().equals(".") || file.getName().equals("..")) continue; row=query.addRow(); query.setAt("attributes",row,""); query.setAt("isdirectory",row,Caster.toBoolean(file.isDirectory())); query.setAt("lastmodified",row,new DateTimeImpl(file.getTimestamp())); query.setAt("length",row,Caster.toDouble(file.getSize())); query.setAt("mode",row,FTPConstant.getPermissionASInteger(file)); query.setAt("type",row,FTPConstant.getTypeAsString(file.getType())); //query.setAt("permission",row,FTPConstant.getPermissionASInteger(file)); query.setAt("raw",row,file.getRawListing()); query.setAt("name",row,file.getName()); query.setAt("path",row,directory+file.getName()); query.setAt("url",row,prefix+"://"+hostName+""+directory+file.getName()); } return query; } /** * Opens a FTP Connection * @return FTPCLinet * @throws IOException * @throws PageException */ private AFTPClient actionOpen() throws IOException, PageException { required("server",server); required("username",username); required("password",password); AFTPClient client = getClient(); writeCfftp(client); return client; } /** * close a existing ftp connection * @return FTPCLient * @throws PageException */ private AFTPClient actionClose() throws PageException { FTPConnection conn = _createConnection(); AFTPClient client = pool.remove(conn); Struct cfftp = writeCfftp(client); cfftp.setEL("succeeded",Caster.toBoolean(client!=null)); return client; } /** * throw a error if the value is empty (null) * @param attributeName * @param atttributValue * @throws ApplicationException */ private void required(String attributeName, String atttributValue) throws ApplicationException { if(atttributValue==null) throw new ApplicationException( "invalid attribute constelation for the tag ftp", "attribute ["+attributeName+"] is required, if action is ["+action+"]"); } /** * writes cfftp variable * @param client * @return FTPCLient * @throws PageException */ private Struct writeCfftp(AFTPClient client) throws PageException { Struct cfftp=new StructImpl(); if(result==null)pageContext.variablesScope().setEL(CFFTP,cfftp); else pageContext.setVariable(result,cfftp); if(client==null) { cfftp.setEL(SUCCEEDED,Boolean.FALSE); cfftp.setEL(ERROR_CODE,new Double(-1)); cfftp.setEL(ERROR_TEXT,""); cfftp.setEL(RETURN_VALUE,""); return cfftp; } int repCode = client.getReplyCode(); String repStr=client.getReplyString(); cfftp.setEL(ERROR_CODE,new Double(repCode)); cfftp.setEL(ERROR_TEXT,repStr); cfftp.setEL(SUCCEEDED,Caster.toBoolean(client.isPositiveCompletion())); cfftp.setEL(RETURN_VALUE,repStr); return cfftp; } /** * check completion status of the client * @param client * @return FTPCLient * @throws ApplicationException */ private boolean checkCompletion(AFTPClient client) throws ApplicationException { boolean isPositiveCompletion=client.isPositiveCompletion(); if(isPositiveCompletion) return false; if(count++<retrycount) return true; if(stoponerror){ throw new lucee.runtime.exp.FTPException(action,client); } return false; } /** * get FTP. ... _FILE_TYPE * @param file * @return type */ private int getType(Resource file) { if(transferMode==FTPConstant.TRANSFER_MODE_BINARY) return AFTPClient.FILE_TYPE_BINARY; else if(transferMode==FTPConstant.TRANSFER_MODE_ASCCI) return AFTPClient.FILE_TYPE_TEXT; else { String ext=ResourceUtil.getExtension(file,null); if(ext==null || ListUtil.listContainsNoCase(ASCIIExtensionList,ext,";",true,false)==-1) return AFTPClient.FILE_TYPE_BINARY; return AFTPClient.FILE_TYPE_TEXT; } } /** * @return return a new FTP Connection Object */ private FTPConnection _createConnection() { return new FTPConnectionImpl(connectionName,server,username,password,getPort(),timeout,transferMode,passive, proxyserver,proxyport,proxyuser,proxypassword, fingerprint,stoponerror,secure); } /** * @param password The password to set. */ public void setPassword(String password) { this.password = password; } /** * @param username The username to set. */ public void setUsername(String username) { this.username = username; } /** * @param server The server to set. */ public void setServer(String server) { this.server = server; } /** * @param timeout The timeout to set. */ public void setTimeout(double timeout) { this.timeout = (int)timeout; } /** * @param port The port to set. */ public void setPort(double port) { this.port = (int)port; } public int getPort() { if(port!=-1) return port; return secure?PORT_SFTP:PORT_FTP; } /** * @param connection The connection to set. */ public void setConnection(String connection) { this.connectionName = connection; } /** * @param proxyserver The proxyserver to set. */ public void setProxyserver(String proxyserver) { this.proxyserver = proxyserver; } /** set the value proxyport * The port number on the proxy server from which the object is requested. Default is 80. When * used with resolveURL, the URLs of retrieved documents that specify a port number are automatically * resolved to preserve links in the retrieved document. * @param proxyport value to set **/ public void setProxyport(double proxyport) { this.proxyport=(int)proxyport; } /** set the value username * When required by a proxy server, a valid username. * @param proxyuser value to set **/ public void setProxyuser(String proxyuser) { this.proxyuser=proxyuser; } /** set the value password * When required by a proxy server, a valid password. * @param proxypassword value to set **/ public void setProxypassword(String proxypassword) { this.proxypassword=proxypassword; } /** * @param retrycount The retrycount to set. */ public void setRetrycount(double retrycount) { this.retrycount = (int)retrycount; } /** * @param stoponerror The stoponerror to set. */ public void setStoponerror(boolean stoponerror) { this.stoponerror = stoponerror; } /** * @param passive The passive to set. */ public void setPassive(boolean passive) { this.passive = passive; } /** * @param directory The directory to set. */ public void setDirectory(String directory) { this.directory = directory; } /** * @param name The name to set. */ public void setName(String name) { this.name = name; } public void setRecurse(boolean recursive) { this.recursive = recursive; } /** * @param extensionList The aSCIIExtensionList to set. */ public void setAsciiextensionlist(String extensionList) { ASCIIExtensionList = extensionList.toLowerCase().trim(); } /** * @param transferMode The transferMode to set. */ public void setTransfermode(String transferMode) { transferMode=transferMode.toLowerCase().trim(); if(transferMode.equals("binary"))this.transferMode=FTPConstant.TRANSFER_MODE_BINARY; else if(transferMode.equals("ascci"))this.transferMode=FTPConstant.TRANSFER_MODE_ASCCI; else this.transferMode=FTPConstant.TRANSFER_MODE_AUTO; } /** * @param localfile The localfile to set. */ public void setLocalfile(String localfile) { this.localfile = localfile; } /** * @param remotefile The remotefile to set. */ public void setRemotefile(String remotefile) { this.remotefile = remotefile; } /** * @param failifexists The failifexists to set. */ public void setFailifexists(boolean failifexists) { this.failifexists = failifexists; } /** * @param _new The _new to set. */ public void setNew(String _new) { this._new = _new; } /** * @param existing The existing to set. */ public void setExisting(String existing) { this.existing = existing; } /** * @param item The item to set. */ public void setItem(String item) { this.item = item; } /** * @param result The result to set. */ public void setResult(String result) { this.result = result; } public void setFingerprint(String fingerprint) { this.fingerprint = fingerprint; } }