You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RLEDecompressingInputStream.java 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  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 org.apache.poi.util;
  16. import java.io.IOException;
  17. import java.io.InputStream;
  18. import java.util.Locale;
  19. import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream;
  20. import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
  21. /**
  22. * Wrapper of InputStream which provides Run Length Encoding (RLE)
  23. * decompression on the fly. Uses MS-OVBA decompression algorithm. See
  24. * http://download.microsoft.com/download/2/4/8/24862317-78F0-4C4B-B355-C7B2C1D997DB/[MS-OVBA].pdf
  25. */
  26. public class RLEDecompressingInputStream extends InputStream {
  27. /**
  28. * Bitmasks for performance
  29. */
  30. private static final int[] POWER2 = new int[] {
  31. 0x0001, // 2^0
  32. 0x0002, // 2^1
  33. 0x0004, // 2^2
  34. 0x0008, // 2^3
  35. 0x0010, // 2^4
  36. 0x0020, // 2^5
  37. 0x0040, // 2^6
  38. 0x0080, // 2^7
  39. 0x0100, // 2^8
  40. 0x0200, // 2^9
  41. 0x0400, // 2^10
  42. 0x0800, // 2^11
  43. 0x1000, // 2^12
  44. 0x2000, // 2^13
  45. 0x4000, // 2^14
  46. 0x8000 // 2^15
  47. };
  48. /** the wrapped inputstream */
  49. private final InputStream in;
  50. /** a byte buffer with size 4096 for storing a single chunk */
  51. private final byte[] buf;
  52. /** the current position in the byte buffer for reading */
  53. private int pos;
  54. /** the number of bytes in the byte buffer */
  55. private int len;
  56. /**
  57. * Creates a new wrapper RLE Decompression InputStream.
  58. *
  59. * @param in The stream to wrap with the RLE Decompression
  60. */
  61. public RLEDecompressingInputStream(InputStream in) throws IOException {
  62. this.in = in;
  63. buf = new byte[4096];
  64. pos = 0;
  65. int header = in.read();
  66. if (header != 0x01) {
  67. throw new IllegalArgumentException(String.format(Locale.ROOT, "Header byte 0x01 expected, received 0x%02X", header & 0xFF));
  68. }
  69. len = readChunk();
  70. }
  71. @Override
  72. public int read() throws IOException {
  73. if (len == -1) {
  74. return -1;
  75. }
  76. if (pos >= len) {
  77. if ((len = readChunk()) == -1) {
  78. return -1;
  79. }
  80. }
  81. return buf[pos++]& 0xFF;
  82. }
  83. @Override
  84. public int read(byte[] b) throws IOException {
  85. return read(b, 0, b.length);
  86. }
  87. @Override
  88. public int read(byte[] b, int off, int l) throws IOException {
  89. if (len == -1) {
  90. return -1;
  91. }
  92. int offset = off;
  93. int length = l;
  94. while (length > 0) {
  95. if (pos >= len) {
  96. if ((len = readChunk()) == -1) {
  97. return offset > off ? offset - off : -1;
  98. }
  99. }
  100. int c = Math.min(length, len - pos);
  101. System.arraycopy(buf, pos, b, offset, c);
  102. pos += c;
  103. length -= c;
  104. offset += c;
  105. }
  106. return l;
  107. }
  108. @Override
  109. public long skip(long n) throws IOException {
  110. //this relies on readChunk's readFully to skipFully
  111. long length = n;
  112. while (length > 0) {
  113. if (pos >= len) {
  114. if ((len = readChunk()) == -1) {
  115. return -1;
  116. }
  117. }
  118. int c = (int) Math.min(n, len - (long)pos);
  119. pos += c;
  120. length -= c;
  121. }
  122. return n;
  123. }
  124. @Override
  125. public int available() {
  126. return (len > 0 ? len - pos : 0);
  127. }
  128. @Override
  129. public void close() throws IOException {
  130. in.close();
  131. }
  132. /**
  133. * Reads a single chunk from the underlying inputstream.
  134. *
  135. * @return number of bytes that were read, or -1 if the end of the stream was reached.
  136. */
  137. private int readChunk() throws IOException {
  138. pos = 0;
  139. int w = readShort(in);
  140. if (w == -1 || w == 0) {
  141. return -1;
  142. }
  143. int chunkSize = (w & 0x0FFF) + 1; // plus 3 bytes minus 2 for the length
  144. if ((w & 0x7000) != 0x3000) {
  145. throw new IllegalArgumentException(String.format(Locale.ROOT, "Chunksize header A should be 0x3000, received 0x%04X", w & 0xE000));
  146. }
  147. boolean rawChunk = (w & 0x8000) == 0;
  148. if (rawChunk) {
  149. if (IOUtils.readFully(in, buf, 0, chunkSize) < chunkSize) {
  150. throw new IllegalStateException(String.format(Locale.ROOT, "Not enough bytes read, expected %d", chunkSize));
  151. }
  152. return chunkSize;
  153. } else {
  154. int inOffset = 0;
  155. int outOffset = 0;
  156. while (inOffset < chunkSize) {
  157. int tokenFlags = in.read();
  158. inOffset++;
  159. if (tokenFlags == -1) {
  160. break;
  161. }
  162. for (int n = 0; n < 8; n++) {
  163. if (inOffset >= chunkSize) {
  164. break;
  165. }
  166. if ((tokenFlags & POWER2[n]) == 0) {
  167. // literal
  168. final int b = in.read();
  169. if (b == -1) {
  170. return -1;
  171. }
  172. buf[outOffset++] = (byte) b;
  173. inOffset++;
  174. } else {
  175. // compressed token
  176. int token = readShort(in);
  177. if (token == -1) {
  178. return -1;
  179. }
  180. inOffset += 2;
  181. int copyLenBits = getCopyLenBits(outOffset - 1);
  182. int copyOffset = (token >> (copyLenBits)) + 1;
  183. int copyLen = (token & (POWER2[copyLenBits] - 1)) + 3;
  184. int startPos = outOffset - copyOffset;
  185. int endPos = startPos + copyLen;
  186. for (int i = startPos; i < endPos; i++) {
  187. buf[outOffset++] = buf[i];
  188. }
  189. }
  190. }
  191. }
  192. return outOffset;
  193. }
  194. }
  195. /**
  196. * Helper method to determine how many bits in the CopyToken are used for the CopyLength.
  197. *
  198. * @return returns the number of bits in the copy token (a value between 4 and 12)
  199. */
  200. static int getCopyLenBits(int offset) {
  201. for (int n = 11; n >= 4; n--) {
  202. if ((offset & POWER2[n]) != 0) {
  203. return 15 - n;
  204. }
  205. }
  206. return 12;
  207. }
  208. /**
  209. * Convenience method for read a 2-bytes short in little endian encoding.
  210. *
  211. * @return short value from the stream, -1 if end of stream is reached
  212. */
  213. public int readShort() throws IOException {
  214. return readShort(this);
  215. }
  216. /**
  217. * Convenience method for read a 4-bytes int in little endian encoding.
  218. *
  219. * @return integer value from the stream, -1 if end of stream is reached
  220. */
  221. public int readInt() throws IOException {
  222. return readInt(this);
  223. }
  224. private int readShort(InputStream stream) throws IOException {
  225. int b0, b1;
  226. if ((b0 = stream.read()) == -1) {
  227. return -1;
  228. }
  229. if ((b1 = stream.read()) == -1) {
  230. return -1;
  231. }
  232. return (b0 & 0xFF) | ((b1 & 0xFF) << 8);
  233. }
  234. private int readInt(InputStream stream) throws IOException {
  235. int b0, b1, b2, b3;
  236. if ((b0 = stream.read()) == -1) {
  237. return -1;
  238. }
  239. if ((b1 = stream.read()) == -1) {
  240. return -1;
  241. }
  242. if ((b2 = stream.read()) == -1) {
  243. return -1;
  244. }
  245. if ((b3 = stream.read()) == -1) {
  246. return -1;
  247. }
  248. return (b0 & 0xFF) | ((b1 & 0xFF) << 8) | ((b2 & 0xFF) << 16) | ((b3 & 0xFF) << 24);
  249. }
  250. public static byte[] decompress(byte[] compressed) throws IOException {
  251. return decompress(compressed, 0, compressed.length);
  252. }
  253. public static byte[] decompress(byte[] compressed, int offset, int length) throws IOException {
  254. try (UnsynchronizedByteArrayOutputStream out = new UnsynchronizedByteArrayOutputStream();
  255. InputStream instream = new UnsynchronizedByteArrayInputStream(compressed, offset, length);
  256. InputStream stream = new RLEDecompressingInputStream(instream)) {
  257. IOUtils.copy(stream, out);
  258. return out.toByteArray();
  259. }
  260. }
  261. }