<commit_before>/**
 *  Copyright (C) 2007 Orbeon, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version
 *  2.1 of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU Lesser General Public License for more details.
 *
 *  The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.servlet;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.Cookie;
import java.io.*;
import java.util.Locale;

/**
 * This filter allows forwarding requests from your web app to an separate Orbeon Forms context.
 *
 * This filter must remain very simple and have as little dependencies as possible, because it must be easily deployable
 * into any web application.
 */
public class OPSXFormsFilter implements Filter {

    public static final String OPS_XFORMS_RENDERER_DOCUMENT_ATTRIBUTE_NAME = "oxf.xforms.renderer.document";
    public static final String OPS_XFORMS_RENDERER_BASE_URI_ATTRIBUTE_NAME = "oxf.xforms.renderer.base-uri";
    public static final String OPS_XFORMS_RENDERER_CONTENT_TYPE_ATTRIBUTE_NAME = "oxf.xforms.renderer.content-type";
    public static final String OPS_XFORMS_RENDERER_HAS_SESSION_ATTRIBUTE_NAME = "oxf.xforms.renderer.has-session";

    public static final String OPS_SERVLET_CONTEXT_ATTRIBUTE_NAME = "oxf.servlet.context";
    public static final String OPS_RENDERER_PATH = "/xforms-renderer";

    private static final String OPS_XFORMS_RENDERER_CONTEXT_PARAMETER_NAME = "oxf.xforms.renderer.context";
    private static final String DEFAULT_ENCODING = "ISO-8859-1"; // must be this per Servlet spec

    private ServletContext servletContext;
    private String opsContextPath;

    public void init(FilterConfig filterConfig) throws ServletException {
        servletContext = filterConfig.getServletContext();
        opsContextPath = filterConfig.getInitParameter(OPS_XFORMS_RENDERER_CONTEXT_PARAMETER_NAME);

        // TODO: check opsContextPath format: starts with /, doesn't end with one, etc.
    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        final HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        final HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;

        if (isOPSResourceRequest(httpRequest)) {
            // Directly forward all requests meant for Orbeon Forms resources
            final String subRequestPath = getRequestPathInfo(httpRequest).substring(opsContextPath.length());
            getOPSDispatcher(subRequestPath).forward(httpRequest, httpResponse);
        } else {
            // Forward the request to the Orbeon Forms renderer
            final MyHttpServletResponseWrapper responseWrapper = new MyHttpServletResponseWrapper(httpResponse);

            // Execute filter
            filterChain.doFilter(servletRequest, responseWrapper);

            // Set document if not present AND output was intercepted
            if (httpRequest.getAttribute(OPS_XFORMS_RENDERER_DOCUMENT_ATTRIBUTE_NAME) == null) {
                final String content = responseWrapper.getContent();
                if (content != null) {
                    httpRequest.setAttribute(OPS_XFORMS_RENDERER_DOCUMENT_ATTRIBUTE_NAME, content);
                }
            }

            // Override Orbeon Forms context so that rewriting works correctly
            if (opsContextPath != null)
                httpRequest.setAttribute(OPS_SERVLET_CONTEXT_ATTRIBUTE_NAME, httpRequest.getContextPath() + opsContextPath);

            // Tell whether there is a session
            httpRequest.setAttribute(OPS_XFORMS_RENDERER_HAS_SESSION_ATTRIBUTE_NAME, new Boolean(httpRequest.getSession(false) != null).toString());

            // Provide media type if available
            if (responseWrapper.getMediaType() != null)
                httpRequest.setAttribute(OPS_XFORMS_RENDERER_CONTENT_TYPE_ATTRIBUTE_NAME, responseWrapper.getMediaType());

            // Set base URI
            final String absoluteBaseURI = httpRequest.getRequestURL().toString();
            httpRequest.setAttribute(OPS_XFORMS_RENDERER_BASE_URI_ATTRIBUTE_NAME, absoluteBaseURI);

            // Forward to Orbeon Forms for rendering
            getOPSDispatcher(OPS_RENDERER_PATH).forward(httpRequest, httpResponse);
        }
    }

    public void destroy() {
    }

    private RequestDispatcher getOPSDispatcher(String path) throws ServletException {
        final ServletContext opsContext = (opsContextPath != null) ? servletContext.getContext(opsContextPath) : servletContext;
        if (opsContext == null)
            throw new ServletException("Can't find Orbeon Forms context called '" + opsContextPath + "'. Check the '" + OPS_XFORMS_RENDERER_CONTEXT_PARAMETER_NAME + "' filter initialization parameter and the <Context crossContext=\"true\"/> attribute.");
        final RequestDispatcher dispatcher = opsContext.getRequestDispatcher(path);
        if (dispatcher == null)
            throw new ServletException("Can't find Orbeon Forms request dispatcher.");

        return dispatcher;
    }

    private boolean isOPSResourceRequest(HttpServletRequest request) {
        if (opsContextPath == null)
            return false;

        final String pathInfo = getRequestPathInfo(request);
        return pathInfo != null && pathInfo.startsWith(opsContextPath + "/");
    }

    // NOTE: This is borrowed from NetUtils but we don't want the dependency
    private static String getRequestPathInfo(HttpServletRequest request) {

        // Get servlet path and path info
        String servletPath = request.getServletPath();
        if (servletPath == null) servletPath = "";
        String pathInfo = request.getPathInfo();
        if (pathInfo == null) pathInfo = "";

        // Concatenate servlet path and path info, avoiding a double slash
        String requestPath = servletPath.endsWith("/") && pathInfo.startsWith("/")
                ? servletPath + pathInfo.substring(1)
                : servletPath + pathInfo;

        // Add starting slash if missing
        if (!requestPath.startsWith("/"))
            requestPath = "/" + requestPath;

        return requestPath;
    }

    // NOTE: This is borrowed from NetUtils but we don't want the dependency
    public static String getContentTypeCharset(String contentType) {
        if (contentType == null)
            return null;
        int semicolumnIndex = contentType.indexOf(";");
        if (semicolumnIndex == -1)
            return null;
        int charsetIndex = contentType.indexOf("charset=", semicolumnIndex);
        if (charsetIndex == -1)
            return null;
        // FIXME: There may be other attributes after charset, right?
        String afterCharset = contentType.substring(charsetIndex + 8);
        afterCharset = afterCharset.replace('"', ' ');
        return afterCharset.trim();
    }

    // NOTE: This is borrowed from NetUtils but we don't want the dependency
    public static String getContentTypeMediaType(String contentType) {
        if (contentType == null || contentType.equalsIgnoreCase("content/unknown"))
            return null;
        int semicolumnIndex = contentType.indexOf(";");
        if (semicolumnIndex == -1)
            return contentType;
        return contentType.substring(0, semicolumnIndex).trim();
    }

    private static class MyHttpServletResponseWrapper extends HttpServletResponseWrapper {

        public MyHttpServletResponseWrapper(HttpServletResponse response) {<commit_msg>Fixed "[ #308287 ] Separate deployment: conditional get produces <null> XML document in the browser".<commit_after>/**
 *  Copyright (C) 2007 Orbeon, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version
 *  2.1 of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU Lesser General Public License for more details.
 *
 *  The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.servlet;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;

/**
 * This filter allows forwarding requests from your web app to an separate Orbeon Forms context.
 *
 * This filter must remain very simple and have as little dependencies as possible, because it must be easily deployable
 * into any web application.
 */
public class OPSXFormsFilter implements Filter {

    public static final String OPS_XFORMS_RENDERER_DOCUMENT_ATTRIBUTE_NAME = "oxf.xforms.renderer.document";
    public static final String OPS_XFORMS_RENDERER_BASE_URI_ATTRIBUTE_NAME = "oxf.xforms.renderer.base-uri";
    public static final String OPS_XFORMS_RENDERER_CONTENT_TYPE_ATTRIBUTE_NAME = "oxf.xforms.renderer.content-type";
    public static final String OPS_XFORMS_RENDERER_HAS_SESSION_ATTRIBUTE_NAME = "oxf.xforms.renderer.has-session";

    public static final String OPS_SERVLET_CONTEXT_ATTRIBUTE_NAME = "oxf.servlet.context";
    public static final String OPS_RENDERER_PATH = "/xforms-renderer";

    private static final String OPS_XFORMS_RENDERER_CONTEXT_PARAMETER_NAME = "oxf.xforms.renderer.context";
    private static final String DEFAULT_ENCODING = "ISO-8859-1"; // must be this per Servlet spec

    private ServletContext servletContext;
    private String opsContextPath;

    public void init(FilterConfig filterConfig) throws ServletException {
        servletContext = filterConfig.getServletContext();
        opsContextPath = filterConfig.getInitParameter(OPS_XFORMS_RENDERER_CONTEXT_PARAMETER_NAME);

        // TODO: check opsContextPath format: starts with /, doesn't end with one, etc.
    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        final HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        final HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;

        if (isOPSResourceRequest(httpRequest)) {
            // Directly forward all requests meant for Orbeon Forms resources
            final String subRequestPath = getRequestPathInfo(httpRequest).substring(opsContextPath.length());
            getOPSDispatcher(subRequestPath).forward(httpRequest, httpResponse);
        } else {
            // Forward the request to the Orbeon Forms renderer
            final MyHttpServletResponseWrapper responseWrapper = new MyHttpServletResponseWrapper(httpResponse);

            // Execute filter
            filterChain.doFilter(new MyHttpServletRequestWrapper(httpRequest), responseWrapper);

            // Set document if not present AND output was intercepted
            if (httpRequest.getAttribute(OPS_XFORMS_RENDERER_DOCUMENT_ATTRIBUTE_NAME) == null) {
                final String content = responseWrapper.getContent();
                if (content != null) {
                    httpRequest.setAttribute(OPS_XFORMS_RENDERER_DOCUMENT_ATTRIBUTE_NAME, content);
                }
            }

            // Override Orbeon Forms context so that rewriting works correctly
            if (opsContextPath != null)
                httpRequest.setAttribute(OPS_SERVLET_CONTEXT_ATTRIBUTE_NAME, httpRequest.getContextPath() + opsContextPath);

            // Tell whether there is a session
            httpRequest.setAttribute(OPS_XFORMS_RENDERER_HAS_SESSION_ATTRIBUTE_NAME, new Boolean(httpRequest.getSession(false) != null).toString());

            // Provide media type if available
            if (responseWrapper.getMediaType() != null)
                httpRequest.setAttribute(OPS_XFORMS_RENDERER_CONTENT_TYPE_ATTRIBUTE_NAME, responseWrapper.getMediaType());

            // Set base URI
            final String absoluteBaseURI = httpRequest.getRequestURL().toString();
            httpRequest.setAttribute(OPS_XFORMS_RENDERER_BASE_URI_ATTRIBUTE_NAME, absoluteBaseURI);

            // Forward to Orbeon Forms for rendering
            getOPSDispatcher(OPS_RENDERER_PATH).forward(httpRequest, httpResponse);
        }
    }

    public void destroy() {
    }

    private RequestDispatcher getOPSDispatcher(String path) throws ServletException {
        final ServletContext opsContext = (opsContextPath != null) ? servletContext.getContext(opsContextPath) : servletContext;
        if (opsContext == null)
            throw new ServletException("Can't find Orbeon Forms context called '" + opsContextPath + "'. Check the '" + OPS_XFORMS_RENDERER_CONTEXT_PARAMETER_NAME + "' filter initialization parameter and the <Context crossContext=\"true\"/> attribute.");
        final RequestDispatcher dispatcher = opsContext.getRequestDispatcher(path);
        if (dispatcher == null)
            throw new ServletException("Can't find Orbeon Forms request dispatcher.");

        return dispatcher;
    }

    private boolean isOPSResourceRequest(HttpServletRequest request) {
        if (opsContextPath == null)
            return false;

        final String pathInfo = getRequestPathInfo(request);
        return pathInfo != null && pathInfo.startsWith(opsContextPath + "/");
    }

    // NOTE: This is borrowed from NetUtils but we don't want the dependency
    private static String getRequestPathInfo(HttpServletRequest request) {

        // Get servlet path and path info
        String servletPath = request.getServletPath();
        if (servletPath == null) servletPath = "";
        String pathInfo = request.getPathInfo();
        if (pathInfo == null) pathInfo = "";

        // Concatenate servlet path and path info, avoiding a double slash
        String requestPath = servletPath.endsWith("/") && pathInfo.startsWith("/")
                ? servletPath + pathInfo.substring(1)
                : servletPath + pathInfo;

        // Add starting slash if missing
        if (!requestPath.startsWith("/"))
            requestPath = "/" + requestPath;

        return requestPath;
    }

    // NOTE: This is borrowed from NetUtils but we don't want the dependency
    public static String getContentTypeCharset(String contentType) {
        if (contentType == null)
            return null;
        int semicolumnIndex = contentType.indexOf(";");
        if (semicolumnIndex == -1)
            return null;
        int charsetIndex = contentType.indexOf("charset=", semicolumnIndex);
        if (charsetIndex == -1)
            return null;
        // FIXME: There may be other attributes after charset, right?
        String afterCharset = contentType.substring(charsetIndex + 8);
        afterCharset = afterCharset.replace('"', ' ');
        return afterCharset.trim();
    }

    // NOTE: This is borrowed from NetUtils but we don't want the dependency
    public static String getContentTypeMediaType(String contentType) {
        if (contentType == null || contentType.equalsIgnoreCase("content/unknown"))
            return null;
        int semicolumnIndex = contentType.indexOf(";");
        if (semicolumnIndex == -1)
            return contentType;
        return contentType.substring(0, semicolumnIndex).trim();
    }

    private static class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {

        private Enumeration headerNames;

        public MyHttpServletRequestWrapper(HttpServletRequest httpServletRequest) {
            super(httpServletRequest);

        }

        public String getHeader(String s) {
            // Filter conditional get headers so that we always get content
            if (s.toLowerCase().startsWith("if-") )
                return null;
            else
                return super.getHeader(s);
        }

        public Enumeration getHeaders(String s) {
            // Filter conditional get headers so that we always get content
            if (s.toLowerCase().startsWith("if-"))
                return null;
            else
                return super.getHeaders(s);
        }

        public Enumeration getHeaderNames() {
            if (headerNames == null) {
                // Filter conditional get headers so that we always get content
                final List newHeaderNames = new ArrayList();
                for (Enumeration e = super.getHeaderNames(); e.hasMoreElements();) {
                    final String currentName = (String) e.nextElement();
                    if (!currentName.toLowerCase().startsWith("if-"))
                        newHeaderNames.add(newHeaderNames);
                }
                headerNames = Collections.enumeration(newHeaderNames);

            }
            return headerNames;
        }

        public long getDateHeader(String s) {
            // Filter conditional get headers so that we always get content
            if (s.toLowerCase().startsWith("if-"))
                return -1;
            else
                return super.getDateHeader(s);
        }
    }

    private static class MyHttpServletResponseWrapper extends HttpServletResponseWrapper {

        public MyHttpServletResponseWrapper(HttpServletResponse response) {
{'fraction_non_alphanumeric': 0.07631107126848946, 'fraction_numerical': 0.0022971761541909458, 'mean_word_length': 3.0164266426642663, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 6, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3744593', 'n_tokens_mistral': 4610, 'n_tokens_neox': 4497, 'n_words': 1329}