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.BufferedReader; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.Serializable; 025import java.io.UnsupportedEncodingException; 026import java.security.Principal; 027import java.util.Enumeration; 028import java.util.HashMap; 029import java.util.Iterator; 030import java.util.LinkedList; 031import java.util.Locale; 032import java.util.Map; 033 034import javax.servlet.RequestDispatcher; 035import javax.servlet.ServletInputStream; 036import javax.servlet.http.Cookie; 037import javax.servlet.http.HttpServletRequest; 038import javax.servlet.http.HttpSession; 039 040import lucee.commons.io.IOUtil; 041import lucee.commons.lang.ExceptionUtil; 042import lucee.commons.lang.StringUtil; 043import lucee.commons.net.URLItem; 044import lucee.runtime.PageContext; 045import lucee.runtime.PageContextImpl; 046import lucee.runtime.config.Config; 047import lucee.runtime.engine.ThreadLocalPageContext; 048import lucee.runtime.op.Caster; 049import lucee.runtime.op.date.DateCaster; 050import lucee.runtime.type.Collection; 051import lucee.runtime.type.KeyImpl; 052import lucee.runtime.type.dt.DateTime; 053import lucee.runtime.type.scope.Form; 054import lucee.runtime.type.scope.FormImpl; 055import lucee.runtime.type.scope.URL; 056import lucee.runtime.type.scope.URLImpl; 057import lucee.runtime.type.scope.UrlFormImpl; 058import lucee.runtime.type.scope.util.ScopeUtil; 059import lucee.runtime.type.util.ArrayUtil; 060import lucee.runtime.util.EnumerationWrapper; 061 062/** 063 * extends a existing {@link HttpServletRequest} with the possibility to reread the input as many you want. 064 */ 065public final class HTTPServletRequestWrap implements HttpServletRequest,Serializable { 066 067 068 private boolean firstRead=true; 069 private byte[] barr; 070 private static final int MIN_STORAGE_SIZE=1*1024*1024; 071 private static final int MAX_STORAGE_SIZE=50*1024*1024; 072 private static final int SPACIG=1024*1024; 073 074 private String servlet_path; 075 private String request_uri; 076 private String context_path; 077 private String path_info; 078 private String query_string; 079 private boolean disconnected; 080 private final HttpServletRequest req; 081 082 private static class DisconnectData { 083 private Map<String, Object> attributes; 084 private String authType; 085 private Cookie[] cookies; 086 private Map<Collection.Key,LinkedList<String>> headers;// this is a Pait List because there could by multiple entries with the same name 087 private String method; 088 private String pathTranslated; 089 private String remoteUser; 090 private String requestedSessionId; 091 private boolean requestedSessionIdFromCookie; 092 //private Request _request; 093 private boolean requestedSessionIdFromURL; 094 private boolean secure; 095 private boolean requestedSessionIdValid; 096 private String characterEncoding; 097 private int contentLength; 098 private String contentType; 099 private int serverPort; 100 private String serverName; 101 private String scheme; 102 private String remoteHost; 103 private String remoteAddr; 104 private String protocol; 105 private Locale locale; 106 private HttpSession session; 107 private Principal userPrincipal; 108 } 109 DisconnectData disconnectData; 110 111 /** 112 * Constructor of the class 113 * @param req 114 * @param max how many is possible to re read 115 */ 116 public HTTPServletRequestWrap(HttpServletRequest req) { 117 this.req=pure(req); 118 if((servlet_path=attrAsString("javax.servlet.include.servlet_path"))!=null){ 119 request_uri=attrAsString("javax.servlet.include.request_uri"); 120 context_path=attrAsString("javax.servlet.include.context_path"); 121 path_info=attrAsString("javax.servlet.include.path_info"); 122 query_string = attrAsString("javax.servlet.include.query_string"); 123 } 124 else { 125 servlet_path=req.getServletPath(); 126 request_uri=req.getRequestURI(); 127 context_path=req.getContextPath(); 128 path_info=req.getPathInfo(); 129 query_string = req.getQueryString(); 130 } 131 } 132 133 private String attrAsString(String key) { 134 Object res = getAttribute(key); 135 if(res==null) return null; 136 return res.toString(); 137 } 138 139 public static HttpServletRequest pure(HttpServletRequest req) { 140 HttpServletRequest req2; 141 while(req instanceof HTTPServletRequestWrap){ 142 req2 = ((HTTPServletRequestWrap)req).getOriginalRequest(); 143 if(req2==req) break; 144 req=req2; 145 } 146 return req; 147 } 148 149 @Override 150 public String getContextPath() { 151 return context_path; 152 } 153 154 @Override 155 public String getPathInfo() { 156 return path_info; 157 } 158 159 @Override 160 public StringBuffer getRequestURL() { 161 return new StringBuffer(isSecure()?"https":"http"). 162 append("://"). 163 append(getServerName()). 164 append(':'). 165 append(getServerPort()). 166 append(request_uri.startsWith("/")?request_uri:"/"+request_uri); 167 } 168 169 @Override 170 public String getQueryString() { 171 return query_string; 172 } 173 @Override 174 public String getRequestURI() { 175 return request_uri; 176 } 177 178 @Override 179 public String getServletPath() { 180 return servlet_path; 181 } 182 183 @Override 184 public RequestDispatcher getRequestDispatcher(String relpath) { 185 return new RequestDispatcherWrap(this,relpath); 186 } 187 188 public RequestDispatcher getOriginalRequestDispatcher(String relpath) { 189 if(disconnected) return null; 190 return req.getRequestDispatcher(relpath); 191 } 192 193 @Override 194 public synchronized void removeAttribute(String name) { 195 if(disconnected) disconnectData.attributes.remove(name); 196 else req.removeAttribute(name); 197 } 198 199 @Override 200 public synchronized void setAttribute(String name, Object value) { 201 if(disconnected) disconnectData.attributes.put(name, value); 202 else req.setAttribute(name, value); 203 } 204 205 /*public void setAttributes(Request request) { 206 this._request=request; 207 }*/ 208 209 210 @Override 211 public synchronized Object getAttribute(String name) { 212 if(disconnected) return disconnectData.attributes.get(name); 213 return req.getAttribute(name); 214 } 215 216 public synchronized Enumeration getAttributeNames() { 217 if(disconnected) { 218 return new EnumerationWrapper(disconnectData.attributes); 219 } 220 return req.getAttributeNames(); 221 222 } 223 224 @Override 225 public ServletInputStream getInputStream() throws IOException { 226 //if(ba rr!=null) throw new IllegalStateException(); 227 if(barr==null) { 228 if(!firstRead) { 229 PageContext pc = ThreadLocalPageContext.get(); 230 if(pc!=null) { 231 return pc.formScope().getInputStream(); 232 } 233 return new ServletInputStreamDummy(new byte[]{}); //throw new IllegalStateException(); 234 } 235 236 firstRead=false; 237 238 if(isToBig(getContentLength())) { 239 return req.getInputStream(); 240 } 241 InputStream is=null; 242 try { 243 barr=IOUtil.toBytes(is=req.getInputStream()); 244 245 //Resource res = ResourcesImpl.getFileResourceProvider().getResource("/Users/mic/Temp/multipart.txt"); 246 //IOUtil.copy(new ByteArrayInputStream(barr), res, true); 247 248 } 249 catch(Throwable t) { 250 ExceptionUtil.rethrowIfNecessary(t); 251 barr=null; 252 return new ServletInputStreamDummy(new byte[]{}); 253 } 254 finally { 255 IOUtil.closeEL(is); 256 } 257 } 258 259 return new ServletInputStreamDummy(barr); 260 } 261 262 @Override 263 public Map<String,String[]> getParameterMap() { 264 PageContext pc = ThreadLocalPageContext.get(); 265 FormImpl form=_form(pc); 266 URLImpl url=_url(pc); 267 268 return ScopeUtil.getParameterMap( 269 new URLItem[][]{form.getRaw(),url.getRaw()}, 270 new String[]{form.getEncoding(),url.getEncoding()}); 271 } 272 273 private static URLImpl _url(PageContext pc) { 274 URL u = pc.urlScope(); 275 if(u instanceof UrlFormImpl) { 276 return ((UrlFormImpl) u).getURL(); 277 } 278 return (URLImpl) u; 279 } 280 281 private static FormImpl _form(PageContext pc) { 282 Form f = pc.formScope(); 283 if(f instanceof UrlFormImpl) { 284 return ((UrlFormImpl) f).getForm(); 285 } 286 return (FormImpl) f; 287 } 288 289 @Override 290 public Enumeration<String> getParameterNames() { 291 return new ItasEnum<String>(getParameterMap().keySet().iterator()); 292 } 293 294 @Override 295 public String[] getParameterValues(String name) { 296 return getParameterValues(ThreadLocalPageContext.get(), name); 297 } 298 299 public static String[] getParameterValues(PageContext pc, String name) { 300 pc = ThreadLocalPageContext.get(pc); 301 FormImpl form = _form(pc); 302 URLImpl url= _url(pc); 303 304 return ScopeUtil.getParameterValues( 305 new URLItem[][]{form.getRaw(),url.getRaw()}, 306 new String[]{form.getEncoding(),url.getEncoding()},name); 307 } 308 309 private boolean isToBig(int contentLength) { 310 if(contentLength<MIN_STORAGE_SIZE) return false; 311 if(contentLength>MAX_STORAGE_SIZE) return true; 312 Runtime rt = Runtime.getRuntime(); 313 long av = rt.maxMemory()-rt.totalMemory()+rt.freeMemory(); 314 return (av-SPACIG)<contentLength; 315 } 316 317 /* * 318 * with this method it is possibiliy to rewrite the input as many you want 319 * @return input stream from request 320 * @throws IOException 321 * / 322 public ServletInputStream getStoredInputStream() throws IOException { 323 if(firstRead || barr!=null) return getInputStream(); 324 return new ServletInputStreamDummy(new byte[]{}); 325 }*/ 326 327 @Override 328 public BufferedReader getReader() throws IOException { 329 String enc = getCharacterEncoding(); 330 if(StringUtil.isEmpty(enc))enc="iso-8859-1"; 331 return IOUtil.toBufferedReader(IOUtil.getReader(getInputStream(), enc)); 332 } 333 334 public void clear() { 335 barr=null; 336 } 337 338 339 340 341 public HttpServletRequest getOriginalRequest() { 342 if(disconnected) return null; 343 return req; 344 } 345 346 public synchronized void disconnect(PageContextImpl pc) { 347 if(disconnected) return; 348 disconnectData=new DisconnectData(); 349 350 // attributes 351 { 352 Enumeration<String> attrNames = req.getAttributeNames(); 353 disconnectData.attributes=new HashMap<String, Object>(); 354 String k; 355 while(attrNames.hasMoreElements()){ 356 k=attrNames.nextElement(); 357 disconnectData.attributes.put(k, req.getAttribute(k)); 358 } 359 } 360 361 // headers 362 { 363 Enumeration headerNames = req.getHeaderNames(); 364 disconnectData.headers=new HashMap<Collection.Key, LinkedList<String>>(); 365 366 String k; 367 Enumeration e; 368 while(headerNames.hasMoreElements()){ 369 k=headerNames.nextElement().toString(); 370 e = req.getHeaders(k); 371 LinkedList<String> list=new LinkedList<String>(); 372 while(e.hasMoreElements()){ 373 list.add(e.nextElement().toString()); 374 } 375 disconnectData.headers.put(KeyImpl.init(k),list); 376 } 377 } 378 379 // cookies 380 { 381 Cookie[] _cookies = req.getCookies(); 382 if(!ArrayUtil.isEmpty(_cookies)) { 383 disconnectData.cookies=new Cookie[_cookies.length]; 384 for(int i=0;i<_cookies.length;i++) 385 disconnectData.cookies[i]=_cookies[i]; 386 } 387 else 388 disconnectData.cookies=new Cookie[0]; 389 } 390 391 disconnectData.authType = req.getAuthType(); 392 disconnectData.method=req.getMethod(); 393 disconnectData.pathTranslated=req.getPathTranslated(); 394 disconnectData.remoteUser=req.getRemoteUser(); 395 disconnectData.requestedSessionId=req.getRequestedSessionId(); 396 disconnectData.requestedSessionIdFromCookie=req.isRequestedSessionIdFromCookie(); 397 disconnectData.requestedSessionIdFromURL=req.isRequestedSessionIdFromURL(); 398 disconnectData.secure = req.isSecure(); 399 disconnectData.requestedSessionIdValid=req.isRequestedSessionIdValid(); 400 disconnectData.characterEncoding = req.getCharacterEncoding(); 401 disconnectData.contentLength = req.getContentLength(); 402 disconnectData.contentType=req.getContentType(); 403 disconnectData.serverPort=req.getServerPort(); 404 disconnectData.serverName=req.getServerName(); 405 disconnectData.scheme=req.getScheme(); 406 disconnectData.remoteHost=req.getRemoteHost(); 407 disconnectData.remoteAddr=req.getRemoteAddr(); 408 disconnectData.protocol=req.getProtocol(); 409 disconnectData.locale=req.getLocale(); 410 // only store it when j2ee sessions are enabled 411 if(pc.getSessionType()==Config.SESSION_TYPE_J2EE) 412 disconnectData.session=req.getSession(true); // create if necessary 413 414 disconnectData.userPrincipal=req.getUserPrincipal(); 415 416 if(barr==null) { 417 try { 418 barr=IOUtil.toBytes(req.getInputStream(),true); 419 } 420 catch (IOException e) { 421 // e.printStackTrace(); 422 } 423 } 424 disconnected=true; 425 //req=null; 426 } 427 428 static class ArrayEnum<E> implements Enumeration<E> { 429 430 @Override 431 public boolean hasMoreElements() { 432 return false; 433 } 434 435 @Override 436 public E nextElement() { 437 return null; 438 } 439 440 } 441 442 static class ItasEnum<E> implements Enumeration<E> { 443 444 private Iterator<E> it; 445 446 public ItasEnum(Iterator<E> it){ 447 this.it=it; 448 } 449 @Override 450 public boolean hasMoreElements() { 451 return it.hasNext(); 452 } 453 454 @Override 455 public E nextElement() { 456 return it.next(); 457 } 458 } 459 460 static class EmptyEnum<E> implements Enumeration<E> { 461 462 @Override 463 public boolean hasMoreElements() { 464 return false; 465 } 466 467 @Override 468 public E nextElement() { 469 return null; 470 } 471 } 472 473 static class StringItasEnum implements Enumeration<String> { 474 475 private Iterator<?> it; 476 477 public StringItasEnum(Iterator<?> it){ 478 this.it=it; 479 } 480 @Override 481 public boolean hasMoreElements() { 482 return it.hasNext(); 483 } 484 485 @Override 486 public String nextElement() { 487 return StringUtil.toStringNative(it.next(),""); 488 } 489 490 } 491 492 @Override 493 public String getAuthType() { 494 if(disconnected) return disconnectData.authType; 495 return req.getAuthType(); 496 } 497 498 @Override 499 public Cookie[] getCookies() { 500 if(disconnected) return disconnectData.cookies; 501 return req.getCookies(); 502 503 } 504 505 @Override 506 public long getDateHeader(String name) { 507 if(!disconnected) return req.getDateHeader(name); 508 509 String h = getHeader(name); 510 if(h==null) return -1; 511 DateTime dt = DateCaster.toDateAdvanced(h, null,null); 512 if(dt==null) throw new IllegalArgumentException("cannot convert ["+getHeader(name)+"] to date time value"); 513 return dt.getTime(); 514 } 515 516 @Override 517 public int getIntHeader(String name) { 518 if(!disconnected) return req.getIntHeader(name); 519 520 String h = getHeader(name); 521 if(h==null) return -1; 522 Integer i = Caster.toInteger(h, null); 523 if(i==null) throw new NumberFormatException("cannot convert ["+getHeader(name)+"] to int value"); 524 return i.intValue(); 525 } 526 527 @Override 528 public String getHeader(String name) { 529 if(!disconnected) return req.getHeader(name); 530 531 LinkedList<String> value = disconnectData.headers.get(KeyImpl.init(name)); 532 if(value==null) return null; 533 return value.getFirst(); 534 } 535 536 @Override 537 public Enumeration getHeaderNames() { 538 if(!disconnected) return req.getHeaderNames(); 539 return new StringItasEnum(disconnectData.headers.keySet().iterator()); 540 } 541 542 @Override 543 public Enumeration getHeaders(String name) { 544 if(!disconnected) return req.getHeaders(name); 545 546 LinkedList<String> value = disconnectData.headers.get(KeyImpl.init(name)); 547 if(value!=null)return new ItasEnum<String>(value.iterator()); 548 return new EmptyEnum<String>(); 549 } 550 551 @Override 552 public String getMethod() { 553 if(!disconnected) return req.getMethod(); 554 return disconnectData.method; 555 } 556 557 @Override 558 public String getPathTranslated() { 559 if(!disconnected) return req.getPathTranslated(); 560 return disconnectData.pathTranslated; 561 } 562 563 @Override 564 public String getRemoteUser() { 565 if(!disconnected) return req.getRemoteUser(); 566 return disconnectData.remoteUser; 567 } 568 569 @Override 570 public String getRequestedSessionId() { 571 if(!disconnected) return req.getRequestedSessionId(); 572 return disconnectData.requestedSessionId; 573 } 574 575 @Override 576 public HttpSession getSession() { 577 return getSession(true); 578 } 579 580 @Override 581 public HttpSession getSession(boolean create) { 582 if(!disconnected) return req.getSession(create); 583 return this.disconnectData.session; 584 } 585 586 @Override 587 public Principal getUserPrincipal() { 588 if(!disconnected) return req.getUserPrincipal(); 589 return this.disconnectData.userPrincipal; 590 } 591 592 @Override 593 public boolean isRequestedSessionIdFromCookie() { 594 if(!disconnected) return req.isRequestedSessionIdFromCookie(); 595 return disconnectData.requestedSessionIdFromCookie; 596 } 597 598 @Override 599 public boolean isRequestedSessionIdFromURL() { 600 if(!disconnected) return req.isRequestedSessionIdFromURL(); 601 return disconnectData.requestedSessionIdFromURL; 602 } 603 604 @Override 605 public boolean isRequestedSessionIdFromUrl() { 606 return isRequestedSessionIdFromURL(); 607 } 608 609 @Override 610 public boolean isRequestedSessionIdValid() { 611 if(!disconnected) return req.isRequestedSessionIdValid(); 612 return disconnectData.requestedSessionIdValid; 613 } 614 615 @Override 616 public String getCharacterEncoding() { 617 if(!disconnected) return req.getCharacterEncoding(); 618 return disconnectData.characterEncoding; 619 } 620 621 @Override 622 public int getContentLength() { 623 if(!disconnected) return req.getContentLength(); 624 return disconnectData.contentLength; 625 } 626 627 @Override 628 public String getContentType() { 629 if(!disconnected) return req.getContentType(); 630 return disconnectData.contentType; 631 } 632 633 @Override 634 public Locale getLocale() { 635 if(!disconnected) return req.getLocale(); 636 return disconnectData.locale; 637 } 638 639 @Override 640 public boolean isUserInRole(String role) { 641 if(!disconnected) return req.isUserInRole(role); 642 // try it anyway, in some servlet engine it is still working 643 try{ 644 return req.isUserInRole(role); 645 } 646 catch(Throwable t){ 647 ExceptionUtil.rethrowIfNecessary(t); 648 } 649 // TODO add support for this 650 throw new RuntimeException("this method is not supported when root request is gone"); 651 } 652 653 @Override 654 public Enumeration getLocales() { 655 if(!disconnected) return req.getLocales(); 656 // try it anyway, in some servlet engine it is still working 657 try{ 658 return req.getLocales(); 659 } 660 catch(Throwable t){ 661 ExceptionUtil.rethrowIfNecessary(t); 662 } 663 // TODO add support for this 664 throw new RuntimeException("this method is not supported when root request is gone"); 665 } 666 667 @Override 668 public String getRealPath(String path) { 669 if(!disconnected) return req.getRealPath(path); 670 // try it anyway, in some servlet engine it is still working 671 try{ 672 return req.getRealPath(path); 673 } 674 catch(Throwable t){ 675 ExceptionUtil.rethrowIfNecessary(t); 676 } 677 // TODO add support for this 678 throw new RuntimeException("this method is not supported when root request is gone"); 679 } 680 681 @Override 682 public String getParameter(String name) { 683 if(!disconnected) return req.getParameter(name); 684 String[] values = getParameterValues(name); 685 if(ArrayUtil.isEmpty(values)) return null; 686 return values[0]; 687 } 688 689 @Override 690 public String getProtocol() { 691 if(!disconnected) return req.getProtocol(); 692 return disconnectData.protocol; 693 } 694 695 @Override 696 public String getRemoteAddr() { 697 if(!disconnected) return req.getRemoteAddr(); 698 return disconnectData.remoteAddr; 699 } 700 701 @Override 702 public String getRemoteHost() { 703 if(!disconnected) return req.getRemoteHost(); 704 return disconnectData.remoteHost; 705 } 706 707 @Override 708 public String getScheme() { 709 if(!disconnected) return req.getScheme(); 710 return disconnectData.scheme; 711 } 712 713 @Override 714 public String getServerName() { 715 if(!disconnected) return req.getServerName(); 716 return disconnectData.serverName; 717 } 718 719 @Override 720 public int getServerPort() { 721 if(!disconnected) return req.getServerPort(); 722 return disconnectData.serverPort; 723 } 724 725 @Override 726 public boolean isSecure() { 727 if(!disconnected) return req.isSecure(); 728 return disconnectData.secure; 729 } 730 731 @Override 732 public void setCharacterEncoding(String enc) throws UnsupportedEncodingException { 733 if(!disconnected) req.setCharacterEncoding(enc); 734 else disconnectData.characterEncoding=enc; 735 } 736}