package com.meterware.servletunit; /******************************************************************************************************************** * $Id: ServletUnitHttpRequest.java,v 1.37 2006/03/24 19:59:12 russgold Exp $ * * Copyright (c) 2000-2006, 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 java.io.BufferedReader; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collections; import java.util.Dictionary; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.StringTokenizer; import java.util.Vector; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import com.meterware.httpunit.Base64; import com.meterware.httpunit.HttpUnitUtils; import com.meterware.httpunit.WebClient; import com.meterware.httpunit.WebRequest; /** * This class represents a servlet request created from a WebRequest. **/ class ServletUnitHttpRequest implements HttpServletRequest { private ServletInputStreamImpl _inputStream; private String _contentType; private Vector<Locale> _locales; private boolean _secure; private RequestContext _requestContext; private String _charset; /** * 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; _secure = request.getURL().getProtocol().equalsIgnoreCase("https"); String contentTypeHeader = (String) _headers.get("Content-Type"); if (contentTypeHeader != null) { String[] res = HttpUnitUtils.parseContentTypeHeader(contentTypeHeader); _contentType = res[0]; _charset = res[1]; } _requestContext = new RequestContext(request.getURL(), _charset); if (_headers.get("Content-Length") == null) _headers.put("Content-Length", Integer.toString(messageBody.length)); if (_messageBody != null && (_contentType == null || _contentType.indexOf("x-www-form-urlencoded") >= 0)) { _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. <br> * 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) { 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. <br> * 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 <code>Object</code>. This method allows the servlet engine to give * the servlet custom information about a request. This method returns <code>null</code> if no attribute of the * given name exists. **/ public Object getAttribute(String name) { return _attributes.get(name); } /** * Returns an <code>Enumeration</code> containing the names of the attributes available to this request. This method * returns an empty <code>Enumeration</code> if the request has no attributes available to it. **/ public Enumeration<String> 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 (_inputStream == null) { _inputStream = new ServletInputStreamImpl(_messageBody); } return _inputStream; } /** * Returns the name of the character encoding style used in this request. This method returns <code>null</code> if * the request does not use character encoding. **/ public String getCharacterEncoding() { return _charset; } /** * Returns an <code>Enumeration</code> of <code>String</code> 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 * <code>Enumeration</code>. The input stream is empty when all the data returned by {@link #getInputStream} has * been read. **/ public Enumeration<Object> getParameterNames() { return _requestContext.getParameterNames(); } /** * Returns the MIME type of the content of the request, or <code>null</code> if the type is not known. Same as the * value of the CGI variable CONTENT_TYPE. **/ public String getContentType() { return _contentType; } /** * Returns the value of a request parameter as a <code>String</code>, or <code>null</code> 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 <code>String</code> objects containing all of the values the given request parameter has, or * <code>null</code> if the parameter does not exist. For example, in an HTTP servlet, this method returns an array * of <code>String</code> 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 * <i>protocol/majorVersion.minorVersion</i>, 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, <code>http</code>, <code>https</code>, or * <code>ftp</code>. Different schemes have different rules for constructing URLs, as noted in RFC 1738. **/ public String getScheme() { return "http"; } /** * 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 <code>BufferedReader</code> that translates character set encodings. **/ public BufferedReader getReader() throws IOException { throwNotImplementedYet(); return null; } /** * 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 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<Locale> 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<Locale> getPreferredLocales() { if (_locales == null) { _locales = new Vector<Locale>(); String languages = getHeader("accept-language"); if (languages == null) { _locales.add(Locale.getDefault()); } else { StringTokenizer st = new StringTokenizer(languages, ","); ArrayList<PrioritizedLocale> al = new ArrayList<PrioritizedLocale>(); while (st.hasMoreTokens()) { String token = st.nextToken(); al.add(new PrioritizedLocale(token)); } Collections.sort(al); for (Iterator<PrioritizedLocale> iterator = al.iterator(); iterator.hasNext();) { _locales.add(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<Object> getHeaders(String name) { Vector<Object> list = new Vector<Object>(); 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<Object, String[]> 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<String, Object> _attributes = new Hashtable<String, Object>(); private Vector<Cookie> _cookies = new Vector<Cookie>(); 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); } } }