package com.xpn.xwiki.web;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <p>
 * A filter that dispatches requests to the right action, depending on the button that was pressed in the editing form.
 * This is needed since the browser cannot send the form data to different URLs depending on the button pressed, and an
 * XHTML form has only one target URL. In previous versions of XWiki this was accomplished using javascript code, with a
 * fall-back on a pseudo-dispatcher inside the {@link PreviewAction}, which was on obvious case of bad code design.
 * </p>
 * <p>
 * The filter dispatches requests based on the presence of a request parameter starting with <tt>action_</tt> followed
 * by the name of the struts action that should actually process the request. For example, the button that does
 * <tt>Save and Continue</tt> looks like:
 * 
 * <pre>
 * &lt;input type=&quot;submit&quot; name=&quot;action_saveandcontinue&quot; value=&quot;...&quot;/&gt;
 * </pre>
 * 
 * As a result, when clicking the button, the request is not sent to the form's target (<tt>preview</tt>), but is
 * actually forwarded internally to <tt>/bin/saveandcontinue/The/Document</tt>.
 * </p>
 * 
 * @version $Id$
 * @since 1.8M1
 */
public class ActionFilter implements Filter
{
    /** Logging helper. */
    private static final Log LOG = LogFactory.getLog(ActionFilter.class);

    /** The query property name prefix that indicates the target action. */
    private static final String ACTION_PREFIX = "action_";

    /** URL path separator. */
    private static final String PATH_SEPARATOR = "/";

    /**
     * {@inheritDoc}
     * 
     * @see Filter#init(FilterConfig)
     */
    public void init(FilterConfig filterConfig) throws ServletException
    {
        // No initialization needed.
    }

    /**
     * {@inheritDoc}
     * 
     * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
     */
    @SuppressWarnings("unchecked")
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
        ServletException
    {
        // Only HTTP requests can be dispatched.
        if (request instanceof HttpServletRequest) {
            HttpServletRequest hrequest = (HttpServletRequest) request;
            Enumeration<String> parameterNames = hrequest.getParameterNames();
            while (parameterNames.hasMoreElements()) {
                String parameter = parameterNames.nextElement();
                if (parameter.startsWith(ACTION_PREFIX)) {
                    String targetURL = getTargetURL(hrequest, parameter);
                    RequestDispatcher dispatcher = hrequest.getRequestDispatcher(targetURL);
                    if (dispatcher != null) {
                        LOG.debug("Forwarding request to " + targetURL);
                        dispatcher.forward(hrequest, response);
                        // If the request was forwarder to another path, don't continue the normal processing chain.
                        return;
                    }
                }
            }
        }
        // Let the request pass through unchanged.
        chain.doFilter(request, response);
    }

    /**
     * {@inheritDoc}
     * 
     * @see Filter#destroy()
     */
    public void destroy()
    {
        // No finalization needed.
    }

    /**
     * Compose a new URL path based on the original request and the specified action. The result is relative to the
     * application context, so that it can be used with {@link HttpServletRequest#getRequestDispatcher(String)}. For
     * example, calling this method with a request for <tt>/xwiki/bin/edit/Some/Document</tt> and <tt>action_save</tt>,
     * the result is <tt>/bin/save/Some/Document</tt>.
     * 
     * @param request the original request
     * @param action the action parameter, starting with <tt>action_</tt>
     * @return The rebuilt URL path, with the specified action in place of the original Struts action. Note that unlike
     *         the HTTP path, this does not contain the application context part.
     */
    private String getTargetURL(HttpServletRequest request, String action)
    {
        String newAction = PATH_SEPARATOR + action.substring(ACTION_PREFIX.length());
        // Extract the document name from the requested path. We don't use getPathInfo() since it is decoded
        // by the container, thus it will not work when XWiki uses a non-UTF-8 encoding.
        // First step, remove the context path, if any.
        String document = StringUtils.substringAfter(request.getRequestURI(), request.getContextPath());
        // Second step, remove the servlet path, if any.
        document = StringUtils.substringAfter(document, request.getServletPath());
        // Third step, remove the struts mapping. This step is mandatory, so this filter will fail if the
        // requested action was a hidden (default) 'view', like in '/bin/Main/'. This is OK, since forms
        // don't use 'view' as a target.
        document = PATH_SEPARATOR + StringUtils.substringAfter(document.substring(1), PATH_SEPARATOR);

        // Compose the target URL starting with the servlet path.
        return request.getServletPath() + newAction + document;
    }
}

@@ -1,8 +1,905 @@
+/*%0A * See the NOTICE file distributed with this work for additional%0A * information regarding copyright ownership.%0A *%0A * This is free software; you can redistribute it and/or modify it%0A * under the terms of the GNU Lesser General Public License as%0A * published by the Free Software Foundation; either version 2.1 of%0A * the License, or (at your option) any later version.%0A *%0A * This software is distributed in the hope that it will be useful,%0A * but WITHOUT ANY WARRANTY; without even the implied warranty of%0A * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU%0A * Lesser General Public License for more details.%0A *%0A * You should have received a copy of the GNU Lesser General Public%0A * License along with this software; if not, write to the Free%0A * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA%0A * 02110-1301 USA, or see the FSF site: http://www.fsf.org.%0A *%0A */%0A
 package 

Add copyright header