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.net.http;
020
021import java.io.ByteArrayInputStream;
022import java.io.UnsupportedEncodingException;
023import java.lang.reflect.Method;
024import java.net.InetAddress;
025import java.net.URL;
026import java.nio.charset.Charset;
027import java.util.ArrayList;
028import java.util.Enumeration;
029import java.util.LinkedList;
030import java.util.List;
031
032import javax.servlet.ServletContext;
033import javax.servlet.ServletInputStream;
034import javax.servlet.ServletRequest;
035import javax.servlet.ServletResponse;
036import javax.servlet.http.Cookie;
037import javax.servlet.http.HttpServletRequest;
038import javax.servlet.http.HttpServletResponse;
039
040import lucee.commons.io.CharsetUtil;
041import lucee.commons.io.IOUtil;
042import lucee.commons.lang.ExceptionUtil;
043import lucee.commons.lang.Pair;
044import lucee.commons.lang.StringUtil;
045import lucee.commons.lang.mimetype.MimeType;
046import lucee.commons.net.HTTPUtil;
047import lucee.commons.net.URLDecoder;
048import lucee.commons.net.URLEncoder;
049import lucee.runtime.PageContext;
050import lucee.runtime.PageContextImpl;
051import lucee.runtime.config.Config;
052import lucee.runtime.config.ConfigImpl;
053import lucee.runtime.converter.JavaConverter;
054import lucee.runtime.converter.WDDXConverter;
055import lucee.runtime.engine.ThreadLocalPageContext;
056import lucee.runtime.exp.PageException;
057import lucee.runtime.functions.decision.IsLocalHost;
058import lucee.runtime.interpreter.CFMLExpressionInterpreter;
059import lucee.runtime.interpreter.JSONExpressionInterpreter;
060import lucee.runtime.listener.ApplicationContext;
061import lucee.runtime.op.Caster;
062import lucee.runtime.security.ScriptProtect;
063import lucee.runtime.text.xml.XMLCaster;
064import lucee.runtime.text.xml.XMLUtil;
065import lucee.runtime.type.UDF;
066import lucee.runtime.type.UDFPlus;
067
068import org.xml.sax.InputSource;
069
070public final class ReqRspUtil {
071
072        
073        
074        private static final Object NULL = new Object();
075        private static final Cookie[] EMPTY = new Cookie[0];
076
077
078        public static String get(Pair<String,Object>[] items, String name) {
079                for(int i=0;i<items.length;i++) {
080                        if(items[i].getName().equalsIgnoreCase(name)) 
081                                return Caster.toString(items[i].getValue(),null);
082                }
083                return null;
084        }
085        
086        public static Pair<String,Object>[] add(Pair<String,Object>[] items, String name, Object value) {
087                Pair<String,Object>[] tmp = new Pair[items.length+1];
088                for(int i=0;i<items.length;i++) {
089                        tmp[i]=items[i];
090                }
091                tmp[items.length]=new Pair<String,Object>(name,value);
092                return tmp;
093        }
094        
095        public static Pair<String,Object>[] set(Pair<String,Object>[] items, String name, Object value) {
096                for(int i=0;i<items.length;i++) {
097                        if(items[i].getName().equalsIgnoreCase(name)) {
098                                items[i]=new Pair<String,Object>(name,value);
099                                return items;
100                        }
101                }
102                 return add(items, name, value);
103        }
104
105        /**
106         * return path to itself
107         * @param req
108         */
109        public static String self(HttpServletRequest req) {
110                StringBuffer sb=new StringBuffer(req.getServletPath());
111                String qs=req.getQueryString();
112                if(!StringUtil.isEmpty(qs))sb.append('?').append(qs);
113                return sb.toString();
114        }
115
116        public static void setContentLength(HttpServletResponse rsp, int length) {
117                rsp.setContentLength(length);
118        }
119        public static void setContentLength(HttpServletResponse rsp, long length) {
120                if(length <= Integer.MAX_VALUE){
121                        setContentLength(rsp,(int)length);
122                }
123                else{
124                        rsp.addHeader("Content-Length", Caster.toString(length));
125                }
126        }
127
128        public static Cookie[] getCookies(HttpServletRequest req, Charset charset) {
129                Cookie[] cookies = req.getCookies();
130                
131                if(cookies!=null) {
132                        Cookie cookie;
133                        String tmp;
134                        for(int i=0;i<cookies.length;i++){
135                                cookie=cookies[i];      
136                                // value (is decoded by the servlet engine with iso-8859-1)
137                                if(!StringUtil.isAscii(cookie.getValue())) {
138                                        tmp=encode(cookie.getValue(), "iso-8859-1");
139                                        cookie.setValue(decode(tmp, charset.name(),false));
140                                }
141                                
142                        }
143                }
144                else {
145                        String str = req.getHeader("Cookie");
146                        if(str!=null) {
147                                String[] arr = lucee.runtime.type.util.ListUtil.listToStringArray(str, ';'),tmp;
148                                java.util.List<Cookie> list=new ArrayList<Cookie>();
149                                Cookie c;
150                                for(int i=0;i<arr.length;i++){
151                                        tmp=lucee.runtime.type.util.ListUtil.listToStringArray(arr[i], '=');
152                                        if(tmp.length>0) {
153                                                c=toCookie(dec(tmp[0],charset.name(),false), tmp.length>1?dec(tmp[1],charset.name(),false):"",null);
154                                                if(c!=null)list.add(c);
155                                        }
156                                }
157                                cookies=list.toArray(new Cookie[list.size()]);
158                        }
159                }
160                if(cookies==null) return EMPTY;
161                return cookies;
162        }
163
164
165        public static Cookie toCookie(String name, String value, Cookie defaultValue) {
166                try{
167                        return new Cookie(name,value);
168                }
169                catch(Throwable t){
170                        ExceptionUtil.rethrowIfNecessary(t);
171                        return defaultValue;
172                }
173        }
174
175        public static void setCharacterEncoding(HttpServletResponse rsp,String charset) {
176                try {
177                        Method setCharacterEncoding = rsp.getClass().getMethod("setCharacterEncoding", new Class[0]);
178                        setCharacterEncoding.invoke(rsp, new Object[0]);
179                } 
180                catch (Throwable t) {
181                        throw ExceptionUtil.toRuntimeException(t);
182                }
183        }
184
185        public static String getQueryString(HttpServletRequest req) {
186                //String qs = req.getAttribute("javax.servlet.include.query_string");
187                return req.getQueryString();
188        }
189
190        public static String getHeader(HttpServletRequest request, String name,String defaultValue) {
191                try {
192                        return request.getHeader(name);
193                }
194                catch(Throwable t){
195                        ExceptionUtil.rethrowIfNecessary(t);
196                        return defaultValue;
197                }
198        }
199
200        public static String getHeaderIgnoreCase(PageContext pc, String name,String defaultValue) {
201                String charset = ((PageContextImpl)pc).getWebCharset().name();
202                HttpServletRequest req = pc.getHttpServletRequest();
203                Enumeration e = req.getHeaderNames();
204                String keyDecoded,key;
205                while(e.hasMoreElements()) {
206                        key=e.nextElement().toString();
207                        keyDecoded=ReqRspUtil.decode(key, charset,false);
208                        if(name.equalsIgnoreCase(key) || name.equalsIgnoreCase(keyDecoded))
209                                return ReqRspUtil.decode(req.getHeader(key),charset,false);
210                }
211                return defaultValue;
212        }
213
214        public static List<String> getHeadersIgnoreCase(PageContext pc, String name) {
215                String charset = ((PageContextImpl)pc).getWebCharset().name();
216                HttpServletRequest req = pc.getHttpServletRequest();
217                Enumeration e = req.getHeaderNames();
218                List<String> rtn=new ArrayList<String>();
219                String keyDecoded,key;
220                while(e.hasMoreElements()) {
221                        key=e.nextElement().toString();
222                        keyDecoded=ReqRspUtil.decode(key, charset,false);
223                        if(name.equalsIgnoreCase(key) || name.equalsIgnoreCase(keyDecoded))
224                                rtn.add(ReqRspUtil.decode(req.getHeader(key),charset,false));
225                }
226                return rtn;
227        }
228
229        public static String getScriptName(PageContext pc,HttpServletRequest req) {
230                String sn = StringUtil.emptyIfNull(req.getContextPath())+StringUtil.emptyIfNull(req.getServletPath());
231                if(pc==null)pc=ThreadLocalPageContext.get();
232                if(pc!=null & (
233                                (pc.getApplicationContext().getScriptProtect()&ApplicationContext.SCRIPT_PROTECT_URL)>0 || 
234                                (pc.getApplicationContext().getScriptProtect()&ApplicationContext.SCRIPT_PROTECT_CGI)>0
235                                )) {
236                sn=ScriptProtect.translate(sn);
237        }
238                return sn;
239        }
240
241        private static boolean isHex(char c) {
242                return (c>='0' && c<='9') || (c>='a' && c<='f') || (c>='A' && c<='F');
243        }
244        
245
246        private static String dec(String str, String charset, boolean force) {
247                str=str.trim();
248                if(StringUtil.startsWith(str, '"') && StringUtil.endsWith(str, '"') && str.length()>1)
249                        str=str.substring(1,str.length()-1);
250
251                return decode(str,charset,force);//java.net.URLDecoder.decode(str.trim(), charset);
252        }
253        
254    public static String decode(String str,String charset, boolean force) {
255        try {
256                        return URLDecoder.decode(str, charset,force);
257                } 
258                catch (UnsupportedEncodingException e) {
259                        return str;
260                }
261        }
262    
263    public static String encode(String str,String charset) {
264                try {
265                        return URLEncoder.encode(str, charset);
266                } 
267                catch (UnsupportedEncodingException e) {
268                        return str;
269                }
270        }
271
272    public static String encode(String str,Charset charset) {
273                try {
274                        return URLEncoder.encode(str, charset);
275                } 
276                catch (UnsupportedEncodingException e) {
277                        return str;
278                }
279        }
280
281    public static boolean needEncoding(String str, boolean allowPlus){
282        if(StringUtil.isEmpty(str,false)) return false;
283        
284        int len=str.length();
285        char c;
286        for(int i=0;i<len;i++){
287                c=str.charAt(i);
288                if(c >='0' && c <= '9') continue;
289                if(c >='a' && c <= 'z') continue;
290                if(c >='A' && c <= 'Z') continue;
291                
292                // _-.*
293                if(c =='-') continue;
294                if(c =='_') continue;
295                if(c =='.') continue;
296                if(c =='*') continue;
297                if(c =='/') continue;
298                if(allowPlus && c =='+') continue;
299                
300                if(c =='%') {
301                        if(i+2>=len) return true;
302                        try{
303                                Integer.parseInt(str.substring(i+1,i+3),16);
304                        }
305                        catch(NumberFormatException nfe){
306                                return true;
307                        }
308                        i+=3;
309                        continue;
310                }
311                return true;
312        }
313        return false;
314    }
315    
316    public static boolean needDecoding(String str){
317        if(StringUtil.isEmpty(str,false)) return false;
318        
319        boolean need=false;
320        int len=str.length();
321        char c;
322        for(int i=0;i<len;i++){
323                c=str.charAt(i);
324                if(c >='0' && c <= '9') continue;
325                if(c >='a' && c <= 'z') continue;
326                if(c >='A' && c <= 'Z') continue;
327                
328                // _-.*
329                if(c =='-') continue;
330                if(c =='_') continue;
331                if(c =='.') continue;
332                if(c =='*') continue;
333                if(c =='+') {
334                        need=true;
335                        continue;
336                }
337                
338                if(c =='%') {
339                        if(i+2>=len) return false;
340                        try{
341                                Integer.parseInt(str.substring(i+1,i+3),16);
342                        }
343                        catch(NumberFormatException nfe){
344                                return false;
345                        }
346                        i+=3;
347                        need=true;
348                        continue;
349                }
350                return false;
351        }
352        return need;
353    }
354
355        public static boolean isThis(HttpServletRequest req, String url) { 
356                try {
357                        return isThis(req, HTTPUtil.toURL(url,true));
358                } 
359                catch (Throwable t) {
360                        ExceptionUtil.rethrowIfNecessary(t);
361                        return false;
362                }
363        }
364
365        public static boolean isThis(HttpServletRequest req, URL url) { 
366                try {
367                        // Port
368                        int reqPort=req.getServerPort();
369                        int urlPort=url.getPort();
370                        if(urlPort<=0) urlPort=HTTPUtil.isSecure(url)?443:80;
371                        if(reqPort<=0) reqPort=req.isSecure()?443:80;
372                        if(reqPort!=urlPort) return false;
373                        
374                        // host
375                        String reqHost = req.getServerName();
376                        String urlHost = url.getHost();
377                        if(reqHost.equalsIgnoreCase(urlHost)) return true;
378                        if(IsLocalHost.invoke(reqHost) && IsLocalHost.invoke(reqHost)) return true;
379                        
380                        InetAddress urlAddr = InetAddress.getByName(urlHost);
381                        
382                        InetAddress reqAddr = InetAddress.getByName(reqHost);
383                        if(reqAddr.getHostName().equalsIgnoreCase(urlAddr.getHostName())) return true;
384                        if(reqAddr.getHostAddress().equalsIgnoreCase(urlAddr.getHostAddress())) return true;
385                        
386                        reqAddr = InetAddress.getByName(req.getRemoteAddr());
387                        if(reqAddr.getHostName().equalsIgnoreCase(urlAddr.getHostName())) return true;
388                        if(reqAddr.getHostAddress().equalsIgnoreCase(urlAddr.getHostAddress())) return true;
389                }
390                catch(Throwable t){
391                        ExceptionUtil.rethrowIfNecessary(t);
392                }
393                return false;
394        }
395        
396
397    public static LinkedList<MimeType> getAccept(PageContext pc) {
398        LinkedList<MimeType> accept=new LinkedList<MimeType>();
399        java.util.Iterator<String> it = ReqRspUtil.getHeadersIgnoreCase(pc, "accept").iterator();
400        String value;
401                while(it.hasNext()){
402                        value=it.next();
403                        MimeType[] mtes = MimeType.getInstances(value, ',');
404                        if(mtes!=null)for(int i=0;i<mtes.length;i++){
405                                accept.add(mtes[i]);
406                        }
407                }
408                return accept;
409        }
410    
411    public static MimeType getContentType(PageContext pc) {
412        java.util.Iterator<String> it = ReqRspUtil.getHeadersIgnoreCase(pc, "content-type").iterator();
413        String value;
414        MimeType rtn=null;
415                while(it.hasNext()){
416                        value=it.next();
417                        MimeType[] mtes = MimeType.getInstances(value, ',');
418                        if(mtes!=null)for(int i=0;i<mtes.length;i++){
419                                rtn= mtes[i];
420                        }
421                }
422                if(rtn==null) return MimeType.ALL;
423                return rtn;
424        }
425    
426    public static String getContentTypeAsString(PageContext pc,String defaultValue) {
427        MimeType mt = getContentType(pc);
428        if(mt==MimeType.ALL) return defaultValue;
429        return mt.toString();
430    }
431
432        /**
433         * returns the body of the request
434         * @param pc
435         * @param deserialized if true lucee tries to deserialize the body based on the content-type, for example when the content type is "application/json"
436         * @param defaultValue value returned if there is no body
437         * @return
438         */
439        public static Object getRequestBody(PageContext pc,boolean deserialized, Object defaultValue) {
440                HttpServletRequest req = pc.getHttpServletRequest();
441        
442                MimeType contentType = getContentType(pc);
443                String strContentType=contentType==MimeType.ALL?null:contentType.toString();
444                Charset cs = getCharacterEncoding(pc,req);
445        
446        boolean isBinary =!(
447                        strContentType == null || 
448                        HTTPUtil.isTextMimeType(contentType) ||
449                        strContentType.toLowerCase().startsWith("application/x-www-form-urlencoded"));
450        
451        if(req.getContentLength() > -1) {
452                ServletInputStream is=null;
453            try {
454                byte[] data = IOUtil.toBytes(is=req.getInputStream());//new byte[req.getContentLength()];
455                Object obj=NULL;
456                
457                if(deserialized){
458                        int format = MimeType.toFormat(contentType, -1);
459                        obj=toObject(pc, data, format, cs, obj);
460                }
461                if(obj==NULL) {
462                        if(isBinary) obj=data;
463                        else obj=toString(data, cs);
464                }
465                return obj;
466                
467                
468            }
469            catch(Exception e) {
470                return defaultValue;
471            }
472            finally {
473                IOUtil.closeEL(is);
474            }
475        }
476        return defaultValue;
477    }
478
479
480    private static String toString(byte[] data, Charset cs) {
481        if(cs!=null)
482            return  new String(data, cs).trim();
483        return new String(data).trim();
484        }
485
486        /**
487     * returns the full request URL
488     *
489     * @param req - the HttpServletRequest
490     * @param includeQueryString - if true, the QueryString will be appended if one exists
491     * */
492    public static String getRequestURL( HttpServletRequest req, boolean includeQueryString ) {
493
494        StringBuffer sb = req.getRequestURL();
495        int maxpos = sb.indexOf( "/", 8 );
496
497        if ( maxpos > -1 ) {
498
499            if ( req.isSecure() ) {
500                if ( sb.substring( maxpos - 4, maxpos ).equals( ":443" ) )
501                    sb.delete( maxpos - 4, maxpos );
502            }
503            else {
504                if ( sb.substring( maxpos - 3, maxpos ).equals( ":80" ) )
505                    sb.delete( maxpos - 3, maxpos );
506            }
507
508            if ( includeQueryString && !StringUtil.isEmpty( req.getQueryString() ) )
509                sb.append( '?' ).append( req.getQueryString() );
510        }
511
512        return sb.toString();
513    }
514
515
516        public static String getRootPath(ServletContext sc) {
517                if(sc==null) throw new RuntimeException("cannot determine webcontext root, because the ServletContext is null");
518                String root = sc.getRealPath("/");
519                if(root==null) throw new RuntimeException("cannot determinae webcontext root, the ServletContext from class ["+sc.getClass().getName()+"] is returning null for the method call sc.getRelPath(\"/\"), possibly due to configuration problem.");
520                return root;
521        }
522
523        public static Object toObject(PageContext pc,byte[] data, int format, Charset charset, Object defaultValue) {
524                switch(format) {
525        case UDF.RETURN_FORMAT_JSON:
526                try{
527                        return new JSONExpressionInterpreter().interpret(pc, toString(data,charset));
528                }
529                catch(PageException pe){}
530        break;
531        case UDF.RETURN_FORMAT_SERIALIZE:
532                try{
533                        return new CFMLExpressionInterpreter().interpret(pc, toString(data,charset));
534                }
535                catch(PageException pe){}
536        break;
537        case UDF.RETURN_FORMAT_WDDX:
538                try{
539                        WDDXConverter converter =new WDDXConverter(pc.getTimeZone(),false,true);
540                        converter.setTimeZone(pc.getTimeZone());
541                        return converter.deserialize(toString(data,charset),false);
542                }
543                catch(Exception pe){}
544        break;
545        case UDF.RETURN_FORMAT_XML:
546                try{
547                        InputSource xml = XMLUtil.toInputSource(pc,toString(data,charset));
548                        InputSource validator =null;
549                        return XMLCaster.toXMLStruct(XMLUtil.parse(xml,validator,false),true);
550                }
551                catch(Exception pe){}
552        break;
553        case UDFPlus.RETURN_FORMAT_JAVA:
554                try{
555                        return JavaConverter.deserialize(new ByteArrayInputStream(data));
556                }
557                catch(Exception pe){}
558        break;
559        }
560                return defaultValue;
561        }
562
563        public static boolean identical(HttpServletRequest left, HttpServletRequest right) { 
564                if(left==right) return true;
565                if(left instanceof HTTPServletRequestWrap) 
566                        left=((HTTPServletRequestWrap)left).getOriginalRequest();
567                if(right instanceof HTTPServletRequestWrap) 
568                        right=((HTTPServletRequestWrap)right).getOriginalRequest();
569                if(left==right) return true;
570                return false;
571        }
572
573        public static Charset getCharacterEncoding(PageContext pc, ServletRequest req) {
574                return _getCharacterEncoding(pc,req.getCharacterEncoding());
575        }
576        
577        public static Charset getCharacterEncoding(PageContext pc, ServletResponse rsp) {
578                return _getCharacterEncoding(pc,rsp.getCharacterEncoding());
579        }
580        
581        private static Charset _getCharacterEncoding(PageContext pc, String ce) {
582                if(!StringUtil.isEmpty(ce,true)) {
583                        Charset c = CharsetUtil.toCharset(ce,null);
584                        if(c!=null) return c;
585                }
586                
587                pc=ThreadLocalPageContext.get(pc);
588                if(pc!=null) return ((PageContextImpl)pc).getWebCharset();
589                Config config = ThreadLocalPageContext.getConfig(pc);
590                return ((ConfigImpl)config)._getWebCharset();
591        }
592
593        public static void removeCookie(HttpServletResponse rsp, String name) {
594                javax.servlet.http.Cookie cookie=new javax.servlet.http.Cookie(name,"");
595                cookie.setMaxAge(0);
596                cookie.setSecure(false);
597                cookie.setPath("/");
598                rsp.addCookie(cookie);
599        }
600
601        /**
602         * if encodings fails the given url is returned
603         * @param rsp
604         * @param url
605         * @return
606         */
607        public static String encodeRedirectURLEL(HttpServletResponse rsp, String url) {
608                try{
609                        return rsp.encodeRedirectURL(url);
610                }
611                catch(Throwable t){
612                        ExceptionUtil.rethrowIfNecessary(t);
613                        return url;
614                }
615        }
616        
617        public static String getDomain(HttpServletRequest req) { // DIFF 23
618                StringBuilder sb=new StringBuilder();
619                sb.append(req.isSecure()?"https://":"http://");
620                sb.append(req.getServerName());
621                sb.append(':');
622                sb.append(req.getServerPort());
623                if(!StringUtil.isEmpty(req.getContextPath()))sb.append(req.getContextPath());
624                return sb.toString();
625        }
626}