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.type.scope;
020
021import java.io.BufferedReader;
022import java.io.File;
023import java.io.InputStream;
024import java.io.UnsupportedEncodingException;
025import java.util.ArrayList;
026import java.util.Iterator;
027import java.util.Map;
028
029import javax.servlet.ServletInputStream;
030import javax.servlet.http.HttpServletRequest;
031
032import lucee.commons.collection.MapFactory;
033import lucee.commons.io.IOUtil;
034import lucee.commons.io.res.Resource;
035import lucee.commons.lang.ByteNameValuePair;
036import lucee.commons.lang.StringUtil;
037import lucee.commons.net.URLItem;
038import lucee.runtime.PageContext;
039import lucee.runtime.PageContextImpl;
040import lucee.runtime.config.ConfigImpl;
041import lucee.runtime.engine.ThreadLocalPageContext;
042import lucee.runtime.exp.PageException;
043import lucee.runtime.listener.ApplicationContext;
044import lucee.runtime.net.http.ServletInputStreamDummy;
045import lucee.runtime.op.Caster;
046import lucee.runtime.type.Array;
047import lucee.runtime.type.util.ArrayUtil;
048import lucee.runtime.type.util.KeyConstants;
049import lucee.runtime.type.util.ListUtil;
050
051import org.apache.commons.fileupload.FileItemFactory;
052import org.apache.commons.fileupload.FileItemIterator;
053import org.apache.commons.fileupload.FileItemStream;
054import org.apache.commons.fileupload.disk.DiskFileItem;
055import org.apache.commons.fileupload.disk.DiskFileItemFactory;
056import org.apache.commons.fileupload.servlet.ServletFileUpload;
057import org.apache.commons.fileupload.servlet.ServletRequestContext;
058
059
060/**
061 * Form Scope
062 */
063public final class FormImpl extends ScopeSupport implements Form,ScriptProtected {
064        
065
066        private byte EQL=61;
067        private byte NL=10;
068        private byte AMP=38;
069        
070        
071        private Map<String,Item> _fileItems=MapFactory.<String,Item>getConcurrentMap();
072        private Exception initException=null;
073
074    private String encoding=null;
075    private int scriptProtected=ScriptProtected.UNDEFINED;
076    private static final URLItem[] empty=new URLItem[0];
077        //private static final ResourceFilter FILTER = new ExtensionResourceFilter(".upload",false);
078    private URLItem[] raw=empty;
079    private static int count=1;
080
081    private static final int HEADER_TEXT_PLAIN=0;
082    private static final int HEADER_MULTIPART_FORM_DATA=1;
083    private static final int HEADER_APP_URL_ENC=2;
084        private int headerType=-1;
085    
086        /**
087         * standart class Constructor
088         */
089        public FormImpl() {
090                super(true,"form",SCOPE_FORM);
091        }
092
093    @Override
094    public String getEncoding() {
095        return encoding;
096    }
097    
098    @Override
099    public void setEncoding(ApplicationContext ac,String encoding) throws UnsupportedEncodingException {
100        encoding=encoding.trim().toUpperCase();
101        if(encoding.equals(this.encoding)) return;
102        this.encoding = encoding;
103        if(!isInitalized()) return;
104        fillDecoded(raw,encoding,isScriptProtected(),ac.getSameFieldAsArray(Scope.SCOPE_FORM));
105        setFieldNames();
106    }
107
108        @Override
109        public void initialize(PageContext pc) {
110                
111                
112                if(encoding==null)encoding=((PageContextImpl)pc).getWebCharset().name();
113                
114                if(scriptProtected==ScriptProtected.UNDEFINED) {
115                        scriptProtected=((pc.getApplicationContext().getScriptProtect()&ApplicationContext.SCRIPT_PROTECT_FORM)>0)?
116                                        ScriptProtected.YES:ScriptProtected.NO;
117                }
118        super.initialize(pc);
119                
120        String contentType=pc. getHttpServletRequest().getContentType();
121        
122                if(contentType==null) return;
123                contentType=StringUtil.toLowerCase(contentType);
124                if(contentType.startsWith("multipart/form-data")) {
125                        headerType=HEADER_MULTIPART_FORM_DATA;
126                        initializeMultiPart(pc,isScriptProtected());
127                }
128                else if(contentType.startsWith("text/plain")) {
129                        headerType=HEADER_TEXT_PLAIN;
130                        initializeUrlEncodedOrTextPlain(pc,'\n',isScriptProtected());
131                }
132                else if(contentType.startsWith("application/x-www-form-urlencoded")) {
133                        headerType=HEADER_APP_URL_ENC;
134                        initializeUrlEncodedOrTextPlain(pc,'&',isScriptProtected());
135                }
136                setFieldNames();
137        }
138
139    void setFieldNames() {
140        if(size()>0) {
141                setEL(KeyConstants._fieldnames,ListUtil.arrayToList(keys(), ","));
142        }
143    }
144
145
146    private void initializeMultiPart(PageContext pc, boolean scriptProteced) {
147        // get temp directory
148        Resource tempDir = ((ConfigImpl)pc.getConfig()).getTempDirectory();
149        Resource tempFile;
150        
151        // Create a new file upload handler
152        final String encoding=getEncoding();
153        FileItemFactory factory = tempDir instanceof File? 
154                        new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD,(File)tempDir):
155                                new DiskFileItemFactory();
156        
157        ServletFileUpload upload = new ServletFileUpload(factory);
158        upload.setHeaderEncoding(encoding);
159        //ServletRequestContext c = new ServletRequestContext(pc.getHttpServletRequest());
160        
161        
162        HttpServletRequest req = pc.getHttpServletRequest();
163        ServletRequestContext context = new ServletRequestContext(req) {
164                public String getCharacterEncoding() {
165                        return encoding;
166                }
167        };
168        
169        // Parse the request
170        try {
171                FileItemIterator iter = upload.getItemIterator(context);
172                //byte[] value;
173                InputStream is;
174                ArrayList<URLItem> list=new ArrayList<URLItem>();
175                String fileName;
176                        while (iter.hasNext()) {
177                            FileItemStream item = iter.next();
178
179                            is=IOUtil.toBufferedInputStream(item.openStream());
180                            if (item.getContentType()==null || StringUtil.isEmpty(item.getName())) {
181                                list.add(new URLItem(item.getFieldName(),new String(IOUtil.toBytes(is),encoding),false));            
182                            } 
183                            else {
184                                fileName=getFileName();
185                                tempFile=tempDir.getRealResource(fileName);
186                                _fileItems.put(fileName,
187                                                new Item(tempFile,item.getContentType(),item.getName(),item.getFieldName()));
188                                        String value=tempFile.toString();
189                                IOUtil.copy(is, tempFile,true);
190                                    list.add(new URLItem(item.getFieldName(),value,false));          
191                            }       
192                        }
193                        
194                        
195                        raw= list.toArray(new URLItem[list.size()]);
196                        fillDecoded(raw,encoding,scriptProteced,pc.getApplicationContext().getSameFieldAsArray(SCOPE_FORM));
197                } 
198        catch (Exception e) {
199                        
200                //throw new PageRuntimeException(Caster.toPageException(e));
201                fillDecodedEL(new URLItem[0],encoding,scriptProteced,pc.getApplicationContext().getSameFieldAsArray(SCOPE_FORM));
202                        initException=e;
203                }
204        }
205    
206    private static String getFileName() { 
207                return "tmp-"+(count++)+".upload";
208        }
209
210        /*private void initializeMultiPart(PageContext pc, boolean scriptProteced) {
211
212        File tempDir=FileWrapper.toFile(pc.getConfig().getTempDirectory());
213        
214        // Create a factory for disk-based file items
215        DiskFileItemFactory factory = new DiskFileItemFactory(-1,tempDir);
216
217        // Create a new file upload handler
218        ServletFileUpload upload = new ServletFileUpload(factory);
219        
220        upload.setHeaderEncoding(getEncoding());
221        
222                //FileUpload fileUpload=new FileUpload(new DiskFileItemFactory(0,tempDir));
223                java.util.List list;
224                try {
225                        list = upload.parseRequest(pc.getHttpServletRequest());
226            raw=new ByteNameValuePair[list.size()];
227                        
228                        for(int i=0;i<raw.length;i++) {
229                                DiskFileItem val=(DiskFileItem) list.get(i);
230                                if(val.isFormField()) {
231                    raw[i]=new ByteNameValuePair(getBytes(val.getFieldName()),val.get(),false);
232                }
233                                else {
234                                        print.out("-------------------------------");
235                                        print.out("fieldname:"+val.getFieldName());
236                                        print.out("name:"+val.getName());
237                                        print.out("formfield:"+val.isFormField());
238                                        print.out("memory:"+val.isInMemory());
239                                        print.out("exist:"+val.getStoreLocation().getCanonicalFile().exists());
240                                        
241                                        fileItems.put(val.getFieldName().toLowerCase(),val);
242                                        
243                    raw[i]=new ByteNameValuePair(getBytes(val.getFieldName()),val.getStoreLocation().getCanonicalFile().toString().getBytes(),false);
244                    //raw.put(val.getFieldName(),val.getStoreLocation().getCanonicalFile().toString());
245                                }
246                        }
247            fillDecoded(raw,encoding,scriptProteced);
248                } 
249        catch (Exception e) {
250                
251                //throw new PageRuntimeException(Caster.toPageException(e));
252                fillDecodedEL(new ByteNameValuePair[0],encoding,scriptProteced);
253                        initException=e;
254                }
255        }*/
256    
257        private void initializeUrlEncodedOrTextPlain(PageContext pc, char delimiter, boolean scriptProteced) {
258                BufferedReader reader=null;
259                try {
260                        reader = pc.getHttpServletRequest().getReader();
261                        raw=setFrom___(IOUtil.toString(reader,false),delimiter);
262                        fillDecoded(raw,encoding,scriptProteced,pc.getApplicationContext().getSameFieldAsArray(SCOPE_FORM));
263                } 
264        catch (Exception e) {
265                
266                fillDecodedEL(new URLItem[0],encoding,scriptProteced,pc.getApplicationContext().getSameFieldAsArray(SCOPE_FORM));
267                        initException=e;
268        }
269        finally {
270                IOUtil.closeEL(reader);
271        }
272        }
273        
274
275        @Override
276        public void release() {
277                release(ThreadLocalPageContext.get());
278        }
279
280        @Override
281        public void release(PageContext pc) {
282                super.release(pc);
283        encoding=null;
284        scriptProtected=ScriptProtected.UNDEFINED;
285        raw=empty;
286                
287                if(!_fileItems.isEmpty()) {
288                        Iterator<Item> it = _fileItems.values().iterator();
289                        Item item;
290                        while(it.hasNext()) {
291                                item=it.next();
292                                item.getResource().delete();
293                        }
294                        _fileItems.clear();
295                }
296                initException=null;
297                
298        }
299
300        @Override
301        public FormItem[] getFileItems() {
302                if(_fileItems==null || _fileItems.isEmpty()) return new FormImpl.Item[0];
303                
304                Iterator<Item> it = _fileItems.values().iterator();
305                FormImpl.Item[] rtn=new FormImpl.Item[_fileItems.size()];
306                int index=0;
307                while(it.hasNext()){
308                        rtn[index++]=it.next();
309                }
310                return rtn;
311        }
312        
313        
314        public DiskFileItem getFileUpload(String key) {
315                return null;
316        }
317        
318        public FormItem getUploadResource(String key) {
319                key=key.trim();
320                String lcKey = StringUtil.toLowerCase(key);
321                
322                if(_fileItems==null || _fileItems.isEmpty()) return null;
323                
324                Iterator<Entry<String, Item>> it = _fileItems.entrySet().iterator();
325                Entry<String, Item> entry;
326                Item item;
327                while(it.hasNext()) {
328                        entry=it.next();
329                        item = entry.getValue();
330                        // x
331                        if(item.getFieldName().equalsIgnoreCase(key)) return item;
332                        
333                        // form.x
334                        if(lcKey.startsWith("form.")) {
335                                lcKey=lcKey.substring(5).trim();
336                                if(item.getFieldName().equalsIgnoreCase(lcKey))return item;
337                        }
338                        
339                        // form . x
340                        try {
341                                Array array =  ListUtil.listToArray(lcKey, '.');
342                                if(array.size()>1 && array.getE(1).toString().trim().equals("form")) {
343                                        array.removeE(1);
344                                        lcKey=ListUtil.arrayToList(array, ".").trim();
345                                        if(item.getFieldName().equalsIgnoreCase(lcKey))return item;
346                                }
347                        } 
348                        catch (PageException e) {}
349                        
350                        // /file.tmp
351                        if(item.getResource().getAbsolutePath().equalsIgnoreCase(key))return item;
352                        if(entry.getKey().equalsIgnoreCase(key))return item;
353                }
354                return null;
355        }
356
357        @Override
358        public PageException getInitException() {
359                if(initException!=null)
360                        return Caster.toPageException(initException);
361                return null;
362        }
363
364        @Override
365        public void setScriptProtecting(ApplicationContext ac,boolean scriptProtected) {
366                int _scriptProtected = scriptProtected?ScriptProtected.YES:ScriptProtected.NO;
367                if(isInitalized() && _scriptProtected!=this.scriptProtected) {
368                        fillDecodedEL(raw,encoding,scriptProtected,ac.getSameFieldAsArray(SCOPE_FORM));
369                        setFieldNames();
370                }
371                this.scriptProtected=_scriptProtected;
372        }
373        
374        public void reinitialize(ApplicationContext ac) {
375                if(isInitalized()) {
376                        
377                        if(scriptProtected==ScriptProtected.UNDEFINED) {
378                                scriptProtected=((ac.getScriptProtect()&ApplicationContext.SCRIPT_PROTECT_FORM)>0)?
379                                                ScriptProtected.YES:ScriptProtected.NO;
380                        }       
381                        fillDecodedEL(raw,encoding,isScriptProtected(),ac.getSameFieldAsArray(SCOPE_FORM));
382                        setFieldNames();
383                }
384        }
385
386        @Override
387        public boolean isScriptProtected() {
388                return scriptProtected==ScriptProtected.YES ;
389        }
390
391        /**
392         * @return the raw
393         */
394        public URLItem[] getRaw() {
395                return raw;
396        }
397
398        public void addRaw(ApplicationContext ac,URLItem[] raw) {
399                URLItem[] nr=new URLItem[this.raw.length+raw.length];
400                for(int i=0;i<this.raw.length;i++) {
401                        nr[i]=this.raw[i];
402                }
403                for(int i=0;i<raw.length;i++) {
404                        nr[this.raw.length+i]=raw[i];
405                }
406                this.raw=nr;
407                
408                if(!isInitalized()) return;
409        fillDecodedEL(this.raw,encoding,isScriptProtected(),ac.getSameFieldAsArray(SCOPE_FORM));
410        setFieldNames();
411        }
412
413        private class Item implements FormItem {
414                Resource resource;
415                String contentType;
416                String name;
417                private String fieldName;
418                
419                public Item(Resource resource, String contentType,String name, String fieldName) {
420                        this.fieldName = fieldName;
421                        this.name = name;
422                        this.resource = resource;
423                        this.contentType = contentType;
424                }
425                /**
426                 * @return the resource
427                 */
428                public Resource getResource() {
429                        return resource;
430                }
431                /**
432                 * @return the contentType
433                 */
434                public String getContentType() {
435                        return contentType;
436                }
437                /**
438                 * @return the name
439                 */
440                public String getName() {
441                        return name;
442                }
443                /**
444                 * @return the fieldName
445                 */
446                public String getFieldName() {
447                        return fieldName;
448                }
449        }
450
451        /**
452         * @return return content as a http header input stream
453         */
454        public ServletInputStream getInputStream() {
455                if(headerType==HEADER_APP_URL_ENC) {
456                        return new ServletInputStreamDummy(toBarr(raw,AMP));
457                }
458                else if(headerType==HEADER_TEXT_PLAIN) {
459                        return new ServletInputStreamDummy(toBarr(raw,NL));
460                }
461                /*else if(headerType==HEADER_MULTIPART_FORM_DATA) {
462                        return new FormImplInputStream(this);
463                        // TODO
464                }*/
465                return new ServletInputStreamDummy(new byte[]{});
466        }
467
468        private byte[] toBarr(URLItem[] items, byte del) {
469                
470                ByteNameValuePair[] raw=new ByteNameValuePair[items.length];
471                for(int i=0;i<raw.length;i++) {
472                        try {
473                                raw[i]=new ByteNameValuePair(items[i].getName().getBytes("iso-8859-1"),items[i].getValue().getBytes("iso-8859-1"),items[i].isUrlEncoded());
474                        } catch (UnsupportedEncodingException e) {}
475                }
476                
477                int size=0;
478                if(!ArrayUtil.isEmpty(raw)){
479                        for(int i=0;i<raw.length;i++) {
480                                size+=raw[i].getName().length;
481                                size+=raw[i].getValue().length;
482                                size+=2;
483                        }
484                        size--;
485                }
486                byte[] barr = new byte[size],bname,bvalue;
487                int count=0;
488                
489                for(int i=0;i<raw.length;i++) {
490                        bname=raw[i].getName();
491                        bvalue=raw[i].getValue();
492                        // name
493                        for(int y=0;y<bname.length;y++) {
494                                barr[count++]=bname[y];
495                        }
496                        barr[count++]=EQL;
497                        // value
498                        for(int y=0;y<bvalue.length;y++) {
499                                barr[count++]=bvalue[y];
500                        }
501                        if(i+1<raw.length)barr[count++]=del;
502                }
503                return barr;
504        }
505
506}