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 static lucee.runtime.tag.util.FileUtil.NAMECONFLICT_ERROR;
022import static lucee.runtime.tag.util.FileUtil.NAMECONFLICT_MAKEUNIQUE;
023import static lucee.runtime.tag.util.FileUtil.NAMECONFLICT_OVERWRITE;
024import static lucee.runtime.tag.util.FileUtil.NAMECONFLICT_SKIP;
025import static lucee.runtime.tag.util.FileUtil.NAMECONFLICT_UNDEFINED;
026
027import java.awt.image.BufferedImage;
028import java.io.ByteArrayInputStream;
029import java.io.File;
030import java.io.IOException;
031import java.io.InputStream;
032import java.io.UnsupportedEncodingException;
033import java.nio.charset.Charset;
034
035import lucee.commons.io.CharsetUtil;
036import lucee.commons.io.IOUtil;
037import lucee.commons.io.ModeUtil;
038import lucee.commons.io.SystemUtil;
039import lucee.commons.io.res.Resource;
040import lucee.commons.io.res.type.s3.S3;
041import lucee.commons.io.res.type.s3.S3Resource;
042import lucee.commons.io.res.util.ModeObjectWrap;
043import lucee.commons.io.res.util.ResourceUtil;
044import lucee.commons.lang.ExceptionUtil;
045import lucee.commons.lang.StringUtil;
046import lucee.commons.lang.mimetype.MimeType;
047import lucee.runtime.PageContext;
048import lucee.runtime.PageContextImpl;
049import lucee.runtime.exp.ApplicationException;
050import lucee.runtime.exp.PageException;
051import lucee.runtime.ext.tag.BodyTagImpl;
052import lucee.runtime.functions.list.ListFirst;
053import lucee.runtime.functions.list.ListLast;
054import lucee.runtime.functions.s3.StoreSetACL;
055import lucee.runtime.img.ImageUtil;
056import lucee.runtime.op.Caster;
057import lucee.runtime.op.Decision;
058import lucee.runtime.tag.util.FileUtil;
059import lucee.runtime.type.Array;
060import lucee.runtime.type.ArrayImpl;
061import lucee.runtime.type.Struct;
062import lucee.runtime.type.StructImpl;
063import lucee.runtime.type.dt.DateImpl;
064import lucee.runtime.type.dt.DateTimeImpl;
065import lucee.runtime.type.scope.Form;
066import lucee.runtime.type.scope.FormItem;
067import lucee.runtime.type.util.ArrayUtil;
068import lucee.runtime.type.util.KeyConstants;
069import lucee.runtime.type.util.ListUtil;
070
071/**
072* Handles all interactions with files. The attributes you use with cffile depend on the value of the action attribute. 
073*  For example, if the action = "write", use the attributes associated with writing a text file.
074*
075*
076*
077**/
078public final class FileTag extends BodyTagImpl {
079
080        private static final int ACTION_UNDEFINED = 0;
081        private static final int ACTION_MOVE = 1;
082        private static final int ACTION_WRITE = 2;
083        private static final int ACTION_APPEND = 3;
084        private static final int ACTION_READ = 4;
085        private static final int ACTION_UPLOAD = 5;
086        private static final int ACTION_UPLOAD_ALL = 6;
087        private static final int ACTION_COPY = 7;
088        private static final int ACTION_INFO = 8;
089        private static final int ACTION_TOUCH = 9;
090        private static final int ACTION_DELETE = 10;
091        private static final int ACTION_READ_BINARY = 11;
092        //private static final Key SET_ACL = KeyImpl.intern("setACL");
093    
094    //private static final String DEFAULT_ENCODING=Charset.getDefault();
095
096        /** Type of file manipulation that the tag performs. */
097        private int action;
098
099        /** Absolute pathname of directory or file on web server. */
100        private String strDestination;
101
102        /** Content of the file to be created. */
103        private Object output;
104
105        /** Absolute pathname of file on web server. */
106        private Resource file;
107
108        /** Applies only to Solaris and HP-UX. Permissions. Octal values of UNIX chmod command. Assigned to owner, group, and other, respectively. */
109        private int mode=-1;
110
111        /** Name of variable to contain contents of text file. */
112        private String variable;
113
114        /** Name of form field used to select the file. */
115        private String filefield;
116
117        /** Character set name for the file contents. */
118        private Charset charset=null;
119
120        /** Yes: appends newline character to text written to file */
121        private boolean addnewline=true;
122        private boolean fixnewline=true;
123        /** One attribute (Windows) or a comma-delimited list of attributes (other platforms) to set on the file. 
124        ** If omitted, the file's attributes are maintained. */
125        private String attributes;
126
127        /** Absolute pathname of file on web server. 
128        ** On Windows, use backward slashes; on UNIX, use forward slashes. */
129        private Resource source;
130
131        /** Action to take if filename is the same as that of a file in the directory. */
132        private int nameconflict=NAMECONFLICT_UNDEFINED;
133
134        /** Limits the MIME types to accept. Comma-delimited list. For example, to permit JPG and Microsoft Word file uploads:
135        ** accept = "image/jpg, application/msword"
136        ** The browser uses file extension to determine file type. */
137        private String accept;
138
139        private boolean strict=true;
140        private boolean createPath=false;
141    
142    private String result=null;
143        
144        private lucee.runtime.security.SecurityManager securityManager;
145
146        private String serverPassword=null;
147        private Object acl=null;
148
149        @Override
150        public void release()   {
151                super.release();
152                acl=null;
153                action=ACTION_UNDEFINED;
154                strDestination=null;
155                output=null;
156                file=null;
157                mode=-1;
158                variable=null;
159                filefield=null;
160                charset=null;
161                addnewline=true;
162                fixnewline=true;
163                attributes=null;
164                source=null;
165                nameconflict=NAMECONFLICT_UNDEFINED;
166                accept=null;
167                strict=true;
168                createPath=false;
169                securityManager=null;
170        result=null;
171        serverPassword=null;
172        }
173
174        /** set the value action
175        *  Type of file manipulation that the tag performs.
176        * @param strAction value to set
177        **/     
178        public void setAction(String strAction) throws ApplicationException     {
179                strAction=strAction.toLowerCase();
180                if(strAction.equals("move") || strAction.equals("rename")) action=ACTION_MOVE;
181                else if(strAction.equals("copy")) action=ACTION_COPY;
182                else if(strAction.equals("delete")) action=ACTION_DELETE;
183                else if(strAction.equals("read")) action=ACTION_READ;
184                else if(strAction.equals("readbinary")) action=ACTION_READ_BINARY;
185                else if(strAction.equals("write")) action=ACTION_WRITE;
186                else if(strAction.equals("append")) action=ACTION_APPEND;
187                else if(strAction.equals("upload")) action=ACTION_UPLOAD;
188                else if(strAction.equals("uploadall")) action=ACTION_UPLOAD_ALL;
189        else if(strAction.equals("info")) action=ACTION_INFO;
190        else if(strAction.equals("touch")) action=ACTION_TOUCH;
191        else 
192                        throw new ApplicationException("invalid value ["+strAction+"] for attribute action","values for attribute action are:info,move,rename,copy,delete,read,readbinary,write,append,upload,uploadall,touch");
193        }
194
195        /** set the value destination
196        *  Absolute pathname of directory or file on web server.
197        * @param destination value to set
198        **/
199        public void setDestination(String destination)  {
200                this.strDestination=destination;//ResourceUtil.toResourceNotExisting(pageContext ,destination);
201        }
202
203        /** set the value output
204        *  Content of the file to be created.
205        * @param output value to set
206        **/
207        public void setOutput(Object output)    {
208                if(output==null)this.output="";
209                else this.output=output;
210        }
211
212        /** set the value file
213        *  Absolute pathname of file on web server.
214        * @param file value to set
215        **/
216        public void setFile(String file)        {
217                this.file=ResourceUtil.toResourceNotExisting(pageContext ,file);
218        
219        }
220
221        /** set the value mode
222        *  Applies only to Solaris and HP-UX. Permissions. Octal values of UNIX chmod command. Assigned to owner, group, and other, respectively.
223        * @param mode value to set
224         * @throws PageException 
225        **/
226        public void setMode(String mode) throws PageException   {
227                this.mode=toMode(mode);
228        }
229        
230
231        public static int toMode(String mode) throws PageException      {
232                if(StringUtil.isEmpty(mode,true)) return -1;
233                try {
234                        return ModeUtil.toOctalMode(mode);
235                } 
236                catch (IOException e) {
237                        throw Caster.toPageException(e);
238                }
239        }
240        
241
242        /** set the value variable
243        *  Name of variable to contain contents of text file.
244        * @param variable value to set
245        **/
246        public void setVariable(String variable)        {
247                this.variable=variable;
248        }
249
250        /** set the value filefield
251        *  Name of form field used to select the file.
252        * @param filefield value to set
253        **/
254        public void setFilefield(String filefield)      {
255                this.filefield=filefield;
256        }
257
258        /** set the value charset
259        *  Character set name for the file contents.
260        * @param charset value to set
261        **/
262        public void setCharset(String charset)  {
263                if(StringUtil.isEmpty(charset)) return;
264                this.charset=CharsetUtil.toCharset(charset.trim());
265        }
266        
267        /** set the value acl
268        *  used only for s3 resources, for all others ignored
269        * @param acl value to set
270         * @throws ApplicationException 
271         * @Deprecated only exists for backward compatibility to old ra files.
272        **/
273        public void setAcl(String acl) throws ApplicationException      {
274                this.acl=acl;
275        }
276        public void setAcl(Object acl)  {
277                this.acl=acl;
278        }
279        public void setStoreacl(Object acl)     {
280                this.acl=acl;
281        }
282        
283        
284        
285        public void setServerpassword(String serverPassword)    {
286            this.serverPassword=serverPassword;
287        }
288
289        /** set the value addnewline
290        *  Yes: appends newline character to text written to file
291        * @param addnewline value to set
292        **/
293        public void setAddnewline(boolean addnewline)   {
294                this.addnewline=addnewline;
295        }
296
297        /** set the value attributes
298        *  One attribute (Windows) or a comma-delimited list of attributes (other platforms) to set on the file. 
299        * If omitted, the file's attributes are maintained.
300        * @param attributes value to set
301        **/
302        public void setAttributes(String attributes)    {
303                this.attributes=attributes;
304        }
305
306        /** set the value source
307        *  Absolute pathname of file on web server. 
308        * On Windows, use backward slashes; on UNIX, use forward slashes.
309        * @param source value to set
310        **/
311        public void setSource(String source)    {
312                this.source=ResourceUtil.toResourceNotExisting(pageContext ,source);
313        }
314
315        /** set the value nameconflict
316        * Action to take if filename is the same as that of a file in the directory.
317        * @param nameconflict value to set
318        * @throws ApplicationException
319        **/
320        public void setNameconflict(String nameconflict) throws ApplicationException    {
321
322                this.nameconflict = FileUtil.toNameConflict( nameconflict );
323        }
324
325
326        /** set the value accept
327        *  Limits the MIME types to accept. Comma-delimited list. For example, to permit JPG and Microsoft Word file uploads:
328        * accept = "image/jpg, application/msword"
329        * The browser uses file extension to determine file type.
330        * @param accept value to set
331        **/
332        public void setAccept(String accept)    {
333                this.accept=accept;
334        }
335
336        public void setStrict(boolean strict)   {
337                this.strict=strict;
338        }
339        public void setCreatepath(boolean createPath)   {
340                this.createPath=createPath;
341        }
342    
343    /**
344     * @param result The result to set.
345     */
346    public void setResult(String result) {
347        this.result = result;
348    }
349
350
351        @Override
352        public int doStartTag() throws PageException    {
353                if(charset==null) charset=((PageContextImpl)pageContext).getResourceCharset();
354
355                securityManager = pageContext.getConfig().getSecurityManager();
356                
357                switch(action){
358                case ACTION_MOVE: actionMove(pageContext, securityManager,source, strDestination, nameconflict,serverPassword,acl, mode, attributes);
359                break;
360                case ACTION_COPY: actionCopy(pageContext, securityManager,source, strDestination, nameconflict,serverPassword,acl, mode, attributes);
361                break;
362                case ACTION_DELETE: actionDelete();
363                break;
364                case ACTION_READ: actionRead();
365                break;
366                case ACTION_READ_BINARY: actionReadBinary();
367                break;
368                case ACTION_UPLOAD: actionUpload();
369                break;
370                case ACTION_UPLOAD_ALL: actionUploadAll();
371                break;
372                case ACTION_INFO: actionInfo();
373                break;
374                case ACTION_TOUCH: actionTouch();
375                break;
376                case ACTION_UNDEFINED: throw new ApplicationException("missing attribute action"); // should never happens
377                
378                // write and append
379                default:
380                        return EVAL_BODY_BUFFERED;
381                }
382                return SKIP_BODY;
383        }
384        
385        @Override
386        public int doAfterBody() throws ApplicationException    {
387                if(action==ACTION_APPEND || action==ACTION_WRITE) {
388                        String body = bodyContent.getString();
389                        if(!StringUtil.isEmpty(body)){
390                                if(!StringUtil.isEmpty(output))
391                                        throw new ApplicationException("if a body is defined for the tag, the attribute [output] is not allowed");
392                                output=body;
393                        }
394                }
395                return SKIP_BODY;
396        }
397
398        @Override
399        public int doEndTag() throws PageException      {
400                switch(action){
401                case ACTION_APPEND: actionAppend();
402                break;
403                case ACTION_WRITE: actionWrite();
404                break;
405                }
406                
407                return EVAL_PAGE;
408        }
409        
410        public void hasBody(boolean hasBody) {
411                if(output==null && hasBody) output="";
412        }
413
414        /**
415         * move source file to destination path or file
416         * @throws PageException
417         */
418        public static void actionMove(PageContext pageContext, lucee.runtime.security.SecurityManager securityManager,
419                        Resource source, String strDestination, int nameconflict,String serverPassword,
420                        Object acl, int mode, String attributes) throws PageException {
421                if(nameconflict==NAMECONFLICT_UNDEFINED) nameconflict=NAMECONFLICT_OVERWRITE;
422                
423                if(source==null)
424                        throw new ApplicationException("attribute source is not defined for tag file");
425                if(StringUtil.isEmpty(strDestination))
426                        throw new ApplicationException("attribute destination is not defined for tag file");
427                
428                Resource destination=toDestination(pageContext,strDestination,source);
429                
430                securityManager.checkFileLocation(pageContext.getConfig(),source,serverPassword);
431                securityManager.checkFileLocation(pageContext.getConfig(),destination,serverPassword);
432                if(source.equals(destination)) return ;
433                
434                // source
435                if(!source.exists())
436                        throw new ApplicationException("source file ["+source.toString()+"] doesn't exist");
437                else if(!source.isFile())
438                        throw new ApplicationException("source file ["+source.toString()+"] is not a file");
439                else if(!source.isReadable() || !source.isWriteable())
440                        throw new ApplicationException("no access to source file ["+source.toString()+"]");
441                
442                // destination
443                if(destination.isDirectory()) destination=destination.getRealResource(source.getName());
444                if(destination.exists()) {
445                        // SKIP
446                        if(nameconflict==NAMECONFLICT_SKIP) return;
447                        // OVERWRITE
448                        else if(nameconflict==NAMECONFLICT_OVERWRITE) destination.delete();
449                        // MAKEUNIQUE
450                        else if(nameconflict==NAMECONFLICT_MAKEUNIQUE) destination=makeUnique(destination);
451                        // ERROR
452                        else throw new ApplicationException("destiniation file ["+destination.toString()+"] already exist");
453                }
454                        
455        
456                try {
457                        source.moveTo(destination);
458                                
459                }
460                catch(Throwable t) {
461                        ExceptionUtil.rethrowIfNecessary(t);
462                        t.printStackTrace();
463                        throw new ApplicationException(t.getMessage());
464                }
465                setACL(destination,acl);
466                setMode(destination,mode);
467        setAttributes(destination,attributes);
468        }
469
470        private static Resource toDestination(PageContext pageContext,String path, Resource source) {
471                if(source!=null && path.indexOf(File.separatorChar)==-1 && path.indexOf('/')==-1 && path.indexOf('\\')==-1) {
472                        Resource p = source.getParentResource();
473                        if(p!=null)return p.getRealResource(path);
474                }
475                return ResourceUtil.toResourceNotExisting(pageContext ,path);
476        }
477
478        /**
479         * copy source file to destination file or path
480         * @throws PageException
481         */
482        public static void actionCopy(PageContext pageContext, lucee.runtime.security.SecurityManager securityManager,
483                        Resource source, String strDestination, int nameconflict,String serverPassword,
484                        Object acl, int mode, String attributes) throws PageException {
485                if(nameconflict==NAMECONFLICT_UNDEFINED) nameconflict=NAMECONFLICT_OVERWRITE;
486                
487                if(source==null)
488                        throw new ApplicationException("attribute source is not defined for tag file");
489                if(StringUtil.isEmpty(strDestination))
490                        throw new ApplicationException("attribute destination is not defined for tag file");
491
492                Resource destination=toDestination(pageContext,strDestination,source);
493                
494                
495                securityManager.checkFileLocation(pageContext.getConfig(),source,serverPassword);
496                securityManager.checkFileLocation(pageContext.getConfig(),destination,serverPassword);
497                
498                // source
499                if(!source.exists())
500                        throw new ApplicationException("source file ["+source.toString()+"] doesn't exist");
501                else if(!source.isFile())
502                        throw new ApplicationException("source file ["+source.toString()+"] is not a file");
503                else if(!source.canRead())
504                        throw new ApplicationException("no access to source file ["+source.toString()+"]");
505                
506                // destination
507                if(destination.isDirectory()) destination=destination.getRealResource(source.getName());
508                if(destination.exists()) {
509                        // SKIP
510                        if(nameconflict==NAMECONFLICT_SKIP) return;
511                        // SKIP
512                        else if(nameconflict==NAMECONFLICT_OVERWRITE) destination.delete();
513                        // MAKEUNIQUE
514                        else if(nameconflict==NAMECONFLICT_MAKEUNIQUE) destination=makeUnique(destination);
515                        // ERROR
516                        else throw new ApplicationException("destiniation file ["+destination.toString()+"] already exist");
517                }
518                
519        try {
520            IOUtil.copy(source,destination);                    
521                }
522                catch(IOException e) {
523                        
524            ApplicationException ae = new ApplicationException("can't copy file ["+source+"] to ["+destination+"]",e.getMessage());
525            ae.setStackTrace(e.getStackTrace());
526            throw ae;
527                }
528                setACL(destination,acl);
529                setMode(destination,mode);
530        setAttributes(destination,attributes);
531        }
532
533        private static void setACL(Resource res,Object acl) throws PageException {
534                String scheme = res.getResourceProvider().getScheme();
535                
536                if("s3".equalsIgnoreCase(scheme)){
537                        S3Resource s3r=(S3Resource) res;
538                        
539                        if(acl!=null){
540                                try {
541                                        // old way
542                                        if(Decision.isString(acl)) {
543                                                s3r.setACL(S3.toIntACL(Caster.toString(acl)));
544                                        }
545                                        // new way
546                                        else {
547                                                StoreSetACL.invoke(s3r, acl);
548                                        }
549                                } catch (IOException e) {
550                                        throw Caster.toPageException(e);
551                                }
552                        }
553                        
554                }
555                // set acl for s3 resource
556                /*if(res instanceof S3Resource) {
557                        ((S3Resource)res).setACL(acl);
558                }*/
559        }
560
561        private static Resource makeUnique(Resource res) {
562
563                String ext=getFileExtension(res);
564                String name=getFileName(res);
565                ext=(ext==null)?"":"."+ext;
566                int count=0;
567                while(res.exists()) {
568                        res=res.getParentResource().getRealResource(name+(++count)+ext);
569                }
570                
571                return res;
572        }
573
574        /**
575         * copy source file to destination file or path
576         * @throws PageException 
577         */
578        private void actionDelete() throws PageException {
579                checkFile(false,false,false,false);
580                setACL(file,acl);
581                try {
582                        if(!file.delete()) throw new ApplicationException("can't delete file ["+file+"]");
583                }
584                catch(Throwable t) {
585                        ExceptionUtil.rethrowIfNecessary(t);
586                        throw new ApplicationException(t.getMessage());
587                }
588        }
589
590        /**
591         * read source file
592         * @throws PageException
593         */
594        private void actionRead() throws PageException {
595                if(variable==null)
596                        throw new ApplicationException("attribute variable is not defined for tag file");
597                checkFile(false,false,true,false);
598                //print.ln(charset);
599                //TextFile tf=new TextFile(file.getAbsolutePath());
600                        
601                try {
602                    pageContext.setVariable(variable,IOUtil.toString(file,charset));
603                }
604        catch (IOException e) {
605                
606                        throw new ApplicationException("can't read file ["+file+"]",e.getMessage());
607                }
608
609        }
610
611        /**
612         * read source file
613         * @throws PageException
614         */
615        private void actionReadBinary() throws PageException {
616                if(variable==null)
617                        throw new ApplicationException("attribute variable is not defined for tag file");
618                checkFile(false,false,true,false);
619                
620                //TextFile tf=new TextFile(file.getAbsolutePath());
621                
622                try {
623            pageContext.setVariable(variable,IOUtil.toBytes(file));
624                }catch (IOException e) {
625                        throw new ApplicationException("can't read binary file ["+source.toString()+"]",e.getMessage());
626                }
627        }
628        
629    /**
630     * write to the source file
631     * @throws PageException
632     */
633    private void actionWrite() throws PageException {
634        if(output==null)
635            throw new ApplicationException("attribute output is not defined for tag file");
636        checkFile(createPath,true,false,true);
637        
638        try {
639                if(output instanceof InputStream)       {
640                        IOUtil.copy(
641                                        (InputStream)output,
642                                        file,
643                                        false);
644                }
645                else if(Decision.isCastableToBinary(output,false))      {
646                        IOUtil.copy(
647                                        new ByteArrayInputStream(Caster.toBinary(output)), 
648                                        file,
649                                        true);
650                }
651                else {
652                        String content=Caster.toString(output);
653                        if(fixnewline)content=doFixNewLine(content);
654                        if(addnewline) content+=SystemUtil.getOSSpecificLineSeparator();
655                        
656                IOUtil.write(file,content,charset,false);
657                        
658                }    
659        } 
660        catch (UnsupportedEncodingException e) {
661            throw new ApplicationException("Unsupported Charset Definition ["+charset+"]",e.getMessage());
662        }
663        catch (IOException e) {
664            
665            throw new ApplicationException("can't write file "+file.getAbsolutePath(),e.getMessage());
666        }
667        setACL(file,acl);
668        setMode(file,mode);
669        setAttributes(file,attributes);
670    }
671    
672    /**
673     * write to the source file
674     * @throws PageException
675     */
676    private void actionTouch() throws PageException {
677        checkFile(createPath,true,true,true);
678        
679        try {
680            ResourceUtil.touch(file);
681        } 
682        catch (IOException e) {
683            
684            throw new ApplicationException("can't touch file "+file.getAbsolutePath(),e.getMessage());
685        }
686        
687        setACL(file,acl);
688        setMode(file,mode);
689        setAttributes(file,attributes);
690    }
691    
692    
693
694        /**
695         * append data to source file
696         * @throws PageException
697         */
698        private void actionAppend() throws PageException {
699                if(output==null)
700                        throw new ApplicationException("attribute output is not defined for tag file");
701                checkFile(createPath,true,false,true);
702                
703        try {
704
705            if(!file.exists()) file.createNewFile();
706            String content=Caster.toString(output);
707            if(fixnewline)content=doFixNewLine(content);
708                if(addnewline) content+=SystemUtil.getOSSpecificLineSeparator();
709            IOUtil.write(file,content,charset,true);
710                
711        } 
712                catch (UnsupportedEncodingException e) {
713            throw new ApplicationException("Unsupported Charset Definition ["+charset+"]",e.getMessage());
714        }
715        catch (IOException e) {
716            throw new ApplicationException("can't write file",e.getMessage());
717        }
718        setACL(file,acl);
719                setMode(file,mode);
720        setAttributes(file,attributes);
721        }
722
723    private String doFixNewLine(String content) {
724                // TODO replace new line with system new line
725                return content;
726        }
727
728        /**
729         * list all files and directories inside a directory
730         * @throws PageException
731         */
732        private void actionInfo() throws PageException {
733                
734                if(variable==null)
735                        throw new ApplicationException("attribute variable is not defined for tag file");
736                checkFile(false,false,false,false);
737                
738                Struct sct =new StructImpl();
739                pageContext.setVariable(variable,sct);
740                
741                // fill data to query
742                sct.setEL(KeyConstants._name,file.getName());
743                sct.setEL(KeyConstants._size,Long.valueOf(file.length()));
744                sct.setEL(KeyConstants._type,file.isDirectory()?"Dir":"File");
745                sct.setEL("dateLastModified",new DateTimeImpl(pageContext,file.lastModified(),false));
746                sct.setEL("attributes",getFileAttribute(file));
747                if(SystemUtil.isUnix())sct.setEL(KeyConstants._mode,new ModeObjectWrap(file));
748        
749                try {           
750                        BufferedImage bi = ImageUtil.toBufferedImage(file, null);
751            if(bi!=null) {
752                    Struct img =new StructImpl();
753                    img.setEL(KeyConstants._width,new Double(bi.getWidth()));
754                    img.setEL(KeyConstants._height,new Double(bi.getHeight()));
755                    sct.setEL(KeyConstants._img,img);
756            }
757        } 
758                catch (Throwable t) {
759                        ExceptionUtil.rethrowIfNecessary(t);
760                }
761        }
762
763        private static String getFileAttribute(Resource file){
764                return  file.exists() && !file.canWrite() ? "R".concat(file.isHidden() ? "H" : "") : file.isHidden() ? "H" : "";
765        }
766        
767        /**
768         * read source file
769         * @throws PageException
770         */
771
772        public void actionUpload() throws PageException {
773                FormItem item=getFormItem(pageContext,filefield);
774                Struct cffile = _actionUpload(pageContext,securityManager,item,strDestination,nameconflict,accept,strict,mode,attributes,acl,serverPassword);
775                if(StringUtil.isEmpty(result)) {
776            pageContext.undefinedScope().set(KeyConstants._file,cffile);
777                    pageContext.undefinedScope().set("cffile",cffile);
778        }
779        else {
780            pageContext.setVariable(result,cffile);
781        }
782        }
783        
784        
785        public static Struct actionUpload(PageContext pageContext,lucee.runtime.security.SecurityManager securityManager,String filefield,
786                        String strDestination,int nameconflict,String accept,boolean strict,int mode,String attributes,Object acl,String serverPassword) throws PageException {
787                FormItem item=getFormItem(pageContext,filefield);
788                return _actionUpload(pageContext,securityManager,item,strDestination,nameconflict,accept,strict,mode,attributes,acl,serverPassword);
789        }
790        
791        public void actionUploadAll() throws PageException {
792                Array arr=actionUploadAll(pageContext,securityManager,strDestination,nameconflict,accept,strict,mode,attributes,acl,serverPassword);
793                if(StringUtil.isEmpty(result)) {
794                        Struct sct;
795                        if(arr!=null && arr.size()>0) sct=(Struct) arr.getE(1);
796                        else sct=new StructImpl();
797                        
798            pageContext.undefinedScope().set(KeyConstants._file,sct);
799                    pageContext.undefinedScope().set("cffile",sct);
800        }
801        else {
802            pageContext.setVariable(result,arr);
803        }
804        }
805        
806
807        public static Array actionUploadAll(PageContext pageContext,lucee.runtime.security.SecurityManager securityManager,
808                        String strDestination,int nameconflict,String accept,boolean strict,int mode,String attributes,Object acl,String serverPassword) throws PageException {
809                FormItem[] items=getFormItems(pageContext);
810                Struct sct=null;
811                Array arr=new ArrayImpl();
812                for(int i=0;i<items.length;i++){
813                        sct = _actionUpload(pageContext,securityManager,items[i],strDestination,nameconflict,accept,strict,mode,attributes,acl,serverPassword);
814                        arr.appendEL(sct);
815                }
816                return arr;
817        }
818        
819        private static synchronized Struct _actionUpload(PageContext pageContext, lucee.runtime.security.SecurityManager securityManager, 
820                        FormItem formItem,String strDestination,int nameconflict,String accept,boolean strict,int mode,String attributes,Object acl,String serverPassword) throws PageException {
821                if(nameconflict==NAMECONFLICT_UNDEFINED) nameconflict=NAMECONFLICT_ERROR;
822
823                boolean fileWasRenamed=false;
824                boolean fileWasAppended=false;
825                boolean fileExisted=false;
826                boolean fileWasOverwritten=false;
827                
828                
829                String contentType=formItem.getContentType();
830                
831                
832                // set cffile struct
833                Struct cffile=new StructImpl();
834        
835        long length = formItem.getResource().length();
836                cffile.set("timecreated",new DateTimeImpl(pageContext.getConfig()));
837                cffile.set("timelastmodified",new DateTimeImpl(pageContext.getConfig()));
838                cffile.set("datelastaccessed",new DateImpl(pageContext));
839                cffile.set("oldfilesize",Long.valueOf(length));
840                cffile.set("filesize",Long.valueOf(length));
841                cffile.set("contenttype",ListFirst.call(pageContext,contentType,"/"));
842                cffile.set("contentsubtype",ListLast.call(pageContext,contentType,"/"));
843                
844                // client file
845                String strClientFile=formItem.getName();
846                while(strClientFile.indexOf('\\')!=-1)
847                        strClientFile=strClientFile.replace('\\','/');
848                Resource clientFile=pageContext.getConfig().getResource(strClientFile);
849                String clientFileName=clientFile.getName();
850                
851                // check file type
852                checkContentType(contentType,accept,getFileExtension(clientFile),strict);
853                
854                        
855                        //String dir=clientFile.getParent();
856                        //dir=correctDirectory(dir);
857                
858                        cffile.set("clientdirectory",getParent(clientFile));
859                        cffile.set("clientfile",clientFile.getName());
860                        cffile.set("clientfileext",getFileExtension(clientFile));
861                        cffile.set("clientfilename",getFileName(clientFile));
862                
863            // check destination
864            if(StringUtil.isEmpty(strDestination))
865                throw new ApplicationException("attribute destination is not defined in tag file");
866
867            
868            Resource destination=toDestination(pageContext,strDestination,null);
869            
870                securityManager.checkFileLocation(pageContext.getConfig(),destination,serverPassword);
871                
872            if(destination.isDirectory()) 
873                destination=destination.getRealResource(clientFileName);
874            else if(!destination.exists() && (strDestination.endsWith("/") || strDestination.endsWith("\\"))) 
875                destination=destination.getRealResource(clientFileName);
876            else if(!clientFileName.equalsIgnoreCase(destination.getName())) {
877                if(ResourceUtil.getExtension(destination, null)==null)
878                        destination=destination.getRealResource(clientFileName);
879                else 
880                        fileWasRenamed=true;
881            }
882            
883            // check parent destination -> directory of the desinatrion
884            Resource parentDestination=destination.getParentResource();
885            
886            if(!parentDestination.exists()) {
887                Resource pp = parentDestination.getParentResource();
888                if(pp==null || !pp.exists()) 
889                        throw new ApplicationException("attribute destination has an invalid value ["+destination+"], directory ["+parentDestination+"] doesn't exist");
890                try {
891                                parentDestination.createDirectory(true);
892                        }
893                        catch (IOException e) {
894                                throw Caster.toPageException(e);
895                        } 
896            }
897            else if(!parentDestination.canWrite())
898                throw new ApplicationException("can't write to destination directory ["+parentDestination+"], no access to write");
899            
900            // set server variables
901                cffile.set("serverdirectory",getParent(destination));
902                cffile.set("serverfile",destination.getName());
903                cffile.set("serverfileext",getFileExtension(destination));
904                cffile.set("serverfilename",getFileName(destination));
905                cffile.set("attemptedserverfile",destination.getName());
906            
907                
908            // check nameconflict
909            if(destination.exists()) {
910                fileExisted=true;
911                if(nameconflict==NAMECONFLICT_ERROR) {
912                        throw new ApplicationException("destination file ["+destination+"] already exist");
913                }
914                else if(nameconflict==NAMECONFLICT_SKIP) {
915                                cffile.set("fileexisted",Caster.toBoolean(fileExisted));
916                                cffile.set("filewasappended",Boolean.FALSE);
917                                cffile.set("filewasoverwritten",Boolean.FALSE);
918                                cffile.set("filewasrenamed",Boolean.FALSE);
919                                cffile.set("filewassaved",Boolean.FALSE);
920                        return cffile;
921                }
922                else if(nameconflict==NAMECONFLICT_MAKEUNIQUE) {
923                        destination=makeUnique(destination);
924                        fileWasRenamed=true;
925                        
926                                //if(fileWasRenamed) {
927                                cffile.set("serverdirectory",getParent(destination));
928                                cffile.set("serverfile",destination.getName());
929                                cffile.set("serverfileext",getFileExtension(destination));
930                                cffile.set("serverfilename",getFileName(destination));
931                                cffile.set("attemptedserverfile",destination.getName());        
932                                //}
933                }
934                else if(nameconflict==NAMECONFLICT_OVERWRITE) {
935                        //fileWasAppended=true; 
936                        fileWasOverwritten=true;
937                        if(!destination.delete())
938                                if(destination.exists()) // hier hatte ich concurrent problem das damit ausgeraeumt ist
939                                        throw new ApplicationException("can't delete destination file ["+destination+"]");
940                }
941                // for "overwrite" no action is neded
942                
943            }
944            
945                        try {
946                                destination.createNewFile();
947                                IOUtil.copy(formItem.getResource(),destination);
948                        }
949                        catch(Throwable t) {
950                                throw Caster.toPageException(t);
951                        }
952                        
953                        // Set cffile/file struct
954                        
955                        cffile.set("fileexisted",Caster.toBoolean(fileExisted));
956                        cffile.set("filewasappended",Caster.toBoolean(fileWasAppended));
957                        cffile.set("filewasoverwritten",Caster.toBoolean(fileWasOverwritten));
958                        cffile.set("filewasrenamed",Caster.toBoolean(fileWasRenamed));
959                        cffile.set("filewassaved",Boolean.TRUE);
960                        
961
962                        setACL(destination,acl);
963                        setMode(destination,mode);
964                setAttributes(destination, attributes);
965                return cffile;
966        }
967
968        /**
969         * check if the content ii ok
970         * @param contentType 
971         * @throws PageException
972         */
973        private static void checkContentType(String contentType,String accept,String ext,boolean strict) throws PageException {
974                
975                if(!StringUtil.isEmpty(ext,true)){
976                        ext=ext.trim().toLowerCase();
977                        if(ext.startsWith("*."))ext=ext.substring(2);
978                        if(ext.startsWith("."))ext=ext.substring(1);
979                }
980                else ext=null;
981                
982                if(StringUtil.isEmpty(accept,true)) return;
983                
984                
985                MimeType mt = MimeType.getInstance(contentType),sub;
986                
987                Array whishedTypes=ListUtil.listToArrayRemoveEmpty(accept,',');
988                int len=whishedTypes.size();
989                for(int i=1;i<=len;i++) {
990                        String whishedType=Caster.toString(whishedTypes.getE(i)).trim().toLowerCase();
991                        if(whishedType.equals("*")) return;
992                        // check mimetype
993                        if(ListUtil.len(whishedType, "/", true)==2){
994                                sub=MimeType.getInstance(whishedType);
995                                if(mt.match(sub)) return;
996                        }
997                        
998                        // check extension
999                        if(ext!=null && !strict){
1000                                if(whishedType.startsWith("*."))whishedType=whishedType.substring(2);
1001                                if(whishedType.startsWith("."))whishedType=whishedType.substring(1);
1002                                if(ext.equals(whishedType)) return;
1003                        }
1004                }
1005                throw new ApplicationException("The MIME type of the uploaded file ["+contentType+"] was not accepted by the server.","only this ["+accept+"] mime type are accepted");
1006        }
1007
1008        /**
1009         * rreturn fileItem matching to filefiled definition or throw a exception
1010         * @return FileItem
1011         * @throws ApplicationException
1012         */
1013        private static FormItem getFormItem(PageContext pageContext, String filefield) throws PageException {
1014                // check filefield
1015                if(StringUtil.isEmpty(filefield)){
1016                        FormItem[] items = getFormItems(pageContext);
1017                        if(ArrayUtil.isEmpty(items))
1018                                throw new ApplicationException("no file send with this form");
1019                        return items[0];
1020                }
1021                        
1022                PageException pe = pageContext.formScope().getInitException();
1023                if(pe!=null) throw pe;
1024                lucee.runtime.type.scope.Form upload = pageContext.formScope();
1025                FormItem fileItem = upload.getUploadResource(filefield);
1026                if(fileItem==null) {
1027                        FormItem[] items = upload.getFileItems();
1028                        StringBuilder sb=new StringBuilder();
1029                        for(int i=0;i<items.length;i++){
1030                                if(i!=0) sb.append(", ");
1031                                sb.append(items[i].getFieldName());
1032                        }
1033                        String add=".";
1034                        if(sb.length()>0) add=", valid field names are ["+sb+"].";
1035                        
1036                        
1037                        if(pageContext.formScope().get(filefield,null)==null) 
1038                                throw new ApplicationException("form field ["+filefield+"] is not a file field"+add);
1039                        throw new ApplicationException("form field ["+filefield+"] doesn't exist or has no content"+add);
1040                }
1041                
1042                return fileItem;
1043        }
1044        
1045        private static FormItem[] getFormItems(PageContext pageContext) throws PageException {
1046                PageException pe = pageContext.formScope().getInitException();
1047                if(pe!=null) throw pe;
1048                
1049                Form scope = pageContext.formScope();
1050                return scope.getFileItems();
1051        }
1052        
1053        
1054        /**
1055         * get file extension of a file object
1056         * @param file file object
1057         * @return extnesion
1058         */
1059        private static String getFileExtension(Resource file) {
1060                String name=file.getName();
1061                String[] arr;
1062                try {
1063                        arr = ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(name, '.'));
1064                } catch (PageException e) {
1065                        arr=null;
1066                }
1067                if(arr.length<2) return "";
1068                
1069                return arr[arr.length-1];
1070        }
1071        
1072        /**
1073         * get file name of a file object without extension
1074         * @param file file object
1075         * @return name of the file 
1076         */
1077        private static String getFileName(Resource file) {
1078                String name=file.getName();
1079                int pos=name.lastIndexOf(".");
1080                
1081                if(pos==-1)return name;
1082                return name.substring(0,pos);
1083        }
1084        
1085        /*private String correctDirectory(Resource resource) {
1086                if(StringUtil.isEmpty(resource,true)) return "";
1087                resource=resource.trim();
1088                if((StringUtil.endsWith(resource, '/') || StringUtil.endsWith(resource, '\\')) && resource.length()>1) {
1089                        return resource.substring(0,resource.length()-1);
1090                }
1091                return resource;
1092        }*/
1093        
1094        private static String getParent(Resource res) {
1095                Resource parent = res.getParentResource();
1096                //print.out("res:"+res);
1097                //print.out("parent:"+parent);
1098                if(parent==null) return "";
1099                return ResourceUtil.getCanonicalPathEL(parent);
1100        }
1101        
1102
1103        private void checkFile(boolean createParent, boolean create, boolean canRead, boolean canWrite) throws PageException {
1104                if(file==null)
1105                        throw new ApplicationException("attribute file is not defined for tag file");
1106
1107                securityManager.checkFileLocation(pageContext.getConfig(),file,serverPassword);
1108                if(!file.exists()) {
1109                        if(create) {
1110                                Resource parent=file.getParentResource();
1111                                if(parent!=null && !parent.exists()) {
1112                                        if(createParent) parent.mkdirs();
1113                                        else throw new ApplicationException("parent directory for ["+file+"] doesn't exist");
1114                                }
1115                                try {
1116                                        file.createFile(false);
1117                                } catch (IOException e) {
1118                                        throw new ApplicationException("invalid file ["+file+"]",e.getMessage());
1119                                }
1120                        }
1121                        else if(!file.isFile()) 
1122                                throw new ApplicationException("source file ["+file.toString()+"] is not a file");
1123                        else 
1124                                throw new ApplicationException("source file ["+file.toString()+"] doesn't exist");
1125                }
1126                else if(!file.isFile())
1127                        throw new ApplicationException("source file ["+file.toString()+"] is not a file");
1128        else if(canRead &&!file.canRead())
1129            throw new ApplicationException("no read access to source file ["+file.toString()+"]");
1130        else if(canWrite && !file.canWrite())
1131            throw new ApplicationException("no write access to source file ["+file.toString()+"]");
1132        
1133        }
1134
1135        /**
1136         * set attributes on file
1137     * @param file
1138         * @throws PageException
1139     */
1140    private static void setAttributes(Resource file,String attributes) throws PageException {
1141        if(!SystemUtil.isWindows() || StringUtil.isEmpty(attributes)) return;
1142        try {
1143                ResourceUtil.setAttribute(file, attributes);
1144        } 
1145        catch (IOException e) {
1146            throw new ApplicationException("can't change attributes of file "+file,e.getMessage());
1147        }
1148    }
1149
1150    /**
1151         * change mode of given file
1152     * @param file
1153         * @throws ApplicationException
1154     */
1155    private static void setMode(Resource file,int mode) throws ApplicationException {
1156        if(mode==-1 || SystemUtil.isWindows()) return;
1157        try {
1158                file.setMode(mode);
1159            //FileUtil.setMode(file,mode);
1160        } catch (IOException e) {
1161            throw new ApplicationException("can't change mode of file "+file,e.getMessage());
1162        }
1163    }
1164
1165        /**
1166         * @param fixnewline the fixnewline to set
1167         */
1168        public void setFixnewline(boolean fixnewline) {
1169                this.fixnewline = fixnewline;
1170        }
1171}