Provide csp_nonce to Soy Templates

The CSP nonce is only provided if available. If a filter is installed
that attaches "nonce" attribute to request. For example in google
deployment of gerrit we have such filter that sets nonce on all requests
before they are processed by a servelet.

CSP helps protects sites from XSS attacks. On Google hosts we set CSP
headers that require all script elements to be accompanied by nonce (per
request generated random string). Soy templates have a built in support
for attaching nonce, as long as the value is provided using Inject Data
mechanism.

Google-Bud-Id: b/33429040
Release-Notes: skip
Change-Id: Ifa3a07b8c77918a8a4ab48775b68e4f3b39bd3cb
diff --git a/java/com/google/gitiles/BaseServlet.java b/java/com/google/gitiles/BaseServlet.java
index 91c66bb..860ffd8 100644
--- a/java/com/google/gitiles/BaseServlet.java
+++ b/java/com/google/gitiles/BaseServlet.java
@@ -228,7 +228,7 @@
       throws IOException {
     req.setAttribute(STREAMING_ATTRIBUTE, true);
     return renderer.renderHtmlStreaming(
-        res, false, templateName, startHtmlResponse(req, res, soyData));
+        req, res, false, templateName, startHtmlResponse(req, res, soyData));
   }
 
   /**
@@ -261,7 +261,7 @@
       gzip = true;
     }
     return renderer.renderHtmlStreaming(
-        res, gzip, templateName, startHtmlResponse(req, res, soyData));
+        req, res, gzip, templateName, startHtmlResponse(req, res, soyData));
   }
 
   private Map<String, ?> startHtmlResponse(
diff --git a/java/com/google/gitiles/Renderer.java b/java/com/google/gitiles/Renderer.java
index 65aea7e..3b85e3d 100644
--- a/java/com/google/gitiles/Renderer.java
+++ b/java/com/google/gitiles/Renderer.java
@@ -37,6 +37,7 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.function.Function;
@@ -148,17 +149,18 @@
     return h.hash();
   }
 
-  public String renderHtml(String templateName, Map<String, ?> soyData) {
-    return newRenderer(templateName).setData(soyData).renderHtml().get().toString();
-  }
-
   void renderHtml(
       HttpServletRequest req, HttpServletResponse res, String templateName, Map<String, ?> soyData)
       throws IOException {
     res.setContentType("text/html");
     res.setCharacterEncoding("UTF-8");
     byte[] data =
-        newRenderer(templateName).setData(soyData).renderHtml().get().toString().getBytes(UTF_8);
+        newRenderer(templateName, Optional.of(req))
+            .setData(soyData)
+            .renderHtml()
+            .get()
+            .toString()
+            .getBytes(UTF_8);
     if (BaseServlet.acceptsGzipEncoding(req)) {
       res.addHeader(HttpHeaders.VARY, HttpHeaders.ACCEPT_ENCODING);
       res.setHeader(HttpHeaders.CONTENT_ENCODING, "gzip");
@@ -169,14 +171,20 @@
   }
 
   OutputStream renderHtmlStreaming(
-      HttpServletResponse res, String templateName, Map<String, ?> soyData) throws IOException {
-    return renderHtmlStreaming(res, false, templateName, soyData);
+      HttpServletRequest req, HttpServletResponse res, String templateName, Map<String, ?> soyData)
+      throws IOException {
+    return renderHtmlStreaming(req, res, false, templateName, soyData);
   }
 
   OutputStream renderHtmlStreaming(
-      HttpServletResponse res, boolean gzip, String templateName, Map<String, ?> soyData)
+      HttpServletRequest req,
+      HttpServletResponse res,
+      boolean gzip,
+      String templateName,
+      Map<String, ?> soyData)
       throws IOException {
-    String html = newRenderer(templateName).setData(soyData).renderHtml().get().toString();
+    String html =
+        newRenderer(templateName, Optional.of(req)).setData(soyData).renderHtml().get().toString();
     int id = html.indexOf(PLACEHOLDER);
     checkArgument(id >= 0, "Template must contain %s", PLACEHOLDER);
 
@@ -214,15 +222,25 @@
   }
 
   SoySauce.Renderer newRenderer(String templateName) {
+    return newRenderer(templateName, Optional.empty());
+  }
+
+  SoySauce.Renderer newRenderer(String templateName, Optional<HttpServletRequest> req) {
     ImmutableMap.Builder<String, Object> staticUrls = ImmutableMap.builder();
     for (String key : STATIC_URL_GLOBALS.keySet()) {
       staticUrls.put(
           key.replaceFirst("^gitiles\\.", ""),
           LegacyConversions.riskilyAssumeTrustedResourceUrl(globals.get(key)));
     }
-    return getSauce()
-        .renderTemplate(templateName)
-        .setIj(ImmutableMap.of("staticUrls", staticUrls.build(), "SITE_TITLE", siteTitle));
+    ImmutableMap.Builder<String, Object> ij =
+        ImmutableMap.<String, Object>builder()
+            .put("staticUrls", staticUrls.build())
+            .put("SITE_TITLE", siteTitle);
+    Optional<String> nonce = req.map((r) -> (String) r.getAttribute("nonce"));
+    if (nonce.isPresent() && nonce.get() != null) {
+      ij.put("csp_nonce", nonce);
+    }
+    return getSauce().renderTemplate(templateName).setIj(ij.build());
   }
 
   protected abstract SoySauce getSauce();