Rendering images

Previously, viewing an image would only display the size of the
binary file in bytes. This change allows common-typed images to
be rendered in browser using MimeTypes for type checking.

Github issue: https://github.com/google/gitiles/issues/136

Change-Id: I7d987962d6632571578af7b66db53945f219eb80
diff --git a/java/com/google/gitiles/BlobSoyData.java b/java/com/google/gitiles/BlobSoyData.java
index e898101..7e96d65 100644
--- a/java/com/google/gitiles/BlobSoyData.java
+++ b/java/com/google/gitiles/BlobSoyData.java
@@ -20,7 +20,9 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
+import com.google.common.io.BaseEncoding;
 import com.google.template.soy.data.SoyListData;
 import com.google.template.soy.data.SoyMapData;
 import java.io.IOException;
@@ -57,6 +59,11 @@
    */
   private static final int MAX_LINE_COUNT = 50000;
 
+  /** Allowed image extensions to render */
+  private static final ImmutableSet<String> ALLOWED_IMAGE_TYPES =
+      ImmutableSet.of(
+          "image/gif", "image/jpeg", "image/jpg", "image/png", "image/tiff", "image/webp");
+
   private final GitilesView view;
   private final ObjectReader reader;
 
@@ -76,8 +83,16 @@
 
     ObjectLoader loader = reader.open(blobId, Constants.OBJ_BLOB);
     String content;
+    String imageBlob;
     try {
       byte[] raw = loader.getCachedBytes(MAX_FILE_SIZE);
+
+      String type = MimeTypes.getMimeType(path);
+      if (ALLOWED_IMAGE_TYPES.contains(type) && raw.length < MAX_FILE_SIZE) {
+        imageBlob = "data:" + type + ";base64," + BaseEncoding.base64().encode(raw);
+      } else {
+        imageBlob = null;
+      }
       content =
           (raw.length < MAX_FILE_SIZE && !RawText.isBinary(raw)) ? RawParseUtils.decode(raw) : null;
       if (isContentTooLargeForDisplay(content)) {
@@ -87,6 +102,7 @@
       throw e;
     } catch (LargeObjectException e) {
       content = null;
+      imageBlob = null;
     }
 
     if (content != null) {
@@ -102,6 +118,9 @@
       data.put("fileUrl", GitilesView.path().copyFrom(view).toUrl());
       data.put("logUrl", GitilesView.log().copyFrom(view).toUrl());
       data.put("blameUrl", GitilesView.blame().copyFrom(view).toUrl());
+      if (imageBlob != null) {
+        data.put("imgBlob", imageBlob);
+      }
     }
     return data;
   }
diff --git a/resources/com/google/gitiles/templates/ObjectDetail.soy b/resources/com/google/gitiles/templates/ObjectDetail.soy
index 054f621..5e5a3a8 100644
--- a/resources/com/google/gitiles/templates/ObjectDetail.soy
+++ b/resources/com/google/gitiles/templates/ObjectDetail.soy
@@ -257,6 +257,7 @@
 {template blobDetail}
   {@param sha: ?}  /** SHA of this file's blob. */
   {@param? logUrl: ?}  /** optional URL to a log for this file. */
+  {@param? imgBlob: ?}  /** optional image blob to render. */
   {@param? blameUrl: ?}  /** optional URL to a blame for this file. */
   {@param lines: ?}  /** lines (may be empty), or null for a binary file. Each line is a list of
       entries with "classes" and "text" fields for pretty-printed spans. */
@@ -292,7 +293,11 @@
     {/if}
   {else}
     <div class="FileContents-binary">
-      {msg desc="size of binary file in bytes"}{$size}-byte binary file{/msg}
+      {if $imgBlob}
+        <img src="{$imgBlob}"/>
+      {else}
+        {msg desc="size of binary file in bytes"}{$size}-byte binary file{/msg}
+      {/if}
     </div>
   {/if}
 {/template}