/** * Copyright (c) 2014, the Railo Company Ltd. * Copyright (c) 2015, Lucee Assosication Switzerland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ package lucee.runtime.net.http; import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.URL; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import javax.servlet.ServletContext; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lucee.commons.io.CharsetUtil; import lucee.commons.io.IOUtil; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.Pair; import lucee.commons.lang.StringUtil; import lucee.commons.lang.mimetype.MimeType; import lucee.commons.net.HTTPUtil; import lucee.commons.net.URLDecoder; import lucee.commons.net.URLEncoder; import lucee.runtime.PageContext; import lucee.runtime.config.Config; import lucee.runtime.converter.JavaConverter; import lucee.runtime.converter.WDDXConverter; import lucee.runtime.engine.ThreadLocalPageContext; import lucee.runtime.exp.PageException; import lucee.runtime.functions.decision.IsLocalHost; import lucee.runtime.interpreter.CFMLExpressionInterpreter; import lucee.runtime.interpreter.JSONExpressionInterpreter; import lucee.runtime.listener.ApplicationContext; import lucee.runtime.op.Caster; import lucee.runtime.security.ScriptProtect; import lucee.runtime.text.xml.XMLCaster; import lucee.runtime.text.xml.XMLUtil; import lucee.runtime.type.UDF; import org.xml.sax.InputSource; public final class ReqRspUtil { private static final Object NULL = new Object(); private static final Cookie[] EMPTY = new Cookie[0]; public static String get(Pair<String,Object>[] items, String name) { for(int i=0;i<items.length;i++) { if(items[i].getName().equalsIgnoreCase(name)) return Caster.toString(items[i].getValue(),null); } return null; } public static Pair<String,Object>[] add(Pair<String,Object>[] items, String name, Object value) { Pair<String,Object>[] tmp = new Pair[items.length+1]; for(int i=0;i<items.length;i++) { tmp[i]=items[i]; } tmp[items.length]=new Pair<String,Object>(name,value); return tmp; } public static Pair<String,Object>[] set(Pair<String,Object>[] items, String name, Object value) { for(int i=0;i<items.length;i++) { if(items[i].getName().equalsIgnoreCase(name)) { items[i]=new Pair<String,Object>(name,value); return items; } } return add(items, name, value); } /** * return path to itself * @param req */ public static String self(HttpServletRequest req) { StringBuffer sb=new StringBuffer(req.getServletPath()); String qs=req.getQueryString(); if(!StringUtil.isEmpty(qs))sb.append('?').append(qs); return sb.toString(); } public static void setContentLength(HttpServletResponse rsp, int length) { rsp.setContentLength(length); } public static void setContentLength(HttpServletResponse rsp, long length) { if(length <= Integer.MAX_VALUE){ setContentLength(rsp,(int)length); } else{ rsp.addHeader("Content-Length", Caster.toString(length)); } } public static void setContentType(HttpServletResponse rsp, String contentType) { rsp.setContentType(contentType); } public static Cookie[] getCookies(HttpServletRequest req, Charset charset) { Cookie[] cookies = req.getCookies(); if(cookies!=null) { Cookie cookie; String tmp; for(int i=0;i<cookies.length;i++){ cookie=cookies[i]; // value (is decoded by the servlet engine with iso-8859-1) if(!StringUtil.isAscii(cookie.getValue())) { tmp=encode(cookie.getValue(), "iso-8859-1"); cookie.setValue(decode(tmp, charset.name(),false)); } } } else { String str = req.getHeader("Cookie"); if(str!=null) { try{ String[] arr = lucee.runtime.type.util.ListUtil.listToStringArray(str, ';'),tmp; java.util.List<Cookie> list=new ArrayList<Cookie>(); Cookie c; for(int i=0;i<arr.length;i++){ tmp=lucee.runtime.type.util.ListUtil.listToStringArray(arr[i], '='); if(tmp.length>0) { c=ReqRspUtil.toCookie(dec(tmp[0],charset.name(),false), tmp.length>1?dec(tmp[1],charset.name(),false):"",null); if(c!=null)list.add(c); } } cookies=list.toArray(new Cookie[list.size()]); } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} } } if(cookies==null) return EMPTY; return cookies; } public static void setCharacterEncoding(HttpServletResponse rsp,String charset) { try { Method setCharacterEncoding = rsp.getClass().getMethod("setCharacterEncoding", new Class[0]); setCharacterEncoding.invoke(rsp, new Object[0]); } catch(Throwable t) { ExceptionUtil.rethrowIfNecessary(t); throw ExceptionUtil.toRuntimeException(t); } } public static String getQueryString(HttpServletRequest req) { //String qs = req.getAttribute("javax.servlet.include.query_string"); return req.getQueryString(); } public static String getHeader(HttpServletRequest request, String name,String defaultValue) { try { return request.getHeader(name); } catch(Throwable t){ ExceptionUtil.rethrowIfNecessary(t); return defaultValue; } } public static String getHeaderIgnoreCase(PageContext pc, String name,String defaultValue) { String charset = pc.getWebCharset().name(); HttpServletRequest req = pc.getHttpServletRequest(); Enumeration e = req.getHeaderNames(); String keyDecoded,key; while(e.hasMoreElements()) { key=e.nextElement().toString(); keyDecoded=ReqRspUtil.decode(key, charset,false); if(name.equalsIgnoreCase(key) || name.equalsIgnoreCase(keyDecoded)) return ReqRspUtil.decode(req.getHeader(key),charset,false); } return defaultValue; } public static List<String> getHeadersIgnoreCase(PageContext pc, String name) { String charset = pc.getWebCharset().name(); HttpServletRequest req = pc.getHttpServletRequest(); Enumeration e = req.getHeaderNames(); List<String> rtn=new ArrayList<String>(); String keyDecoded,key; while(e.hasMoreElements()) { key=e.nextElement().toString(); keyDecoded=ReqRspUtil.decode(key, charset,false); if(name.equalsIgnoreCase(key) || name.equalsIgnoreCase(keyDecoded)) rtn.add(ReqRspUtil.decode(req.getHeader(key),charset,false)); } return rtn; } public static String getScriptName(PageContext pc,HttpServletRequest req) { String sn = StringUtil.emptyIfNull(req.getContextPath())+StringUtil.emptyIfNull(req.getServletPath()); if(pc==null)pc=ThreadLocalPageContext.get(); if(pc!=null && ( (pc.getApplicationContext().getScriptProtect()&ApplicationContext.SCRIPT_PROTECT_URL)>0 || (pc.getApplicationContext().getScriptProtect()&ApplicationContext.SCRIPT_PROTECT_CGI)>0 )) { sn=ScriptProtect.translate(sn); } return sn; } private static boolean isHex(char c) { return (c>='0' && c<='9') || (c>='a' && c<='f') || (c>='A' && c<='F'); } private static String dec(String str, String charset, boolean force) { str=str.trim(); if(StringUtil.startsWith(str, '"') && StringUtil.endsWith(str, '"') && str.length()>1) str=str.substring(1,str.length()-1); return decode(str,charset,force);//java.net.URLDecoder.decode(str.trim(), charset); } public static String decode(String str,String charset, boolean force) { try { if(str==null) return null; return URLDecoder.decode(str, charset,force); } catch (UnsupportedEncodingException e) { return str; } } public static String encode(String str,String charset) { try { return URLEncoder.encode(str, charset); } catch (UnsupportedEncodingException e) { return str; } } public static String encode(String str,Charset charset) { try { return URLEncoder.encode(str, charset); } catch (UnsupportedEncodingException e) { return str; } } public static boolean needEncoding(String str, boolean allowPlus){ if(StringUtil.isEmpty(str,false)) return false; int len=str.length(); char c; for(int i=0;i<len;i++){ c=str.charAt(i); if(c >='0' && c <= '9') continue; if(c >='a' && c <= 'z') continue; if(c >='A' && c <= 'Z') continue; // _-.* if(c =='-') continue; if(c =='_') continue; if(c =='.') continue; if(c =='*') continue; if(c =='/') continue; if(allowPlus && c =='+') continue; if(c =='%') { if(i+2>=len) return true; try{ Integer.parseInt(str.substring(i+1,i+3),16); } catch(NumberFormatException nfe){ return true; } i+=3; continue; } return true; } return false; } public static boolean needDecoding(String str){ if(StringUtil.isEmpty(str,false)) return false; boolean need=false; int len=str.length(); char c; for(int i=0;i<len;i++){ c=str.charAt(i); if(c >='0' && c <= '9') continue; if(c >='a' && c <= 'z') continue; if(c >='A' && c <= 'Z') continue; // _-.* if(c =='-') continue; if(c =='_') continue; if(c =='.') continue; if(c =='*') continue; if(c =='+') { need=true; continue; } if(c =='%') { if(i+2>=len) return false; try{ Integer.parseInt(str.substring(i+1,i+3),16); } catch(NumberFormatException nfe){ return false; } i+=3; need=true; continue; } return false; } return need; } public static boolean isThis(HttpServletRequest req, String url) { try { return isThis(req, HTTPUtil.toURL(url,true)); } catch(Throwable t) { ExceptionUtil.rethrowIfNecessary(t); return false; } } public static boolean isThis(HttpServletRequest req, URL url) { try { // Port int reqPort=req.getServerPort(); int urlPort=url.getPort(); if(urlPort<=0) urlPort=HTTPUtil.isSecure(url)?443:80; if(reqPort<=0) reqPort=req.isSecure()?443:80; if(reqPort!=urlPort) return false; // host String reqHost = req.getServerName(); String urlHost = url.getHost(); if(reqHost.equalsIgnoreCase(urlHost)) return true; if(IsLocalHost.invoke(reqHost) && IsLocalHost.invoke(reqHost)) return true; InetAddress urlAddr = InetAddress.getByName(urlHost); InetAddress reqAddr = InetAddress.getByName(reqHost); if(reqAddr.getHostName().equalsIgnoreCase(urlAddr.getHostName())) return true; if(reqAddr.getHostAddress().equalsIgnoreCase(urlAddr.getHostAddress())) return true; reqAddr = InetAddress.getByName(req.getRemoteAddr()); if(reqAddr.getHostName().equalsIgnoreCase(urlAddr.getHostName())) return true; if(reqAddr.getHostAddress().equalsIgnoreCase(urlAddr.getHostAddress())) return true; } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} return false; } public static LinkedList<MimeType> getAccept(PageContext pc) { LinkedList<MimeType> accept=new LinkedList<MimeType>(); java.util.Iterator<String> it = ReqRspUtil.getHeadersIgnoreCase(pc, "accept").iterator(); String value; while(it.hasNext()){ value=it.next(); MimeType[] mtes = MimeType.getInstances(value, ','); if(mtes!=null)for(int i=0;i<mtes.length;i++){ accept.add(mtes[i]); } } return accept; } public static MimeType getContentType(PageContext pc) { java.util.Iterator<String> it = ReqRspUtil.getHeadersIgnoreCase(pc, "content-type").iterator(); String value; MimeType rtn=null; while(it.hasNext()){ value=it.next(); MimeType[] mtes = MimeType.getInstances(value, ','); if(mtes!=null)for(int i=0;i<mtes.length;i++){ rtn= mtes[i]; } } if(rtn==null) return MimeType.ALL; return rtn; } public static String getContentTypeAsString(PageContext pc,String defaultValue) { MimeType mt = getContentType(pc); if(mt==MimeType.ALL) return defaultValue; return mt.toString(); } /** * returns the body of the request * @param pc * @param deserialized if true lucee tries to deserialize the body based on the content-type, for example when the content type is "application/json" * @param defaultValue value returned if there is no body * @return */ public static Object getRequestBody(PageContext pc,boolean deserialized, Object defaultValue) { HttpServletRequest req = pc.getHttpServletRequest(); MimeType contentType = getContentType(pc); String strContentType=contentType==MimeType.ALL?null:contentType.toString(); Charset cs = getCharacterEncoding(pc,req); boolean isBinary =!( strContentType == null || HTTPUtil.isTextMimeType(contentType) || strContentType.toLowerCase().startsWith("application/x-www-form-urlencoded")); if(req.getContentLength() > -1) { ServletInputStream is=null; try { byte[] data = IOUtil.toBytes(is=req.getInputStream());//new byte[req.getContentLength()]; Object obj=NULL; if(deserialized){ int format = MimeType.toFormat(contentType, -1); obj=toObject(pc, data, format, cs, obj); } if(obj==NULL) { if(isBinary) obj=data; else obj=toString(data, cs); } return obj; } catch(Exception e) { return defaultValue; } finally { IOUtil.closeEL(is); } } return defaultValue; } private static String toString(byte[] data, Charset cs) { if(cs!=null) return new String(data, cs).trim(); return new String(data).trim(); } /** * returns the full request URL * * @param req - the HttpServletRequest * @param includeQueryString - if true, the QueryString will be appended if one exists * */ public static String getRequestURL( HttpServletRequest req, boolean includeQueryString ) { StringBuffer sb = req.getRequestURL(); int maxpos = sb.indexOf( "/", 8 ); if ( maxpos > -1 ) { if ( req.isSecure() ) { if ( sb.substring( maxpos - 4, maxpos ).equals( ":443" ) ) sb.delete( maxpos - 4, maxpos ); } else { if ( sb.substring( maxpos - 3, maxpos ).equals( ":80" ) ) sb.delete( maxpos - 3, maxpos ); } if ( includeQueryString && !StringUtil.isEmpty( req.getQueryString() ) ) sb.append( '?' ).append( req.getQueryString() ); } return sb.toString(); } public static String getRootPath(ServletContext sc) { if(sc==null) throw new RuntimeException("cannot determinate webcontext root, because the ServletContext is null"); String root = sc.getRealPath("/"); if(root==null) throw new RuntimeException("cannot determinate webcontext root, the ServletContext from class ["+sc.getClass().getName()+"] is returning null for the method call sc.getRealPath(\"/\"), possibly due to configuration problem."); return root; } public static Object toObject(PageContext pc,byte[] data, int format, Charset charset, Object defaultValue) { switch(format) { case UDF.RETURN_FORMAT_JSON: try{ return new JSONExpressionInterpreter().interpret(pc, toString(data,charset)); } catch(PageException pe){} break; case UDF.RETURN_FORMAT_SERIALIZE: try{ return new CFMLExpressionInterpreter().interpret(pc, toString(data,charset)); } catch(PageException pe){} break; case UDF.RETURN_FORMAT_WDDX: try{ WDDXConverter converter =new WDDXConverter(pc.getTimeZone(),false,true); converter.setTimeZone(pc.getTimeZone()); return converter.deserialize(toString(data,charset),false); } catch(Exception pe){} break; case UDF.RETURN_FORMAT_XML: try{ InputSource xml = XMLUtil.toInputSource(pc,toString(data,charset)); InputSource validator =null; return XMLCaster.toXMLStruct(XMLUtil.parse(xml,validator,false),true); } catch(Exception pe){} break; case UDF.RETURN_FORMAT_JAVA: try{ return JavaConverter.deserialize(new ByteArrayInputStream(data)); } catch(Exception pe){} break; } return defaultValue; } public static boolean identical(HttpServletRequest left, HttpServletRequest right) { if(left==right) return true; if(left instanceof HTTPServletRequestWrap) left=((HTTPServletRequestWrap)left).getOriginalRequest(); if(right instanceof HTTPServletRequestWrap) right=((HTTPServletRequestWrap)right).getOriginalRequest(); if(left==right) return true; return false; } public static Charset getCharacterEncoding(PageContext pc, ServletRequest req) { return _getCharacterEncoding(pc,req.getCharacterEncoding()); } public static Charset getCharacterEncoding(PageContext pc, ServletResponse rsp) { return _getCharacterEncoding(pc,rsp.getCharacterEncoding()); } private static Charset _getCharacterEncoding(PageContext pc, String ce) { if(!StringUtil.isEmpty(ce,true)) { Charset c = CharsetUtil.toCharset(ce,null); if(c!=null) return c; } pc=ThreadLocalPageContext.get(pc); if(pc!=null) return pc.getWebCharset(); Config config = ThreadLocalPageContext.getConfig(pc); return config.getWebCharset(); } public static void removeCookie(HttpServletResponse rsp, String name) { javax.servlet.http.Cookie cookie=new javax.servlet.http.Cookie(name,""); cookie.setMaxAge(0); cookie.setSecure(false); cookie.setPath("/"); rsp.addCookie(cookie); } /** * if encodings fails the given url is returned * @param rsp * @param url * @return */ public static String encodeRedirectURLEL(HttpServletResponse rsp, String url) { try{ return rsp.encodeRedirectURL(url); } catch(Throwable t){ ExceptionUtil.rethrowIfNecessary(t); return url; } } public static String getDomain(HttpServletRequest req) { // DIFF 23 StringBuilder sb=new StringBuilder(); sb.append(req.isSecure()?"https://":"http://"); sb.append(req.getServerName()); sb.append(':'); sb.append(req.getServerPort()); if(!StringUtil.isEmpty(req.getContextPath()))sb.append(req.getContextPath()); return sb.toString(); } public static Cookie toCookie(String name, String value, Cookie defaultValue) { try{ return new Cookie(name,value); } catch(Throwable t){ ExceptionUtil.rethrowIfNecessary(t); return defaultValue; } } }