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.

RecordInputStream.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  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.hssf.record;
  16. import java.io.IOException;
  17. import java.io.InputStream;
  18. import java.util.Locale;
  19. import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
  20. import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream;
  21. import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  22. import org.apache.poi.poifs.crypt.EncryptionInfo;
  23. import org.apache.poi.util.IOUtils;
  24. import org.apache.poi.util.Internal;
  25. import org.apache.poi.util.LittleEndianConsts;
  26. import org.apache.poi.util.LittleEndianInput;
  27. import org.apache.poi.util.LittleEndianInputStream;
  28. import org.apache.poi.util.RecordFormatException;
  29. /**
  30. * Title: Record Input Stream
  31. *
  32. * Description: Wraps a stream and provides helper methods for the construction of records.
  33. */
  34. public final class RecordInputStream implements LittleEndianInput {
  35. /** Maximum size of a single record (minus the 4 byte header) without a continue*/
  36. public static final short MAX_RECORD_DATA_SIZE = 8224;
  37. private static final int INVALID_SID_VALUE = -1;
  38. /**
  39. * When {@link #_currentDataLength} has this value, it means that the previous BIFF record is
  40. * finished, the next sid has been properly read, but the data size field has not been read yet.
  41. */
  42. private static final int DATA_LEN_NEEDS_TO_BE_READ = -1;
  43. private static final byte[] EMPTY_BYTE_ARRAY = { };
  44. /**
  45. * For use in BiffViewer which may construct {@link Record}s that don't completely
  46. * read all available data. This exception should never be thrown otherwise.
  47. */
  48. public static final class LeftoverDataException extends RuntimeException {
  49. public LeftoverDataException(int sid, int remainingByteCount) {
  50. super("Initialisation of record 0x" + Integer.toHexString(sid).toUpperCase(Locale.ROOT)
  51. + "(" + getRecordName(sid) + ") left " + remainingByteCount
  52. + " bytes remaining still to be read.");
  53. }
  54. private static String getRecordName(int sid) {
  55. Class<? extends Record> recordClass = RecordFactory.getRecordClass(sid);
  56. if(recordClass == null) {
  57. return null;
  58. }
  59. return recordClass.getSimpleName();
  60. }
  61. }
  62. /** Header {@link LittleEndianInput} facet of the wrapped {@link InputStream} */
  63. private final BiffHeaderInput _bhi;
  64. /** Data {@link LittleEndianInput} facet of the wrapped {@link InputStream} */
  65. private final LittleEndianInput _dataInput;
  66. /** the record identifier of the BIFF record currently being read */
  67. private int _currentSid;
  68. /**
  69. * Length of the data section of the current BIFF record (always 4 less than the total record size).
  70. * When uninitialised, this field is set to {@link #DATA_LEN_NEEDS_TO_BE_READ}.
  71. */
  72. private int _currentDataLength;
  73. /**
  74. * The BIFF record identifier for the next record is read when just as the current record
  75. * is finished.
  76. * This field is only really valid during the time that ({@link #_currentDataLength} ==
  77. * {@link #DATA_LEN_NEEDS_TO_BE_READ}). At most other times its value is not really the
  78. * 'sid of the next record'. Wwhile mid-record, this field coincidentally holds the sid
  79. * of the current record.
  80. */
  81. private int _nextSid;
  82. /**
  83. * index within the data section of the current BIFF record
  84. */
  85. private int _currentDataOffset;
  86. /**
  87. * index within the data section when mark() was called
  88. */
  89. private int _markedDataOffset;
  90. private static final class SimpleHeaderInput implements BiffHeaderInput {
  91. private final LittleEndianInput _lei;
  92. private SimpleHeaderInput(LittleEndianInput lei) {
  93. _lei = lei;
  94. }
  95. @Override
  96. public int available() {
  97. return _lei.available();
  98. }
  99. @Override
  100. public int readDataSize() {
  101. return _lei.readUShort();
  102. }
  103. @Override
  104. public int readRecordSID() {
  105. return _lei.readUShort();
  106. }
  107. }
  108. public RecordInputStream(InputStream in) throws RecordFormatException {
  109. this (in, null, 0);
  110. }
  111. public RecordInputStream(InputStream in, EncryptionInfo key, int initialOffset) throws RecordFormatException {
  112. if (key == null) {
  113. _dataInput = (in instanceof LittleEndianInput)
  114. // accessing directly is an optimisation
  115. ? (LittleEndianInput)in
  116. // less optimal, but should work OK just the same. Often occurs in junit tests.
  117. : new LittleEndianInputStream(in);
  118. _bhi = new SimpleHeaderInput(_dataInput);
  119. } else {
  120. Biff8DecryptingStream bds = new Biff8DecryptingStream(in, initialOffset, key);
  121. _dataInput = bds;
  122. _bhi = bds;
  123. }
  124. _nextSid = readNextSid();
  125. }
  126. /**
  127. * @return the number of bytes available in the current BIFF record
  128. * @see #remaining()
  129. */
  130. @Override
  131. public int available() {
  132. return remaining();
  133. }
  134. public int read(byte[] b, int off, int len) {
  135. int limit = Math.min(len, remaining());
  136. if (limit == 0) {
  137. return 0;
  138. }
  139. readFully(b, off,limit);
  140. return limit;
  141. }
  142. public short getSid() {
  143. return (short) _currentSid;
  144. }
  145. /**
  146. * Note - this method is expected to be called only when completed reading the current BIFF
  147. * record.
  148. *
  149. * @return true, if there's another record in the stream
  150. *
  151. * @throws LeftoverDataException if this method is called before reaching the end of the
  152. * current record.
  153. */
  154. public boolean hasNextRecord() throws LeftoverDataException {
  155. if (_currentDataLength != -1 && _currentDataLength != _currentDataOffset) {
  156. throw new LeftoverDataException(_currentSid, remaining());
  157. }
  158. if (_currentDataLength != DATA_LEN_NEEDS_TO_BE_READ) {
  159. _nextSid = readNextSid();
  160. }
  161. return _nextSid != INVALID_SID_VALUE;
  162. }
  163. /**
  164. * @return the sid of the next record or {@link #INVALID_SID_VALUE} if at end of stream
  165. */
  166. private int readNextSid() {
  167. int nAvailable = _bhi.available();
  168. if (nAvailable < EOFRecord.ENCODED_SIZE) {
  169. // some scrap left over, if nAvailable > 0?
  170. // ex45582-22397.xls has one extra byte after the last record
  171. // Excel reads that file OK
  172. return INVALID_SID_VALUE;
  173. }
  174. int result = _bhi.readRecordSID();
  175. if (result == INVALID_SID_VALUE) {
  176. throw new RecordFormatException("Found invalid sid (" + result + ")");
  177. }
  178. _currentDataLength = DATA_LEN_NEEDS_TO_BE_READ;
  179. return result;
  180. }
  181. /** Moves to the next record in the stream.
  182. *
  183. * <i>Note: The auto continue flag is reset to true</i>
  184. */
  185. public void nextRecord() throws RecordFormatException {
  186. if (_nextSid == INVALID_SID_VALUE) {
  187. throw new IllegalStateException("EOF - next record not available");
  188. }
  189. if (_currentDataLength != DATA_LEN_NEEDS_TO_BE_READ) {
  190. throw new IllegalStateException("Cannot call nextRecord() without checking hasNextRecord() first");
  191. }
  192. _currentSid = _nextSid;
  193. _currentDataOffset = 0;
  194. _currentDataLength = _bhi.readDataSize();
  195. if (_currentDataLength > MAX_RECORD_DATA_SIZE) {
  196. throw new RecordFormatException("The content of an excel record cannot exceed "
  197. + MAX_RECORD_DATA_SIZE + " bytes");
  198. }
  199. }
  200. private void checkRecordPosition(int requiredByteCount) {
  201. int nAvailable = remaining();
  202. if (nAvailable >= requiredByteCount) {
  203. // all OK
  204. return;
  205. }
  206. if (nAvailable == 0 && isContinueNext()) {
  207. nextRecord();
  208. return;
  209. }
  210. throw new RecordFormatException("Not enough data (" + nAvailable
  211. + ") to read requested (" + requiredByteCount +") bytes");
  212. }
  213. /**
  214. * Reads an 8 bit, signed value
  215. */
  216. @Override
  217. public byte readByte() {
  218. checkRecordPosition(LittleEndianConsts.BYTE_SIZE);
  219. _currentDataOffset += LittleEndianConsts.BYTE_SIZE;
  220. return _dataInput.readByte();
  221. }
  222. /**
  223. * Reads a 16 bit, signed value
  224. */
  225. @Override
  226. public short readShort() {
  227. checkRecordPosition(LittleEndianConsts.SHORT_SIZE);
  228. _currentDataOffset += LittleEndianConsts.SHORT_SIZE;
  229. return _dataInput.readShort();
  230. }
  231. /**
  232. * Reads a 32 bit, signed value
  233. */
  234. @Override
  235. public int readInt() {
  236. checkRecordPosition(LittleEndianConsts.INT_SIZE);
  237. _currentDataOffset += LittleEndianConsts.INT_SIZE;
  238. return _dataInput.readInt();
  239. }
  240. /**
  241. * Reads a 64 bit, signed value
  242. */
  243. @Override
  244. public long readLong() {
  245. checkRecordPosition(LittleEndianConsts.LONG_SIZE);
  246. _currentDataOffset += LittleEndianConsts.LONG_SIZE;
  247. return _dataInput.readLong();
  248. }
  249. /**
  250. * Reads an 8 bit, unsigned value
  251. */
  252. @Override
  253. public int readUByte() {
  254. return readByte() & 0x00FF;
  255. }
  256. /**
  257. * Reads a 16 bit, unsigned value.
  258. */
  259. @Override
  260. public int readUShort() {
  261. checkRecordPosition(LittleEndianConsts.SHORT_SIZE);
  262. _currentDataOffset += LittleEndianConsts.SHORT_SIZE;
  263. return _dataInput.readUShort();
  264. }
  265. @Override
  266. public double readDouble() {
  267. // YK: Excel doesn't write NaN but instead converts the cell type into {@link CellType#ERROR}.
  268. return Double.longBitsToDouble(readLong());
  269. }
  270. @Override
  271. public void readPlain(byte[] buf, int off, int len) {
  272. readFully(buf, 0, buf.length, true);
  273. }
  274. @Override
  275. public void readFully(byte[] buf) {
  276. readFully(buf, 0, buf.length, false);
  277. }
  278. @Override
  279. public void readFully(byte[] buf, int off, int len) {
  280. readFully(buf, off, len, false);
  281. }
  282. private void readFully(byte[] buf, int off, int len, boolean isPlain) {
  283. int origLen = len;
  284. if (buf == null) {
  285. throw new NullPointerException();
  286. } else if (off < 0 || len < 0 || len > buf.length - off) {
  287. throw new IndexOutOfBoundsException();
  288. }
  289. while (len > 0) {
  290. int nextChunk = Math.min(available(),len);
  291. if (nextChunk == 0) {
  292. if (!hasNextRecord()) {
  293. throw new RecordFormatException("Can't read the remaining "+len+" bytes of the requested "+origLen+" bytes. No further record exists.");
  294. } else {
  295. nextRecord();
  296. nextChunk = Math.min(available(),len);
  297. if (nextChunk <= 0) {
  298. throw new RecordFormatException("Need to have a valid next chunk, but had: " + nextChunk +
  299. " with len: " + len + " and available: " + available());
  300. }
  301. }
  302. }
  303. checkRecordPosition(nextChunk);
  304. if (isPlain) {
  305. _dataInput.readPlain(buf, off, nextChunk);
  306. } else {
  307. _dataInput.readFully(buf, off, nextChunk);
  308. }
  309. _currentDataOffset+=nextChunk;
  310. off += nextChunk;
  311. len -= nextChunk;
  312. }
  313. }
  314. public String readString() {
  315. int requestedLength = readUShort();
  316. byte compressFlag = readByte();
  317. return readStringCommon(requestedLength, compressFlag == 0);
  318. }
  319. /**
  320. * given a byte array of 16-bit unicode characters, compress to 8-bit and
  321. * return a string
  322. *
  323. * { 0x16, 0x00 } -0x16
  324. *
  325. * @param requestedLength the length of the final string
  326. * @return the converted string
  327. * @throws IllegalArgumentException if len is too large (i.e.,
  328. * there is not enough data in string to create a String of that
  329. * length)
  330. */
  331. public String readUnicodeLEString(int requestedLength) {
  332. return readStringCommon(requestedLength, false);
  333. }
  334. public String readCompressedUnicode(int requestedLength) {
  335. return readStringCommon(requestedLength, true);
  336. }
  337. private String readStringCommon(int requestedLength, boolean pIsCompressedEncoding) {
  338. // Sanity check to detect garbage string lengths
  339. if (requestedLength < 0 || requestedLength > 0x100000) { // 16 million chars?
  340. throw new IllegalArgumentException("Bad requested string length (" + requestedLength + ")");
  341. }
  342. char[] buf = new char[requestedLength];
  343. boolean isCompressedEncoding = pIsCompressedEncoding;
  344. int curLen = 0;
  345. while(true) {
  346. int availableChars =isCompressedEncoding ? remaining() : remaining() / LittleEndianConsts.SHORT_SIZE;
  347. if (requestedLength - curLen <= availableChars) {
  348. // enough space in current record, so just read it out
  349. while(curLen < requestedLength) {
  350. char ch;
  351. if (isCompressedEncoding) {
  352. ch = (char)readUByte();
  353. } else {
  354. ch = (char)readShort();
  355. }
  356. buf[curLen] = ch;
  357. curLen++;
  358. }
  359. return new String(buf);
  360. }
  361. // else string has been spilled into next continue record
  362. // so read what's left of the current record
  363. while(availableChars > 0) {
  364. char ch;
  365. if (isCompressedEncoding) {
  366. ch = (char)readUByte();
  367. } else {
  368. ch = (char)readShort();
  369. }
  370. buf[curLen] = ch;
  371. curLen++;
  372. availableChars--;
  373. }
  374. if (!isContinueNext()) {
  375. throw new RecordFormatException("Expected to find a ContinueRecord in order to read remaining "
  376. + (requestedLength-curLen) + " of " + requestedLength + " chars");
  377. }
  378. if(remaining() != 0) {
  379. throw new RecordFormatException("Odd number of bytes(" + remaining() + ") left behind");
  380. }
  381. nextRecord();
  382. // note - the compressed flag may change on the fly
  383. byte compressFlag = readByte();
  384. assert(compressFlag == 0 || compressFlag == 1);
  385. isCompressedEncoding = (compressFlag == 0);
  386. }
  387. }
  388. /** Returns the remaining bytes for the current record.
  389. *
  390. * @return The remaining bytes of the current record.
  391. */
  392. public byte[] readRemainder() {
  393. int size = remaining();
  394. if (size ==0) {
  395. return EMPTY_BYTE_ARRAY;
  396. }
  397. byte[] result = IOUtils.safelyAllocate(size, HSSFWorkbook.getMaxRecordLength());
  398. readFully(result);
  399. return result;
  400. }
  401. /**
  402. * Reads all byte data for the current record, including any that overlaps
  403. * into any following continue records.
  404. *
  405. * @return all byte data for the current record
  406. *
  407. * @deprecated POI 2.0 Best to write a input stream that wraps this one
  408. * where there is special sub record that may overlap continue
  409. * records.
  410. */
  411. @Deprecated
  412. public byte[] readAllContinuedRemainder() {
  413. try (UnsynchronizedByteArrayOutputStream out = new UnsynchronizedByteArrayOutputStream(2 * MAX_RECORD_DATA_SIZE)) {
  414. while (true) {
  415. byte[] b = readRemainder();
  416. out.write(b, 0, b.length);
  417. if (!isContinueNext()) {
  418. break;
  419. }
  420. nextRecord();
  421. }
  422. return out.toByteArray();
  423. } catch (IOException ex) {
  424. throw new RecordFormatException(ex);
  425. }
  426. }
  427. /** The remaining number of bytes in the <i>current</i> record.
  428. *
  429. * @return The number of bytes remaining in the current record
  430. */
  431. public int remaining() {
  432. if (_currentDataLength == DATA_LEN_NEEDS_TO_BE_READ) {
  433. // already read sid of next record. so current one is finished
  434. return 0;
  435. }
  436. return _currentDataLength - _currentDataOffset;
  437. }
  438. /**
  439. *
  440. * @return {@code true} when a {@link ContinueRecord} is next.
  441. */
  442. private boolean isContinueNext() {
  443. if (_currentDataLength != DATA_LEN_NEEDS_TO_BE_READ && _currentDataOffset != _currentDataLength) {
  444. throw new IllegalStateException("Should never be called before end of current record");
  445. }
  446. if (!hasNextRecord()) {
  447. return false;
  448. }
  449. // At what point are records continued?
  450. // - Often from within the char data of long strings (caller is within readStringCommon()).
  451. // - From UnicodeString construction (many different points - call via checkRecordPosition)
  452. // - During TextObjectRecord construction (just before the text, perhaps within the text,
  453. // and before the formatting run data)
  454. return _nextSid == ContinueRecord.sid;
  455. }
  456. /**
  457. @return sid of next record. Can be called after hasNextRecord()
  458. */
  459. public int getNextSid() {
  460. return _nextSid;
  461. }
  462. /**
  463. * Mark the stream position - experimental function
  464. *
  465. * @param readlimit the read ahead limit
  466. *
  467. * @see InputStream#mark(int)
  468. */
  469. @Internal
  470. public void mark(int readlimit) {
  471. ((InputStream)_dataInput).mark(readlimit);
  472. _markedDataOffset = _currentDataOffset;
  473. }
  474. /**
  475. * Resets the stream position to the previously marked position.
  476. * Experimental function - this only works, when nextRecord() wasn't called in the meantime.
  477. *
  478. * @throws IOException if marking is not supported
  479. *
  480. * @see InputStream#reset()
  481. */
  482. @Internal
  483. public void reset() throws IOException {
  484. ((InputStream)_dataInput).reset();
  485. _currentDataOffset = _markedDataOffset;
  486. }
  487. }