Add tests for PathServlet text rendering Fix a bug where we were setting the content type to text/plain even though we meant not to. Change-Id: I440b57aac035a4986846d2992d8089cf72557624
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 cf45af7..7990267 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/BaseServlet.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/BaseServlet.java
@@ -23,6 +23,7 @@ import static javax.servlet.http.HttpServletResponse.SC_OK; import com.google.common.base.Charsets; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.net.HttpHeaders; @@ -234,6 +235,19 @@ } /** + * @see #startRenderText(HttpServletRequest, HttpServletResponse) + * @param req in-progress request. + * @param res in-progress response. + * @param contentType contentType to set. + * @return the response's writer. + */ + protected PrintWriter startRenderText(HttpServletRequest req, HttpServletResponse res, + String contentType) throws IOException { + setApiHeaders(res, contentType); + return res.getWriter(); + } + + /** * Prepare the response to render plain text. * <p> * Unlike @@ -287,14 +301,20 @@ setNotCacheable(res); } - protected void setApiHeaders(HttpServletResponse res, FormatType type) { - res.setContentType(type.getMimeType()); + protected void setApiHeaders(HttpServletResponse res, String contentType) { + if (!Strings.isNullOrEmpty(contentType)) { + res.setContentType(contentType); + } res.setCharacterEncoding(Charsets.UTF_8.name()); res.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment"); res.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*"); setCacheHeaders(res); } + protected void setApiHeaders(HttpServletResponse res, FormatType type) { + setApiHeaders(res, type.getMimeType()); + } + protected void setDownloadHeaders(HttpServletResponse res, String filename, String contentType) { res.setContentType(contentType); res.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename);
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/PathServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/PathServlet.java index d946f2b..e48ea15 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/PathServlet.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/PathServlet.java
@@ -55,6 +55,7 @@ import java.io.IOException; import java.io.OutputStream; +import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.regex.Pattern; @@ -68,6 +69,8 @@ private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(PathServlet.class); + static final String MODE_HEADER = "X-Gitiles-Path-Mode"; + /** * Submodule URLs where we know there is a web page if the user visits the * repository URL verbatim in a web browser. @@ -204,8 +207,9 @@ // under the assumption that any hint we can give to a browser that // this is base64 data might cause it to try to decode it and render // as HTML, which would be bad. - res.setHeader("X-Gitiles-Path-Mode", String.format("%06o", tw.getRawMode(0))); - try (OutputStream out = BaseEncoding.base64().encodingStream(startRenderText(req, res))) { + PrintWriter writer = startRenderText(req, res, null); + res.setHeader(MODE_HEADER, String.format("%06o", tw.getRawMode(0))); + try (OutputStream out = BaseEncoding.base64().encodingStream(writer)) { rw.getObjectReader().open(tw.getObjectId(0)).copyTo(out); } break;
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 6fc0bc4..a6d29b6 100644 --- a/gitiles-servlet/src/test/java/com/google/gitiles/PathServletTest.java +++ b/gitiles-servlet/src/test/java/com/google/gitiles/PathServletTest.java
@@ -14,9 +14,13 @@ package com.google.gitiles; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import com.google.common.collect.ImmutableList; +import com.google.common.io.BaseEncoding; +import com.google.common.net.HttpHeaders; import com.google.template.soy.data.SoyListData; import com.google.template.soy.data.restricted.StringData; @@ -153,6 +157,52 @@ assertEquals("https://gerrit.googlesource.com/gitiles", linkData.get("httpUrl")); } + @Test + public void blobText() throws Exception { + repo.branch("master").commit().add("foo", "contents").create(); + String text = buildText("/repo/+/master/foo?format=TEXT", "100644"); + assertEquals("contents", new String(BaseEncoding.base64().decode(text), UTF_8)); + } + + @Test + public void symlinkText() throws Exception { + final RevBlob link = repo.blob("foo"); + repo.branch("master").commit() + .edit(new PathEdit("baz") { + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.SYMLINK); + ent.setObjectId(link); + } + }).create(); + String text = buildText("/repo/+/master/baz?format=TEXT", "120000"); + assertEquals("foo", new String(BaseEncoding.base64().decode(text), UTF_8)); + } + + @Test + public void nonBlobText() throws Exception { + String gitmodules = "[submodule \"gitiles\"]\n" + + " path = gitiles\n" + + " url = https://gerrit.googlesource.com/gitiles\n"; + final String gitilesSha = "2b2f34bba3c2be7e2506ce6b1f040949da350cf9"; + repo.branch("master").commit() + .add("foo/bar", "contents") + .add(".gitmodules", gitmodules) + .edit(new PathEdit("gitiles") { + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(FileMode.GITLINK); + ent.setObjectId(ObjectId.fromString(gitilesSha)); + } + }).create(); + + assertNotFound("/repo/+/master/nonexistent?format=TEXT"); + assertNotFound("/repo/+/master/?format=TEXT"); + assertNotFound("/repo/+/master/foo?format=TEXT"); + assertNotFound("/repo/+/master/foo/?format=TEXT"); + assertNotFound("/repo/+/master/gitiles?format=TEXT"); + } + private Map<String, ?> getBlobData(Map<String, ?> data) { return ((Map<String, Map<String, ?>>) data).get("data"); } @@ -169,6 +219,17 @@ return res; } + private void assertNotFound(String pathAndQuery) throws Exception { + assertEquals(404, service(pathAndQuery).getResponse().getStatus()); + } + + private String buildText(String pathAndQuery, String expectedMode) throws Exception { + TestViewFilter.Result res = service(pathAndQuery); + assertNull(res.getResponse().getHeader(HttpHeaders.CONTENT_TYPE)); + assertEquals(expectedMode, res.getResponse().getHeader(PathServlet.MODE_HEADER)); + return res.getResponse().getActualBodyString(); + } + private Map<String, ?> buildData(String pathAndQuery) throws Exception { // Render the page through Soy to ensure templates are valid, then return // the Soy data for introspection.