<commit_before>/*******************************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *******************************************************************************/
package com.ilscipio.scipio.product.seo;

import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;

import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.UtilHttp;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.common.UrlServletHelper;
import org.ofbiz.entity.Delegator;
import org.ofbiz.entity.DelegatorFactory;
import org.ofbiz.product.catalog.CatalogWorker;
import org.ofbiz.product.category.CatalogUrlFilter;
import org.ofbiz.product.category.CategoryWorker;
import org.ofbiz.webapp.website.WebSiteWorker;

import com.ilscipio.scipio.product.seo.SeoCatalogUrlWorker.SeoCatalogUrlInfo;

import javolution.util.FastList;

/**
 * SCIPIO: 2017: Seo catalog URL filter.
 * <p>
 * Some parts adapted from the original <code>org.ofbiz.product.category.ftl.CatalogUrlSeoTransform</code>.
 * <p>
 * TODO: FUTURE: This filter should not conceptually extend CatalogUrlFilter; should try to
 * separate them.
 */
public class SeoCatalogUrlFilter extends CatalogUrlFilter { // extends ContextFilter implements Filter

    public static final String module = SeoCatalogUrlFilter.class.getName();

    public static final String SEOURLINFO_ATTR = "_SCPSEO_URLINFO_";
    public static final String FORWARDED_ATTR = "_SCPSEO_FWDED_";
    public static final String REQWRAPPED_ATTR = "_SCPSEO_REQWRAP_";

    static {
        // TODO?: unhardcode via properties?
        SeoCatalogUrlWorker.registerUrlBuilder();
    }

    protected String defaultLocaleString = null;
    protected String redirectUrl = null;
    protected String productRequestPath = "/" + PRODUCT_REQUEST;
    protected String categoryRequestPath = "/" + CATEGORY_REQUEST;
    protected boolean seoUrlEnabled = true;
    protected boolean debug = false;

    protected static SeoCatalogUrlWorker urlWorker = null;

    @Override
    public void init(FilterConfig config) throws ServletException {
        super.init(config);

        debug = Boolean.TRUE.equals(UtilMisc.booleanValueVersatile(config.getInitParameter("debug")));

        seoUrlEnabled = !Boolean.FALSE.equals(UtilMisc.booleanValueVersatile(config.getInitParameter("seoUrlEnabled")));
        if (seoUrlEnabled) {
            String initDefaultLocalesString = config.getInitParameter("defaultLocaleString");
            String initRedirectUrl = config.getInitParameter("redirectUrl");
            defaultLocaleString = UtilValidate.isNotEmpty(initDefaultLocalesString) ? initDefaultLocalesString : "";
            redirectUrl = UtilValidate.isNotEmpty(initRedirectUrl) ? initRedirectUrl : "";

            WebsiteSeoConfig.registerWebsiteForSeo(WebsiteSeoConfig.makeConfig(config.getServletContext(), true));

            urlWorker = SeoCatalogUrlWorker.getInstance(null, config.getServletContext().getInitParameter("webSiteId"));
        }
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        
        // TODO: REVIEW:
        //UrlServletHelper.setRequestAttributes(request, delegator, request.getServletContext());

        if (seoUrlEnabled) {
            
            // TODO: REVIEW: it's possible some of the "always-run" calls below (such as prepareRequestAlways)
            // should actually run again even after forward...
            if (!Boolean.TRUE.equals(request.getAttribute(FORWARDED_ATTR))) {
                Delegator delegator = getDelegatorForControl(request, request.getServletContext());
            
                CatalogUrlFilter.prepareRequestAlways(request, response, delegator);
                
                String path = getMatchablePath(request); 
                
                if (UtilValidate.isNotEmpty(path)) {
                    if (urlWorker.getConfig().isCategoryUrlEnabledForContextPath(request.getContextPath())) {
                        getCatalogTopCategory(request); // TODO: REVIEW
                        boolean forwarded = matchSeoCatalogUrlAndForward(request, response, delegator, path);
                        if (forwarded) return;
                    } else {
                        // TODO/FIXME: see CatalogUrlFitler for explanation for why these are here
                        getCatalogTopCategory(request);
                        UrlServletHelper.setViewQueryParameters(request, new StringBuilder());
                    }
        
                    //Check path alias (from CatalogUrlFilter)
                    // TODO/FIXME: REVIEW: control flow makes no sense for this (no boolean result to check)
                    //UrlServletHelper.checkPathAlias(request, response, delegator, pathInfo);
                }
            
                // NOTE: For this filter, we must wrap the request/response even if we don't forward!
                // TODO/FIXME: REVIEW: even if disabled for this webapp, may need to wrap responses
                // to process inter-webapp links... so this might need to go outside the (enabled) block...
                // or need 2 disable flags for debugging...
                if (!Boolean.TRUE.equals(request.getAttribute(REQWRAPPED_ATTR))) {
                    request.setAttribute(REQWRAPPED_ATTR, Boolean.TRUE);
                    chain.doFilter(getRequestWrapper(request, delegator), getResponseWrapper(request, response, delegator));
                    return;
                }
            }

            chain.doFilter(request, response);
        } else {
            // currently delegates to CatalogUrlFilter.
            // TODO: compose them differently in the future
            super.doFilter(request, response, chain);
        }
    }
    
    public boolean matchSeoCatalogUrlAndForward(HttpServletRequest request, HttpServletResponse response, Delegator delegator, String matchablePath) throws ServletException, IOException {
        SeoCatalogUrlInfo urlInfo = matchInboundSeoCatalogUrl(request, delegator, matchablePath);
        if (urlInfo != null) {
            boolean res = updateRequestForSeoCatalogUrl(request, delegator, urlInfo);
            if (!res) return false;
            return forwardSeoUrl(request, response, delegator, urlInfo);
        }
        return false;
    }

    public static String getMatchablePath(HttpServletRequest request) {
        if (request.getPathInfo() != null) return request.getServletPath() + request.getPathInfo();
        else return request.getServletPath();
    }

    public SeoCatalogUrlInfo matchInboundSeoCatalogUrl(HttpServletRequest request, Delegator delegator, String matchablePath) {
        return urlWorker.matchInboundSeoCatalogUrl(delegator, matchablePath, request.getContextPath(), 
                WebSiteWorker.getWebSiteId(request), CatalogWorker.getCurrentCatalogId(request));
    }

    /**
     * Sets the product/category IDs in request and updates trail.
     */<commit_msg>NoRef product: seo/alt urls: delegate<commit_after>/*******************************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *******************************************************************************/
package com.ilscipio.scipio.product.seo;

import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;

import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.UtilHttp;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.common.UrlServletHelper;
import org.ofbiz.entity.Delegator;
import org.ofbiz.entity.DelegatorFactory;
import org.ofbiz.product.catalog.CatalogWorker;
import org.ofbiz.product.category.CatalogUrlFilter;
import org.ofbiz.product.category.CategoryWorker;
import org.ofbiz.webapp.control.RequestLinkUtil;
import org.ofbiz.webapp.website.WebSiteWorker;

import com.ilscipio.scipio.product.seo.SeoCatalogUrlWorker.SeoCatalogUrlInfo;

import javolution.util.FastList;

/**
 * SCIPIO: 2017: Seo catalog URL filter.
 * <p>
 * Some parts adapted from the original <code>org.ofbiz.product.category.ftl.CatalogUrlSeoTransform</code>.
 * <p>
 * TODO: FUTURE: This filter should not conceptually extend CatalogUrlFilter; should try to
 * separate them.
 */
public class SeoCatalogUrlFilter extends CatalogUrlFilter { // extends ContextFilter implements Filter

    public static final String module = SeoCatalogUrlFilter.class.getName();

    public static final String SEOURLINFO_ATTR = "_SCPSEO_URLINFO_";
    public static final String FORWARDED_ATTR = "_SCPSEO_FWDED_";
    public static final String REQWRAPPED_ATTR = "_SCPSEO_REQWRAP_";

    static {
        // TODO?: unhardcode via properties?
        SeoCatalogUrlWorker.registerUrlBuilder();
    }

    protected String defaultLocaleString = null;
    protected String redirectUrl = null;
    protected String productRequestPath = "/" + PRODUCT_REQUEST;
    protected String categoryRequestPath = "/" + CATEGORY_REQUEST;
    protected boolean seoUrlEnabled = true;
    protected boolean debug = false;

    protected static SeoCatalogUrlWorker urlWorker = null;

    @Override
    public void init(FilterConfig config) throws ServletException {
        super.init(config);

        debug = Boolean.TRUE.equals(UtilMisc.booleanValueVersatile(config.getInitParameter("debug")));

        seoUrlEnabled = !Boolean.FALSE.equals(UtilMisc.booleanValueVersatile(config.getInitParameter("seoUrlEnabled")));
        if (seoUrlEnabled) {
            String initDefaultLocalesString = config.getInitParameter("defaultLocaleString");
            String initRedirectUrl = config.getInitParameter("redirectUrl");
            defaultLocaleString = UtilValidate.isNotEmpty(initDefaultLocalesString) ? initDefaultLocalesString : "";
            redirectUrl = UtilValidate.isNotEmpty(initRedirectUrl) ? initRedirectUrl : "";

            WebsiteSeoConfig.registerWebsiteForSeo(WebsiteSeoConfig.makeConfig(config.getServletContext(), true));

            urlWorker = SeoCatalogUrlWorker.getInstance(null, config.getServletContext().getInitParameter("webSiteId"));
        }
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        
        // TODO: REVIEW:
        //UrlServletHelper.setRequestAttributes(request, delegator, request.getServletContext());

        if (seoUrlEnabled) {
            
            // TODO: REVIEW: it's possible some of the "always-run" calls below (such as prepareRequestAlways)
            // should actually run again even after forward...
            if (!Boolean.TRUE.equals(request.getAttribute(FORWARDED_ATTR))) {
                Delegator delegator = getDelegatorForControl(request, request.getServletContext());
            
                CatalogUrlFilter.prepareRequestAlways(request, response, delegator);
                
                String path = getMatchablePath(request); 
                
                if (UtilValidate.isNotEmpty(path)) {
                    if (urlWorker.getConfig().isCategoryUrlEnabledForContextPath(request.getContextPath())) {
                        getCatalogTopCategory(request); // TODO: REVIEW
                        boolean forwarded = matchSeoCatalogUrlAndForward(request, response, delegator, path);
                        if (forwarded) return;
                    } else {
                        // TODO/FIXME: see CatalogUrlFitler for explanation for why these are here
                        getCatalogTopCategory(request);
                        UrlServletHelper.setViewQueryParameters(request, new StringBuilder());
                    }
        
                    //Check path alias (from CatalogUrlFilter)
                    // TODO/FIXME: REVIEW: control flow makes no sense for this (no boolean result to check)
                    //UrlServletHelper.checkPathAlias(request, response, delegator, pathInfo);
                }
            
                // NOTE: For this filter, we must wrap the request/response even if we don't forward!
                // TODO/FIXME: REVIEW: even if disabled for this webapp, may need to wrap responses
                // to process inter-webapp links... so this might need to go outside the (enabled) block...
                // or need 2 disable flags for debugging...
                if (!Boolean.TRUE.equals(request.getAttribute(REQWRAPPED_ATTR))) {
                    request.setAttribute(REQWRAPPED_ATTR, Boolean.TRUE);
                    chain.doFilter(getRequestWrapper(request, delegator), getResponseWrapper(request, response, delegator));
                    return;
                }
            }

            chain.doFilter(request, response);
        } else {
            // currently delegates to CatalogUrlFilter.
            // TODO: compose them differently in the future
            super.doFilter(request, response, chain);
        }
    }
    
    public boolean matchSeoCatalogUrlAndForward(HttpServletRequest request, HttpServletResponse response, Delegator delegator, String matchablePath) throws ServletException, IOException {
        SeoCatalogUrlInfo urlInfo = matchInboundSeoCatalogUrl(request, delegator, matchablePath);
        if (urlInfo != null) {
            boolean res = updateRequestForSeoCatalogUrl(request, delegator, urlInfo);
            if (!res) return false;
            return forwardSeoUrl(request, response, delegator, urlInfo);
        }
        return false;
    }

    public static String getMatchablePath(HttpServletRequest request) {
        return RequestLinkUtil.getServletAndPathInfo(request);
    }

    public SeoCatalogUrlInfo matchInboundSeoCatalogUrl(HttpServletRequest request, Delegator delegator, String matchablePath) {
        return urlWorker.matchInboundSeoCatalogUrl(delegator, matchablePath, request.getContextPath(), 
                WebSiteWorker.getWebSiteId(request), CatalogWorker.getCurrentCatalogId(request));
    }

    /**
     * Sets the product/category IDs in request and updates trail.
     */
{'fraction_non_alphanumeric': 0.09809119830328739, 'fraction_numerical': 0.0010604453870625664, 'mean_word_length': 3.146311675622863, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, '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': '7663622', 'n_tokens_mistral': 4303, 'n_tokens_neox': 4199, 'n_words': 1125}