diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java index 188011c31e..103874f752 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java @@ -61,6 +61,7 @@ public abstract class BinaryResult implements Closeable { private String characterEncoding; private long contentLength = -1; private boolean gzip = true; + private boolean base64 = false; /** @return the MIME type of the result, for HTTP clients. */ public String getContentType() { @@ -110,6 +111,17 @@ public abstract class BinaryResult implements Closeable { return this; } + /** @return true if the result must be base64 encoded. */ + public boolean isBase64() { + return base64; + } + + /** Wrap the binary data in base64 encoding. */ + public BinaryResult base64() { + base64 = true; + return this; + } + /** * Write or copy the result onto the specified output stream. * diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java index 1040da31ee..fa1e9020f8 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java @@ -16,6 +16,7 @@ package com.google.gerrit.httpd.restapi; import static com.google.common.base.Charsets.UTF_8; import static com.google.common.base.Preconditions.checkNotNull; +import static java.math.RoundingMode.CEILING; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static javax.servlet.http.HttpServletResponse.SC_CONFLICT; import static javax.servlet.http.HttpServletResponse.SC_CREATED; @@ -26,6 +27,7 @@ import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static javax.servlet.http.HttpServletResponse.SC_OK; import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED; +import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Objects; @@ -38,6 +40,8 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; +import com.google.common.io.BaseEncoding; +import com.google.common.math.IntMath; import com.google.common.net.HttpHeaders; import com.google.gerrit.audit.AuditService; import com.google.gerrit.audit.HttpAuditEvent; @@ -627,10 +631,28 @@ public class RestApiServlet extends HttpServlet { HttpServletResponse res, BinaryResult bin) throws IOException { try { - res.setContentType(bin.getContentType()); OutputStream dst = res.getOutputStream(); try { long len = bin.getContentLength(); + if (bin.isBase64() && 0 <= len && len <= (10 << 20)) { + final TemporaryBuffer.Heap buf = base64(bin); + len = buf.length(); + base64(res, bin); + bin = new BinaryResult() { + @Override + public void writeTo(OutputStream os) throws IOException { + buf.writeTo(os, null); + } + }.setContentLength(len); + } else if (bin.isBase64()) { + len = -1; + base64(res, bin); + dst = BaseEncoding.base64().encodingStream( + new OutputStreamWriter(dst, Charsets.ISO_8859_1)); + } else { + res.setContentType(bin.getContentType()); + } + boolean gzip = bin.canGzip() && acceptsGzip(req); if (gzip && 256 <= len && len <= (10 << 20)) { TemporaryBuffer.Heap buf = compress(bin); @@ -656,6 +678,12 @@ public class RestApiServlet extends HttpServlet { } } + private static void base64(HttpServletResponse res, BinaryResult bin) { + res.setContentType("text/plain; charset=ISO-8859-1"); + res.setHeader("X-FYI-Content-Encoding", "base64"); + res.setHeader("X-FYI-Content-Type", bin.getContentType()); + } + private static void replyUncompressed(HttpServletResponse res, OutputStream dst, BinaryResult bin, long len) throws IOException { if (0 <= len && len < Integer.MAX_VALUE) { @@ -842,6 +870,18 @@ public class RestApiServlet extends HttpServlet { return false; } + private static TemporaryBuffer.Heap base64(BinaryResult bin) + throws IOException { + int len = (int) bin.getContentLength(); + int max = 4 * IntMath.divide(len, 3, CEILING); + TemporaryBuffer.Heap buf = heap(max); + OutputStream encoded = BaseEncoding.base64().encodingStream( + new OutputStreamWriter(buf, Charsets.ISO_8859_1)); + bin.writeTo(encoded); + encoded.close(); + return buf; + } + private static TemporaryBuffer.Heap compress(BinaryResult bin) throws IOException { TemporaryBuffer.Heap buf = heap(20 << 20); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java index 569df6fcf6..7c8ec34fd1 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java @@ -14,11 +14,9 @@ package com.google.gerrit.server.change; -import com.google.common.base.Charsets; -import com.google.common.io.BaseEncoding; +import com.google.gerrit.extensions.restapi.BinaryResult; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.RestReadView; -import com.google.gerrit.extensions.restapi.StreamingResponse; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.inject.Inject; @@ -32,7 +30,6 @@ import org.eclipse.jgit.treewalk.TreeWalk; import java.io.IOException; import java.io.OutputStream; -import java.io.OutputStreamWriter; public class GetContent implements RestReadView { private final GitRepositoryManager repoManager; @@ -43,7 +40,7 @@ public class GetContent implements RestReadView { } @Override - public StreamingResponse apply(FileResource rsrc) + public BinaryResult apply(FileResource rsrc) throws ResourceNotFoundException, IOException { Project.NameKey project = rsrc.getRevision().getControl().getProject().getNameKey(); @@ -61,21 +58,13 @@ public class GetContent implements RestReadView { throw new ResourceNotFoundException(); } try { - final ObjectLoader loader = repo.open(tw.getObjectId(0)); - return new StreamingResponse() { + final ObjectLoader object = repo.open(tw.getObjectId(0)); + return new BinaryResult() { @Override - public String getContentType() { - return "text/plain;charset=UTF-8"; + public void writeTo(OutputStream os) throws IOException { + object.copyTo(os); } - - @Override - public void stream(OutputStream out) throws IOException { - OutputStream b64Out = BaseEncoding.base64().encodingStream( - new OutputStreamWriter(out, Charsets.UTF_8)); - loader.copyTo(b64Out); - b64Out.close(); - } - }; + }.setContentLength(object.getSize()).base64(); } finally { tw.release(); }