/*
 * Copyright (C) 2013 salesforce.com, inc.
 *
 * Licensed 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 org.auraframework.http;

import java.io.IOException;
import java.util.Set;

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

import org.auraframework.http.CSP.PolicyBuilder;
import org.auraframework.util.javascript.directive.JavascriptGeneratorMode;

import com.google.common.collect.ImmutableSet;

/**
 * Servlet filter for adding Content Security Policy headers,
 * per the <a href="http://www.w3.org/TR/CSP/">W3C Content Security
 * Policy 1.0 spec</a>.
 */
public class AuraCSPFilter implements Filter {
    
    /* TODO: try and get rid of these--see comments on doesUrlAllowInline() */
    private static final String APPLICATION = ".*\\.app";
    private static final String COMPONENT   = ".*\\.cmp";
    private static final String FRAMEWORK_JS= ".*/aura_" + getFrameworkJsSuffixRegex() + "\\.js";
    
    private static final Set<String> INLINE_ALLOWED_URLS = ImmutableSet.of(APPLICATION, COMPONENT, FRAMEWORK_JS);
    
    private static final String CHROME_EXTENSION = "chrome-extension:";

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
            ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        
        // set the policy to only report--not disallow--violations
        if (response.getHeader(CSP.Header.REPORT_ONLY) == null) {
            /* 
             * only set header if not already set--important because Aura request URIs get rewritten
             * and run through here multiple times, and we must use the client's (first) version of the URI
             * to choose our policy
             */
            response.setHeader(CSP.Header.REPORT_ONLY, getPolicy(request.getRequestURI()));
        }
        
        chain.doFilter(req, res);
    }

    @Override
    public void init(FilterConfig config) throws ServletException {}
    
    @Override
    public void destroy() {}
    
    protected String getPolicy(String url) { 
        PolicyBuilder p = new PolicyBuilder();
        p
        .default_src(CSP.SELF)
        .img_src(CSP.ALL)
        .font_src(CSP.ALL)
        .report_uri(CSPReporterServlet.URL);
        
        // note that chrome-extensions can cause violations, and we don't generally care.
        if (doesUrlAllowInline(url)) {
            p
            .script_src(CSP.SELF, CHROME_EXTENSION, CSP.UNSAFE_EVAL, CSP.UNSAFE_INLINE)
            .style_src(CSP.SELF, CHROME_EXTENSION, CSP.UNSAFE_INLINE);
        } else {
            p
            .script_src(CSP.SELF, CHROME_EXTENSION)
            .style_src(CSP.SELF, CHROME_EXTENSION);
        }
        
        return p.build();
    }
    
    /*
     * This is a (hopefully) temporary solution.
     * 
     * As the names imply, CSP strongly discourages use of unsafe-eval and unsafe-inline,
     * but parts of the Aura client depend on inlining, in particular the initial page template,
     * and fixing this the last week of feature development is scary.
     * 
     * NOTE: this should be fixed sooner rather than later, because the initial template *is*
     * configurable by consumers, and is therefore a vector for attack.
     */
    private boolean doesUrlAllowInline(String url) {
        for (String pattern : INLINE_ALLOWED_URLS) {
            if (url.matches(pattern)) {
                return true;
            }
        }
        
        return false;
    }
    
    private static String getFrameworkJsSuffixRegex() {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        boolean first = true;
        for (String suffix : JavascriptGeneratorMode.getSuffixes()) {
            if (!first) {
                sb.append("|");
            }
            sb.append(suffix);
            
            first = false;
        }
        sb.append(")");
        
        return sb.toString();
    }
}

@@ -1610,37 +1610,39 @@
 JS);%0A    %0A    pr
-ivate
+otected
  static final St
@@ -3916,21 +3916,29 @@
 /%0A    pr
-ivate
+otected final
  boolean

Make it easier to override the policy @bug W-2129820@ @rev goliver@