001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.tag;
020
021import java.util.Iterator;
022
023import lucee.commons.io.res.Resource;
024import lucee.commons.io.res.util.ResourceUtil;
025import lucee.commons.lang.SerializableObject;
026import lucee.commons.lang.StringUtil;
027import lucee.runtime.exp.ApplicationException;
028import lucee.runtime.exp.PageException;
029import lucee.runtime.exp.SecurityException;
030import lucee.runtime.ext.tag.BodyTagImpl;
031import lucee.runtime.op.Caster;
032import lucee.runtime.security.SecurityManager;
033
034/**
035* Enables CFML developers to execute a process on a server computer.
036*
037*
038*
039**/
040public final class Execute extends BodyTagImpl {
041
042        /** Command-line arguments passed to the application. */
043        private String arguments=null;
044
045        /** Indicates how long, in seconds, the CFML executing thread waits for the spawned process. 
046        **              A timeout of 0 is equivalent to the non-blocking mode of executing. A very high timeout value is 
047        **              equivalent to a blocking mode of execution. The default is 0; therefore, the CFML thread spawns 
048        **              a process and returns without waiting for the process to terminate.If no output file is specified, 
049        **              and the timeout value is 0, the program output is discarded. */
050        private long timeout;
051
052        /** The full pathname of the application to execute.
053        **              Note: On Windows, you must specify the extension as part of the application's name. For example, 
054        **              myapp.exe, */
055        private String name=null;
056
057        /** The file to which to direct the output of the program. If not specified, the output is 
058        **              displayed on the page from which it was called. */
059        private Resource outputfile;
060        private Resource errorFile;
061
062    private String variable;
063    private String errorVariable;
064
065        private String body;
066
067        private boolean terminateOnTimeout=false;
068
069        @Override
070        public void release()   {
071                super.release();
072                arguments=null;
073                timeout=0L;
074                name=null;
075                outputfile=null;
076                errorFile=null;
077                variable=null;
078                errorVariable=null;
079                body=null;
080                terminateOnTimeout=false;
081        }
082
083        /** set the value arguments
084        *  Command-line arguments passed to the application.
085        * @param args value to set
086        **/
087        public void setArguments(Object args)   {
088                
089            if(args instanceof lucee.runtime.type.Collection) {
090                    StringBuffer sb=new StringBuffer();
091                    lucee.runtime.type.Collection coll=(lucee.runtime.type.Collection)args;
092                    //lucee.runtime.type.Collection.Key[] keys=coll.keys();
093                    Iterator<Object> it = coll.valueIterator();
094                    while(it.hasNext()) {
095                        sb.append(' ');
096                        sb.append(it.next());
097                    }
098                    arguments=args.toString();
099                }
100            else if(args instanceof String) {
101                arguments=" "+args.toString();
102            }
103            else this.arguments="";
104        }
105
106        /** set the value timeout
107        *  Indicates how long, in seconds, the CFML executing thread waits for the spawned process. 
108        *               A timeout of 0 is equivalent to the non-blocking mode of executing. A very high timeout value is 
109        *               equivalent to a blocking mode of execution. The default is 0; therefore, the CFML thread spawns 
110        *               a process and returns without waiting for the process to terminate.If no output file is specified, 
111        *               and the timeout value is 0, the program output is discarded.
112        * @param timeout value to set
113         * @throws ApplicationException 
114        **/
115        public void setTimeout(double timeout) throws ApplicationException      {
116                if(timeout<0) 
117                        throw new ApplicationException("value must be a positive number now ["+Caster.toString(timeout)+"]");
118                this.timeout=(long)(timeout*1000L);
119        }
120
121        public void setTerminateontimeout(boolean terminateontimeout)   {
122                this.terminateOnTimeout=terminateontimeout;
123        }
124        
125        /** set the value name
126        *  The full pathname of the application to execute.
127        *               Note: On Windows, you must specify the extension as part of the application's name. For example, 
128        *               myapp.exe,
129        * @param name value to set
130        **/
131        public void setName(String name)        {
132                this.name=name;
133        }
134        
135        /**
136         * define name of variable where output is written to
137         * @param variable
138         * @throws PageException
139         */
140        public void setVariable(String variable) throws PageException   {
141                this.variable=variable;
142                pageContext.setVariable(variable,"");
143        }
144
145        public void setErrorvariable(String errorVariable) throws PageException {
146                this.errorVariable = errorVariable;
147                pageContext.setVariable(errorVariable, "");
148        }
149
150        /** set the value outputfile
151        *  The file to which to direct the output of the program. If not specified, the output is 
152        *               displayed on the page from which it was called.
153        * @param outputfile value to set
154         * @throws SecurityException
155        **/
156        public void setOutputfile(String outputfile)    {
157            try {
158            this.outputfile=ResourceUtil.toResourceExistingParent(pageContext,outputfile);
159            pageContext.getConfig().getSecurityManager().checkFileLocation(this.outputfile);
160                
161        } 
162            catch (PageException e) {
163            this.outputfile=pageContext.getConfig().getTempDirectory().getRealResource(outputfile);
164            if(!this.outputfile.getParentResource().exists())
165                this.outputfile=null;
166            else if(!this.outputfile.isFile())
167                this.outputfile=null;
168            else if(!this.outputfile.exists()) {
169                ResourceUtil.createFileEL(this.outputfile, false);
170                //try {
171                    //this.outputfile.createNewFile();
172                /*} catch (IOException e1) {
173                    this.outputfile=null;
174                }*/
175            }
176        }
177        }
178
179
180        public void setErrorfile(String errorfile)      {
181
182                try {
183                        this.errorFile = ResourceUtil.toResourceExistingParent(pageContext,errorfile);
184                        pageContext.getConfig().getSecurityManager().checkFileLocation(this.errorFile);
185                }
186                catch (PageException e) {
187
188                        this.errorFile = pageContext.getConfig().getTempDirectory().getRealResource(errorfile);
189
190                        if(!this.errorFile.getParentResource().exists())
191                                this.errorFile=null;
192                        else if(!this.errorFile.isFile())
193                                this.errorFile=null;
194                        else if(!this.errorFile.exists()) {
195                                ResourceUtil.createFileEL(this.errorFile, false);
196                        }
197                }
198        }
199
200
201        @Override
202        public int doStartTag() throws PageException    {
203                return EVAL_BODY_BUFFERED;
204        }
205        
206        private void _execute() throws Exception        {
207            Object monitor=new SerializableObject();
208            
209            String command="";
210            if(name==null) {
211                if(StringUtil.isEmpty(body)) {
212                        required("execute", "name", name);
213                        required("execute", "arguments", arguments);
214                }
215                else command=body;
216            }
217            else {
218                if(arguments==null)command=name;
219                else command=name+arguments;
220            }
221
222
223            _Execute execute=new _Execute(pageContext, monitor, command, outputfile, variable, errorFile, errorVariable);
224            
225            //if(timeout<=0)execute._run();
226            //else {
227        execute.start();
228        if(timeout>0){
229                try {
230                    synchronized(monitor) {
231                        monitor.wait(timeout);
232                    }
233                } 
234                    finally {
235                        execute.abort(terminateOnTimeout);
236                }
237                    if(execute.hasException()) {
238                        throw execute.getException();
239                    }
240                    if(!execute.hasFinished())
241                        throw new ApplicationException("timeout ["+(timeout)+" ms] expired while executing ["+command+"]");
242                    //}
243        }
244            
245        }
246
247        @Override
248        public int doEndTag() throws PageException      {
249                if(pageContext.getConfig().getSecurityManager().getAccess(SecurityManager.TYPE_TAG_EXECUTE)==SecurityManager.VALUE_NO) 
250                        throw new SecurityException("can't access tag [execute]","access is prohibited by security manager");
251            try {
252            _execute();
253        } 
254            catch (PageException pe) {
255           throw pe;
256        }
257        catch (Exception e) {e.printStackTrace();
258           throw new ApplicationException("Error invoking external process",e.getMessage());
259        }
260            return EVAL_PAGE;
261        }
262
263        @Override
264        public void doInitBody()        {
265                
266        }
267
268        @Override
269        public int doAfterBody()        {
270                body=bodyContent.getString();
271                if(!StringUtil.isEmpty(body))body=body.trim();
272                return SKIP_BODY;
273        }
274}