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