package org.springframework.security.web.firewall;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Default implementation which wraps requests in order to provide consistent values of the {@code servletPath} and
 * {@code pathInfo}, which do not contain path parameters (as defined in
 * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>). Different servlet containers
 * interpret the servlet spec differently as to how path parameters are treated and it is possible they might be added
 * in order to bypass particular security constraints. When using this implementation, they will be removed for all
 * requests as the request passes through the security filter chain. Note that this means that any segments in the
 * decoded path which contain a semi-colon, will have the part following the semi-colon removed for
 * request matching. Your application should not contain any valid paths which contain semi-colons.
 * <p>
 * If any un-normalized paths are found (containing directory-traversal character sequences), the request will be
 * rejected immediately. Most containers normalize the paths before performing the servlet-mapping, but again this is
 * not guaranteed by the servlet spec.
 *
 * @author Luke Taylor
 */
public class DefaultHttpFirewall implements HttpFirewall {

    public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
        FirewalledRequest fwr = new RequestWrapper(request);

        if (!isNormalized(fwr.getServletPath()) || !isNormalized(fwr.getPathInfo())) {
            throw new RequestRejectedException("Un-normalized paths are not supported: " + fwr.getServletPath() +
                (fwr.getPathInfo() != null ? fwr.getPathInfo() : ""));
        }

        return fwr;
    }

    public HttpServletResponse getFirewalledResponse(HttpServletResponse response) {
        return new FirewalledResponse(response);
    }

    /**
     * Checks whether a path is normalized (doesn't contain path traversal sequences like "./", "/../" or "/.")
     *
     * @param path the path to test
     * @return true if the path doesn't contain any path-traversal character sequences.
     */
    private boolean isNormalized(String path) {
        if (path == null) {
            return true;
        }

        for (int j = path.length(); j > 0;) {
            int i = path.lastIndexOf('/', j - 1);
            int gap = j - i;

            if (gap == 2 && path.charAt(i+1) == '.') {
                // ".", "/./" or "/."
                return false;
            } else if (gap == 3 && path.charAt(i+1) == '.'&& path.charAt(i+2) == '.') {
                return false;
            }

            j = i;
        }

        return true;
    }

}
