/** * * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. * * 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.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.security.Principal; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedList; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.AsyncContext; import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; 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 javax.servlet.http.HttpSession; import javax.servlet.http.HttpUpgradeHandler; import javax.servlet.http.Part; import lucee.commons.collection.concurrent.ConcurrentHashMapPro; import lucee.commons.io.IOUtil; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.StringUtil; import lucee.commons.net.URLItem; import lucee.runtime.PageContext; import lucee.runtime.PageContextImpl; import lucee.runtime.config.Config; import lucee.runtime.engine.ThreadLocalPageContext; import lucee.runtime.op.Caster; import lucee.runtime.op.date.DateCaster; import lucee.runtime.type.Collection; import lucee.runtime.type.Collection.Key; import lucee.runtime.type.KeyImpl; import lucee.runtime.type.dt.DateTime; import lucee.runtime.type.it.StringIterator; import lucee.runtime.type.scope.Form; import lucee.runtime.type.scope.FormImpl; import lucee.runtime.type.scope.URL; import lucee.runtime.type.scope.URLImpl; import lucee.runtime.type.scope.UrlFormImpl; import lucee.runtime.type.scope.util.ScopeUtil; import lucee.runtime.type.util.ArrayUtil; import lucee.runtime.util.EnumerationWrapper; /** * extends a existing {@link HttpServletRequest} with the possibility to reread the input as many you want. */ public final class HTTPServletRequestWrap implements HttpServletRequest,Serializable { private boolean firstRead=true; private byte[] barr; private static final int MIN_STORAGE_SIZE=1*1024*1024; private static final int MAX_STORAGE_SIZE=50*1024*1024; private static final int SPACIG=1024*1024; private String servlet_path; private String request_uri; private String context_path; private String path_info; private String query_string; private boolean disconnected; private final HttpServletRequest req; private static class DisconnectData { private Map<String, Object> attributes; private String authType; private Cookie[] cookies; private Map<Collection.Key,LinkedList<String>> headers;// this is a Pait List because there could by multiple entries with the same name private String method; private String pathTranslated; private String remoteUser; private String requestedSessionId; private boolean requestedSessionIdFromCookie; //private Request _request; private boolean requestedSessionIdFromURL; private boolean secure; private boolean requestedSessionIdValid; private String characterEncoding; private int contentLength; private String contentType; private int serverPort; private String serverName; private String scheme; private String remoteHost; private String remoteAddr; private String protocol; private Locale locale; private HttpSession session; private Principal userPrincipal; } DisconnectData disconnectData; /** * Constructor of the class * @param req * @param max how many is possible to re read */ public HTTPServletRequestWrap(HttpServletRequest req) { this.req=pure(req); if((servlet_path=attrAsString("javax.servlet.include.servlet_path"))!=null){ request_uri=attrAsString("javax.servlet.include.request_uri"); context_path=attrAsString("javax.servlet.include.context_path"); path_info=attrAsString("javax.servlet.include.path_info"); query_string = attrAsString("javax.servlet.include.query_string"); } else { servlet_path=req.getServletPath(); request_uri=req.getRequestURI(); context_path=req.getContextPath(); path_info=req.getPathInfo(); query_string = req.getQueryString(); } } private String attrAsString(String key) { Object res = getAttribute(key); if(res==null) return null; return res.toString(); } public static HttpServletRequest pure(HttpServletRequest req) { HttpServletRequest req2; while(req instanceof HTTPServletRequestWrap){ req2 = ((HTTPServletRequestWrap)req).getOriginalRequest(); if(req2==req) break; req=req2; } return req; } @Override public String getContextPath() { return context_path; } @Override public String getPathInfo() { return path_info; } @Override public StringBuffer getRequestURL() { return new StringBuffer(isSecure()?"https":"http"). append("://"). append(getServerName()). append(':'). append(getServerPort()). append(request_uri.startsWith("/")?request_uri:"/"+request_uri); } @Override public String getQueryString() { return query_string; } @Override public String getRequestURI() { return request_uri; } @Override public String getServletPath() { return servlet_path; } @Override public RequestDispatcher getRequestDispatcher(String realpath) { return new RequestDispatcherWrap(this,realpath); } public RequestDispatcher getOriginalRequestDispatcher(String realpath) { if(disconnected) return null; return req.getRequestDispatcher(realpath); } @Override public synchronized void removeAttribute(String name) { if(disconnected) disconnectData.attributes.remove(name); else req.removeAttribute(name); } @Override public synchronized void setAttribute(String name, Object value) { if(disconnected) disconnectData.attributes.put(name, value); else req.setAttribute(name, value); } /*public void setAttributes(Request request) { this._request=request; }*/ @Override public synchronized Object getAttribute(String name) { if(disconnected) return disconnectData.attributes.get(name); return req.getAttribute(name); } @Override public synchronized Enumeration getAttributeNames() { if(disconnected) { return new EnumerationWrapper(disconnectData.attributes.keySet().toArray()); } return req.getAttributeNames(); } @Override public ServletInputStream getInputStream() throws IOException { //if(ba rr!=null) throw new IllegalStateException(); if(barr==null) { if(!firstRead) { PageContext pc = ThreadLocalPageContext.get(); if(pc!=null) { return pc.formScope().getInputStream(); } return new ServletInputStreamDummy(new byte[]{}); //throw new IllegalStateException(); } firstRead=false; if(isToBig(getContentLength())) { return req.getInputStream(); } InputStream is=null; try { barr=IOUtil.toBytes(is=req.getInputStream()); //Resource res = ResourcesImpl.getFileResourceProvider().getResource("/Users/mic/Temp/multipart.txt"); //IOUtil.copy(new ByteArrayInputStream(barr), res, true); } catch(Throwable t) { ExceptionUtil.rethrowIfNecessary(t); barr=null; return new ServletInputStreamDummy(new byte[]{}); } finally { IOUtil.closeEL(is); } } return new ServletInputStreamDummy(barr); } @Override public Map<String,String[]> getParameterMap() { PageContext pc = ThreadLocalPageContext.get(); FormImpl form=_form(pc); URLImpl url=_url(pc); return ScopeUtil.getParameterMap( new URLItem[][]{form.getRaw(),url.getRaw()}, new String[]{form.getEncoding(),url.getEncoding()}); } @Override public String getParameter(String name) { if(!disconnected) { String val = req.getParameter(name); if(val!=null) return val; } String[] values = getParameterValues(name); if(ArrayUtil.isEmpty(values)) return null; return values[0]; } private static URLImpl _url(PageContext pc) { URL u = pc.urlScope(); if(u instanceof UrlFormImpl) { return ((UrlFormImpl) u).getURL(); } return (URLImpl) u; } private static FormImpl _form(PageContext pc) { Form f = pc.formScope(); if(f instanceof UrlFormImpl) { return ((UrlFormImpl) f).getForm(); } return (FormImpl) f; } @Override public Enumeration<String> getParameterNames() { return new ItasEnum<String>(getParameterMap().keySet().iterator()); } @Override public String[] getParameterValues(String name) { return getParameterValues(ThreadLocalPageContext.get(), name); } public static String[] getParameterValues(PageContext pc, String name) { pc = ThreadLocalPageContext.get(pc); FormImpl form = _form(pc); URLImpl url= _url(pc); return ScopeUtil.getParameterValues( new URLItem[][]{form.getRaw(),url.getRaw()}, new String[]{form.getEncoding(),url.getEncoding()},name); } private boolean isToBig(int contentLength) { if(contentLength<MIN_STORAGE_SIZE) return false; if(contentLength>MAX_STORAGE_SIZE) return true; Runtime rt = Runtime.getRuntime(); long av = rt.maxMemory()-rt.totalMemory()+rt.freeMemory(); return (av-SPACIG)<contentLength; } /* * * with this method it is possibiliy to rewrite the input as many you want * @return input stream from request * @throws IOException * / public ServletInputStream getStoredInputStream() throws IOException { if(firstRead || barr!=null) return getInputStream(); return new ServletInputStreamDummy(new byte[]{}); }*/ @Override public BufferedReader getReader() throws IOException { String enc = getCharacterEncoding(); if(StringUtil.isEmpty(enc))enc="iso-8859-1"; return IOUtil.toBufferedReader(IOUtil.getReader(getInputStream(), enc)); } public void clear() { barr=null; } public HttpServletRequest getOriginalRequest() { if(disconnected) return null; return req; } public synchronized void disconnect(PageContextImpl pc) { if(disconnected) return; disconnectData=new DisconnectData(); // attributes { Enumeration<String> attrNames = req.getAttributeNames(); disconnectData.attributes=new ConcurrentHashMapPro<String, Object>(); String k; while(attrNames.hasMoreElements()){ k=attrNames.nextElement(); if(!StringUtil.isEmpty(k))disconnectData.attributes.put(k, req.getAttribute(k)); } } // headers { Enumeration headerNames = req.getHeaderNames(); disconnectData.headers=new ConcurrentHashMapPro<Collection.Key, LinkedList<String>>(); String k; Enumeration e; while(headerNames.hasMoreElements()){ k=headerNames.nextElement().toString(); e = req.getHeaders(k); LinkedList<String> list=new LinkedList<String>(); while(e.hasMoreElements()){ list.add(e.nextElement().toString()); } if(!StringUtil.isEmpty(k))disconnectData.headers.put(KeyImpl.init(k),list); } } // cookies { Cookie[] _cookies = req.getCookies(); if(!ArrayUtil.isEmpty(_cookies)) { disconnectData.cookies=new Cookie[_cookies.length]; for(int i=0;i<_cookies.length;i++) disconnectData.cookies[i]=_cookies[i]; } else disconnectData.cookies=new Cookie[0]; } disconnectData.authType = req.getAuthType(); disconnectData.method=req.getMethod(); disconnectData.pathTranslated=req.getPathTranslated(); disconnectData.remoteUser=req.getRemoteUser(); disconnectData.requestedSessionId=req.getRequestedSessionId(); disconnectData.requestedSessionIdFromCookie=req.isRequestedSessionIdFromCookie(); disconnectData.requestedSessionIdFromURL=req.isRequestedSessionIdFromURL(); disconnectData.secure = req.isSecure(); disconnectData.requestedSessionIdValid=req.isRequestedSessionIdValid(); disconnectData.characterEncoding = req.getCharacterEncoding(); disconnectData.contentLength = req.getContentLength(); disconnectData.contentType=req.getContentType(); disconnectData.serverPort=req.getServerPort(); disconnectData.serverName=req.getServerName(); disconnectData.scheme=req.getScheme(); disconnectData.remoteHost=req.getRemoteHost(); disconnectData.remoteAddr=req.getRemoteAddr(); disconnectData.protocol=req.getProtocol(); disconnectData.locale=req.getLocale(); // only store it when j2ee sessions are enabled if(pc.getSessionType()==Config.SESSION_TYPE_JEE) disconnectData.session=req.getSession(true); // create if necessary disconnectData.userPrincipal=req.getUserPrincipal(); if(barr==null) { try { barr=IOUtil.toBytes(req.getInputStream(),true); } catch (IOException e) { // e.printStackTrace(); } } disconnected=true; //req=null; } static class ArrayEnum<E> implements Enumeration<E> { @Override public boolean hasMoreElements() { return false; } @Override public E nextElement() { return null; } } static class ItasEnum<E> implements Enumeration<E> { private Iterator<E> it; public ItasEnum(Iterator<E> it){ this.it=it; } @Override public boolean hasMoreElements() { return it.hasNext(); } @Override public E nextElement() { return it.next(); } } static class EmptyEnum<E> implements Enumeration<E> { @Override public boolean hasMoreElements() { return false; } @Override public E nextElement() { return null; } } static class StringItasEnum implements Enumeration<String> { private Iterator<?> it; public StringItasEnum(Iterator<?> it){ this.it=it; } @Override public boolean hasMoreElements() { return it.hasNext(); } @Override public String nextElement() { return StringUtil.toStringNative(it.next(),""); } } @Override public String getAuthType() { if(disconnected) return disconnectData.authType; return req.getAuthType(); } @Override public Cookie[] getCookies() { if(disconnected) return disconnectData.cookies; return req.getCookies(); } @Override public long getDateHeader(String name) { if(!disconnected) return req.getDateHeader(name); String h = getHeader(name); if(h==null) return -1; DateTime dt = DateCaster.toDateAdvanced(h, null,null); if(dt==null) throw new IllegalArgumentException("cannot convert ["+getHeader(name)+"] to date time value"); return dt.getTime(); } @Override public int getIntHeader(String name) { if(!disconnected) return req.getIntHeader(name); String h = getHeader(name); if(h==null) return -1; Integer i = Caster.toInteger(h, null); if(i==null) throw new NumberFormatException("cannot convert ["+getHeader(name)+"] to int value"); return i.intValue(); } @Override public String getHeader(String name) { if(!disconnected) return req.getHeader(name); LinkedList<String> value = disconnectData.headers.get(KeyImpl.init(name)); if(value==null) return null; return value.getFirst(); } @Override public Enumeration getHeaderNames() { if(!disconnected) return req.getHeaderNames(); Set<Key> set = disconnectData.headers.keySet(); return new StringIterator(set.toArray(new Key[set.size()])); } @Override public Enumeration getHeaders(String name) { if(!disconnected) return req.getHeaders(name); LinkedList<String> value = disconnectData.headers.get(KeyImpl.init(name)); if(value!=null)return new ItasEnum<String>(value.iterator()); return new EmptyEnum<String>(); } @Override public String getMethod() { if(!disconnected) return req.getMethod(); return disconnectData.method; } @Override public String getPathTranslated() { if(!disconnected) return req.getPathTranslated(); return disconnectData.pathTranslated; } @Override public String getRemoteUser() { if(!disconnected) return req.getRemoteUser(); return disconnectData.remoteUser; } @Override public String getRequestedSessionId() { if(!disconnected) return req.getRequestedSessionId(); return disconnectData.requestedSessionId; } @Override public HttpSession getSession() { return getSession(true); } @Override public HttpSession getSession(boolean create) { if(!disconnected) return req.getSession(create); return this.disconnectData.session; } @Override public Principal getUserPrincipal() { if(!disconnected) return req.getUserPrincipal(); return this.disconnectData.userPrincipal; } @Override public boolean isRequestedSessionIdFromCookie() { if(!disconnected) return req.isRequestedSessionIdFromCookie(); return disconnectData.requestedSessionIdFromCookie; } @Override public boolean isRequestedSessionIdFromURL() { if(!disconnected) return req.isRequestedSessionIdFromURL(); return disconnectData.requestedSessionIdFromURL; } @Override public boolean isRequestedSessionIdFromUrl() { return isRequestedSessionIdFromURL(); } @Override public boolean isRequestedSessionIdValid() { if(!disconnected) return req.isRequestedSessionIdValid(); return disconnectData.requestedSessionIdValid; } @Override public String getCharacterEncoding() { if(!disconnected) return req.getCharacterEncoding(); return disconnectData.characterEncoding; } @Override public int getContentLength() { if(!disconnected) return req.getContentLength(); return disconnectData.contentLength; } @Override public String getContentType() { if(!disconnected) return req.getContentType(); return disconnectData.contentType; } @Override public Locale getLocale() { if(!disconnected) return req.getLocale(); return disconnectData.locale; } @Override public boolean isUserInRole(String role) { if(!disconnected) return req.isUserInRole(role); // try it anyway, in some servlet engine it is still working try{ return req.isUserInRole(role); } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} // TODO add support for this throw new RuntimeException("this method is not supported when root request is gone"); } @Override public Enumeration getLocales() { if(!disconnected) return req.getLocales(); // try it anyway, in some servlet engine it is still working try{ return req.getLocales(); } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} // TODO add support for this throw new RuntimeException("this method is not supported when root request is gone"); } @Override public String getRealPath(String path) { if(!disconnected) return req.getRealPath(path); // try it anyway, in some servlet engine it is still working try{ return req.getRealPath(path); } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} // TODO add support for this throw new RuntimeException("this method is not supported when root request is gone"); } @Override public String getProtocol() { if(!disconnected) return req.getProtocol(); return disconnectData.protocol; } @Override public String getRemoteAddr() { if(!disconnected) return req.getRemoteAddr(); return disconnectData.remoteAddr; } @Override public String getRemoteHost() { if(!disconnected) return req.getRemoteHost(); return disconnectData.remoteHost; } @Override public String getScheme() { if(!disconnected) return req.getScheme(); return disconnectData.scheme; } @Override public String getServerName() { if(!disconnected) return req.getServerName(); return disconnectData.serverName; } @Override public int getServerPort() { if(!disconnected) return req.getServerPort(); return disconnectData.serverPort; } @Override public boolean isSecure() { if(!disconnected) return req.isSecure(); return disconnectData.secure; } @Override public void setCharacterEncoding(String enc) throws UnsupportedEncodingException { if(!disconnected) req.setCharacterEncoding(enc); else disconnectData.characterEncoding=enc; } @Override public AsyncContext getAsyncContext() { if(!disconnected) return req.getAsyncContext(); throw new RuntimeException("not supported!"); } @Override public long getContentLengthLong() { if(!disconnected) return req.getContentLengthLong(); return getContentLength(); } @Override public DispatcherType getDispatcherType() { if(!disconnected) return req.getDispatcherType(); throw new RuntimeException("not supported!"); } @Override public String getLocalAddr() { if(!disconnected) return req.getLocalAddr(); throw new RuntimeException("not supported!"); } @Override public String getLocalName() { if(!disconnected) return req.getLocalName(); throw new RuntimeException("not supported!"); } @Override public int getLocalPort() { if(!disconnected) return req.getLocalPort(); throw new RuntimeException("not supported!"); } @Override public int getRemotePort() { if(!disconnected) return req.getRemotePort(); throw new RuntimeException("not supported!"); } @Override public ServletContext getServletContext() { if(!disconnected) return req.getServletContext(); throw new RuntimeException("not supported!"); } @Override public boolean isAsyncStarted() { if(!disconnected) return req.isAsyncStarted(); throw new RuntimeException("not supported!"); } @Override public boolean isAsyncSupported() { if(!disconnected) return req.isAsyncSupported(); throw new RuntimeException("not supported!"); } @Override public AsyncContext startAsync() throws IllegalStateException { if(!disconnected) return req.startAsync(); throw new RuntimeException("not supported!"); } @Override public AsyncContext startAsync(ServletRequest arg0, ServletResponse arg1) throws IllegalStateException { if(!disconnected) return req.startAsync(arg0, arg1); throw new RuntimeException("not supported!"); } @Override public boolean authenticate(HttpServletResponse arg0) throws IOException, ServletException { if(!disconnected) return req.authenticate(arg0); throw new RuntimeException("not supported!"); } @Override public String changeSessionId() { if(!disconnected) return req.changeSessionId(); throw new RuntimeException("not supported!"); } @Override public Part getPart(String arg0) throws IOException, ServletException { if(!disconnected) return req.getPart(arg0); throw new RuntimeException("not supported!"); } @Override public java.util.Collection<Part> getParts() throws IOException, ServletException { if(!disconnected) return req.getParts(); throw new RuntimeException("not supported!"); } @Override public void login(String arg0, String arg1) throws ServletException { if(!disconnected) req.login(arg0, arg1); throw new RuntimeException("not supported!"); } @Override public void logout() throws ServletException { if(!disconnected) req.logout(); throw new RuntimeException("not supported!"); } @Override public <T extends HttpUpgradeHandler> T upgrade(Class<T> arg0) throws IOException, ServletException { if(!disconnected) return req.upgrade(arg0); throw new RuntimeException("not supported!"); } }