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}