/* * Copyright 2011 Future Systems * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.krakenapps.httpd.impl; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import java.net.URLDecoder; import java.nio.charset.Charset; import java.security.Principal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; 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.Part; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.handler.codec.http.HttpHeaders; import org.jboss.netty.handler.codec.http.HttpMethod; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.ssl.SslHandler; import org.krakenapps.httpd.HttpContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SuppressWarnings("rawtypes") public class Request implements HttpServletRequest { private ChannelHandlerContext ctx; private boolean secure; private boolean asyncStarted; /** * can be null if not found */ private HttpContext httpContext; /** * can be null if not found */ private String servletPath; private String pathInfo; private String queryString; private HttpRequest req; private Response resp; private HttpSession session; private ServletInputStream is; private Map<String, Object> attributes = new HashMap<String, Object>(); private Map<String, List<String>> parameters = new HashMap<String, List<String>>(); private Cookie[] cookies; private List<Locale> locales = new ArrayList<Locale>(); private Logger logger = LoggerFactory.getLogger(this.getClass().getName()); public Request(ChannelHandlerContext ctx, HttpRequest req) { this.ctx = ctx; this.req = req; this.queryString = ""; ChannelBuffer c = req.getContent(); this.is = new RequestInputStream(new ByteArrayInputStream(c.array(), 0, c.readableBytes())); parseCookies(req); parseLocales(req); parseParameters(); setSslAttributes(ctx); setChannel(ctx); } public void setHttpContext(HttpContext httpContext) { this.httpContext = httpContext; setSession(); } public void setResponse(Response resp) { this.resp = resp; } private void setSession() { // TODO: domain check String key = getRequestedSessionId(); if (key != null) session = httpContext.getHttpSessions().get(key); } private void parseParameters() { String contentType = req.getHeader("Content-Type"); if (req.getMethod().equals(HttpMethod.POST)) { if (!(contentType != null && contentType.equals("application/octet-stream"))) { ChannelBuffer c = req.getContent(); String body = new String(c.array(), c.readerIndex(), c.readableBytes(), Charset.forName("utf-8")); setParams(body); } } if (req.getUri().contains("?")) { int p = req.getUri().indexOf("?"); this.queryString = req.getUri().substring(p + 1); setParams(this.queryString); } } private void setSslAttributes(ChannelHandlerContext ctx) { SslHandler sslHandler = (SslHandler) ctx.getPipeline().get("ssl"); this.secure = sslHandler != null; if (secure) { SSLSession session = sslHandler.getEngine().getSession(); String cipherSuite = session.getCipherSuite(); try { setAttribute("javax.servlet.request.X509Certificate", session.getPeerCertificateChain()); } catch (SSLPeerUnverifiedException e) { } setAttribute("javax.servlet.request.cipher_suite", cipherSuite); setAttribute("javax.servlet.request.key_size", deduceKeyLength(cipherSuite)); } } private void setChannel(ChannelHandlerContext ctx) { // set netty channel (only for internal use) setAttribute("netty.channel", ctx.getChannel()); } private void parseLocales(HttpRequest req) { List<String> langs = req.getHeaders(HttpHeaders.Names.ACCEPT_LANGUAGE); if (langs != null) for (String lang : langs) locales.add(new Locale(lang)); // see servlet spec section 3.9 if (langs.size() == 0) locales.add(Locale.getDefault()); } private void parseCookies(HttpRequest req) { List<String> cs = req.getHeaders(HttpHeaders.Names.COOKIE); ArrayList<Cookie> parsed = new ArrayList<Cookie>(); this.cookies = new Cookie[cs.size()]; for (int i = 0; i < cs.size(); i++) { String s = cs.get(i); if (s == null || s.trim().isEmpty()) continue; String name = null; String value = null; if (s.contains("=")) { String[] split = s.split("=", 2); name = split[0].trim(); value = split[1].trim(); } else { name = s.trim(); } logger.debug("kraken httpd: cookie [{} -> name={}, value={}]", new Object[] { s, name, value }); parsed.add(new Cookie(name, value)); } this.cookies = new Cookie[parsed.size()]; parsed.toArray(this.cookies); } private int deduceKeyLength(String cipherSuite) { if (cipherSuite.equals("IDEA_CBC")) return 128; if (cipherSuite.equals("RC2_CBC_40")) return 40; if (cipherSuite.equals("RC4_40")) return 40; if (cipherSuite.equals("RC4_128")) return 128; if (cipherSuite.equals("DES40_CBC")) return 40; if (cipherSuite.equals("DES_CBC")) return 56; if (cipherSuite.equals("3DES_EDE_CBC")) return 168; return 0; } private void setParams(String params) { if (params == null || params.isEmpty()) return; for (String param : params.split("&")) { logger.trace("param: {}", param); int pos = param.indexOf("="); if (pos > 0) { String name = param.substring(0, pos); String value = null; try { String encodedValue = param.substring(pos + 1); value = URLDecoder.decode(encodedValue, "utf-8"); } catch (UnsupportedEncodingException e) { } if (value.isEmpty()) value = null; List<String> values = new ArrayList<String>(); if (!parameters.containsKey(name)) parameters.put(name, values); else values = parameters.get(name); values.add(value); logger.trace("kraken webconsole: param name [{}], value [{}]", name, value); } else { parameters.put(param, null); logger.trace("kraken webconsole: param name: {}", param); } } } private class RequestInputStream extends ServletInputStream { private InputStream is; public RequestInputStream(InputStream is) { this.is = is; } @Override public int read() throws IOException { return is.read(); } } @Override public ServletContext getServletContext() { return null; } @Override public DispatcherType getDispatcherType() { // TODO: implement other dispatcher routines return DispatcherType.REQUEST; } @Override public boolean isSecure() { return secure; } // TODO: parse should be deferred for character encoding support @Override public void setCharacterEncoding(String env) throws UnsupportedEncodingException { } @Override public Object getAttribute(String name) { return attributes.get(name); } @SuppressWarnings("unchecked") @Override public Enumeration getAttributeNames() { return Collections.enumeration(attributes.keySet()); } @Override public void setAttribute(String name, Object o) { attributes.put(name, o); } @Override public void removeAttribute(String name) { attributes.remove(name); } @Override public String getCharacterEncoding() { String contentType = getContentType(); if (contentType == null || !contentType.contains("charset")) return null; for (String t : contentType.split(";")) { if (t.trim().startsWith("charset")) return t.split("=")[1].trim(); } return null; } @Override public int getContentLength() { return (int) HttpHeaders.getContentLength(req); } @Override public String getContentType() { return req.getHeader(HttpHeaders.Names.CONTENT_TYPE); } @Override public ServletInputStream getInputStream() throws IOException { return is; } @Override public String getParameter(String name) { String[] values = getParameterValues(name); if (values == null || values.length == 0) return null; return values[0]; } @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> m = new HashMap<String, String[]>(); for (String key : parameters.keySet()) m.put(key, (String[]) parameters.get(key).toArray()); return m; } @SuppressWarnings("unchecked") @Override public Enumeration getParameterNames() { return Collections.enumeration(parameters.keySet()); } @Override public String[] getParameterValues(String name) { List<String> values = parameters.get(name); if (values == null) return null; return values.toArray(new String[0]); } @Override public String getProtocol() { return req.getProtocolVersion().getText(); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(is)); } @Deprecated @Override public String getRealPath(String path) { return null; } @Override public String getLocalName() { return null; } @Override public String getLocalAddr() { return ((InetSocketAddress) ctx.getChannel().getLocalAddress()).getAddress().getHostAddress(); } @Override public int getLocalPort() { return ((InetSocketAddress) ctx.getChannel().getLocalAddress()).getPort(); } @Override public String getRemoteAddr() { return ((InetSocketAddress) ctx.getChannel().getRemoteAddress()).getAddress().getHostAddress(); } @Override public String getRemoteHost() { return ((InetSocketAddress) ctx.getChannel().getRemoteAddress()).getHostName(); } @Override public int getRemotePort() { return ((InetSocketAddress) ctx.getChannel().getRemoteAddress()).getPort(); } @Override public String getScheme() { return ctx.getPipeline().get("ssl") == null ? "http" : "https"; } @Override public String getServerName() { String host = req.getHeader("Host"); if (host == null) return null; if (host.contains(":")) return host.substring(0, host.indexOf(":")); else return host; } @Override public int getServerPort() { String host = req.getHeader("Host"); if (host == null || !host.contains(":")) return 80; return Integer.parseInt(host.substring(host.indexOf(":") + 1)); } @Override public String getAuthType() { String auth = req.getHeader(HttpHeaders.Names.AUTHORIZATION); if (auth == null) return null; return auth.substring(0, auth.indexOf(" ")); } @Override public Cookie[] getCookies() { return cookies; } @SuppressWarnings("unchecked") @Override public Enumeration getHeaderNames() { return Collections.enumeration(req.getHeaderNames()); } @Override public String getHeader(String name) { return req.getHeader(name); } @Override public Enumeration<String> getHeaders(String name) { return Collections.enumeration(req.getHeaders(name)); } @Override public long getDateHeader(String name) { try { return Long.parseLong(req.getHeader(name)); } catch (NumberFormatException e) { throw new IllegalArgumentException(e); } } @Override public int getIntHeader(String name) { return HttpHeaders.getIntHeader(req, name); } @Override public String getMethod() { return req.getMethod().getName(); } @Override public String getQueryString() { return queryString; } @Override public String getRemoteUser() { return null; } /** * requested path except query string, protocol scheme and domain */ @Override public String getRequestURI() { String path = req.getUri(); // cut protocol scheme and domain int p = path.indexOf("://"); if (p > 0) { int p2 = path.indexOf('/', p + 3); if (p2 > 0) path = path.substring(p2); else path = "/"; } // cut query string off p = path.indexOf('?'); if (p > 0) path = path.substring(0, p); return path; } @Override public String getRequestedSessionId() { // TODO: support session id from url String key = null; for (Cookie c : getCookies()) if (c.getName().equals("JSESSIONID")) key = c.getValue(); return key; } @Override public StringBuffer getRequestURL() { String uri = req.getUri(); int p = uri.indexOf('?'); if (p < 0) return new StringBuffer(uri); else return new StringBuffer(uri.substring(0, p)); } @Override public String getContextPath() { if (httpContext != null) return httpContext.getServletContext().getContextPath(); return null; } @Override public String getServletPath() { return servletPath; } public void setServletPath(String servletPath) { this.servletPath = servletPath; } @Override public String getPathInfo() { return pathInfo; } public void setPathInfo(String pathInfo) { this.pathInfo = pathInfo; } @Override public String getPathTranslated() { // it must return null if local file system path cannot be determined return null; } @Override public Enumeration<Locale> getLocales() { return Collections.enumeration(locales); } @Override public Locale getLocale() { return locales.get(0); } @Override public HttpSession getSession() { return getSession(true); } @Override public HttpSession getSession(boolean create) { if (!create) return session; if (session == null) { String key = getRequestedSessionId(); if (key == null) key = UUID.randomUUID().toString(); session = new HttpSessionImpl(key); HttpSession old = httpContext.getHttpSessions().putIfAbsent(key, session); if (old != null) session = old; } return session; } @Override public boolean isRequestedSessionIdFromCookie() { // TODO: support session key from url return getSession(false) != null; } @Override public boolean isRequestedSessionIdFromURL() { // TODO: will be implemented later return false; } @Deprecated @Override public boolean isRequestedSessionIdFromUrl() { return isRequestedSessionIdFromURL(); } @Override public boolean isRequestedSessionIdValid() { String key = getRequestedSessionId(); if (key == null) return false; return httpContext.getHttpSessions().containsKey(key); } @Override public RequestDispatcher getRequestDispatcher(String path) { // TODO Auto-generated method stub return null; } @Override public AsyncContext getAsyncContext() { // TODO Auto-generated method stub return null; } @Override public AsyncContext startAsync() throws IllegalStateException { asyncStarted = true; return new AsyncContextImpl(this, resp); } @Override public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { // TODO Auto-generated method stub return null; } @Override public boolean isAsyncStarted() { return asyncStarted; } @Override public boolean isAsyncSupported() { return false; } @Override public boolean isUserInRole(String role) { // TODO Auto-generated method stub return false; } @Override public Principal getUserPrincipal() { // TODO Auto-generated method stub return null; } @Override public boolean authenticate(HttpServletResponse response) throws IOException, ServletException { // TODO Auto-generated method stub return false; } @Override public void login(String username, String password) throws ServletException { // TODO Auto-generated method stub } @Override public void logout() throws ServletException { // TODO Auto-generated method stub } @Override public Collection<Part> getParts() throws IOException, ServletException { // TODO Auto-generated method stub return null; } @Override public Part getPart(String name) throws IOException, ServletException { // TODO Auto-generated method stub return null; } }