LCOV - code coverage report
Current view: top level - extensions/restapi - BinaryResult.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 52 73 71.2 %
Date: 2022-11-19 15:00:39 Functions: 23 28 82.1 %

          Line data    Source code
       1             : // Copyright (C) 2012 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.extensions.restapi;
      16             : 
      17             : import static java.nio.charset.StandardCharsets.UTF_8;
      18             : 
      19             : import java.io.ByteArrayOutputStream;
      20             : import java.io.Closeable;
      21             : import java.io.IOException;
      22             : import java.io.InputStream;
      23             : import java.io.OutputStream;
      24             : import java.nio.ByteBuffer;
      25             : import java.nio.charset.CharacterCodingException;
      26             : import java.nio.charset.Charset;
      27             : import java.nio.charset.CodingErrorAction;
      28             : import java.nio.charset.UnsupportedCharsetException;
      29             : 
      30             : /**
      31             :  * Wrapper around a non-JSON result from a {@link RestView}.
      32             :  *
      33             :  * <p>Views may return this type to signal they want the server glue to write raw data to the
      34             :  * client, instead of attempting automatic conversion to JSON. The create form is overloaded to
      35             :  * handle plain text from a String, or binary data from a {@code byte[]} or {@code InputSteam}.
      36             :  */
      37          38 : public abstract class BinaryResult implements Closeable {
      38             :   /** Default MIME type for unknown binary data. */
      39             :   static final String OCTET_STREAM = "application/octet-stream";
      40             : 
      41             :   /** Produce a UTF-8 encoded result from a string. */
      42             :   public static BinaryResult create(String data) {
      43          25 :     return new StringResult(data);
      44             :   }
      45             : 
      46             :   /** Produce an {@code application/octet-stream} result from a byte array. */
      47             :   public static BinaryResult create(byte[] data) {
      48          18 :     return new Array(data);
      49             :   }
      50             : 
      51             :   /**
      52             :    * Produce an {@code application/octet-stream} of unknown length by copying the InputStream until
      53             :    * EOF. The server glue will automatically close this stream when copying is complete.
      54             :    */
      55             :   public static BinaryResult create(InputStream data) {
      56           0 :     return new Stream(data);
      57             :   }
      58             : 
      59          38 :   private String contentType = OCTET_STREAM;
      60             :   private Charset characterEncoding;
      61          38 :   private long contentLength = -1;
      62          38 :   private boolean gzip = true;
      63             :   private boolean base64;
      64             :   private String attachmentName;
      65             : 
      66             :   /** Returns the MIME type of the result, for HTTP clients. */
      67             :   public String getContentType() {
      68          27 :     Charset enc = getCharacterEncoding();
      69          27 :     if (enc != null) {
      70          27 :       return contentType + "; charset=" + enc.name();
      71             :     }
      72          19 :     return contentType;
      73             :   }
      74             : 
      75             :   /** Set the MIME type of the result, and return {@code this}. */
      76             :   public BinaryResult setContentType(String contentType) {
      77          38 :     this.contentType = contentType != null ? contentType : OCTET_STREAM;
      78          38 :     return this;
      79             :   }
      80             : 
      81             :   /** Get the character encoding; null if not known. */
      82             :   public Charset getCharacterEncoding() {
      83          35 :     return characterEncoding;
      84             :   }
      85             : 
      86             :   /** Set the character set used to encode text data and return {@code this}. */
      87             :   public BinaryResult setCharacterEncoding(Charset encoding) {
      88          30 :     characterEncoding = encoding;
      89          30 :     return this;
      90             :   }
      91             : 
      92             :   /** Get the attachment file name; null if not set. */
      93             :   public String getAttachmentName() {
      94          27 :     return attachmentName;
      95             :   }
      96             : 
      97             :   /** Set the attachment file name and return {@code this}. */
      98             :   public BinaryResult setAttachmentName(String attachmentName) {
      99           4 :     this.attachmentName = attachmentName;
     100           4 :     return this;
     101             :   }
     102             : 
     103             :   /** Returns length in bytes of the result; -1 if not known. */
     104             :   public long getContentLength() {
     105          27 :     return contentLength;
     106             :   }
     107             : 
     108             :   /** Set the content length of the result; -1 if not known. */
     109             :   public BinaryResult setContentLength(long len) {
     110          38 :     this.contentLength = len;
     111          38 :     return this;
     112             :   }
     113             : 
     114             :   /** Returns true if this result can be gzip compressed to clients. */
     115             :   public boolean canGzip() {
     116          27 :     return gzip;
     117             :   }
     118             : 
     119             :   /** Disable gzip compression for already compressed responses. */
     120             :   public BinaryResult disableGzip() {
     121           3 :     this.gzip = false;
     122           3 :     return this;
     123             :   }
     124             : 
     125             :   /** Returns true if the result must be base64 encoded. */
     126             :   public boolean isBase64() {
     127          27 :     return base64;
     128             :   }
     129             : 
     130             :   /** Wrap the binary data in base64 encoding. */
     131             :   public BinaryResult base64() {
     132          20 :     base64 = true;
     133          20 :     return this;
     134             :   }
     135             : 
     136             :   /**
     137             :    * Write or copy the result onto the specified output stream.
     138             :    *
     139             :    * @param os stream to write result data onto. This stream will be closed by the caller after this
     140             :    *     method returns.
     141             :    * @throws IOException if the data cannot be produced, or the OutputStream {@code os} throws any
     142             :    *     IOException during a write or flush call.
     143             :    */
     144             :   public abstract void writeTo(OutputStream os) throws IOException;
     145             : 
     146             :   /**
     147             :    * Return a copy of the result as a String.
     148             :    *
     149             :    * <p>The default version of this method copies the result into a temporary byte array and then
     150             :    * tries to decode it using the configured encoding.
     151             :    *
     152             :    * @return string version of the result.
     153             :    * @throws IOException if the data cannot be produced or could not be decoded to a String.
     154             :    */
     155             :   public String asString() throws IOException {
     156           3 :     long len = getContentLength();
     157             :     ByteArrayOutputStream buf;
     158           3 :     if (0 < len) {
     159           0 :       buf = new ByteArrayOutputStream((int) len);
     160             :     } else {
     161           3 :       buf = new ByteArrayOutputStream();
     162             :     }
     163           3 :     writeTo(buf);
     164           3 :     return decode(buf.toByteArray(), getCharacterEncoding());
     165             :   }
     166             : 
     167             :   /** Close the result and release any resources it holds. */
     168             :   @Override
     169          32 :   public void close() throws IOException {}
     170             : 
     171             :   @Override
     172             :   public String toString() {
     173           0 :     if (getContentLength() >= 0) {
     174           0 :       return String.format(
     175             :           "BinaryResult[Content-Type: %s, Content-Length: %d]",
     176           0 :           getContentType(), getContentLength());
     177             :     }
     178           0 :     return String.format(
     179           0 :         "BinaryResult[Content-Type: %s, Content-Length: unknown]", getContentType());
     180             :   }
     181             : 
     182             :   private static String decode(byte[] data, Charset enc) {
     183             :     try {
     184          16 :       Charset cs = enc != null ? enc : UTF_8;
     185          16 :       return cs.newDecoder()
     186          16 :           .onMalformedInput(CodingErrorAction.REPORT)
     187          16 :           .onUnmappableCharacter(CodingErrorAction.REPORT)
     188          16 :           .decode(ByteBuffer.wrap(data))
     189          16 :           .toString();
     190           0 :     } catch (UnsupportedCharsetException | CharacterCodingException e) {
     191             :       // Fallback to ISO-8850-1 style encoding.
     192           0 :       StringBuilder r = new StringBuilder(data.length);
     193           0 :       for (byte b : data) {
     194           0 :         r.append((char) (b & 0xff));
     195             :       }
     196           0 :       return r.toString();
     197             :     }
     198             :   }
     199             : 
     200             :   private static class Array extends BinaryResult {
     201             :     private final byte[] data;
     202             : 
     203          33 :     Array(byte[] data) {
     204          33 :       this.data = data;
     205          33 :       setContentLength(data.length);
     206          33 :     }
     207             : 
     208             :     @Override
     209             :     public void writeTo(OutputStream os) throws IOException {
     210          25 :       os.write(data);
     211          25 :     }
     212             : 
     213             :     @Override
     214             :     public String asString() {
     215          15 :       return decode(data, getCharacterEncoding());
     216             :     }
     217             :   }
     218             : 
     219             :   private static class StringResult extends Array {
     220             :     private final String str;
     221             : 
     222             :     StringResult(String str) {
     223          25 :       super(str.getBytes(UTF_8));
     224          25 :       setContentType("text/plain");
     225          25 :       setCharacterEncoding(UTF_8);
     226          25 :       this.str = str;
     227          25 :     }
     228             : 
     229             :     @Override
     230             :     public String asString() {
     231           5 :       return str;
     232             :     }
     233             :   }
     234             : 
     235             :   private static class Stream extends BinaryResult {
     236             :     private final InputStream src;
     237             : 
     238           0 :     Stream(InputStream src) {
     239           0 :       this.src = src;
     240           0 :     }
     241             : 
     242             :     @Override
     243             :     public void writeTo(OutputStream dst) throws IOException {
     244           0 :       byte[] tmp = new byte[4096];
     245             :       int n;
     246           0 :       while (0 < (n = src.read(tmp))) {
     247           0 :         dst.write(tmp, 0, n);
     248             :       }
     249           0 :     }
     250             : 
     251             :     @Override
     252             :     public void close() throws IOException {
     253           0 :       src.close();
     254           0 :     }
     255             :   }
     256             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750