package com.meterware.servletunit; /******************************************************************************************************************** * * Copyright (c) 2000-2008, Russell Gold * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated * documentation files (the "Software"), to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and * to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions * of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * *******************************************************************************************************************/ import com.meterware.httpunit.WebRequest; import com.meterware.httpunit.WebClient; import com.meterware.httpunit.Base64; import com.meterware.httpunit.HttpUnitUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.util.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.Cookie; import javax.servlet.http.HttpSession; import javax.servlet.ServletInputStream; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; /** * This class represents a servlet request created from a WebRequest. **/ class ServletUnitHttpRequest implements HttpServletRequest { private ServletInputStreamImpl _inputStream; // TODO remove when test case for [ 1509117 ] getContentType() // gets available private String _contentType; private Vector _locales; private String _protocol; private boolean _secure; private RequestContext _requestContext; private String _charset; private boolean _gotReader; private boolean _gotInputStream; private BufferedReader _reader; /** * Constructs a ServletUnitHttpRequest from a WebRequest object. **/ ServletUnitHttpRequest( ServletMetaData servletRequest, WebRequest request, ServletUnitContext context, Dictionary clientHeaders, byte[] messageBody ) throws MalformedURLException { if (context == null) throw new IllegalArgumentException( "Context must not be null" ); _servletRequest = servletRequest; _request = request; _context = context; _headers = new WebClient.HeaderDictionary(); _headers.addEntries( clientHeaders ); _headers.addEntries( request.getHeaders() ); setCookiesFromHeader( _headers ); _messageBody = messageBody; _protocol=request.getURL().getProtocol().toLowerCase(); _secure = _protocol.endsWith("s" ); _requestContext = new RequestContext( request.getURL() ); String contentTypeHeader = (String) _headers.get( "Content-Type" ); if (contentTypeHeader != null) { String[] res = HttpUnitUtils.parseContentTypeHeader( contentTypeHeader ); _contentType = res[0]; _charset = res[1]; _requestContext.setMessageEncoding( _charset ); } if (_headers.get( "Content-Length") == null) _headers.put( "Content-Length", Integer.toString( messageBody.length ) ); boolean setBody= // pre [ 1509117 ] getContentType() // _messageBody != null && (_contentType == null || _contentType.indexOf( "x-www-form-urlencoded" ) >= 0 ); // patch version: _messageBody != null && (contentTypeHeader == null || contentTypeHeader.indexOf( "x-www-form-urlencoded" ) >= 0 ); if (setBody) { _requestContext.setMessageBody( _messageBody ); } } //----------------------------------------- HttpServletRequest methods -------------------------- /** * Returns the name of the authentication scheme used to protect the servlet, for example, "BASIC" or "SSL," * or null if the servlet was not protected. **/ public String getAuthType() { return null; } /** * Returns the query string that is contained in the request URL after the path. **/ public String getQueryString() { return _request.getQueryString(); } /** * Returns an array containing all of the Cookie objects the client sent with this request. * This method returns null if no cookies were sent. **/ public Cookie[] getCookies() { if (_cookies.size() == 0) { return null; } else { Cookie[] result = new Cookie[ _cookies.size() ]; _cookies.copyInto( result ); return result; } } /** * Returns the value of the specified request header as an int. If the request does not have a header * of the specified name, this method returns -1. If the header cannot be converted to an integer, * this method throws a NumberFormatException. **/ public int getIntHeader( String name ) { return Integer.parseInt( getHeader( name ) ); } /** * Returns the value of the specified request header as a long value that represents a Date object. * Use this method with headers that contain dates, such as If-Modified-Since. *
* The date is returned as the number of milliseconds since January 1, 1970 GMT. The header name is case insensitive. * * If the request did not have a header of the specified name, this method returns -1. * If the header can't be converted to a date, the method throws an IllegalArgumentException. **/ public long getDateHeader( String name ) { try { String dateString=getHeader(name); Date headerDate=new Date(dateString); return headerDate.getTime(); } catch (Exception e) { return -1; } } /** * Returns the value of the specified request header as a String. If the request did not include * a header of the specified name, this method returns null. The header name is case insensitive. * You can use this method with any request header. **/ public String getHeader( String name ) { return (String) _headers.get( name ); } /** * Returns an enumeration of all the header names this request contains. If the request has no headers, * this method returns an empty enumeration. * * Some servlet containers do not allow do not allow servlets to access headers using this method, * in which case this method returns null. **/ public Enumeration getHeaderNames() { return _headers.keys(); } /** * Returns the part of this request's URL that calls the servlet. This includes either the servlet name * or a path to the servlet, but does not include any extra path information or a query string. **/ public String getServletPath() { return _servletRequest.getServletPath(); } /** * Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT. **/ public String getMethod() { return _request.getMethod(); } /** * Returns any extra path information associated with the URL the client sent when it made this request. * The extra path information follows the servlet path but precedes the query string. * This method returns null if there was no extra path information. **/ public String getPathInfo() { return _servletRequest.getPathInfo(); } /** * Returns any extra path information after the servlet name but before the query string, * and translates it to a real path. If the URL does not have any extra path information, this method returns null. **/ public String getPathTranslated() { return null; } /** * Checks whether the requested session ID came in as a cookie. **/ public boolean isRequestedSessionIdFromCookie() { return _sessionID != null; } /** * Returns the login of the user making this request, if the user has been authenticated, * or null if the user has not been authenticated. * Whether the user name is sent with each subsequent request depends on the browser and type of authentication. **/ public String getRemoteUser() { return _userName; } /** * Returns the session ID specified by the client. This may not be the same as the ID of the actual session in use. * For example, if the request specified an old (expired) session ID and the server has started a new session, * this method gets a new session with a new ID. If the request did not specify a session ID, this method returns null. **/ public String getRequestedSessionId() { return _sessionID; } /** * Returns the part of this request's URL from the protocol name up to the query string in the first line of the HTTP request. **/ public String getRequestURI() { return _requestContext.getRequestURI(); } /** * Returns the current HttpSession associated with this request or, if there is no current session * and create is true, returns a new session. *
* If create is false and the request has no valid HttpSession, this method returns null. **/ public HttpSession getSession( boolean create ) { _session = _context.getValidSession( getRequestedSessionId(), _session, create ); return _session; } /** * Returns the current session associated with this request, or if the request does not have a session, creates one. **/ public HttpSession getSession() { return getSession( true ); } /** * Checks whether the requested session ID is still valid. **/ public boolean isRequestedSessionIdValid() { return false; } /** * Checks whether the requested session ID came in as part of the request URL. **/ public boolean isRequestedSessionIdFromURL() { return false; } /** * @deprecated use #isRequestedSessionIdFromURL **/ public boolean isRequestedSessionIdFromUrl() { return isRequestedSessionIdFromURL(); } //--------------------------------- ServletRequest methods ---------------------------------------------------- /** * Returns the length, in bytes, of the content contained in the * request and sent by way of the input stream or -1 if the * length is not known. **/ public int getContentLength() { return getIntHeader( "Content-length" ); } /** * * Returns the value of the named attribute as an Object. * This method allows the servlet engine to give the servlet * custom information about a request. This method returns * null if no attribute of the given name exists. **/ public Object getAttribute( String name ) { return _attributes.get( name ); } /** * Returns an Enumeration containing the * names of the attributes available to this request. * This method returns an empty Enumeration * if the request has no attributes available to it. **/ public Enumeration getAttributeNames() { return _attributes.keys(); } /** * Retrieves binary data from the body of the request as * a {@link ServletInputStream}, which * gives you the ability to read one line at a time. * * @return a {@link ServletInputStream} object containing * the body of the request * * @exception IllegalStateException if the {@link #getReader} method * has already been called for this request * * @exception IOException if an input or output exception occurred * */ public ServletInputStream getInputStream() throws IOException { if (_gotReader) { throw new IllegalStateException("getReader() has already been called for this request"); } initializeInputStream(); _gotInputStream = true; return _inputStream; } /** * initialize the inputStream */ private void initializeInputStream() { if (_inputStream == null) { _inputStream = new ServletInputStreamImpl( _messageBody ); } } /** * Returns the name of the character encoding style used in this * request. This method returns null if the request * does not use character encoding. **/ public String getCharacterEncoding() { return _charset; } /** * * Returns an Enumeration of String * objects containing the names of the parameters contained * in this request. If the request has * no parameters or if the input stream is empty, returns an * empty Enumeration. The input stream is empty * when all the data returned by {@link #getInputStream} has * been read. **/ public Enumeration getParameterNames() { return _requestContext.getParameterNames(); } /** * Returns the MIME type of the content of the request, or * null if the type is not known. Same as the value * of the CGI variable CONTENT_TYPE. **/ public String getContentType() { String result; result=_contentType; /** * suggested fix by bug report * [ 1509117 ] getContentType() * by Tom Parker */ result=this.getHeader( "Content-Type" ); return result; } /** * Returns the value of a request parameter as a String, * or null if the parameter does not exist. Request parameters * are extra information sent with the request. **/ public String getParameter( String name ) { String[] parameters = getParameterValues( name ); return parameters == null ? null : parameters[0]; } /** * Returns an array of String objects containing * all of the values the * given request parameter has, or null if the * parameter does not exist. For example, in an HTTP servlet, * this method returns an array of String objects * containing the values of a query string or posted form. **/ public String[] getParameterValues( String name ) { return _requestContext.getParameterValues( name ); } /** * Returns the name and version of the protocol the request uses * in the form protocol/majorVersion.minorVersion, for * example, HTTP/1.1. **/ public String getProtocol() { return "HTTP/1.1"; } /** * Returns the name of the scheme used to make this request, * for example, * http, https, or ftp. * Different schemes have different rules for constructing URLs, * as noted in RFC 1738. **/ public String getScheme() { String result= isSecure() ? "https" : "http"; result=_protocol; return result; } /** * Returns the fully qualified name of the client that sent the * request. **/ public String getRemoteHost() { return "localhost"; } /** * Returns the host name of the server that received the request. **/ public String getServerName() { return "localhost"; } /** * Returns the port number on which this request was received. **/ public int getServerPort() { return 0; } /** * * @deprecated As of Version 2.1 of the Java Servlet API, * use {@link javax.servlet.ServletContext#getRealPath} instead. * */ public String getRealPath( String path ) { throwNotImplementedYet(); return ""; } /** * Returns the body of the request as a BufferedReader * that translates character set encodings. * @since [ 1221537 ] Patch: ServletUnitHttpRequest.getReader not implemented yet * @author Tim - timmorrow (SourceForge) * @return the reader **/ public BufferedReader getReader() throws IOException { if (_gotInputStream) { throw new IllegalStateException("getInputStream() has already been called on this request"); } if (_reader == null) { initializeInputStream(); String encoding = getCharacterEncoding(); if (encoding == null) { encoding = HttpUnitUtils.DEFAULT_CHARACTER_SET; } _reader = new BufferedReader(new InputStreamReader(_inputStream, encoding)); _gotReader = true; } return _reader; } /** * Returns the Internet Protocol (IP) address of the client * that sent the request. **/ public String getRemoteAddr() { return LOOPBACK_ADDRESS; } /** * * Stores an attribute in the context of this request. * Attributes are reset between requests. **/ public void setAttribute( String key, Object o ) { if (o == null) _attributes.remove( key ); else _attributes.put( key, o ); } //--------------------------------- methods added to ServletRequest in Servlet API 2.2 ------------------------------------------------ /** * Returns a boolean indicating whether this request was made using a secure channel, such as HTTPS. **/ public boolean isSecure() { return _secure; } /** * Returns the preferred Locale that the client will accept content in, based on the Accept-Language header. * If the client request doesn't provide an Accept-Language header, this method returns the default locale for the server. **/ public Locale getLocale() { return (Locale) getPreferredLocales().firstElement(); } /** * Returns an Enumeration of Locale objects indicating, in decreasing order starting with the preferred locale, * the locales that are acceptable to the client based on the Accept-Language header. * If the client request doesn't provide an Accept-Language header, this * method returns an Enumeration containing one Locale, the default locale for the server. **/ public java.util.Enumeration getLocales() { return getPreferredLocales().elements(); } /** * Parses the accept-language header to obtain a vector of preferred locales * @return the preferred locales, sorted by qvalue */ private Vector getPreferredLocales() { if (_locales == null) { _locales = new Vector(); String languages = getHeader( "accept-language" ); if (languages == null) { _locales.add( Locale.getDefault() ); } else { StringTokenizer st = new StringTokenizer( languages, "," ); ArrayList al = new ArrayList(); while (st.hasMoreTokens()) { String token = st.nextToken(); al.add( new PrioritizedLocale( token ) ); } Collections.sort( al ); for (Iterator iterator = al.iterator(); iterator.hasNext();) { _locales.add( ((PrioritizedLocale) iterator.next()).getLocale() ); } } } return _locales; } /** * Removes an attribute from this request. This method is not generally needed * as attributes only persist as long as the request is being handled. **/ public void removeAttribute( String name ) { _attributes.remove( name ); } /** * Returns a RequestDispatcher object that acts as a wrapper for the resource located at the given path. * A RequestDispatcher object can be used to forward a request to the resource or to include the * resource in a response. The resource can be dynamic or static. * * The pathname specified may be relative, although it cannot extend outside the current servlet * context. If the path begins with a "/" it is interpreted as relative to the current context root. * This method returns null if the servlet container cannot return a RequestDispatcher. * * The difference between this method and ServletContext.getRequestDispatcher(java.lang.String) * is that this method can take a relative path. **/ public RequestDispatcher getRequestDispatcher( String path ) { try { if (!path.startsWith( "/" )) path = combinedPath( getServletPath(), path ); return _servletRequest.getServlet().getServletConfig().getServletContext().getRequestDispatcher( path ); } catch (ServletException e) { return null; } } private String combinedPath( String basePath, String relativePath ) { if (basePath.indexOf( '/' ) < 0) return relativePath; return basePath.substring( 0, basePath.lastIndexOf( '/' ) ) + '/' + relativePath; } //--------------------------------- methods added to HttpServletRequest in Servlet API 2.2 ------------------------------------------------ /** * Returns a java.security.Principal object containing the name of the current authenticated user. * If the user has not been authenticated, the method returns null. **/ public java.security.Principal getUserPrincipal() { return null; } /** * Returns a boolean indicating whether the authenticated user is included in the specified * logical "role". Roles and role membership can be defined using deployment descriptors. * If the user has not been authenticated, the method returns false. **/ public boolean isUserInRole( String role ) { if (_roles == null) return false; for (int i = 0; i < _roles.length; i++) { if (role.equals( _roles[i] )) return true; } return false; } /** * Returns all the values of the specified request header as an Enumeration of String objects. **/ public java.util.Enumeration getHeaders( String name ) { Vector list = new Vector(); if (_headers.containsKey( name )) list.add( _headers.get( name )); return list.elements(); } /** * Returns the portion of the request URI that indicates the context of the request. * The context path always comes first in a request URI. The path starts with a "/" character * but does not end with a "/" character. For servlets in the default (root) context, * this method returns "". **/ public String getContextPath() { return _context.getContextPath(); } //--------------------------------------- methods added to ServletRequest in Servlet API 2.3 ---------------------------- /** * Returns a java.util.Map of the parameters of this request. * Request parameters are extra information sent with the request. For HTTP servlets, parameters are contained * in the query string or posted form data. * * @since 1.3 **/ public Map getParameterMap() { return _requestContext.getParameterMap(); } /** * Overrides the name of the character encoding used in the body of this request. * This method must be called prior to reading request parameters or reading input using getReader(). * * @since 1.3 **/ public void setCharacterEncoding( String charset ) throws UnsupportedEncodingException { _charset = charset; _requestContext.setMessageEncoding( charset ); } //--------------------------------------- methods added to HttpServletRequest in Servlet API 2.3 ---------------------------- /** * Reconstructs the URL the client used to make the request. * The returned URL contains a protocol, server name, port number, and server path, but * it does not include query string parameters. * * Because this method returns a StringBuffer, not a string, you can modify the URL easily, for example, * to append query parameters. * * This method is useful for creating redirect messages and for reporting errors. * * @since 1.3 */ public StringBuffer getRequestURL() { StringBuffer url = new StringBuffer(); try { url.append( _request.getURL().getProtocol() ).append( "://" ); url.append( _request.getURL().getHost() ); url.append( _request.getURL().getPath() ); } catch (MalformedURLException e) { throw new RuntimeException( "unable to read URL from request: " + _request ); } return url; } //--------------------------------------- methods added to ServletRequest in Servlet API 2.4 ---------------------------- public int getRemotePort() { return 0; //To change body of implemented methods use File | Settings | File Templates. } public String getLocalName() { return "localhost"; } public String getLocalAddr() { return "127.0.0.1"; } public int getLocalPort() { return 0; //To change body of implemented methods use File | Settings | File Templates. } //--------------------------------------------- package members ---------------------------------------------- private void addCookie( Cookie cookie ) { _cookies.addElement( cookie ); if (cookie.getName().equalsIgnoreCase( ServletUnitHttpSession.SESSION_COOKIE_NAME )) { _sessionID = cookie.getValue(); } } private ServletUnitHttpSession getServletSession() { return (ServletUnitHttpSession) getSession(); } void readFormAuthentication() { if (getSession( /* create */ false ) != null) { recordAuthenticationInfo( getServletSession().getUserName(), getServletSession().getRoles() ); } } void readBasicAuthentication() { String authorizationHeader = (String) _headers.get( "Authorization" ); if (authorizationHeader != null) { String userAndPassword = Base64.decode( authorizationHeader.substring( authorizationHeader.indexOf( ' ' ) + 1 ) ); int colonPos = userAndPassword.indexOf( ':' ); recordAuthenticationInfo( userAndPassword.substring( 0, colonPos ), toArray( userAndPassword.substring( colonPos+1 ) ) ); } } static String[] toArray( String roleList ) { StringTokenizer st = new StringTokenizer( roleList, "," ); String[] result = new String[ st.countTokens() ]; for (int i = 0; i < result.length; i++) { result[i] = st.nextToken(); } return result; } void recordAuthenticationInfo( String userName, String[] roles ) { _userName = userName; _roles = roles; } //--------------------------------------------- private members ---------------------------------------------- final static private String LOOPBACK_ADDRESS = "127.0.0.1"; private WebRequest _request; private ServletMetaData _servletRequest; private WebClient.HeaderDictionary _headers; private ServletUnitContext _context; private ServletUnitHttpSession _session; private Hashtable _attributes = new Hashtable(); private Vector _cookies = new Vector(); private String _sessionID; private byte[] _messageBody; private String _userName; private String[] _roles; private void throwNotImplementedYet() { throw new RuntimeException( "Not implemented yet" ); } private void setCookiesFromHeader( Dictionary clientHeaders ) { String cookieHeader = (String) clientHeaders.get( "Cookie" ); if (cookieHeader == null) return; StringTokenizer st = new StringTokenizer( cookieHeader, ",;=", true ); String lastToken = st.nextToken(); while (st.hasMoreTokens()) { String token = st.nextToken(); if (token.equals( "=" )) { if (st.hasMoreTokens()) addCookie( new Cookie( lastToken.trim(), st.nextToken().trim() ) ); } lastToken = token; } } static class PrioritizedLocale implements Comparable { private Locale _locale; private float _priority; PrioritizedLocale( String languageSpec ) { int semiIndex = languageSpec.indexOf( ';' ); if (semiIndex < 0) { _priority = 1; _locale = parseLocale( languageSpec ); } else { _priority = Float.parseFloat( languageSpec.substring( languageSpec.indexOf( '=', semiIndex )+1 ) ); _locale = parseLocale( languageSpec.substring( 0, semiIndex ) ); } } private Locale parseLocale( String range ) { range = range.trim(); int dashIndex = range.indexOf( '-' ); if (dashIndex < 0) { return new Locale( range, "" ); } else { return new Locale( range.substring( 0, dashIndex ), range.substring( dashIndex+1 ) ); } } public Locale getLocale() { return _locale; } public int compareTo( Object o ) { if (!(o instanceof PrioritizedLocale)) throw new IllegalArgumentException( "may only combine with other prioritized locales" ); PrioritizedLocale other = (PrioritizedLocale) o; return _priority == other._priority ? _locale.getLanguage().compareTo( other._locale.getLanguage() ) : (_priority < other._priority ? +1 : -1 ); } } }