package org.apache.poi.hssf.record.crypto;
import java.io.InputStream;
import java.io.PushbackInputStream;
import org.apache.poi.hssf.record.BOFRecord;
import org.apache.poi.hssf.record.BiffHeaderInput;
import org.apache.poi.hssf.record.FilePassRecord;
import org.apache.poi.hssf.record.InterfaceHdrRecord;
import org.apache.poi.poifs.crypt.ChunkedCipherInputStream;
import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.RecordFormatException;
import org.apache.poi.util.SuppressForbidden;
public final class Biff8DecryptingStream implements BiffHeaderInput, LittleEndianInput {
public static final int RC4_REKEYING_INTERVAL = 1024;
private static final int MAX_RECORD_LENGTH = 100_000;
private ChunkedCipherInputStream ccis;
private final byte[] buffer = new byte[LittleEndianConsts.LONG_SIZE];
private boolean shouldSkipEncryptionOnCurrentRecord;
public Biff8DecryptingStream(InputStream in, int initialOffset, EncryptionInfo info) throws RecordFormatException {
try {
byte[] initialBuf = IOUtils.safelyAllocate(initialOffset, MAX_RECORD_LENGTH);
InputStream stream;
if (initialOffset == 0) {
stream = in;
} else {
stream = new PushbackInputStream(in, initialOffset);
((PushbackInputStream)stream).unread(initialBuf);
}
Decryptor dec = info.getDecryptor();
dec.setChunkSize(RC4_REKEYING_INTERVAL);
ccis = (ChunkedCipherInputStream)dec.getDataStream(stream, Integer.MAX_VALUE, 0);
if (initialOffset > 0) {
ccis.readFully(initialBuf);
}
} catch (Exception e) {
throw new RecordFormatException(e);
}
}
@Override
@SuppressForbidden("just delegating")
public int available() {
return ccis.available();
}
@Override
public int readRecordSID() {
readPlain(buffer, 0, LittleEndianConsts.SHORT_SIZE);
int sid = LittleEndian.getUShort(buffer, 0);
shouldSkipEncryptionOnCurrentRecord = isNeverEncryptedRecord(sid);
return sid;
}
@Override
public int readDataSize() {
readPlain(buffer, 0, LittleEndianConsts.SHORT_SIZE);
int dataSize = LittleEndian.getUShort(buffer, 0);
ccis.setNextRecordSize(dataSize);
return dataSize;
}
@Override
public double readDouble() {
long valueLongBits = readLong();
double result = Double.longBitsToDouble(valueLongBits);
if (Double.isNaN(result)) {
throw new RuntimeException("Did not expect to read NaN");
}
return result;
}
@Override
public void readFully(byte[] buf) {
readFully(buf, 0, buf.length);
}
@Override
public void readFully(byte[] buf, int off, int len) {
if (shouldSkipEncryptionOnCurrentRecord) {
readPlain(buf, off, buf.length);
} else {
ccis.readFully(buf, off, len);
}
}
@Override
public int readUByte() {
return readByte() & 0xFF;
}
@Override
public byte readByte() {
if (shouldSkipEncryptionOnCurrentRecord) {
readPlain(buffer, 0, LittleEndianConsts.BYTE_SIZE);
return buffer[0];
} else {
return ccis.readByte();
}
}
@Override
public int readUShort() {
return readShort() & 0xFFFF;
}
@Override
public short readShort() {
if (shouldSkipEncryptionOnCurrentRecord) {
readPlain(buffer, 0, LittleEndianConsts.SHORT_SIZE);
return LittleEndian.getShort(buffer);
} else {
return ccis.readShort();
}
}
@Override
public int readInt() {
if (shouldSkipEncryptionOnCurrentRecord) {
readPlain(buffer, 0, LittleEndianConsts.INT_SIZE);
return LittleEndian.getInt(buffer);
} else {
return ccis.readInt();
}
}
@Override
public long readLong() {
if (shouldSkipEncryptionOnCurrentRecord) {
readPlain(buffer, 0, LittleEndianConsts.LONG_SIZE);
return LittleEndian.getLong(buffer);
} else {
return ccis.readLong();
}
}
public long getPosition() {
return ccis.getPos();
}
public static boolean isNeverEncryptedRecord(int sid) {
switch (sid) {
case BOFRecord.sid:
case InterfaceHdrRecord.sid:
case FilePassRecord.sid:
return true;
default:
return false;
}
}
@Override
public void readPlain(byte[] b, int off, int len) {
ccis.readPlain(b, off, len);
}
}