org.owasp.esapi.reference.DefaultHTTPUtilities.java Source code

Java tutorial

Introduction

Here is the source code for org.owasp.esapi.reference.DefaultHTTPUtilities.java

Source

/**
 * OWASP Enterprise Security API (ESAPI)
 *
 * This file is part of the Open Web Application Security Project (OWASP)
 * Enterprise Security API (ESAPI) project. For details, please see
 * <a href="http://www.owasp.org/index.php/ESAPI">http://www.owasp.org/index.php/ESAPI</a>.
 *
 * Copyright (c) 2007 - The OWASP Foundation
 *
 * The ESAPI is published by OWASP under the BSD license. You should read and accept the
 * LICENSE before you use, modify, and/or redistribute this software.
 *
 * @author Jeff Williams <a href="http://www.aspectsecurity.com">Aspect Security</a>
 * @created 2007
 */
package org.owasp.esapi.reference;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.HTTPUtilities;
import org.owasp.esapi.Logger;
import org.owasp.esapi.StringUtilities;
import org.owasp.esapi.User;
import org.owasp.esapi.ValidationErrorList;
import org.owasp.esapi.codecs.Hex;
import org.owasp.esapi.crypto.CipherText;
import org.owasp.esapi.crypto.PlainText;
import org.owasp.esapi.errors.AccessControlException;
import org.owasp.esapi.errors.AuthenticationException;
import org.owasp.esapi.errors.EncodingException;
import org.owasp.esapi.errors.EncryptionException;
import org.owasp.esapi.errors.IntegrityException;
import org.owasp.esapi.errors.IntrusionException;
import org.owasp.esapi.errors.ValidationException;
import org.owasp.esapi.errors.ValidationUploadException;

/**
 * Reference implementation of the HTTPUtilities interface. This implementation
 * uses the Apache Commons FileUploader library, which in turn uses the Apache
 * Commons IO library.
 * <P>
 * To simplify the interface, some methods use the current request and response that
 * are tracked by ThreadLocal variables in the Authenticator. This means that you
 * must have called ESAPI.authenticator().setCurrentHTTP(request, response) before
 * calling these methods.
 * <P>
 * Typically, this is done by calling the Authenticator.login() method, which
 * calls setCurrentHTTP() automatically. However if you want to use these methods
 * in another application, you should explicitly call setCurrentHTTP() in your
 * own code. In either case, you *must* call ESAPI.clearCurrent() to clear threadlocal
 * variables before the thread is reused. The advantages of having identity everywhere
 * outweigh the disadvantages of this approach.
 *
 * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) <a
 *         href="http://www.aspectsecurity.com">Aspect Security</a>
 * @since June 1, 2007
 * @see org.owasp.esapi.HTTPUtilities
 */
public class DefaultHTTPUtilities implements org.owasp.esapi.HTTPUtilities {
    private static volatile HTTPUtilities instance = null;

    public static HTTPUtilities getInstance() {
        if (instance == null) {
            synchronized (DefaultHTTPUtilities.class) {
                if (instance == null) {
                    instance = new DefaultHTTPUtilities();
                }
            }
        }
        return instance;
    }

    /**
      * Defines the ThreadLocalRequest to store the current request for this thread.
      */
    private class ThreadLocalRequest extends InheritableThreadLocal<HttpServletRequest> {

        public HttpServletRequest getRequest() {
            return super.get();
        }

        public HttpServletRequest initialValue() {
            return null;
        }

        public void setRequest(HttpServletRequest newRequest) {
            super.set(newRequest);
        }
    }

    /**
      * Defines the ThreadLocalResponse to store the current response for this thread.
      */
    private class ThreadLocalResponse extends InheritableThreadLocal<HttpServletResponse> {

        public HttpServletResponse getResponse() {
            return super.get();
        }

        public HttpServletResponse initialValue() {
            return null;
        }

        public void setResponse(HttpServletResponse newResponse) {
            super.set(newResponse);
        }
    }

    /** The logger. */
    private final Logger logger = ESAPI.getLogger("HTTPUtilities");

    /** The max bytes. */
    static final int maxBytes = ESAPI.securityConfiguration().getAllowedFileUploadSize();

    /*
     * The currentRequest ThreadLocal variable is used to make the currentRequest available to any call in any part of an
     * application. This enables API's for actions that require the request to be much simpler. For example, the logout()
     * method in the Authenticator class requires the currentRequest to get the session in order to invalidate it.
     */
    private ThreadLocalRequest currentRequest = new ThreadLocalRequest();

    /*
      * The currentResponse ThreadLocal variable is used to make the currentResponse available to any call in any part of an
      * application. This enables API's for actions that require the response to be much simpler. For example, the logout()
      * method in the Authenticator class requires the currentResponse to kill the Session ID cookie.
      */
    private ThreadLocalResponse currentResponse = new ThreadLocalResponse();

    /**
      * No arg constructor.
      */
    public DefaultHTTPUtilities() {
    }

    /**
     * {@inheritDoc}
      * This implementation uses a custom "set-cookie" header rather than Java's
      * cookie interface which doesn't allow the use of HttpOnly. Configure the
      * HttpOnly and Secure settings in ESAPI.properties.
     */
    public void addCookie(Cookie cookie) {
        addCookie(getCurrentResponse(), cookie);
    }

    /**
    * {@inheritDoc}
     * This implementation uses a custom "set-cookie" header rather than Java's
     * cookie interface which doesn't allow the use of HttpOnly. Configure the
     * HttpOnly and Secure settings in ESAPI.properties.
    */
    public void addCookie(HttpServletResponse response, Cookie cookie) {
        String name = cookie.getName();
        String value = cookie.getValue();
        int maxAge = cookie.getMaxAge();
        String domain = cookie.getDomain();
        String path = cookie.getPath();
        boolean secure = cookie.getSecure();

        // validate the name and value
        ValidationErrorList errors = new ValidationErrorList();
        String cookieName = ESAPI.validator().getValidInput("cookie name", name, "HTTPCookieName", 50, false,
                errors);
        String cookieValue = ESAPI.validator().getValidInput("cookie value", value, "HTTPCookieValue", 5000, false,
                errors);

        // if there are no errors, then set the cookie either with a header or normally
        if (errors.size() == 0) {
            if (ESAPI.securityConfiguration().getForceHttpOnlyCookies()) {
                String header = createCookieHeader(cookieName, cookieValue, maxAge, domain, path, secure);
                addHeader(response, "Set-Cookie", header);
            } else {
                // Issue 23 - If the ESAPI Configuration is set to force secure cookies, force the secure flag on the cookie before setting it
                cookie.setSecure(secure || ESAPI.securityConfiguration().getForceSecureCookies());
                response.addCookie(cookie);
            }
            return;
        }
        logger.warning(Logger.SECURITY_FAILURE,
                "Attempt to add unsafe data to cookie (skip mode). Skipping cookie and continuing.");
    }

    /**
     * {@inheritDoc}
     */
    public String addCSRFToken(String href) {
        User user = ESAPI.authenticator().getCurrentUser();
        if (user.isAnonymous()) {
            return href;
        }

        // if there are already parameters append with &, otherwise append with ?
        String token = CSRF_TOKEN_NAME + "=" + user.getCSRFToken();
        return href.indexOf('?') != -1 ? href + "&" + token : href + "?" + token;
    }

    /**
    * {@inheritDoc}
     */
    public void addHeader(String name, String value) {
        addHeader(getCurrentResponse(), name, value);
    }

    /**
    * {@inheritDoc}
     */
    public void addHeader(HttpServletResponse response, String name, String value) {
        try {
            String strippedName = StringUtilities.replaceLinearWhiteSpace(name);
            String strippedValue = StringUtilities.replaceLinearWhiteSpace(value);
            String safeName = ESAPI.validator().getValidInput("addHeader", strippedName, "HTTPHeaderName", 20,
                    false);
            String safeValue = ESAPI.validator().getValidInput("addHeader", strippedValue, "HTTPHeaderValue", 500,
                    false);
            response.addHeader(safeName, safeValue);
        } catch (ValidationException e) {
            logger.warning(Logger.SECURITY_FAILURE, "Attempt to add invalid header denied", e);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void assertSecureChannel() throws AccessControlException {
        assertSecureChannel(getCurrentRequest());
    }

    /**
     * {@inheritDoc}
     * 
     * This implementation ignores the built-in isSecure() method
     * and uses the URL to determine if the request was transmitted over SSL.
     * This is because SSL may have been terminated somewhere outside the
     * container.
     */
    public void assertSecureChannel(HttpServletRequest request) throws AccessControlException {
        if (request == null) {
            throw new AccessControlException("Insecure request received", "HTTP request was null");
        }
        StringBuffer sb = request.getRequestURL();
        if (sb == null) {
            throw new AccessControlException("Insecure request received", "HTTP request URL was null");
        }
        String url = sb.toString();
        if (!url.startsWith("https")) {
            throw new AccessControlException("Insecure request received", "HTTP request did not use SSL");
        }
    }

    /**
     * {@inheritDoc}
     */
    public void assertSecureRequest() throws AccessControlException {
        assertSecureRequest(getCurrentRequest());
    }

    /**
     * {@inheritDoc}
      */
    public void assertSecureRequest(HttpServletRequest request) throws AccessControlException {
        assertSecureChannel(request);
        String receivedMethod = request.getMethod();
        String requiredMethod = "POST";
        if (!receivedMethod.equals(requiredMethod)) {
            throw new AccessControlException("Insecure request received",
                    "Received request using " + receivedMethod + " when only " + requiredMethod + " is allowed");
        }
    }

    /**
     * {@inheritDoc}
     */
    public HttpSession changeSessionIdentifier() throws AuthenticationException {
        return changeSessionIdentifier(getCurrentRequest());
    }

    /**
     * {@inheritDoc}
      */
    public HttpSession changeSessionIdentifier(HttpServletRequest request) throws AuthenticationException {

        // get the current session
        HttpSession oldSession = request.getSession();

        // make a copy of the session content
        Map<String, Object> temp = new ConcurrentHashMap<String, Object>();
        Enumeration e = oldSession.getAttributeNames();
        while (e != null && e.hasMoreElements()) {
            String name = (String) e.nextElement();
            Object value = oldSession.getAttribute(name);
            temp.put(name, value);
        }

        // kill the old session and create a new one
        oldSession.invalidate();
        HttpSession newSession = request.getSession();
        User user = ESAPI.authenticator().getCurrentUser();
        user.addSession(newSession);
        user.removeSession(oldSession);

        // copy back the session content
        for (Map.Entry<String, Object> stringObjectEntry : temp.entrySet()) {
            newSession.setAttribute(stringObjectEntry.getKey(), stringObjectEntry.getValue());
        }
        return newSession;
    }

    /**
     * {@inheritDoc}
     */
    public void clearCurrent() {
        currentRequest.set(null);
        currentResponse.set(null);
    }

    private String createCookieHeader(String name, String value, int maxAge, String domain, String path,
            boolean secure) {
        // create the special cookie header instead of creating a Java cookie
        // Set-Cookie:<name>=<value>[; <name>=<value>][; expires=<date>][;
        // domain=<domain_name>][; path=<some_path>][; secure][;HttpOnly]
        String header = name + "=" + value;
        header += "; Max-Age=" + maxAge;
        if (domain != null) {
            header += "; Domain=" + domain;
        }
        if (path != null) {
            header += "; Path=" + path;
        }
        if (secure || ESAPI.securityConfiguration().getForceSecureCookies()) {
            header += "; Secure";
        }
        if (ESAPI.securityConfiguration().getForceHttpOnlyCookies()) {
            header += "; HttpOnly";
        }
        return header;
    }

    /**
     * {@inheritDoc}
     */
    public String decryptHiddenField(String encrypted) {
        try {
            return decryptString(encrypted);
        } catch (EncryptionException e) {
            throw new IntrusionException("Invalid request",
                    "Tampering detected. Hidden field data did not decrypt properly.", e);
        }
    }

    /**
     * {@inheritDoc}
     */
    public Map<String, String> decryptQueryString(String encrypted) throws EncryptionException {
        String plaintext = decryptString(encrypted);
        return queryToMap(plaintext);
    }

    /**
     * {@inheritDoc}
     */
    public Map<String, String> decryptStateFromCookie() throws EncryptionException {
        return decryptStateFromCookie(getCurrentRequest());
    }

    /**
     * {@inheritDoc}
      *
      * @param request
      */
    public Map<String, String> decryptStateFromCookie(HttpServletRequest request) throws EncryptionException {
        try {
            String encrypted = getCookie(request, ESAPI_STATE);
            if (encrypted == null)
                return new HashMap<String, String>();
            String plaintext = decryptString(encrypted);
            return queryToMap(plaintext);
        } catch (ValidationException e) {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    public String encryptHiddenField(String value) throws EncryptionException {
        return encryptString(value);
    }

    /**
     * {@inheritDoc}
     */
    public String encryptQueryString(String query) throws EncryptionException {
        return encryptString(query);
    }

    /**
     * {@inheritDoc}
      */
    public void encryptStateInCookie(HttpServletResponse response, Map<String, String> cleartext)
            throws EncryptionException {
        StringBuilder sb = new StringBuilder();
        Iterator i = cleartext.entrySet().iterator();
        while (i.hasNext()) {
            try {
                Map.Entry entry = (Map.Entry) i.next();

                // What do these need to be URL encoded? They are encrypted!
                String name = ESAPI.encoder().encodeForURL(entry.getKey().toString());
                String value = ESAPI.encoder().encodeForURL(entry.getValue().toString());
                sb.append(name).append("=").append(value);
                if (i.hasNext())
                    sb.append("&");
            } catch (EncodingException e) {
                logger.error(Logger.SECURITY_FAILURE, "Problem encrypting state in cookie - skipping entry", e);
            }
        }

        String encrypted = encryptString(sb.toString());

        if (encrypted.length() > (MAX_COOKIE_LEN)) {
            logger.error(Logger.SECURITY_FAILURE, "Problem encrypting state in cookie - skipping entry");
            throw new EncryptionException("Encryption failure",
                    "Encrypted cookie state of " + encrypted.length() + " longer than allowed " + MAX_COOKIE_LEN);
        }

        Cookie cookie = new Cookie(ESAPI_STATE, encrypted);
        addCookie(response, cookie);
    }

    /**
     * {@inheritDoc}
     */
    public void encryptStateInCookie(Map<String, String> cleartext) throws EncryptionException {
        encryptStateInCookie(getCurrentResponse(), cleartext);
    }

    /**
     * {@inheritDoc}
     */
    public String getCookie(HttpServletRequest request, String name) throws ValidationException {
        Cookie c = getFirstCookie(request, name);
        if (c == null)
            return null;
        String value = c.getValue();
        return ESAPI.validator().getValidInput("HTTP cookie value: " + value, value, "HTTPCookieValue", 1000,
                false);
    }

    /**
     * {@inheritDoc}
     */
    public String getCookie(String name) throws ValidationException {
        return getCookie(getCurrentRequest(), name);
    }

    /**
     * {@inheritDoc}
     */
    public String getCSRFToken() {
        User user = ESAPI.authenticator().getCurrentUser();
        if (user == null)
            return null;
        return user.getCSRFToken();
    }

    /**
     * {@inheritDoc}
     */
    public HttpServletRequest getCurrentRequest() {
        return currentRequest.getRequest();
    }

    /**
     * {@inheritDoc}
     */
    public HttpServletResponse getCurrentResponse() {
        return currentResponse.getResponse();
    }

    /**
     * {@inheritDoc}
     */
    public List<File> getFileUploads() throws ValidationException {
        return getFileUploads(getCurrentRequest(), ESAPI.securityConfiguration().getUploadDirectory(),
                ESAPI.securityConfiguration().getAllowedFileExtensions());
    }

    /**
    * {@inheritDoc}
    */
    public List<File> getFileUploads(HttpServletRequest request) throws ValidationException {
        return getFileUploads(request, ESAPI.securityConfiguration().getUploadDirectory(),
                ESAPI.securityConfiguration().getAllowedFileExtensions());
    }

    /**
    * {@inheritDoc}
    */
    public List<File> getFileUploads(HttpServletRequest request, File finalDir) throws ValidationException {
        return getFileUploads(request, finalDir, ESAPI.securityConfiguration().getAllowedFileExtensions());
    }

    /**
     * {@inheritDoc}
     */
    public List<File> getFileUploads(HttpServletRequest request, File finalDir, List allowedExtensions)
            throws ValidationException {
        File tempDir = ESAPI.securityConfiguration().getUploadTempDirectory();
        if (!tempDir.exists()) {
            if (!tempDir.mkdirs())
                throw new ValidationUploadException("Upload failed",
                        "Could not create temp directory: " + tempDir.getAbsolutePath());
        }

        if (finalDir != null) {
            if (!finalDir.exists()) {
                if (!finalDir.mkdirs())
                    throw new ValidationUploadException("Upload failed",
                            "Could not create final upload directory: " + finalDir.getAbsolutePath());
            }
        } else {
            if (!ESAPI.securityConfiguration().getUploadDirectory().exists()) {
                if (!ESAPI.securityConfiguration().getUploadDirectory().mkdirs())
                    throw new ValidationUploadException("Upload failed", "Could not create final upload directory: "
                            + ESAPI.securityConfiguration().getUploadDirectory().getAbsolutePath());
            }
            finalDir = ESAPI.securityConfiguration().getUploadDirectory();
        }

        List<File> newFiles = new ArrayList<File>();
        try {
            final HttpSession session = request.getSession(false);
            if (!ServletFileUpload.isMultipartContent(request)) {
                throw new ValidationUploadException("Upload failed", "Not a multipart request");
            }

            // this factory will store ALL files in the temp directory,
            // regardless of size
            DiskFileItemFactory factory = new DiskFileItemFactory(0, tempDir);
            ServletFileUpload upload = new ServletFileUpload(factory);
            upload.setSizeMax(maxBytes);

            // Create a progress listener
            ProgressListener progressListener = new ProgressListener() {
                private long megaBytes = -1;
                private long progress = 0;

                public void update(long pBytesRead, long pContentLength, int pItems) {
                    if (pItems == 0)
                        return;
                    long mBytes = pBytesRead / 1000000;
                    if (megaBytes == mBytes)
                        return;
                    megaBytes = mBytes;
                    progress = (long) (((double) pBytesRead / (double) pContentLength) * 100);
                    if (session != null) {
                        session.setAttribute("progress", Long.toString(progress));
                    }
                    // logger.logSuccess(Logger.SECURITY, "   Item " + pItems + " (" + progress + "% of " + pContentLength + " bytes]");
                }
            };
            upload.setProgressListener(progressListener);

            List<FileItem> items = upload.parseRequest(request);
            for (FileItem item : items) {
                if (!item.isFormField() && item.getName() != null && !(item.getName().equals(""))) {
                    String[] fparts = item.getName().split("[\\/\\\\]");
                    String filename = fparts[fparts.length - 1];

                    if (!ESAPI.validator().isValidFileName("upload", filename, allowedExtensions, false)) {
                        throw new ValidationUploadException(
                                "Upload only simple filenames with the following extensions " + allowedExtensions,
                                "Upload failed isValidFileName check");
                    }

                    logger.info(Logger.SECURITY_SUCCESS, "File upload requested: " + filename);
                    File f = new File(finalDir, filename);
                    if (f.exists()) {
                        String[] parts = filename.split("\\/.");
                        String extension = "";
                        if (parts.length > 1) {
                            extension = parts[parts.length - 1];
                        }
                        String filenm = filename.substring(0, filename.length() - extension.length());
                        f = File.createTempFile(filenm, "." + extension, finalDir);
                    }
                    item.write(f);
                    newFiles.add(f);
                    // delete temporary file
                    item.delete();
                    logger.fatal(Logger.SECURITY_SUCCESS, "File successfully uploaded: " + f);
                    if (session != null) {
                        session.setAttribute("progress", Long.toString(0));
                    }
                }
            }
        } catch (Exception e) {
            if (e instanceof ValidationUploadException) {
                throw (ValidationException) e;
            }
            throw new ValidationUploadException("Upload failure", "Problem during upload:" + e.getMessage(), e);
        }
        return Collections.synchronizedList(newFiles);
    }

    /**
      * Utility to return the first cookie matching the provided name.
      * @param request
      * @param name
      */
    private Cookie getFirstCookie(HttpServletRequest request, String name) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(name)) {
                    return cookie;
                }
            }
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    public String getHeader(HttpServletRequest request, String name) throws ValidationException {
        String value = request.getHeader(name);
        return ESAPI.validator().getValidInput("HTTP header value: " + value, value, "HTTPHeaderValue", 150, false);
    }

    /**
     * {@inheritDoc}
     */
    public String getHeader(String name) throws ValidationException {
        return getHeader(getCurrentRequest(), name);
    }

    /**
    * {@inheritDoc}
    */
    public String getParameter(HttpServletRequest request, String name) throws ValidationException {
        String value = request.getParameter(name);
        return ESAPI.validator().getValidInput("HTTP parameter value: " + value, value, "HTTPParameterValue", 2000,
                true);
    }

    /**
     * {@inheritDoc}
     */
    public String getParameter(String name) throws ValidationException {
        return getParameter(getCurrentRequest(), name);
    }

    /**
     * {@inheritDoc}
     */
    public void killAllCookies() {
        killAllCookies(getCurrentRequest(), getCurrentResponse());
    }

    /**
     * {@inheritDoc}
      *
      * @param request
      * @param response
      */
    public void killAllCookies(HttpServletRequest request, HttpServletResponse response) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                killCookie(request, response, cookie.getName());
            }
        }
    }

    /**
     * {@inheritDoc}
      *
      * @param request
      * @param response
      * @param name
      */
    public void killCookie(HttpServletRequest request, HttpServletResponse response, String name) {
        String path = "//";
        String domain = "";
        Cookie cookie = getFirstCookie(request, name);
        if (cookie != null) {
            path = cookie.getPath();
            domain = cookie.getDomain();
        }
        Cookie deleter = new Cookie(name, "deleted");
        deleter.setMaxAge(0);
        if (domain != null)
            deleter.setDomain(domain);
        if (path != null)
            deleter.setPath(path);
        response.addCookie(deleter);
    }

    /**
     * {@inheritDoc}
     */
    public void killCookie(String name) {
        killCookie(getCurrentRequest(), getCurrentResponse(), name);
    }

    /**
     * {@inheritDoc}
     */
    public void logHTTPRequest() {
        logHTTPRequest(getCurrentRequest(), logger, null);
    }

    /**
     * {@inheritDoc}
     */
    public void logHTTPRequest(HttpServletRequest request, Logger logger) {
        logHTTPRequest(request, logger, null);
    }

    /**
     * Formats an HTTP request into a log suitable string. This implementation logs the remote host IP address (or
     * hostname if available), the request method (GET/POST), the URL, and all the querystring and form parameters. All
     * the parameters are presented as though they were in the URL even if they were in a form. Any parameters that
     * match items in the parameterNamesToObfuscate are shown as eight asterisks.
     *
     *
     * @param request
     */
    public void logHTTPRequest(HttpServletRequest request, Logger logger, List parameterNamesToObfuscate) {
        StringBuilder params = new StringBuilder();
        Iterator i = request.getParameterMap().keySet().iterator();
        while (i.hasNext()) {
            String key = (String) i.next();
            String[] value = (String[]) request.getParameterMap().get(key);
            for (int j = 0; j < value.length; j++) {
                params.append(key).append("=");
                if (parameterNamesToObfuscate != null && parameterNamesToObfuscate.contains(key)) {
                    params.append("********");
                } else {
                    params.append(value[j]);
                }
                if (j < value.length - 1) {
                    params.append("&");
                }
            }
            if (i.hasNext())
                params.append("&");
        }
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cooky : cookies) {
                if (!cooky.getName().equals(ESAPI.securityConfiguration().getHttpSessionIdName())) {
                    params.append("+").append(cooky.getName()).append("=").append(cooky.getValue());
                }
            }
        }
        String msg = request.getMethod() + " " + request.getRequestURL()
                + (params.length() > 0 ? "?" + params : "");
        logger.info(Logger.SECURITY_SUCCESS, msg);
    }

    private Map<String, String> queryToMap(String query) {
        TreeMap<String, String> map = new TreeMap<String, String>();
        String[] parts = query.split("&");
        for (String part : parts) {
            try {
                String[] nvpair = part.split("=");
                String name = ESAPI.encoder().decodeFromURL(nvpair[0]);
                String value = ESAPI.encoder().decodeFromURL(nvpair[1]);
                map.put(name, value);
            } catch (EncodingException e) {
                // skip the nvpair with the encoding problem - note this is already logged.
            }
        }
        return map;
    }

    /**
     * {@inheritDoc}
     *
     * This implementation simply checks to make sure that the forward location starts with "WEB-INF" and
     * is intended for use in frameworks that forward to JSP files inside the WEB-INF folder.
     */
    public void sendForward(HttpServletRequest request, HttpServletResponse response, String location)
            throws AccessControlException, ServletException, IOException {
        if (!location.startsWith("WEB-INF")) {
            throw new AccessControlException("Forward failed", "Bad forward location: " + location);
        }
        RequestDispatcher dispatcher = request.getRequestDispatcher(location);
        dispatcher.forward(request, response);
    }

    /**
     * {@inheritDoc}
     */
    public void sendForward(String location) throws AccessControlException, ServletException, IOException {
        sendForward(getCurrentRequest(), getCurrentResponse(), location);
    }

    /**
     * {@inheritDoc}
     *
     * This implementation checks against the list of safe redirect locations defined in ESAPI.properties.
      */
    public void sendRedirect(HttpServletResponse response, String location)
            throws AccessControlException, IOException {
        if (!ESAPI.validator().isValidRedirectLocation("Redirect", location, false)) {
            logger.fatal(Logger.SECURITY_FAILURE, "Bad redirect location: " + location);
            throw new AccessControlException("Redirect failed", "Bad redirect location: " + location);
        }
        response.sendRedirect(location);
    }

    /**
     * {@inheritDoc}
     */
    public void sendRedirect(String location) throws AccessControlException, IOException {
        sendRedirect(getCurrentResponse(), location);
    }

    /**
     * {@inheritDoc}
     */
    public void setContentType() {
        setContentType(getCurrentResponse());
    }

    /**
     * {@inheritDoc}
     */
    public void setContentType(HttpServletResponse response) {
        response.setContentType((ESAPI.securityConfiguration()).getResponseContentType());
    }

    /**
    * {@inheritDoc}
    */
    public void setCurrentHTTP(HttpServletRequest request, HttpServletResponse response) {
        currentRequest.setRequest(request);
        currentResponse.setResponse(response);
    }

    /**
    * {@inheritDoc}
     */
    public void setHeader(HttpServletResponse response, String name, String value) {
        try {
            String strippedName = StringUtilities.replaceLinearWhiteSpace(name);
            String strippedValue = StringUtilities.replaceLinearWhiteSpace(value);
            String safeName = ESAPI.validator().getValidInput("setHeader", strippedName, "HTTPHeaderName", 50,
                    false);
            String safeValue = ESAPI.validator().getValidInput("setHeader", strippedValue, "HTTPHeaderValue", 500,
                    false);
            response.setHeader(safeName, safeValue);
        } catch (ValidationException e) {
            logger.warning(Logger.SECURITY_FAILURE, "Attempt to set invalid header denied", e);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void setHeader(String name, String value) {
        setHeader(getCurrentResponse(), name, value);
    }

    /**
    * {@inheritDoc}
    */
    public void setNoCacheHeaders() {
        setNoCacheHeaders(getCurrentResponse());
    }

    /**
     * {@inheritDoc}
      *
      * @param response
      */
    public void setNoCacheHeaders(HttpServletResponse response) {
        // HTTP 1.1
        response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");

        // HTTP 1.0
        response.setHeader("Pragma", "no-cache");
        response.setDateHeader("Expires", -1);
    }

    /**
     * {@inheritDoc}
     *
     * Save the user's remember me data in an encrypted cookie and send it to the user.
     * Any old remember me cookie is destroyed first. Setting this cookie will keep the user
     * logged in until the maxAge passes, the password is changed, or the cookie is deleted.
     * If the cookie exists for the current user, it will automatically be used by ESAPI to
     * log the user in, if the data is valid and not expired.
      *
      * @param request
      * @param response
      */
    public String setRememberToken(HttpServletRequest request, HttpServletResponse response, String password,
            int maxAge, String domain, String path) {
        User user = ESAPI.authenticator().getCurrentUser();
        try {
            killCookie(request, response, REMEMBER_TOKEN_COOKIE_NAME);
            // seal already contains random data
            String clearToken = user.getAccountName() + "|" + password;
            long expiry = ESAPI.encryptor().getRelativeTimeStamp(maxAge * 1000);
            String cryptToken = ESAPI.encryptor().seal(clearToken, expiry);

            // Do NOT URLEncode cryptToken before creating cookie. See Google Issue # 144,
            // which was marked as "WontFix".

            Cookie cookie = new Cookie(REMEMBER_TOKEN_COOKIE_NAME, cryptToken);
            cookie.setMaxAge(maxAge);
            cookie.setDomain(domain);
            cookie.setPath(path);
            response.addCookie(cookie);
            logger.info(Logger.SECURITY_SUCCESS, "Enabled remember me token for " + user.getAccountName());
            return cryptToken;
        } catch (IntegrityException e) {
            logger.warning(Logger.SECURITY_FAILURE,
                    "Attempt to set remember me token failed for " + user.getAccountName(), e);
            return null;
        }
    }

    /**
    * {@inheritDoc}
    */
    public String setRememberToken(String password, int maxAge, String domain, String path) {
        return setRememberToken(getCurrentRequest(), getCurrentResponse(), password, maxAge, domain, path);
    }

    /**
    * {@inheritDoc}
    */
    public void verifyCSRFToken() throws IntrusionException {
        verifyCSRFToken(getCurrentRequest());
    }

    /**
    * {@inheritDoc}
    *
    * This implementation uses the CSRF_TOKEN_NAME parameter for the token.
     *
     * @param request
     */
    public void verifyCSRFToken(HttpServletRequest request) throws IntrusionException {
        User user = ESAPI.authenticator().getCurrentUser();

        // check if user authenticated with this request - no CSRF protection required
        if (request.getAttribute(user.getCSRFToken()) != null) {
            return;
        }
        String token = request.getParameter(CSRF_TOKEN_NAME);
        if (!user.getCSRFToken().equals(token)) {
            throw new IntrusionException("Authentication failed",
                    "Possibly forged HTTP request without proper CSRF token detected");
        }
    }

    /**
     * {@inheritDoc}
     */
    public <T> T getSessionAttribute(String key) {
        final HttpSession session = ESAPI.currentRequest().getSession(false);
        if (session != null)
            return (T) session.getAttribute(key);
        return null;
    }

    /**
     * {@inheritDoc}
     */
    public <T> T getSessionAttribute(HttpSession session, String key) {
        return (T) session.getAttribute(key);
    }

    /**
     * {@inheritDoc}
     */
    public <T> T getRequestAttribute(String key) {
        return (T) ESAPI.currentRequest().getAttribute(key);
    }

    /**
     * {@inheritDoc}
     */
    public <T> T getRequestAttribute(HttpServletRequest request, String key) {
        return (T) request.getAttribute(key);
    }

    /////////////////////

    /* Helper method to encrypt using new Encryptor encryption methods and
     * return the serialized ciphertext as a hex-encoded string.
     */
    private String encryptString(String plaintext) throws EncryptionException {
        PlainText pt = new PlainText(plaintext);
        CipherText ct = ESAPI.encryptor().encrypt(pt);
        byte[] serializedCiphertext = ct.asPortableSerializedByteArray();
        return Hex.encode(serializedCiphertext, false);
    }

    /* Helper method to decrypt a hex-encode serialized ciphertext string and
     * to decrypt it using the new Encryptor decryption methods.
     */
    private String decryptString(String ciphertext) throws EncryptionException {
        byte[] serializedCiphertext = Hex.decode(ciphertext);
        CipherText restoredCipherText = CipherText.fromPortableSerializedBytes(serializedCiphertext);
        PlainText plaintext = ESAPI.encryptor().decrypt(restoredCipherText);
        return plaintext.toString();
    }
}