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.