/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.swf.io; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.zip.InflaterInputStream; import org.apache.commons.io.IOUtils; import org.apache.flex.swf.Header; import org.apache.flex.utils.DAByteArrayOutputStream; /** * Implementation of {@link InputBitStream}. This implementation allows you to * swap the underlying {@code InputStream} source with another one. * <p> * {@code InputBitStream} doesn't buffer the source InputStream internally. It * has one byte buffer for reading SWF bit-values. The buffer is filled 8 bits * at a time. All the bit value methods read from this buffer. * <p> * If a SWF file is compressed, InputBitStream uses an * {@link InflaterInputStream} to decompress the source input. */ public class InputBitStream extends InputStream implements IInputBitStream { // source private InputStream in; // bit value cache private int bitPos = 0; private int bitBuf = 0; private long offset = 0; private long readBoundary = 0; /** * Create an {@code InputBitStream}. * * @param in source {@code InputStream} */ public InputBitStream(InputStream in) { this.in = in; } public InputBitStream(byte[] bytes) { this.in = new ByteArrayInputStream(bytes); } /** * Discard the data left in the bit value cache. Always call this method * after reading bit values and before reading other byte-aligned data. */ @Override public void byteAlign() { bitPos = 0; } @Override public int read() throws IOException { return readByte(); } @Override public byte[] read(int length) { final byte[] data = new byte[length]; try { read(data); } catch (IOException e) { throw new RuntimeException(e); } return data; } @Override public boolean readBit() { return readBits(1) != 0; } /** * Read multiple bits as a padded int. * * @param length number of bits to read * @return The value that was read. */ protected int readBits(int length) { if (length == 0) { return 0; } int bitsLeft = length; int result = 0; if (bitPos == 0) // no value in the buffer - read a byte { bitBuf = readUI8(); bitPos = 8; } while (true) { int shift = bitsLeft - bitPos; if (shift > 0) { // Consume the entire buffer result |= bitBuf << shift; bitsLeft -= bitPos; // Get the next byte from the input stream bitBuf = readUI8(); bitPos = 8; } else { // Consume a portion of the buffer result |= bitBuf >> -shift; bitPos -= bitsLeft; bitBuf &= 0xff >> (8 - bitPos); // mask off the consumed bits return result; } } } // The following are SWF primitive type decoder methods. /** * This helps to mute the {@code IOException}. If there's no data in the * source input stream, calling this method will raise an runtime exception. * <p> * All the methods implementing {@code IInputBitStream} should call this * method when consuming the next byte from the input stream. * * @return next byte in the input stream */ protected int readByte() { byteAlign(); try { if (offset >= readBoundary) { throw new RuntimeException(String.format("About to read over or reading over the boundary: %d -> %d.", offset, readBoundary)); } final int n = in.read(); offset++; if (-1 == n) { throw new RuntimeException("No more data to read."); } else { return n; } } catch (IOException e) { throw new RuntimeException(e); } } @Override public double readDOUBLE() { return Double.longBitsToDouble(readSI64()); } @Override public long readEncodedU32() { long decoded = 0; for (int i = 0; i < 5; i++) { final int nextByte = readByte(); decoded = (decoded << 7) | (nextByte & 0x7f); if ((nextByte & 0x80) == 0) { break; } } return decoded & 0x000000ff << 24 | decoded & 0x0000ff00 << 8 | decoded & 0x00ff0000 >>> 8 | decoded & 0xff000000 >>> 24; } @Override public float readFB(int length) { // Convert bits to x.16 FIXED point number return readSB(length) / (float)0x10000; } @Override public float readFIXED() { // Convert 32-bit int to 16.16 FIXED point number return readSI32() / (float)0x10000; } @Override public float readFIXED8() { // Convert 16-bit int to 8.8 FIXED point number return (short)readSI16() / (float)0x100; } @Override public float readFLOAT() { return Float.intBitsToFloat(readSI32()); } @Override public int readSB(int length) { int bits = readBits(length); return bits << 32 - length >> 32 - length; } @Override public short readSI16() { return (short)(readByte() | readByte() << 8); } @Override public int readSI32() { return readByte() | readByte() << 8 | readByte() << 16 | readByte() << 24; } @Override public long readSI64() { return (readUI32() & 0xFFFFFFFFL) | (readUI32() << 32); } @Override public byte readSI8() { return (byte)readByte(); } @Override public String readString() { final DAByteArrayOutputStream buffer = new DAByteArrayOutputStream(); for (int nextByte = readUI8(); nextByte != 0; nextByte = readUI8()) { buffer.write(nextByte); } try { return new String(buffer.getDirectByteArray(), 0, buffer.size(), "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } finally { IOUtils.closeQuietly(buffer); } } @Override public int readUB(int length) { return readBits(length); } @Override public int readUI16() { return 0xFFFF & readSI16(); } @Override public int readUI24() { return 0xFFFFFF & (readByte() | readByte() << 8 | readByte() << 16); } @Override public long readUI32() { return 0xFFFFFFFFl & readSI32(); } @Override public short readUI8() { return (short)(0xFF & readSI8()); } /** * Set if the InputStream is a compressed SWF stream. */ public void setCompress(Header.Compression compression) throws IOException { switch (compression) { case NONE: break; case ZLIB: this.in = new BufferedInputStream(new InflaterInputStream(in)); break; case LZMA: this.in = new LZMAInputStream(in); break; default: assert false; } } @Override public byte[] readToBoundary() { assert readBoundary > 0 : "Must set boundary before readToBoundary"; // The conversion is safe because a tag length is SI32. final int len = (int)(readBoundary - offset); final byte[] result = this.read(len); return result; } @Override public long getOffset() { return offset; } @Override public void setReadBoundary(long offset) { assert offset > 0 : "Read boundary must > 0."; this.readBoundary = offset; } @Override public long getReadBoundary() { return this.readBoundary; } }