View Javadoc
1   /* Copyright 2015 Google Inc. All Rights Reserved.
2   
3      Distributed under MIT license.
4      See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
5   */
6   
7   package org.htmlunit.util.brotli;
8   
9   import java.io.IOException;
10  import java.io.InputStream;
11  
12  /**
13   * {@link InputStream} decorator that decompresses brotli data.
14   *
15   * <p> Not thread-safe.
16   */
17  public class BrotliInputStream extends InputStream {
18  
19    public static final int DEFAULT_INTERNAL_BUFFER_SIZE = 256;
20  
21    /**
22     * Value expected by InputStream contract when stream is over.
23     *
24     * In Java it is -1.
25     * In C# it is 0 (should be patched during transpilation).
26     */
27    private static final int END_OF_STREAM_MARKER = -1;
28  
29    /**
30     * Internal buffer used for efficient byte-by-byte reading.
31     */
32    private byte[] buffer;
33  
34    /**
35     * Number of decoded but still unused bytes in internal buffer.
36     */
37    private int remainingBufferBytes;
38  
39    /**
40     * Next unused byte offset.
41     */
42    private int bufferOffset;
43  
44    /**
45     * Decoder state.
46     */
47    private final State state = new State();
48  
49    /**
50     * Creates a {@link InputStream} wrapper that decompresses brotli data.
51     *
52     * <p> For byte-by-byte reading ({@link #read()}) internal buffer with
53     * {@link #DEFAULT_INTERNAL_BUFFER_SIZE} size is allocated and used.
54     *
55     * <p> Will block the thread until first {@link BitReader#CAPACITY} bytes of data of source
56     * are available.
57     *
58     * @param source underlying data source
59     * @throws IOException in case of corrupted data or source stream problems
60     */
61    public BrotliInputStream(InputStream source) throws IOException {
62      this(source, DEFAULT_INTERNAL_BUFFER_SIZE);
63    }
64  
65    /**
66     * Creates a {@link InputStream} wrapper that decompresses brotli data.
67     *
68     * <p> For byte-by-byte reading ({@link #read()}) internal buffer of specified size is
69     * allocated and used.
70     *
71     * <p> Will block the thread until first {@link BitReader#CAPACITY} bytes of data of source
72     * are available.
73     *
74     * @param source compressed data source
75     * @param byteReadBufferSize size of internal buffer used in case of
76     *        byte-by-byte reading
77     * @throws IOException in case of corrupted data or source stream problems
78     */
79    public BrotliInputStream(InputStream source, int byteReadBufferSize) throws IOException {
80      if (byteReadBufferSize <= 0) {
81        throw new IllegalArgumentException("Bad buffer size:" + byteReadBufferSize);
82      } else if (source == null) {
83        throw new IllegalArgumentException("source is null");
84      }
85      this.buffer = new byte[byteReadBufferSize];
86      this.remainingBufferBytes = 0;
87      this.bufferOffset = 0;
88      try {
89        state.input = source;
90        Decode.initState(state);
91      } catch (BrotliRuntimeException ex) {
92        throw new IOException("Brotli decoder initialization failed", ex);
93      }
94    }
95  
96    public void attachDictionaryChunk(byte[] data) {
97      Decode.attachDictionaryChunk(state, data);
98    }
99  
100   public void enableEagerOutput() {
101     Decode.enableEagerOutput(state);
102   }
103 
104   public void enableLargeWindow() {
105     Decode.enableLargeWindow(state);
106   }
107 
108   /**
109    * {@inheritDoc}
110    */
111   @Override
112   public void close() throws IOException {
113     Decode.close(state);
114     Utils.closeInput(state);
115   }
116 
117   /**
118    * {@inheritDoc}
119    */
120   @Override
121   public int read() throws IOException {
122     if (bufferOffset >= remainingBufferBytes) {
123       remainingBufferBytes = read(buffer, 0, buffer.length);
124       bufferOffset = 0;
125       if (remainingBufferBytes == END_OF_STREAM_MARKER) {
126         // Both Java and C# return the same value for EOF on single-byte read.
127         return -1;
128       }
129     }
130     return buffer[bufferOffset++] & 0xFF;
131   }
132 
133   /**
134    * {@inheritDoc}
135    */
136   @Override
137   public int read(byte[] destBuffer, int destOffset, int destLen) throws IOException {
138     if (destOffset < 0) {
139       throw new IllegalArgumentException("Bad offset: " + destOffset);
140     } else if (destLen < 0) {
141       throw new IllegalArgumentException("Bad length: " + destLen);
142     } else if (destOffset + destLen > destBuffer.length) {
143       throw new IllegalArgumentException(
144           "Buffer overflow: " + (destOffset + destLen) + " > " + destBuffer.length);
145     } else if (destLen == 0) {
146       return 0;
147     }
148     int copyLen = Math.max(remainingBufferBytes - bufferOffset, 0);
149     if (copyLen != 0) {
150       copyLen = Math.min(copyLen, destLen);
151       System.arraycopy(buffer, bufferOffset, destBuffer, destOffset, copyLen);
152       bufferOffset += copyLen;
153       destOffset += copyLen;
154       destLen -= copyLen;
155       if (destLen == 0) {
156         return copyLen;
157       }
158     }
159     try {
160       state.output = destBuffer;
161       state.outputOffset = destOffset;
162       state.outputLength = destLen;
163       state.outputUsed = 0;
164       Decode.decompress(state);
165       copyLen += state.outputUsed;
166       copyLen = (copyLen > 0) ? copyLen : END_OF_STREAM_MARKER;
167       return copyLen;
168     } catch (BrotliRuntimeException ex) {
169       throw new IOException("Brotli stream decoding failed", ex);
170     }
171 
172     // <{[INJECTED CODE]}>
173   }
174 }