Support for CORS in gitiles. Gitiles now sets the Access-Control-Allow-Origin header to the HTTP origin of the client iff the client's domain matches a regular expression defined in gitiles.config. This should allow XMLHttpRequest to work with Gitiles REST API. Change-Id: Ica74e3ff3f6af9afd11132a9e3ba1d6f0d53065b
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/BaseServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/BaseServlet.java index 70c48f1..824339e 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/BaseServlet.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/BaseServlet.java
@@ -25,6 +25,7 @@ import static javax.servlet.http.HttpServletResponse.SC_OK; import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP; +import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; @@ -39,6 +40,8 @@ import java.io.Writer; import java.lang.reflect.Type; import java.util.Map; +import java.util.List; +import java.util.regex.Pattern; import java.util.zip.GZIPOutputStream; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -261,7 +264,7 @@ protected void renderJson( HttpServletRequest req, HttpServletResponse res, Object src, Type typeOfSrc) throws IOException { - setApiHeaders(res, JSON); + setApiHeaders(req, res, JSON); res.setStatus(SC_OK); try (Writer writer = newWriter(req, res)) { newGsonBuilder(req).create().toJson(src, typeOfSrc, writer); @@ -286,7 +289,7 @@ */ protected Writer startRenderText( HttpServletRequest req, HttpServletResponse res, String contentType) throws IOException { - setApiHeaders(res, contentType); + setApiHeaders(req, res, contentType); return newWriter(req, res); } @@ -320,7 +323,7 @@ HttpServletRequest req, HttpServletResponse res, int statusCode, String message) throws IOException { res.setStatus(statusCode); - setApiHeaders(res, TEXT); + setApiHeaders(req, res, TEXT); setCacheHeaders(res); try (Writer out = newWriter(req, res)) { out.write(message); @@ -340,18 +343,33 @@ setNotCacheable(res); } - protected void setApiHeaders(HttpServletResponse res, String contentType) { + protected void setApiHeaders( + HttpServletRequest req, HttpServletResponse res, String contentType) throws IOException { if (!Strings.isNullOrEmpty(contentType)) { res.setContentType(contentType); } res.setCharacterEncoding(UTF_8.name()); res.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment"); - res.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*"); + + GitilesAccess access = getAccess(req); + String[] allowOrigin = access.getConfig().getStringList("gitiles", null, "allowOriginRegex"); + + if (allowOrigin.length > 0) { + String origin = req.getHeader(HttpHeaders.ORIGIN); + Pattern allowOriginPattern = Pattern.compile(Joiner.on("|").join(allowOrigin)); + + if (!Strings.isNullOrEmpty(origin) && allowOriginPattern.matcher(origin).matches()) { + res.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin); + } + } else { + res.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*"); + } setCacheHeaders(res); } - protected void setApiHeaders(HttpServletResponse res, FormatType type) { - setApiHeaders(res, type.getMimeType()); + protected void setApiHeaders(HttpServletRequest req, HttpServletResponse res, FormatType type) + throws IOException { + setApiHeaders(req, res, type.getMimeType()); } protected void setDownloadHeaders(HttpServletResponse res, String filename, String contentType) {
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/FakeHttpServletRequest.java b/gitiles-servlet/src/test/java/com/google/gitiles/FakeHttpServletRequest.java index f113d94..51cb9ae 100644 --- a/gitiles-servlet/src/test/java/com/google/gitiles/FakeHttpServletRequest.java +++ b/gitiles-servlet/src/test/java/com/google/gitiles/FakeHttpServletRequest.java
@@ -276,6 +276,10 @@ return Iterables.getFirst(headers.get(name), null); } + public boolean setHeader(String name, String value) { + return headers.put(name, value); + } + @Override public Enumeration<String> getHeaderNames() { return Collections.enumeration(headers.keySet());
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/PathServletTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/PathServletTest.java index b492ed3..a32567e 100644 --- a/gitiles-servlet/src/test/java/com/google/gitiles/PathServletTest.java +++ b/gitiles-servlet/src/test/java/com/google/gitiles/PathServletTest.java
@@ -16,8 +16,10 @@ import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; +import static javax.servlet.http.HttpServletResponse.SC_OK; import com.google.common.io.BaseEncoding; +import com.google.common.net.HttpHeaders; import com.google.gitiles.TreeJsonData.Tree; import com.google.template.soy.data.SoyListData; import com.google.template.soy.data.restricted.StringData; @@ -331,6 +333,25 @@ assertThat(tree.entries.get(0).name).isEqualTo("bar"); } + @Test + public void allowOrigin() throws Exception { + repo.branch("master").commit().add("foo", "contents").create(); + FakeHttpServletResponse res = buildText("/repo/+/master/foo"); + assertThat(res.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)) + .isEqualTo("http://localhost"); + } + + @Test + public void rejectOrigin() throws Exception { + repo.branch("master").commit().add("foo", "contents").create(); + FakeHttpServletResponse res = buildResponse( + "/repo/+/master/foo", "format=text", SC_OK, "http://notlocalhost"); + assertThat(res.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo("text/plain"); + assertThat(res.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)) + .isEqualTo(null); + } + + private Map<String, ?> getBlobData(Map<String, ?> data) { return ((Map<String, Map<String, ?>>) data).get("data"); }
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/ServletTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/ServletTest.java index 24c551c..4e08714 100644 --- a/gitiles-servlet/src/test/java/com/google/gitiles/ServletTest.java +++ b/gitiles-servlet/src/test/java/com/google/gitiles/ServletTest.java
@@ -59,8 +59,9 @@ } protected FakeHttpServletResponse buildResponse( - String path, String queryString, int expectedStatus) throws Exception { + String path, String queryString, int expectedStatus, String origin) throws Exception { FakeHttpServletRequest req = FakeHttpServletRequest.newRequest(); + req.setHeader(HttpHeaders.ORIGIN, origin); req.setPathInfo(path); if (queryString != null) { req.setQueryString(queryString); @@ -71,6 +72,11 @@ return res; } + protected FakeHttpServletResponse buildResponse( + String path, String queryString, int expectedStatus) throws Exception { + return buildResponse(path, queryString, expectedStatus, "http://localhost"); + } + protected FakeHttpServletResponse build(String path) throws Exception { return buildResponse(path, null, SC_OK); }
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/TestGitilesAccess.java b/gitiles-servlet/src/test/java/com/google/gitiles/TestGitilesAccess.java index 3571270..9046e0c 100644 --- a/gitiles-servlet/src/test/java/com/google/gitiles/TestGitilesAccess.java +++ b/gitiles-servlet/src/test/java/com/google/gitiles/TestGitilesAccess.java
@@ -17,6 +17,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.CharMatcher; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.Collections; import java.util.Map; @@ -76,7 +77,10 @@ @Override public Config getConfig() { - return new Config(); + Config config = new Config(); + config.setStringList( + "gitiles", null, "allowOriginRegex", ImmutableList.of("http://localhost")); + return config; } }; }