142 lines
4.5 KiB
Java
142 lines
4.5 KiB
Java
/* ====================================================================
|
|
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.poi.poifs.crypt;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.security.GeneralSecurityException;
|
|
|
|
import javax.crypto.Cipher;
|
|
|
|
import org.apache.poi.EncryptedDocumentException;
|
|
import org.apache.poi.util.Internal;
|
|
import org.apache.poi.util.LittleEndianInput;
|
|
import org.apache.poi.util.LittleEndianInputStream;
|
|
|
|
@Internal
|
|
public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
|
|
private final int chunkSize;
|
|
private final int chunkMask;
|
|
private final int chunkBits;
|
|
|
|
private int _lastIndex = 0;
|
|
private long _pos = 0;
|
|
private long _size;
|
|
private byte[] _chunk;
|
|
private Cipher _cipher;
|
|
|
|
public ChunkedCipherInputStream(LittleEndianInput stream, long size, int chunkSize)
|
|
throws GeneralSecurityException {
|
|
super((InputStream)stream);
|
|
_size = size;
|
|
this.chunkSize = chunkSize;
|
|
chunkMask = chunkSize-1;
|
|
chunkBits = Integer.bitCount(chunkMask);
|
|
|
|
_cipher = initCipherForBlock(null, 0);
|
|
}
|
|
|
|
protected abstract Cipher initCipherForBlock(Cipher existing, int block)
|
|
throws GeneralSecurityException;
|
|
|
|
public int read() throws IOException {
|
|
byte[] b = new byte[1];
|
|
if (read(b) == 1)
|
|
return b[0];
|
|
return -1;
|
|
}
|
|
|
|
// do not implement! -> recursion
|
|
// public int read(byte[] b) throws IOException;
|
|
|
|
public int read(byte[] b, int off, int len) throws IOException {
|
|
int total = 0;
|
|
|
|
if (available() <= 0) return -1;
|
|
|
|
while (len > 0) {
|
|
if (_chunk == null) {
|
|
try {
|
|
_chunk = nextChunk();
|
|
} catch (GeneralSecurityException e) {
|
|
throw new EncryptedDocumentException(e.getMessage(), e);
|
|
}
|
|
}
|
|
int count = (int)(chunkSize - (_pos & chunkMask));
|
|
int avail = available();
|
|
if (avail == 0) {
|
|
return total;
|
|
}
|
|
count = Math.min(avail, Math.min(count, len));
|
|
System.arraycopy(_chunk, (int)(_pos & chunkMask), b, off, count);
|
|
off += count;
|
|
len -= count;
|
|
_pos += count;
|
|
if ((_pos & chunkMask) == 0)
|
|
_chunk = null;
|
|
total += count;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
@Override
|
|
public long skip(long n) throws IOException {
|
|
long start = _pos;
|
|
long skip = Math.min(available(), n);
|
|
|
|
if ((((_pos + skip) ^ start) & ~chunkMask) != 0)
|
|
_chunk = null;
|
|
_pos += skip;
|
|
return skip;
|
|
}
|
|
|
|
@Override
|
|
public int available() {
|
|
return (int)(_size - _pos);
|
|
}
|
|
|
|
@Override
|
|
public boolean markSupported() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public synchronized void mark(int readlimit) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public synchronized void reset() throws IOException {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
private byte[] nextChunk() throws GeneralSecurityException, IOException {
|
|
int index = (int)(_pos >> chunkBits);
|
|
initCipherForBlock(_cipher, index);
|
|
|
|
if (_lastIndex != index) {
|
|
super.skip((index - _lastIndex) << chunkBits);
|
|
}
|
|
|
|
byte[] block = new byte[Math.min(super.available(), chunkSize)];
|
|
super.read(block, 0, block.length);
|
|
_lastIndex = index + 1;
|
|
return _cipher.doFinal(block);
|
|
}
|
|
}
|