/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.felix.httplite.servlet; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.Socket; import java.net.URLDecoder; import java.security.Principal; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.StringTokenizer; import java.util.TimeZone; import javax.servlet.RequestDispatcher; import javax.servlet.ServletInputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.felix.httplite.osgi.Logger; import org.apache.felix.httplite.osgi.ServiceRegistration; import org.apache.felix.httplite.osgi.ServiceRegistrationResolver; /** * This class represents an HTTP request, which is parses from a given input * stream, and implements HttpServletRequest for servlet processing. **/ public class HttpServletRequestImpl implements HttpServletRequest { private static final SimpleDateFormat formatsTemplate[] = { new SimpleDateFormat(HttpConstants.HTTP_DATE_FORMAT, Locale.US), new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) }; /** * HTTP Method */ private String m_method; /** * Host info of URI */ private String m_uriHost; /** * URI of HTTP request */ private String m_uri; /** * HTTP version */ private String m_version; /** * Headers in HTTP request */ private final Map m_headers = new HashMap(); private final Socket m_socket; private Cookie[] m_cookies; //TODO: Make locale static and perhaps global to the service. private final Locale m_locale = Locale.getDefault(); private Map m_attributes; private final ServiceRegistrationResolver m_resolver; private String m_servletPath; /** * Map of the parameters of the request. */ private Map m_parameters; /** * When the body is parsed this value will be set to a non-null value * regardless of the body content. As such it serves as a flag for parsing * the body. */ private byte[] m_requestBody = null; //private final static String m_encoding = "UTF-8"; private final Logger m_logger; private String m_queryString; /** * Used to enforce the servlet API getInputStream()/getReader() calls. */ private boolean m_getInputStreamCalled = false; /** * Used to enforce the servlet API getInputStream()/getReader() calls. */ private boolean m_getReaderCalled = false; /** * @param socket Socket associated with request * @param serviceRegistrationResolver resolver for services. * @param logger the logger */ public HttpServletRequestImpl( final Socket socket, final ServiceRegistrationResolver serviceRegistrationResolver, final Logger logger ) { this.m_socket = socket; this.m_resolver = serviceRegistrationResolver; this.m_logger = logger; } /** * @return The socket this request is associated with. */ protected Socket getSocket() { return m_socket; } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getInputStream() */ public ServletInputStream getInputStream() throws IOException { if ( m_getReaderCalled ) { throw new IllegalStateException( "getReader() has already been called." ); } if ( m_requestBody == null ) { parseBody( new BufferedInputStream( m_socket.getInputStream() ) ); } m_getInputStreamCalled = true; return new ConcreteServletInputStream( new ByteArrayInputStream( m_requestBody ) ); } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getReader() */ public BufferedReader getReader() throws IOException { if ( m_getInputStreamCalled ) { throw new IllegalStateException( "getInputStream() has already been called." ); } if ( m_requestBody == null ) { parseBody( new BufferedInputStream( m_socket.getInputStream() ) ); } m_getReaderCalled = true; return new BufferedReader( new InputStreamReader( new ByteArrayInputStream( m_requestBody ) ) ); } /** * This method parses the HTTP request line from the specified input stream * and stores the result. * * @param is * The input stream from which to read the HTTP request. * @throws java.io.IOException * If any I/O error occurs. **/ public void parseRequestLine( final ConcreteServletInputStream is ) throws IOException { String requestLine = is.readLine(); if ( requestLine == null ) { throw new IOException( "Unexpected end of file when reading request line." ); } StringTokenizer st = new StringTokenizer( requestLine, " " ); if ( st.countTokens() != 3 ) { throw new IOException( "Malformed HTTP request: " + requestLine ); } m_method = st.nextToken(); m_uri = st.nextToken(); m_version = st.nextToken(); // If the URI has query string, parse it. int qsIdx = m_uri.indexOf( "?" ); if ( qsIdx > 0 ) { m_queryString = m_uri.substring( qsIdx + 1 ); m_uri = m_uri.substring( 0, qsIdx ); } // If path contains multiple successive path separators (a//b/c a/b////c, etc.), strip them. if ( m_uri.indexOf( "//" ) > -1 ) { // separator m_uri = stripRedundantSeparators( m_uri ); } } /** * Remove successive '/' characters. * * @param in input string * @return stripped string */ private String stripRedundantSeparators( String in ) { StringBuffer sb = new StringBuffer(); boolean lastIsSeparator = false; for ( int i = 0; i < in.length(); ++i ) { char c = in.charAt( i ); if ( lastIsSeparator && c == '/' ) { continue; } sb.append( c ); if ( c == '/' ) { lastIsSeparator = true; } else { lastIsSeparator = false; } } return sb.toString(); } /** * This method parses the HTTP header lines from the specified input stream * and stores the results. * * The map m_headers is populated with two types of values, Strings if the * header occurs once or a List in the case that the same header is * specified multiple times. * * @param is * The input stream from which to read the HTTP header lines. * @throws java.io.IOException * If any I/O error occurs. **/ public void parseHeader( final ConcreteServletInputStream is ) throws IOException { for ( String s = is.readLine(); ( s != null ) && ( s.length() != 0 ); s = is.readLine() ) { int idx = s.indexOf( ":" ); if ( idx > 0 ) { String header = s.substring( 0, idx ).trim(); String value = s.substring( idx + 1 ).trim(); String key = header.toLowerCase(); if ( !m_headers.containsKey( key ) ) { m_headers.put( key, value ); } else { Object originalValue = m_headers.get( key ); if ( originalValue instanceof String ) { List headerList = new ArrayList(); headerList.add( originalValue ); headerList.add( value ); m_headers.put( key, headerList ); } else if ( originalValue instanceof List ) { ( ( List ) originalValue ).add( value ); } else { throw new RuntimeException( "Unexpected type in m_headers: " + originalValue.getClass().getName() ); } } } } if ( m_headers.containsKey( "Host" ) ) { m_uriHost = m_headers.get( "Host" ).toString(); } } /** * This method parses the HTTP body from the specified input stream and * ignores the result. * * @param is * The input stream from which to read the HTTP body. * @throws java.io.IOException * If any I/O error occurs. **/ public void parseBody( final InputStream is ) throws IOException { int length = getContentLength(); if ( length > 0 ) { ByteArrayOutputStream baos = null; byte[] buf = new byte[length]; int left = length; do { left = left - is.read( buf ); if ( left > 0 ) { if ( baos == null ) { baos = new ByteArrayOutputStream( length ); } baos.write( buf ); } } while ( left > 0 ); if ( baos != null ) { m_requestBody = baos.toByteArray(); } else { m_requestBody = buf; } } else { // Set this to a non-null value so we know that the body has been // parsed. m_requestBody = new byte[0]; } } /* * (non-Javadoc) * * @see javax.servlet.http.HttpServletRequest#getMethod() */ public String getMethod() { return m_method; } /** * Returns the value of the specified header, if present. * * @param header * The header value to retrieve. * @return The value of the specified header or <tt>null</tt>. **/ public String getHeader( final String header ) { Object value = m_headers.get( header.toLowerCase() ); if ( value == null ) { return null; } return value.toString(); } public Enumeration getHeaders( final String name ) { Object v = m_headers.get( name ); if ( v == null ) { return HttpConstants.EMPTY_ENUMERATION; } if ( v instanceof String ) { return Collections.enumeration( Arrays.asList( new String[] { ( String ) v } ) ); } if ( v instanceof List ) { return Collections.enumeration( ( List ) v ); } throw new RuntimeException( "Unexpected type in m_headers: " + v.getClass().getName() ); } public Enumeration getHeaderNames() { if ( m_headers.isEmpty() ) { return HttpConstants.EMPTY_ENUMERATION; } return Collections.enumeration( m_headers.keySet() ); } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getAttribute(java.lang.String) */ public Object getAttribute( final String arg0 ) { if ( m_attributes != null ) { return m_attributes.get( arg0 ); } return null; } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getAttributeNames() */ public Enumeration getAttributeNames() { if ( m_attributes != null ) { return Collections.enumeration( m_attributes.keySet() ); } return HttpConstants.EMPTY_ENUMERATION; } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getCharacterEncoding() */ public String getCharacterEncoding() { return getHeader( "Accept-Encoding" ); } public int getContentLength() { int len = 0; try { len = Integer.parseInt( getHeader( "Content-Length" ) ); } catch ( NumberFormatException e ) { // Ignore this exception intentionally. } return len; } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getContentType() */ public String getContentType() { return getHeader( "Content-Type" ); } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getLocale() */ public Locale getLocale() { return m_locale; } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getLocales() */ public Enumeration getLocales() { return Collections.enumeration( Arrays.asList( new Object[] { m_locale } ) ); } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getParameter(java.lang.String) */ public String getParameter( final String arg0 ) { if ( m_parameters == null ) { try { m_parameters = parseParameters(); } catch ( UnsupportedEncodingException e ) { m_logger.log( Logger.LOG_ERROR, "Failed to parse request parameters.", e ); return null; } } return ( String ) m_parameters.get( arg0 ); } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getParameterMap() */ public Map getParameterMap() { if ( m_parameters == null ) { try { m_parameters = parseParameters(); } catch ( UnsupportedEncodingException e ) { m_logger.log( Logger.LOG_ERROR, "Failed to parse request parameters.", e ); return null; } } return m_parameters; } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getParameterNames() */ public Enumeration getParameterNames() { if ( m_parameters == null ) { try { m_parameters = parseParameters(); } catch ( UnsupportedEncodingException e ) { m_logger.log( Logger.LOG_ERROR, "Failed to parse request parameters.", e ); return null; } } return Collections.enumeration( m_parameters.keySet() ); } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String) */ public String[] getParameterValues( String arg0 ) { if ( m_parameters == null ) { try { m_parameters = parseParameters(); } catch ( UnsupportedEncodingException e ) { m_logger.log( Logger.LOG_ERROR, "Failed to parse request parameters.", e ); return null; } } return ( String[] ) m_parameters.values().toArray( new String[m_parameters.size()] ); } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getProtocol() */ public String getProtocol() { return m_version; } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getRealPath(java.lang.String) */ public String getRealPath( final String arg0 ) { throw new UnimplementedAPIException(); } /* * (non-Javadoc) * * @see javax.servlet.ServletRequest#getRemoteAddr() */ public String getRemoteAddr() { return getSocket().getInetAddress().getHostAddress(); } /* (non-Javadoc) * @see javax.servlet.ServletRequest#getRemoteHost() */ public String getRemoteHost() { return getSocket().getInetAddress().getHostName(); } /* (non-Javadoc) * @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String) */ public RequestDispatcher getRequestDispatcher( String arg0 ) { return null; } /* (non-Javadoc) * @see javax.servlet.ServletRequest#getScheme() */ public String getScheme() { return HttpConstants.HTTP_SCHEME; } /* (non-Javadoc) * @see javax.servlet.ServletRequest#getServerName() */ public String getServerName() { return HttpConstants.SERVER_INFO; } /* (non-Javadoc) * @see javax.servlet.ServletRequest#getServerPort() */ public int getServerPort() { return getSocket().getLocalPort(); } /* (non-Javadoc) * @see javax.servlet.ServletRequest#isSecure() */ public boolean isSecure() { return false; } /* (non-Javadoc) * @see javax.servlet.ServletRequest#removeAttribute(java.lang.String) */ public void removeAttribute( String arg0 ) { if ( m_attributes != null ) { m_attributes.remove( arg0 ); } } /* (non-Javadoc) * @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object) */ public void setAttribute( String arg0, Object arg1 ) { if ( m_attributes == null ) { m_attributes = new HashMap(); } m_attributes.put( arg0, arg1 ); } /* (non-Javadoc) * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String) */ public void setCharacterEncoding( String arg0 ) throws UnsupportedEncodingException { throw new UnimplementedAPIException(); } /* * (non-Javadoc) * * @see javax.servlet.http.HttpServletRequest#getAuthType() */ public String getAuthType() { return null; } /* * (non-Javadoc) * * @see javax.servlet.http.HttpServletRequest#getCookies() */ public Cookie[] getCookies() { if ( m_cookies == null ) { String cookieHeader = getHeader( "Cookie" ); if ( cookieHeader == null ) { return null; } List cookieList = new ArrayList(); for ( Iterator i = Arrays.asList( splitString( cookieHeader, ";" ) ).iterator(); i.hasNext(); ) { String[] nvp = splitString( i.next().toString(), "=" ); if ( nvp.length != 2 ) { //Ignore invalid cookie and and continue. continue; } cookieList.add( new Cookie( nvp[0].trim(), nvp[1].trim() ) ); } m_cookies = ( Cookie[] ) cookieList.toArray( new Cookie[cookieList.size()] ); } return m_cookies; } /* * (non-Javadoc) * * @see * javax.servlet.http.HttpServletRequest#getDateHeader(java.lang.String) */ public long getDateHeader( final String name ) { String headerValue = getHeader( name ); if ( headerValue == null ) { return -1; } for (int x = 0; x < formatsTemplate.length; x++) { SimpleDateFormat sdf = formatsTemplate[x]; try { sdf.setTimeZone(TimeZone.getTimeZone(HttpConstants.HTTP_TIMEZONE)); return sdf.parse(headerValue).getTime(); } catch (ParseException e) { // } catch (NumberFormatException nfe) { // } } // if none of them work. throw new IllegalArgumentException("Unable to convert to date: " + headerValue); } /* * (non-Javadoc) * * @see javax.servlet.http.HttpServletRequest#getIntHeader(java.lang.String) */ public int getIntHeader( final String name ) { String value = getHeader( name ); if ( value == null ) { return -1; } return Integer.parseInt( value ); } /* * (non-Javadoc) * * @see javax.servlet.http.HttpServletRequest#getPathInfo() */ public String getPathInfo() { String alias = getAlias(); if ( m_uri != null && alias.length() > 0 ) { if ( m_uri.length() == alias.length() ) { return null; } return m_uri.substring( alias.length() ); } return null; } public String getPathTranslated() { // TODO: Always returning null may be incorrect. return null; } public String getContextPath() { return ""; } /* * (non-Javadoc) * * @see javax.servlet.http.HttpServletRequest#getQueryString() */ public String getQueryString() { return m_queryString; } public String getRemoteUser() { return null; } public boolean isUserInRole( String role ) { return false; } public Principal getUserPrincipal() { return null; } public String getRequestedSessionId() { return null; } /* * (non-Javadoc) * * @see javax.servlet.http.HttpServletRequest#getRequestURI() */ public String getRequestURI() { return m_uri; } /* * (non-Javadoc) * * @see javax.servlet.http.HttpServletRequest#getRequestURL() */ public StringBuffer getRequestURL() { StringBuffer sb = new StringBuffer(); if ( m_uriHost != null ) { sb.append( m_uriHost ); } sb.append( m_uri ); return sb; } /* * (non-Javadoc) * * @see javax.servlet.http.HttpServletRequest#getServletPath() */ public String getServletPath() { if ( m_servletPath == null ) { ServiceRegistration element = m_resolver.getServiceRegistration( m_uri ); if ( element == null ) { throw new IllegalStateException( "Unable to get ServletElement for HttpRequest." ); } m_servletPath = element.getAlias(); } return m_servletPath; } /** * @return Alias associated with this request */ private String getAlias() { ServiceRegistration element = m_resolver.getServiceRegistration( m_uri ); if ( element == null ) { throw new IllegalStateException( "Unable to get ServletElement for HttpRequest." ); } return element.getAlias(); } public HttpSession getSession( boolean create ) { throw new UnimplementedAPIException(); } public HttpSession getSession() { throw new UnimplementedAPIException(); } public boolean isRequestedSessionIdValid() { throw new UnimplementedAPIException(); } public boolean isRequestedSessionIdFromCookie() { throw new UnimplementedAPIException(); } public boolean isRequestedSessionIdFromURL() { throw new UnimplementedAPIException(); } public boolean isRequestedSessionIdFromUrl() { throw new UnimplementedAPIException(); } /** * Parse the parameters in the request and return as a Map of <String, * String>. * * @return * @throws UnsupportedEncodingException */ private Map parseParameters() throws UnsupportedEncodingException { Map params = new HashMap(); String queryString = getQueryString(); if ( queryString != null && queryString.length() > 0 ) { parseParameterString( queryString, params ); } if ( m_requestBody != null && m_requestBody.length > 0 ) { parseParameterString( new String( m_requestBody ), params ); } return Collections.unmodifiableMap( params ); } /** * * @param queryString * A String formatted like: 'home=Cosby&favorite+flavor=flies' * @param params * Map of <String, String> of existing parameters to be added to. * * @throws UnsupportedEncodingException * if encoding type is unsupported */ private void parseParameterString( final String queryString, final Map params ) throws UnsupportedEncodingException { String[] parameters = splitString( queryString, "&" ); for ( Iterator i = Arrays.asList( parameters ).iterator(); i.hasNext(); ) { String[] nva = splitString( i.next().toString(), "=" ); if ( nva.length == 2 ) { // Also decode value, not just name. boolean mustDecodeValue = nva[1].indexOf('+') >= 0 || nva[1].indexOf('%') >= 0; //Deprecated method decode() intentionally used for Java 1.3 compatibility. //Tomcat only does this if it sees some evidence of encoding. String val = nva[1].trim(); if (mustDecodeValue) { val = URLDecoder.decode(val); } params.put( URLDecoder.decode( nva[0].trim() ), val ); } } } /** * @param method * HTTP method * @return true if the psased HTTP method is supported by this server. */ public static boolean isSupportedMethod( final String method ) { if ( method.equals( HttpConstants.OPTIONS_REQUEST ) ) { return false; } return true; } /* (non-Javadoc) * @see java.lang.Object#toString() */ public String toString() { if ( m_method != null && m_uri != null ) { return m_method + " " + m_uri; } return super.toString(); } /* (non-Javadoc) * @see javax.servlet.ServletRequest#getLocalAddr() */ public String getLocalAddr() { return m_socket.getLocalAddress().getHostAddress(); } /* (non-Javadoc) * @see javax.servlet.ServletRequest#getLocalName() */ public String getLocalName() { return m_socket.getLocalAddress().getHostName(); } /* (non-Javadoc) * @see javax.servlet.ServletRequest#getLocalPort() */ public int getLocalPort() { return m_socket.getLocalPort(); } /* (non-Javadoc) * @see javax.servlet.ServletRequest#getRemotePort() */ public int getRemotePort() { return m_socket.getPort(); } /** * Split a string into substrings based on delimiter character. * * @param instr input string * @param separator separator char * @return array of substrings * @throws IllegalArgumentException if instr parameter is null. */ private static String[] splitString( String instr, String separator ) { if ( instr == null ) { throw new IllegalArgumentException( "Input string must not be null." ); } StringTokenizer tokenizer = new StringTokenizer(instr, separator); int length = tokenizer.countTokens(); String[] str_array = new String[length]; for ( int i = 0; i < length; i++ ) { str_array[i] = tokenizer.nextToken(); } return str_array; } }