001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.main;
029
030import org.opencms.util.CmsStringUtil;
031
032import java.io.IOException;
033import java.util.HashMap;
034import java.util.Map;
035
036import javax.servlet.Filter;
037import javax.servlet.FilterChain;
038import javax.servlet.FilterConfig;
039import javax.servlet.ServletException;
040import javax.servlet.ServletRequest;
041import javax.servlet.ServletResponse;
042import javax.servlet.http.HttpServletRequest;
043
044import org.apache.commons.lang3.RandomStringUtils;
045import org.apache.commons.logging.Log;
046import org.apache.logging.log4j.CloseableThreadContext;
047
048/**
049 * Implements a servlet filter for URL rewriting.
050 * It adds the servlet name (typically "/opencms") if not already present, but necessary.
051 */
052public class OpenCmsUrlServletFilter implements Filter {
053
054    /** The static log object for this class. */
055    static final Log LOG = CmsLog.getLog(OpenCmsUrlServletFilter.class);
056
057    /**
058     * Name of the init-param of the filter configuration that is used to provide
059     * a pipe separated list of additional prefixes for which the URIs should not be adjusted.
060     */
061    private static final String INIT_PARAM_ADDITIONAL_EXCLUDEPREFIXES = "additionalExcludePrefixes";
062
063    /** A pipe separated list of URI prefixes, for which URIs should not be adjusted, additionally to the default prefixes. */
064    private String m_additionalExcludePrefixes;
065
066    /** The servlet context. */
067    private String m_contextPath;
068    /** Flag, indicating if the filter has been initialized. */
069    private boolean m_isInitialized;
070
071    /**
072     * A regex that matches if the requested URI starts with one of the default exclude prefixes
073     * or a prefix listed in the value of the {@link #INIT_PARAM_ADDITIONAL_EXCLUDEPREFIXES} init-param.
074     */
075    private String m_regex;
076
077    /** The servlet name, prefixed by "/". */
078    private String m_servletPath;
079
080    /**
081     * Creates a regex that matches all URIs starting with one of the <code>defaultExcludePrefixes</code>
082     * or one of the <code>additionalExcludePrefixes</code>.
083     *
084     * @param contextPath The context path that every URI starts with.
085     * @param defaultExcludePrefixes the default exclude prefixes.
086     * @param additionalExcludePrefixes a pipe separated list of URI prefixes for which the URLs
087     *  should not be adjusted - additionally to the default exclude prefixes
088     *
089     * @return a regex that matches all URIs starting with one of the <code>defaultExcludePrefixes</code>
090     *  or one of the <code>additionalExcludePrefixes</code>.
091     */
092    static String createRegex(String contextPath, String[] defaultExcludePrefixes, String additionalExcludePrefixes) {
093
094        StringBuffer regex = new StringBuffer();
095        regex.append(contextPath);
096        regex.append('(');
097        regex.append(defaultExcludePrefixes[0]);
098        for (int i = 1; i < defaultExcludePrefixes.length; i++) {
099            regex.append('|').append(defaultExcludePrefixes[i]);
100        }
101        if (!((null == additionalExcludePrefixes) || additionalExcludePrefixes.isEmpty())) {
102            regex.append('|').append(additionalExcludePrefixes);
103        }
104        regex.append(')');
105        regex.append(".*");
106        return regex.toString();
107    }
108
109    /**
110     * @see javax.servlet.Filter#destroy()
111     */
112    public void destroy() {
113
114        m_additionalExcludePrefixes = null;
115
116    }
117
118    /**
119     * Adjusts the requested URIs by prepending the name of the {@link org.opencms.main.OpenCmsServlet},
120     * if the request should be handled by that servlet.
121     *
122     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
123     */
124    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
125    throws IOException, ServletException {
126
127        Map<String, String> loggingInfo = new HashMap<>();
128        loggingInfo.put("request_id", RandomStringUtils.randomAlphanumeric(6));
129        if (request instanceof HttpServletRequest) {
130            String url = ((HttpServletRequest)request).getRequestURL().toString();
131            String query = ((HttpServletRequest)request).getQueryString();
132            if (!CmsStringUtil.isEmpty(query)) {
133                url = url + "?" + query;
134            }
135            loggingInfo.put("request_url", url);
136        }
137        try (CloseableThreadContext.Instance threadContext = CloseableThreadContext.putAll(loggingInfo)) {
138            LOG.info("Updating log context: " + loggingInfo);
139            if (m_isInitialized || tryToInitialize()) {
140                if (request instanceof HttpServletRequest) {
141                    HttpServletRequest req = (HttpServletRequest)request;
142                    String uri = req.getRequestURI();
143                    if (!uri.matches(m_regex)) {
144                        String adjustedUri = uri.replaceFirst(m_contextPath + "/", m_servletPath);
145                        req.getRequestDispatcher(adjustedUri).forward(request, response);
146                        return;
147                    }
148                }
149            }
150            chain.doFilter(request, response);
151        }
152    }
153
154    /**
155     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
156     */
157    public void init(FilterConfig filterConfig) {
158
159        m_additionalExcludePrefixes = filterConfig.getInitParameter(INIT_PARAM_ADDITIONAL_EXCLUDEPREFIXES);
160    }
161
162    /**
163     * Checks if OpenCms has reached run level 4 and if so, it initializes the member variables.
164     * In particular {@link #m_regex}, {@link #m_contextPath} and {@link OpenCmsUrlServletFilter#m_servletPath} are initialized and sets {@link #m_isInitialized} to true.
165     *
166     * @return <code>true</code> if initialization was successful, <code>false</code> otherwise.
167     */
168    boolean tryToInitialize() {
169
170        if (m_isInitialized) {
171            return true;
172        }
173        if (OpenCms.getRunLevel() == 4) {
174            m_contextPath = OpenCms.getSystemInfo().getContextPath();
175            m_servletPath = OpenCms.getSystemInfo().getServletPath() + "/";
176
177            /**
178             * The URI prefixes, for which the requested URI should not be rewritten,
179             * i.e., the ones that should not be handled by the {@link org.opencms.main.OpenCmsServlet}.
180             */
181            String[] defaultExcludePrefixes = new String[] {
182                "/" + OpenCms.getStaticExportManager().getExportPathForConfiguration(),
183                "/workplace",
184                "/VAADIN/",
185                m_servletPath,
186                "/resources/",
187                "/webdav",
188                "/webdav2",
189                "/cmisatom",
190                "/handle404",
191                "/services"};
192            StringBuffer regex = new StringBuffer();
193            regex.append(m_contextPath);
194            regex.append('(');
195            regex.append(defaultExcludePrefixes[0]);
196            for (int i = 1; i < defaultExcludePrefixes.length; i++) {
197                regex.append('|').append(defaultExcludePrefixes[i]);
198            }
199            if (!((null == m_additionalExcludePrefixes) || m_additionalExcludePrefixes.isEmpty())) {
200                regex.append('|').append(m_additionalExcludePrefixes);
201            }
202            regex.append(')');
203            regex.append(".*");
204            m_regex = regex.toString();
205            m_isInitialized = true;
206            return true;
207        }
208        return false;
209    }
210}