From f2e013f3ce221b327b7bdfc5b89d66546b372ded Mon Sep 17 00:00:00 2001
From: KnyazSh
Date: Fri, 15 Jul 2022 15:52:40 +0300
Subject: [PATCH 1/2] Fix decrypt XSSF workbook
---
main/POIFS/Crypt/ChunkedCipherInputStream.cs | 261 ++++++++++++++----
main/POIFS/Crypt/Cipher.cs | 65 ++---
.../Crypt/CryptoAPI/CryptoAPIDecryptor.cs | 2 +-
main/POIFS/Crypt/CryptoFunctions.cs | 9 +-
.../POIFS/FileSystem/DocumentFactoryHelper.cs | 19 +-
main/POIFS/FileSystem/DocumentInputStream.cs | 108 ++++----
main/POIFS/FileSystem/NDocumentInputStream.cs | 71 ++---
main/Util/ByteArrayInputStream.cs | 77 +++---
main/Util/FileInputStream.cs | 72 +++++
main/Util/FilterInputStream.cs | 31 ++-
main/Util/IOUtils.cs | 113 ++++++--
main/Util/InputStream.cs | 184 ++++++------
main/Util/LittleEndianInputStream.cs | 112 ++++----
main/Util/RLEDecompressingInputStream.cs | 106 +++----
ooxml/POIFS/Crypt/Agile/AgileDecryptor.cs | 8 +-
15 files changed, 766 insertions(+), 472 deletions(-)
create mode 100644 main/Util/FileInputStream.cs
diff --git a/main/POIFS/Crypt/ChunkedCipherInputStream.cs b/main/POIFS/Crypt/ChunkedCipherInputStream.cs
index 8607c89bb..58eff115d 100644
--- a/main/POIFS/Crypt/ChunkedCipherInputStream.cs
+++ b/main/POIFS/Crypt/ChunkedCipherInputStream.cs
@@ -26,132 +26,299 @@ namespace NPOI.POIFS.Crypt
public abstract class ChunkedCipherInputStream : LittleEndianInputStream
{
- private int chunkSize;
- private int chunkMask;
- private int chunkBits;
+ private readonly int _chunkSize;
+ private readonly int _chunkBits;
+
+ private readonly long _size;
+ private readonly byte[] _chunk;
+ private readonly byte[] _plain;
+ private readonly Cipher _cipher;
private int _lastIndex = 0;
private long _pos = 0;
- private long _size;
- private byte[] _chunk;
- private Cipher _cipher;
+ private bool _chunkIsValid;
+
protected IEncryptionInfoBuilder builder;
protected Decryptor decryptor;
- public ChunkedCipherInputStream(ILittleEndianInput stream, long size, int chunkSize
- , IEncryptionInfoBuilder builder, Decryptor decryptor)
- : base((Stream)stream)
- {
-
- _size = size;
- this.chunkSize = chunkSize;
- chunkMask = chunkSize - 1;
- chunkBits = Number.BitCount(chunkMask);
+
+ protected ChunkedCipherInputStream(
+ InputStream stream,
+ long size,
+ int chunkSize,
+ IEncryptionInfoBuilder builder,
+ Decryptor decryptor)
+ : this(stream, size, chunkSize, 0, builder, decryptor)
+ { }
+
+ protected ChunkedCipherInputStream(
+ InputStream stream,
+ long size,
+ int chunkSize,
+ int initialPos,
+ IEncryptionInfoBuilder builder,
+ Decryptor decryptor)
+ : base(stream)
+ {
+ this._size = size;
+ this._pos = initialPos;
+ this._chunkSize = chunkSize;
+
this.builder = builder;
this.decryptor = decryptor;
- _cipher = InitCipherForBlock(null, 0);
+
+ var cs = chunkSize == -1 ? 4096 : chunkSize;
+
+ this._chunk = IOUtils.SafelyAllocate(cs, CryptoFunctions.MAX_RECORD_LENGTH);
+ this._plain = IOUtils.SafelyAllocate(cs, CryptoFunctions.MAX_RECORD_LENGTH);
+ this._chunkBits = Number.BitCount(_chunk.Length - 1);
+ this._lastIndex = (int)(_pos >> _chunkBits);
+ this._cipher = InitCipherForBlock(null, _lastIndex);
+ }
+
+ public Cipher InitCipherForBlock(int block)
+ {
+ if (_chunkSize != -1)
+ {
+ throw new SecurityException("the cipher block can only be set for streaming encryption, e.g. CryptoAPI...");
+ }
+
+ _chunkIsValid = false;
+ return InitCipherForBlock(_cipher, block);
}
protected abstract Cipher InitCipherForBlock(Cipher existing, int block);
- public int Read()
+ public override int Read()
{
byte[] b = new byte[1];
if (Read(b) == 1)
- return b[0];
+ return b[0] & 0xFF;
return -1;
}
// do not implement! -> recursion
// public int Read(byte[] b) throws IOException;
- public new int Read(byte[] b, int off, int len)
+ public override int Read(byte[] b, int off, int len)
+ {
+ return Read(b, off, len, false);
+ }
+
+ public int Read(byte[] b, int off, int len, bool readPlain)
{
int total = 0;
- if (Available() <= 0) return -1;
+ if (RemainingBytes() <= 0) return 0;
+
+ int chunkMask = GetChunkMask();
while (len > 0)
{
- if (_chunk == null)
+ if (!_chunkIsValid)
{
try
{
- _chunk = NextChunk();
+ NextChunk();
+ _chunkIsValid = true;
}
catch (SecurityException e)
{
throw new EncryptedDocumentException(e.Message, e);
}
}
- int count = (int) (chunkSize - (_pos & chunkMask));
- int avail = Available();
+ int count = (int)(_chunk.Length - (_pos & chunkMask));
+ int avail = RemainingBytes();
if (avail == 0)
{
return total;
}
count = Math.Min(avail, Math.Min(count, len));
- Array.Copy(_chunk, (int) (_pos & chunkMask), b, off, count);
+
+ Array.Copy(readPlain ? _plain : _chunk, (int)(_pos & chunkMask), b, off, count);
+
off += count;
len -= count;
_pos += count;
if ((_pos & chunkMask) == 0)
- _chunk = null;
+ {
+ _chunkIsValid = false;
+ }
total += count;
}
return total;
}
-
- public new long Skip(long n)
+ public override long Skip(long n)
{
long start = _pos;
- long skip = Math.Min(Available(), n);
+ long skip = Math.Min(RemainingBytes(), n);
+
+ if ((((_pos + skip) ^ start) & ~GetChunkMask()) != 0)
+ {
+ _chunkIsValid = false;
+ }
- if ((((_pos + skip) ^ start) & ~chunkMask) != 0)
- _chunk = null;
_pos += skip;
return skip;
}
-
- public new int Available()
+ public override int Available()
{
- return (int) (_size - _pos);
+ return RemainingBytes();
}
+ ///
+ /// Helper method for forbidden available call - we know the size beforehand, so it's ok ...
+ ///
+ /// the remaining byte until EOF
+ private int RemainingBytes()
+ {
+ return (int)(_size - _pos);
+ }
- public bool MarkSupported()
+ public override bool MarkSupported()
{
return false;
}
+ public override void Mark(int readlimit)
+ {
+ throw new InvalidOperationException();
+ }
- public void Mark(int Readlimit)
+ public override void Reset()
{
throw new InvalidOperationException();
}
+ protected int GetChunkMask()
+ {
+ return _chunk.Length - 1;
+ }
- public void Reset()
+ private void NextChunk()
{
- throw new InvalidOperationException();
+ if (_chunkSize != 0)
+ {
+ int index = (int)(_pos >> _chunkBits);
+ InitCipherForBlock(_cipher, index);
+
+ if (_lastIndex != index)
+ {
+ long skipN = (index - _lastIndex) << _chunkBits;
+ if (base.Skip(skipN) < skipN)
+ {
+ throw new EndOfStreamException("buffer underrun");
+ }
+ }
+
+ _lastIndex = index + 1;
+ }
+
+ int todo = (int)Math.Min(_size, _chunk.Length);
+ int readBytes, totalBytes = 0;
+ do
+ {
+ readBytes = base.Read(_plain, totalBytes, todo - totalBytes);
+ totalBytes += Math.Max(0, readBytes);
+ } while (readBytes != 0 && totalBytes < todo);
+
+ if (readBytes == 0 && _pos + totalBytes < _size && _size < int.MaxValue)
+ {
+ throw new EndOfStreamException("buffer underrun");
+ }
+
+ Array.Copy(_plain, 0, _chunk, 0, totalBytes);
+
+ InvokeCipher(totalBytes, totalBytes == _chunkSize);
}
- private byte[] NextChunk()
+ ///
+ /// Helper function for overriding the cipher invocation, i.e. XOR doesn't use a cipher and uses its own implementation
+ ///
+ /// The total bytes.
+ /// The do final.
+ ///
+ protected int InvokeCipher(int totalBytes, bool doFinal)
{
- int index = (int) (_pos >> chunkBits);
- InitCipherForBlock(_cipher, index);
+ if (doFinal)
+ {
+ return _cipher.DoFinal(_chunk, 0, totalBytes, _chunk);
+ }
+ else
+ {
+ return _cipher.Update(_chunk, 0, totalBytes, _chunk);
+ }
+ }
- if (_lastIndex != index)
+ ///
+ /// Used when BIFF header fields (sid, size) are being read. The internal instance must step even when unencrypted bytes are read
+ ///
+ /// The buffet.
+ /// The offset.
+ /// The length.
+ /// buffer underrun
+ ///
+ public void ReadPlain(byte[] b, int off, int len)
+ {
+ if (len <= 0)
{
- base.Skip((index - _lastIndex) << chunkBits);
+ return;
}
- byte[] block = new byte[Math.Min(base.Available(), chunkSize)];
- base.Read(block, 0, block.Length);
- _lastIndex = index + 1;
- return _cipher.DoFinal(block);
+ try
+ {
+ int readBytes, total = 0;
+ do
+ {
+ readBytes = Read(b, off, len, true);
+ total += Math.Max(0, readBytes);
+ } while (readBytes > 0 && total < len);
+
+ if (total < len)
+ {
+ throw new EndOfStreamException("buffer underrun");
+ }
+ }
+ catch (IOException e)
+ {
+ // need to wrap checked exception, because of LittleEndianInput interface :(
+ throw new RuntimeException(e);
+ }
+ }
+
+ ///
+ /// Some ciphers (actually just XOR) are based on the record size, which needs to be set before decryption
+ ///
+ /// The size of the next record.
+ public void SetNextRecordSize(int recordSize) { }
+
+ ///
+ /// Gets the chunk bytes.
+ ///
+ /// the chunk bytes
+ protected byte[] GetChunk()
+ {
+ return _chunk;
+ }
+
+ ///
+ /// Gets the plain bytes.
+ ///
+ /// the plain bytes
+ protected byte[] GetPlain()
+ {
+ return _plain;
+ }
+
+ ///
+ /// Gets the position.
+ ///
+ /// the absolute position in the stream
+ public long GetPos()
+ {
+ return _pos;
}
}
diff --git a/main/POIFS/Crypt/Cipher.cs b/main/POIFS/Crypt/Cipher.cs
index 2a92dd3ad..73ea87892 100644
--- a/main/POIFS/Crypt/Cipher.cs
+++ b/main/POIFS/Crypt/Cipher.cs
@@ -10,44 +10,32 @@ namespace NPOI.POIFS.Crypt
{
public class Cipher
{
- public static int DECRYPT_MODE = 2;
- public static int ENCRYPT_MODE = 1;
- public static int WRAP_MODE = 3;
- public static int UNWRAP_MODE = 4;
+ public const int DECRYPT_MODE = 2;
+ public const int ENCRYPT_MODE = 1;
+ public const int WRAP_MODE = 3;
+ public const int UNWRAP_MODE = 4;
- public static int PUBLIC_KEY = 1;
- public static int PRIVATE_KEY = 2;
- public static int SECRET_KEY = 3;
+ public const int PUBLIC_KEY = 1;
+ public const int PRIVATE_KEY = 2;
+ public const int SECRET_KEY = 3;
protected IBufferedCipher cipherImpl;
- public byte[] DoFinal(byte[] block)
- {
- return cipherImpl.DoFinal(block);
- }
- //public byte[] DoFinal(byte[] block, int v, int posInChunk)
- //{
- // return cipherImpl.DoFinal(block, v, posInChunk);
- //}
-
- public int DoFinal(byte[] input, int inputOffset, int inputLen, byte[] output)
- {
- return cipherImpl.DoFinal(input, inputOffset, inputLen, output, 0);
- }
-
public static Cipher GetInstance(string transformation)
{
Cipher cihpher = new Cipher()
{
cipherImpl = CipherUtilities.GetCipher(transformation)
};
- return cihpher;
+ return cihpher;
}
+
public static Cipher GetInstance(string transformation, string provider)
{
return GetInstance(transformation);
}
+
public void Init(int cipherMode, IKey key, AlgorithmParameterSpec aps)
{
ICipherParameters cp;
@@ -103,34 +91,47 @@ public static int GetMaxAllowedKeyLength(string jceId)
}
}
-
-
- public void Update(byte[] input, int inputOffset, int inputLen, byte[] output)
+ public int Update(byte[] input, int inputOffset, int inputLen, byte[] output)
{
if ((input == null) || (inputOffset < 0) || (inputLen > input.Length - inputOffset) || (inputLen < 0))
{
throw new ArgumentException("Bad arguments");
}
- cipherImpl.ProcessBytes(input, inputOffset, inputLen, output, 0);
+ return cipherImpl.ProcessBytes(input, inputOffset, inputLen, output, 0);
}
- public void Update(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)
+ public int Update(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)
{
if ((input == null) || (inputOffset < 0) || (inputLen > input.Length - inputOffset) || (inputLen < 0) || (outputOffset < 0))
{
throw new ArgumentException("Bad arguments");
}
- cipherImpl.ProcessBytes(input, inputOffset, inputLen, output, outputOffset);
+ return cipherImpl.ProcessBytes(input, inputOffset, inputLen, output, outputOffset);
}
- public byte[] DoFinal()
+ public byte[] Update(byte[] ibuffer, int inOff, int length)
{
- return cipherImpl.DoFinal();
+ return cipherImpl.ProcessBytes(ibuffer, inOff, length);
}
- public byte[] Update(byte[] ibuffer, int inOff, int length)
+ public byte[] DoFinal(byte[] block)
{
- return cipherImpl.ProcessBytes(ibuffer, inOff, length);
+ return cipherImpl.DoFinal(block);
+ }
+
+ //public byte[] DoFinal(byte[] block, int v, int posInChunk)
+ //{
+ // return cipherImpl.DoFinal(block, v, posInChunk);
+ //}
+
+ public int DoFinal(byte[] input, int inputOffset, int inputLen, byte[] output)
+ {
+ return cipherImpl.DoFinal(input, inputOffset, inputLen, output, 0);
+ }
+
+ public byte[] DoFinal()
+ {
+ return cipherImpl.DoFinal();
}
}
}
diff --git a/main/POIFS/Crypt/CryptoAPI/CryptoAPIDecryptor.cs b/main/POIFS/Crypt/CryptoAPI/CryptoAPIDecryptor.cs
index 5ccb182f4..b3d9261fe 100644
--- a/main/POIFS/Crypt/CryptoAPI/CryptoAPIDecryptor.cs
+++ b/main/POIFS/Crypt/CryptoAPI/CryptoAPIDecryptor.cs
@@ -61,7 +61,7 @@ private class SeekableMemoryStream : MemoryStream {
public override int Read(byte[] b, int off, int len) {
int readLen = base.Read(b, off, len);
- if (readLen == -1) return -1;
+ if (readLen == -1) return 0;
try {
cipher.Update(b, off, readLen, b, off);
} catch (Exception e) {
diff --git a/main/POIFS/Crypt/CryptoFunctions.cs b/main/POIFS/Crypt/CryptoFunctions.cs
index 605e267d8..82327ede8 100644
--- a/main/POIFS/Crypt/CryptoFunctions.cs
+++ b/main/POIFS/Crypt/CryptoFunctions.cs
@@ -26,6 +26,11 @@ namespace NPOI.POIFS.Crypt
* Helper functions used for standard and agile encryption
*/
public class CryptoFunctions {
+
+ //arbitrarily selected; may need to increase
+ private const int DEFAULT_MAX_RECORD_LENGTH = 100_000;
+ public const int MAX_RECORD_LENGTH = DEFAULT_MAX_RECORD_LENGTH;
+
/**
* 2.3.4.7 ECMA-376 Document Encryption Key Generation (Standard Encryption)
* 2.3.4.11 Encryption Key Generation (Agile Encryption)
@@ -204,9 +209,9 @@ public class CryptoFunctions {
try {
// Ensure the JCE policies files allow for this sized key
- if (Cipher.GetMaxAllowedKeyLength(cipherAlgorithm.jceId) < keySizeInBytes * 8) {
+ /*if (Cipher.GetMaxAllowedKeyLength(cipherAlgorithm.jceId) < keySizeInBytes * 8) {
throw new EncryptedDocumentException("Export Restrictions in place - please install JCE Unlimited Strength Jurisdiction Policy files");
- }
+ }*/
Cipher cipher;
if (cipherAlgorithm == CipherAlgorithm.rc4) {
diff --git a/main/POIFS/FileSystem/DocumentFactoryHelper.cs b/main/POIFS/FileSystem/DocumentFactoryHelper.cs
index a23d2f3fe..6fe8ab438 100644
--- a/main/POIFS/FileSystem/DocumentFactoryHelper.cs
+++ b/main/POIFS/FileSystem/DocumentFactoryHelper.cs
@@ -26,7 +26,7 @@ namespace NPOI.POIFS.FileSystem
///
/// A small base class for the various factories, e.g. WorkbookFactory, SlideShowFactory to combine common code here.
///
- public class DocumentFactoryHelper
+ public static class DocumentFactoryHelper
{
///
/// Wrap the OLE2 data in the NPOIFSFileSystem into a decrypted stream by using the given password.
@@ -54,7 +54,7 @@ public static InputStream GetDecryptedStream(NPOIFSFileSystem fs, String passwor
}
if (passwordCorrect)
{
- return new FilterInputStream1(d.GetDataStream(fs.Root), fs);
+ return d.GetDataStream(fs.Root);
}
else
{
@@ -70,26 +70,11 @@ public static InputStream GetDecryptedStream(NPOIFSFileSystem fs, String passwor
}
}
- private class FilterInputStream1 : FilterInputStream
- {
- NPOIFSFileSystem fs;
- public FilterInputStream1(InputStream input, NPOIFSFileSystem fs)
- : base(input)
- {
- this.fs = fs;
- }
- public override void Close()
- {
- fs.Close();
- base.Close();
- }
- }
///
/// Checks that the supplied InputStream (which MUST support mark and reset, or be a PushbackInputStream) has a OOXML (zip) header at the start of it.
/// If your InputStream does not support mark / reset, then wrap it in a PushBackInputStream, then be sure to always use that, and not the original!
///
/// An InputStream which supports either mark/reset, or is a PushbackInputStream
- ///
public static bool HasOOXMLHeader(Stream inp)
{
// We want to peek at the first 4 bytes
diff --git a/main/POIFS/FileSystem/DocumentInputStream.cs b/main/POIFS/FileSystem/DocumentInputStream.cs
index 03ac35325..25c9e602d 100644
--- a/main/POIFS/FileSystem/DocumentInputStream.cs
+++ b/main/POIFS/FileSystem/DocumentInputStream.cs
@@ -20,34 +20,37 @@ namespace NPOI.POIFS.FileSystem
using System.IO;
using NPOI.Util;
- /**
- * This class provides methods to read a DocumentEntry managed by a
- * {@link POIFSFileSystem} or {@link NPOIFSFileSystem} instance.
- * It Creates the appropriate one, and delegates, allowing us to
- * work transparently with the two.
- */
+ ///
+ /// This class provides methods to read a DocumentEntry managed by a
+ /// or instance.
+ /// It Creates the appropriate one, and delegates, allowing us to
+ /// work transparently with the two.
+ ///
+ ///
public class DocumentInputStream : ByteArrayInputStream, ILittleEndianInput
{
/** returned by read operations if we're at end of document */
- protected static int EOF = -1;
+ protected const int EOF = 0;
- protected static int SIZE_SHORT = 2;
- protected static int SIZE_INT = 4;
- protected static int SIZE_LONG = 8;
+ protected const int SIZE_SHORT = 2;
+ protected const int SIZE_INT = 4;
+ protected const int SIZE_LONG = 8;
- private DocumentInputStream delegate1;
+ private readonly DocumentInputStream delegate1;
- /** For use by downstream implementations */
+ ///
+ /// For use by downstream implementations
+ ///
protected DocumentInputStream() { }
- /**
- * Create an InputStream from the specified DocumentEntry
- *
- * @param document the DocumentEntry to be read
- *
- * @exception IOException if the DocumentEntry cannot be opened (like, maybe it has
- * been deleted?)
- */
+ ///
+ /// Initializes a new instance of the class.
+ /// Create an from the specified DocumentEntry
+ ///
+ /// the DocumentEntry to be read
+ ///
+ /// IOException if the DocumentEntry cannot be opened (like, maybe it has been deleted?)
+ ///
public DocumentInputStream(DocumentEntry document)
{
if (!(document is DocumentNode))
@@ -74,6 +77,27 @@ public DocumentInputStream(DocumentEntry document)
throw new IOException("No FileSystem bound on the parent, can't read contents");
}
}
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Create an from the specified DocumentEntry
+ ///
+ /// the DocumentEntry to be read
+ public DocumentInputStream(OPOIFSDocument document)
+ {
+ delegate1 = new ODocumentInputStream(document);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Create an from the specified DocumentEntry
+ ///
+ /// the DocumentEntry to be read
+ public DocumentInputStream(NPOIFSDocument document)
+ {
+ delegate1 = new NDocumentInputStream(document);
+ }
+
public override long Seek(long offset, SeekOrigin origin)
{
return delegate1.Seek(offset, origin);
@@ -97,25 +121,6 @@ public override long Position
delegate1.Position = value;
}
}
- /**
- * Create an InputStream from the specified Document
- *
- * @param document the Document to be read
- */
- public DocumentInputStream(OPOIFSDocument document)
- {
- delegate1 = new ODocumentInputStream(document);
- }
-
- /**
- * Create an InputStream from the specified Document
- *
- * @param document the Document to be read
- */
- public DocumentInputStream(NPOIFSDocument document)
- {
- delegate1 = new NDocumentInputStream(document);
- }
public override int Available()
{
@@ -127,16 +132,15 @@ public override void Close()
delegate1.Close();
}
- public override void Mark(int ignoredReadlimit)
+ public override void Mark(int readlimit)
{
- delegate1.Mark(ignoredReadlimit);
+ delegate1.Mark(readlimit);
}
- /**
- * Tests if this input stream supports the mark and reset methods.
- *
- * @return true
always
- */
+ ///
+ /// Tests if this input stream supports the mark and reset methods.
+ ///
+ /// true always
public override bool MarkSupported()
{
return true;
@@ -157,17 +161,17 @@ public override int Read(byte[] b, int off, int len)
return delegate1.Read(b, off, len);
}
- /**
- * Repositions this stream to the position at the time the mark() method was
- * last called on this input stream. If mark() has not been called this
- * method repositions the stream to its beginning.
- */
+ ///
+ /// Repositions this stream to the position at the time the mark() method was
+ /// last called on this input stream. If mark() has not been called this
+ /// method repositions the stream to its beginning.
+ ///
public override void Reset()
{
delegate1.Reset();
}
- public virtual long Skip(long n)
+ public override long Skip(long n)
{
return delegate1.Skip(n);
}
diff --git a/main/POIFS/FileSystem/NDocumentInputStream.cs b/main/POIFS/FileSystem/NDocumentInputStream.cs
index 6e19492e0..407c6375f 100644
--- a/main/POIFS/FileSystem/NDocumentInputStream.cs
+++ b/main/POIFS/FileSystem/NDocumentInputStream.cs
@@ -15,23 +15,18 @@
limitations under the License.
==================================================================== */
-
-
using NPOI.POIFS.Properties;
using NPOI.Util;
using System.IO;
using System;
using System.Collections.Generic;
-
namespace NPOI.POIFS.FileSystem
{
-
-
- /**
- * This class provides methods to read a DocumentEntry managed by a
- * {@link NPOIFSFileSystem} instance.
- */
+ ///
+ /// This class provides methods to read a DocumentEntry managed by a instance
+ ///
+ ///
public class NDocumentInputStream : DocumentInputStream//DocumentReader
{
/** current offset into the Document */
@@ -45,27 +40,23 @@ public class NDocumentInputStream : DocumentInputStream//DocumentReader
private int _marked_offset_count;
/** the Document's size */
- private int _document_size;
+ private readonly int _document_size;
/** have we been closed? */
private bool _closed;
/** the actual Document */
- private NPOIFSDocument _document;
+ private readonly NPOIFSDocument _document;
private IEnumerator _data;
private ByteBuffer _buffer;
-
-
- /**
- * Create an InputStream from the specified DocumentEntry
- *
- * @param document the DocumentEntry to be read
- *
- * @exception IOException if the DocumentEntry cannot be opened (like, maybe it has
- * been deleted?)
- */
+ ///
+ /// Initializes a new instance of the class
+ /// Create an InputStream from the specified DocumentEntry
+ ///
+ /// the DocumentEntry to be read
+ /// IOException if the DocumentEntry cannot be opened (like, maybe it has been deleted?)
public NDocumentInputStream(DocumentEntry document)
{
if (!(document is DocumentNode))
@@ -88,11 +79,10 @@ public NDocumentInputStream(DocumentEntry document)
_data = _document.GetBlockIterator();
}
- /**
- * Create an InputStream from the specified Document
- *
- * @param document the Document to be read
- */
+ ///
+ /// Initializes a new instance of the class from the specified Document
+ ///
+ /// the Document to be read
public NDocumentInputStream(NPOIFSDocument document)
{
_current_offset = 0;
@@ -134,7 +124,7 @@ public override int Read()
DieIfClosed();
if (atEOD())
{
- return EOF;
+ return -1;
}
byte[] b = new byte[1];
int result = Read(b, 0, 1);
@@ -174,12 +164,11 @@ public override int Read(byte[] b, int off, int len)
return limit;
}
- /**
- * Repositions this stream to the position at the time the mark() method was
- * last called on this input stream. If mark() has not been called this
- * method repositions the stream to its beginning.
- */
-
+ ///
+ /// Repositions this stream to the position at the time the mark() method was
+ /// last called on this input stream. If mark() has not been called this
+ /// method repositions the stream to its beginning.
+ ///
public override void Reset()
{
// Special case for Reset to the start
@@ -217,7 +206,7 @@ public override void Reset()
// (It should be positioned already at the start of the block,
// we need to Move further inside the block)
int skipBy = _marked_offset - _current_offset;
- _buffer.Position = (_buffer.Position + skipBy);
+ _buffer.Position = _buffer.Position + skipBy;
}
// All done
@@ -278,7 +267,6 @@ private void CheckAvaliable(int requestedSize)
}
}
-
public override void ReadFully(byte[] buf, int off, int len)
{
CheckAvaliable(len);
@@ -303,19 +291,21 @@ public override void ReadFully(byte[] buf, int off, int len)
}
}
-
public override int ReadByte()
{
+ if (atEOD())
+ {
+ return -1;
+ }
+
return ReadUByte();
}
-
public override double ReadDouble()
{
return BitConverter.Int64BitsToDouble(ReadLong());
}
-
public override long ReadLong()
{
CheckAvaliable(SIZE_LONG);
@@ -329,7 +319,6 @@ public override void ReadFully(byte[] buf)
ReadFully(buf, 0, buf.Length);
}
-
public override short ReadShort()
{
CheckAvaliable(SIZE_SHORT);
@@ -338,7 +327,6 @@ public override short ReadShort()
return LittleEndian.GetShort(data);
}
-
public override int ReadInt()
{
CheckAvaliable(SIZE_INT);
@@ -347,7 +335,6 @@ public override int ReadInt()
return LittleEndian.GetInt(data);
}
-
public override int ReadUShort()
{
CheckAvaliable(SIZE_SHORT);
@@ -356,7 +343,6 @@ public override int ReadUShort()
return LittleEndian.GetUShort(data);
}
-
public override int ReadUByte()
{
CheckAvaliable(1);
@@ -400,7 +386,6 @@ public override long Seek(long offset, SeekOrigin origin)
if (offset == 0)
{
Reset();
-
}
else
{
diff --git a/main/Util/ByteArrayInputStream.cs b/main/Util/ByteArrayInputStream.cs
index 0b4a015f3..9e9619013 100644
--- a/main/Util/ByteArrayInputStream.cs
+++ b/main/Util/ByteArrayInputStream.cs
@@ -5,20 +5,24 @@ namespace NPOI.Util
{
public class ByteArrayInputStream : InputStream
{
-
- public ByteArrayInputStream()
- {
- }
protected byte[] buf;
protected int pos;
protected int mark = 0;
protected int count;
+
+ private readonly object _lockObject = new object();
+
+ public ByteArrayInputStream()
+ {
+ }
+
public ByteArrayInputStream(byte[] buf)
{
this.buf = buf;
this.pos = 0;
this.count = buf.Length;
}
+
public ByteArrayInputStream(byte[] buf, int offset, int length)
{
this.buf = buf;
@@ -26,17 +30,18 @@ public ByteArrayInputStream(byte[] buf, int offset, int length)
this.count = Math.Min(offset + length, buf.Length);
this.mark = offset;
}
-
+
public override int Read()
{
- lock (this)
+ lock (_lockObject)
{
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
}
+
public override int Read(byte[] b, int off, int len)
{
- lock (this)
+ lock (_lockObject)
{
if (b == null)
{
@@ -49,7 +54,7 @@ public override int Read(byte[] b, int off, int len)
if (pos >= count)
{
- return -1;
+ return 0;
}
int avail = count - pos;
@@ -57,57 +62,53 @@ public override int Read(byte[] b, int off, int len)
{
len = avail;
}
+
if (len <= 0)
{
return 0;
}
+
Array.Copy(buf, pos, b, off, len);
pos += len;
return len;
}
-
}
+
public override int Available()
{
return count - pos;
}
+
public override bool MarkSupported()
{
return true;
}
- public override void Mark(int readAheadLimit)
+
+ public override void Mark(int readlimit)
{
mark = pos;
}
+
public override void Reset()
{
pos = mark;
}
- public override void Close()
- {
- }
+ public override void Close() { }
public override bool CanRead
{
- get
- {
- return true;
- }
+ get { return true; }
}
+
public override bool CanWrite
{
- get
- {
- return false;
- }
+ get { return false; }
}
+
public override bool CanSeek
{
- get
- {
- return true;
- }
+ get { return true; }
}
public override void Flush()
@@ -117,22 +118,13 @@ public override void Flush()
public override long Length
{
- get
- {
- return this.count;
- }
+ get { return this.count; }
}
public override long Position
{
- get
- {
- return this.pos;
- }
- set
- {
- this.pos = (int)value;
- }
+ get { return this.pos; }
+ set { this.pos = (int)value; }
}
public override long Seek(long offset, SeekOrigin origin)
@@ -145,7 +137,7 @@ public override long Seek(long offset, SeekOrigin origin)
case SeekOrigin.Begin:
if (0L > offset)
{
- throw new ArgumentOutOfRangeException("offset", "offset must be positive");
+ throw new ArgumentOutOfRangeException(nameof(offset), "offset must be positive");
}
this.Position = offset < this.Length ? offset : this.Length;
break;
@@ -159,7 +151,7 @@ public override long Seek(long offset, SeekOrigin origin)
break;
default:
- throw new ArgumentException("incorrect SeekOrigin", "origin");
+ throw new ArgumentException("incorrect SeekOrigin", nameof(origin));
}
return Position;
}
@@ -168,12 +160,5 @@ public override void SetLength(long value)
{
throw new NotImplementedException();
}
-
- public override void Write(byte[] buffer, int offset, int count)
- {
- throw new NotImplementedException();
- }
-
-
}
}
diff --git a/main/Util/FileInputStream.cs b/main/Util/FileInputStream.cs
new file mode 100644
index 000000000..1febec6b2
--- /dev/null
+++ b/main/Util/FileInputStream.cs
@@ -0,0 +1,72 @@
+using System;
+using System.IO;
+
+namespace NPOI.Util
+{
+ public class FileInputStream : InputStream
+ {
+ readonly Stream inner;
+
+ public FileInputStream(Stream fs)
+ {
+ this.inner = fs;
+ }
+
+ public override bool CanRead
+ {
+ get { return inner.CanRead; }
+ }
+
+ public override bool CanSeek
+ {
+ get { return false; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return false; }
+ }
+
+ public override long Length
+ {
+ get { return inner.Length; }
+ }
+
+ public override long Position
+ {
+ get { return inner.Position;}
+ set { inner.Position = value;}
+ }
+
+ public override void Flush()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override int Read()
+ {
+ return inner.ReadByte();
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Close()
+ {
+ if (inner != null)
+ inner.Close();
+ }
+ }
+}
diff --git a/main/Util/FilterInputStream.cs b/main/Util/FilterInputStream.cs
index c77298915..d66de8f34 100644
--- a/main/Util/FilterInputStream.cs
+++ b/main/Util/FilterInputStream.cs
@@ -12,62 +12,75 @@ public class FilterInputStream : InputStream
public override bool CanRead
{
- get { throw new NotImplementedException(); }
+ get { return input.CanRead; }
}
public override bool CanSeek
{
- get { throw new NotImplementedException(); }
+ get { return input.CanSeek; }
}
public override bool CanWrite
{
- get { throw new NotImplementedException(); }
+ get { return false; }
}
public override long Length
{
- get { throw new NotImplementedException(); }
+ get { return input.Length; }
}
- public override long Position { get; set; }
+ public override long Position
+ {
+ get { return input.Position; }
+ set { input.Position = value; }
+ }
protected FilterInputStream(InputStream input)
{
this.input = input;
}
+
public override int Read()
{
return input.Read();
}
+
public override int Read(byte[] b)
{
return Read(b, 0, b.Length);
}
+
public override int Read(byte[] b, int off, int len)
{
return input.Read(b, off, len);
}
+
public override long Skip(long n)
{
return input.Skip(n);
}
- public int available()
+
+ public override int Available()
{
return input.Available();
}
+
public override void Close()
{
input.Close();
}
+
public override void Mark(int readlimit)
{
input.Mark(readlimit);
}
+
public override void Reset()
{
input.Reset();
}
+
public override bool MarkSupported()
{
return input.MarkSupported();
@@ -75,17 +88,17 @@ public override bool MarkSupported()
public override void Flush()
{
- throw new NotImplementedException();
+ input.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
- throw new NotImplementedException();
+ return input.Seek(offset, origin);
}
public override void SetLength(long value)
{
- throw new NotImplementedException();
+ input.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
diff --git a/main/Util/IOUtils.cs b/main/Util/IOUtils.cs
index b49b89449..1b4b00a5d 100644
--- a/main/Util/IOUtils.cs
+++ b/main/Util/IOUtils.cs
@@ -34,13 +34,32 @@ namespace NPOI.Util
public class IOUtils
{
private static POILogger logger = POILogFactory.GetLogger(typeof(IOUtils));
- /**
- * Peeks at the first 8 bytes of the stream. Returns those bytes, but
- * with the stream unaffected. Requires a stream that supports mark/reset,
- * or a PushbackInputStream. If the stream has >0 but <8 bytes,
- * remaining bytes will be zero.
- * @throws EmptyFileException if the stream is empty
- */
+
+ ///
+ /// The current set global allocation limit override,
+ /// -1 means limits are applied per record type.
+ /// The current set global allocation limit override,
+ ///
+ private static int BYTE_ARRAY_MAX_OVERRIDE = -1;
+
+ ///
+ /// The max init size of ByteArrayOutputStream.
+ /// -1 means init size of ByteArrayOutputStream could be up to Integer.MAX_VALUE
+ ///
+ private static int MAX_BYTE_ARRAY_INIT_SIZE = -1;
+
+ ///
+ /// The default size of the bytearray used while reading input streams. This is meant to be pretty small.
+ ///
+ private static readonly int DEFAULT_BUFFER_SIZE = 4096;
+
+ ///
+ /// Peeks at the first 8 bytes of the stream. Returns those bytes, but
+ /// with the stream unaffected. Requires a stream that supports mark/reset,
+ /// or a PushbackInputStream. If the stream has >0 but <8 bytes,
+ /// remaining bytes will be zero.
+ /// @throws EmptyFileException if the stream is empty
+ ///
public static byte[] PeekFirst8Bytes(InputStream stream)
{
// We want to peek at the first 8 bytes
@@ -63,6 +82,7 @@ public static byte[] PeekFirst8Bytes(InputStream stream)
return header;
}
+
public static byte[] PeekFirst8Bytes(Stream stream)
{
// We want to peek at the first 8 bytes
@@ -88,6 +108,7 @@ public static byte[] PeekFirst8Bytes(Stream stream)
return header;
}
+
///
/// Reads all the data from the input stream, and returns
/// the bytes Read.
@@ -170,7 +191,6 @@ public static int ReadFully(Stream stream, byte[] b)
/// the start offset in array b at which the data is written.
/// the maximum number of bytes to read.
///
-
public static int ReadFully(Stream stream, byte[] b, int off, int len)
{
int total = 0;
@@ -187,7 +207,6 @@ public static int ReadFully(Stream stream, byte[] b, int off, int len)
return total;
}
}
-
}
///
@@ -213,13 +232,11 @@ public static long CalculateChecksum(byte[] data)
return (long)sum.ByteCRC(ref data);
}
- /**
- * Quietly (no exceptions) close Closable resource. In case of error it will
- * be printed to {@link IOUtils} class logger.
- *
- * @param closeable
- * resource to close
- */
+ ///
+ ///Quietly (no exceptions) close Closable resource. In case of error it will
+ ///be printed to {@link IOUtils} class logger.
+ ///
+ ///resource to close
public static void CloseQuietly(Stream closeable )
{
// no need to log a NullPointerException here
@@ -236,6 +253,7 @@ public static void CloseQuietly(Stream closeable )
logger.Log(POILogger.ERROR, "Unable to close resource: " + exc, exc);
}
}
+
public static void CloseQuietly(ICloseable closeable)
{
// no need to log a NullPointerException here
@@ -252,5 +270,68 @@ public static void CloseQuietly(ICloseable closeable)
logger.Log(POILogger.ERROR, "Unable to close resource: " + exc, exc);
}
}
+
+ public static byte[] SafelyAllocate(long length, int maxLength)
+ {
+ SafelyAllocateCheck(length, maxLength);
+
+ CheckByteSizeLimit((int)length);
+
+ return new byte[(int)length];
+ }
+
+ public static void SafelyAllocateCheck(long length, int maxLength)
+ {
+ if (length < 0L)
+ {
+ throw new RecordFormatException("Can't allocate an array of length < 0, but had " + length + " and " + maxLength);
+ }
+ if (length > (long)int.MaxValue)
+ {
+ throw new RecordFormatException("Can't allocate an array > " + int.MaxValue);
+ }
+ CheckLength(length, maxLength);
+ }
+
+ private static void CheckByteSizeLimit(int length)
+ {
+ if (BYTE_ARRAY_MAX_OVERRIDE != -1 && length > BYTE_ARRAY_MAX_OVERRIDE)
+ {
+ ThrowRFE(length, BYTE_ARRAY_MAX_OVERRIDE);
+ }
+ }
+
+ private static void CheckLength(long length, int maxLength)
+ {
+ if (BYTE_ARRAY_MAX_OVERRIDE > 0)
+ {
+ if (length > BYTE_ARRAY_MAX_OVERRIDE)
+ {
+ ThrowRFE(length, BYTE_ARRAY_MAX_OVERRIDE);
+ }
+ }
+ else if (length > maxLength)
+ {
+ ThrowRFE(length, maxLength);
+ }
+ }
+
+ private static void ThrowRFE(long length, int maxLength)
+ {
+ throw new RecordFormatException(string.Format("Tried to allocate an array of length {0}" +
+ ", but the maximum length for this record type is {1}.\n" +
+ "If the file is not corrupt and not large, please open an issue on GitHub to request \n" +
+ "increasing the maximum allowable size for this record type.\n" +
+ "You can set a higher override value with IOUtils.SetByteArrayMaxOverride()", length, maxLength));
+ }
+
+ private static void ThrowRecordTruncationException(int maxLength)
+ {
+ throw new RecordFormatException(string.Format("Tried to read data but the maximum length " +
+ "for this record type is {0}.\n" +
+ "If the file is not corrupt and not large, please open an issue on GitHub to request \n" +
+ "increasing the maximum allowable size for this record type.\n" +
+ "You can set a higher override value with IOUtils.SetByteArrayMaxOverride()", maxLength));
+ }
}
}
diff --git a/main/Util/InputStream.cs b/main/Util/InputStream.cs
index bf05ca68c..80f7963ad 100644
--- a/main/Util/InputStream.cs
+++ b/main/Util/InputStream.cs
@@ -6,11 +6,19 @@
namespace NPOI.Util
{
+ ///
+ /// This abstract class is the superclass of all classes representing
+ /// an input stream of bytes.
+ /// Applications that need to define a subclass of
+ /// must always provide a method that returns the next byte of input.
+ ///
+ ///
public abstract class InputStream : Stream
{
// MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to
// use when skipping.
private static int MAX_SKIP_BUFFER_SIZE = 2048;
+
///
/// Reads the next byte of data from the input stream. The value byte is
/// returned as an int in the range 0 to
@@ -26,19 +34,26 @@ public abstract class InputStream : Stream
/// stream is reached.
///
/// if an I/O error occurs
- public abstract int Read();
+ public virtual int Read()
+ {
+ return ReadByte();
+ }
+
///
+ ///
/// Reads some number of bytes from the input stream and stores them into
/// the buffer array b. The number of bytes actually read is
/// returned as an integer. This method blocks until input data is
/// available, end of file is detected, or an exception is thrown.
- ///
- /// If the length of b is zero, then no bytes are read and
+ ///
+ ///
+ /// If the length of b is zero, then no bytes are read and
/// 0 is returned; otherwise, there is an attempt to read at
/// least one byte. If no byte is available because the stream is at the
- /// end of the file, the value -1 is returned; otherwise, at
- /// least one byte is read and stored into b.
- ///
+ /// end of the file, the value 0 is returned; otherwise, at
+ /// least one byte is read and stored into b.
+ ///
+ ///
/// The first byte read is stored into element b[0], the
/// next one into b[1], and so on. The number of bytes read is,
/// at most, equal to the length of b. Let k be the
@@ -46,14 +61,16 @@ public abstract class InputStream : Stream
/// b[0] through b[k-1],
/// leaving elements b[k] through
/// b[b.length-1] unaffected.
- ///
+ ///
+ ///
/// The read(b) method for class InputStream
/// has the same effect as:
read(b, 0, b.length)
+ ///
///
/// the buffer into which the data is read.
///
/// the total number of bytes read into the buffer, or
- /// -1 if there is no more data because the end of
+ /// 0 if there is no more data because the end of
/// the stream has been reached.
///
/// If the first byte cannot be read for any reason
@@ -65,21 +82,26 @@ public virtual int Read(byte[] b)
{
return Read(b, 0, b.Length);
}
+
///
+ ///
/// Reads up to len bytes of data from the input stream into
/// an array of bytes. An attempt is made to read as many as
/// len bytes, but a smaller number may be read.
/// The number of bytes actually read is returned as an integer.
- ///
+ ///
+ ///
/// This method blocks until input data is available, end of file is
/// detected, or an exception is thrown.
- ///
+ ///
+ ///
/// If len is zero, then no bytes are read and
/// 0 is returned; otherwise, there is an attempt to read at
/// least one byte. If no byte is available because the stream is at end of
- /// file, the value -1 is returned; otherwise, at least one
+ /// file, the value 0 is returned; otherwise, at least one
/// byte is read and stored into b.
- ///
+ ///
+ ///
/// The first byte read is stored into element b[off], the
/// next one into b[off+1], and so on. The number of bytes read
/// is, at most, equal to len. Let k be the number of
@@ -87,11 +109,13 @@ public virtual int Read(byte[] b)
/// b[off] through b[off+k-1],
/// leaving elements b[off+k] through
/// b[off+len-1] unaffected.
- ///
+ ///
+ ///
/// In every case, elements b[0] through
/// b[off] and elements b[off+len] through
/// b[b.length-1] are unaffected.
- ///
+ ///
+ ///
/// The read(b, off, len) method
/// for class InputStream simply calls the method
/// read() repeatedly. If the first such call results in an
@@ -105,13 +129,14 @@ public virtual int Read(byte[] b)
/// until the requested amount of input data len has been read,
/// end of file is detected, or an exception is thrown. Subclasses are encouraged
/// to provide a more efficient implementation of this method.
+ ///
///
/// the buffer into which the data is read.
/// the start offset in array b at which the data is written.
/// the maximum number of bytes to read.
///
/// the total number of bytes read into the buffer, or
- /// -1 if there is no more data because the end of
+ /// 0 if there is no more data because the end of
/// the stream has been reached.
/// If the first byte cannot be read for any reason
/// other than end of file, or if the input stream has been closed, or if
@@ -139,7 +164,7 @@ public override int Read(byte[] b, int off, int len)
int c = Read();
if (c == -1)
{
- return -1;
+ return 0;
}
b[off] = (byte)c;
@@ -156,12 +181,15 @@ public override int Read(byte[] b, int off, int len)
b[off + i] = (byte)c;
}
}
- catch (IOException ee)
+ catch (IOException)
{
}
+
return i;
}
+
///
+ ///
/// Skips over and discards n bytes of data from this input
/// stream. The skip method may, for a variety of reasons, end
/// up skipping over some smaller number of bytes, possibly 0.
@@ -171,12 +199,14 @@ public override int Read(byte[] b, int off, int len)
/// negative, the {@code skip} method for class {@code InputStream} always
/// returns 0, and no bytes are skipped. Subclasses may handle the negative
/// value differently.
- ///
+ ///
+ ///
/// The skip method of this class creates a
/// byte array and then repeatedly reads into it until n bytes
/// have been read or the end of the stream has been reached. Subclasses are
/// encouraged to provide a more efficient implementation of this method.
/// For instance, the implementation may depend on the ability to seek.
+ ///
///
/// the number of bytes to be skipped.
/// the actual number of bytes skipped.
@@ -209,51 +239,58 @@ public virtual long Skip(long n)
}
///
+ ///
/// Returns an estimate of the number of bytes that can be read (or
/// skipped over) from this input stream without blocking by the next
/// invocation of a method for this input stream. The next invocation
/// might be the same thread or another thread. A single read or skip of this
/// many bytes will not block, but may read or skip fewer bytes.
- ///
- /// Note that while some implementations of {@code InputStream} will return
+ ///
+ ///
+ /// Note that while some implementations of will return
/// the total number of bytes in the stream, many will not. It is
/// never correct to use the return value of this method to allocate
/// a buffer intended to hold all data in this stream.
- ///
+ ///
+ ///
/// A subclass' implementation of this method may choose to throw an
- /// {@link IOException} if this input stream has been closed by
- /// invoking the {@link #close()} method.
- ///
- /// The {@code available} method for class {@code InputStream} always
+ /// if this input stream has been closed by
+ /// invoking the method.
+ ///
+ ///
+ /// The method for class always
/// returns {@code 0}.
- ///
- /// This method should be overridden by subclasses.
+ ///
+ /// This method should be overridden by subclasses.
///
/// if an I/O error occurs.
public virtual int Available()
{
return 0;
}
+
///
+ ///
/// Closes this input stream and releases any system resources associated
/// with the stream.
- ///
- /// The Close method of InputStream does nothing.
+ ///
+ /// The Close method of InputStream does nothing.
///
/// if an I/O error occurs.
- public override void Close()
- {
+ public override void Close() { }
- }
///
+ ///
/// Marks the current position in this input stream. A subsequent call to
/// the reset method repositions this stream at the last marked
/// position so that subsequent reads re-read the same bytes.
- ///
+ ///
+ ///
/// The readlimit arguments tells this input stream to
/// allow that many bytes to be read before the mark position gets
/// invalidated.
- ///
+ ///
+ ///
/// The general contract of mark is that, if the method
/// markSupported returns true, the stream somehow
/// remembers all the bytes read after the call to mark and
@@ -261,23 +298,26 @@ public override void Close()
/// reset is called. However, the stream is not required to
/// remember any data at all if more than readlimit bytes are
/// read from the stream before reset is called.
- ///
- /// Marking a closed stream should not have any effect on the stream.
- ///
+ ///
+ /// Marking a closed stream should not have any effect on the stream.
+ ///
/// The mark method of InputStream does
/// nothing.
+ ///
///
/// the maximum limit of bytes that can be read before
/// the mark position becomes invalid.
///
///
public virtual void Mark(int readlimit) { }
+
///
+ ///
/// Repositions this stream to the position at the time the
/// mark method was last called on this input stream.
- ///
- /// The general contract of reset is:
- ///
+ ///
+ /// The general contract of reset is:
+ ///
///
/// - If the method markSupported returns
/// true, then:
@@ -307,14 +347,17 @@ public override void Close()
/// input stream and how it was created. The bytes that will be supplied
/// to subsequent callers of the read method depend on the
/// particular type of the input stream.
- ///
+ ///
+ ///
/// The method reset for class InputStream
/// does nothing except throw an IOException.
+ ///
///
public virtual void Reset()
{
throw new IOException("mark/reset not supported");
}
+
///
/// Tests if this input stream supports the mark and
/// reset methods. Whether or not mark and
@@ -332,74 +375,15 @@ public virtual bool MarkSupported()
{
return false;
}
- }
- public class FileInputStream : InputStream
- {
- Stream inner;
- public FileInputStream(Stream fs)
- {
- this.inner = fs;
- }
-
- public override bool CanRead
- {
- get
- {
- return inner.CanRead;
- }
- }
- public override bool CanSeek
- {
- get { return false; }
- }
public override bool CanWrite
{
get { return false; }
- }
- public override long Length
- {
- get
- {
- return inner.Length;
- }
- }
-
- public override long Position
- {
- get { return inner.Position;}
- set { inner.Position = value;}
- }
-
- public override void Flush()
- {
- throw new NotImplementedException();
- }
-
- public override int Read()
- {
- return inner.ReadByte();
- }
-
- public override long Seek(long offset, SeekOrigin origin)
- {
- throw new NotImplementedException();
- }
-
- public override void SetLength(long value)
- {
- throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
-
- public override void Close()
- {
- if (inner != null)
- inner.Close();
- }
}
}
diff --git a/main/Util/LittleEndianInputStream.cs b/main/Util/LittleEndianInputStream.cs
index d8ef25429..c6537694f 100644
--- a/main/Util/LittleEndianInputStream.cs
+++ b/main/Util/LittleEndianInputStream.cs
@@ -21,83 +21,93 @@ namespace NPOI.Util
using System.IO;
///
- /// Wraps an providing
- ///
+ /// Wraps an providing
+ ///
/// This class does not buffer any input, so the stream Read position maintained
/// by this class is consistent with that of the inner stream.
+ ///
///
///
/// @author Josh Micich
///
- public class LittleEndianInputStream : ILittleEndianInput
+ public class LittleEndianInputStream : FilterInputStream, ILittleEndianInput
{
- Stream in1 = null;
+ private int readLimit = -1;
+ private long markPos = -1;
+
+ public LittleEndianInputStream(Stream is1) : base(new FileInputStream(is1))
+ {
+ }
+
+ public LittleEndianInputStream(InputStream is1) : base(is1)
+ {
+ }
///
- /// Reads up to byte.length
bytes of data from this
+ ///
+ /// Reads up to byte.length bytes of data from this
/// input stream into an array of bytes. This method blocks until some
/// input is available.
- ///
- /// simulate java FilterInputStream
+ ///
+ /// simulate java FilterInputStream
///
///
///
- public int Read(byte[] b)
+ public override int Read(byte[] b)
{
return Read(b, 0, b.Length);
}
+
///
- /// Reads up to len
bytes of data from this input stream
- /// into an array of bytes.If len
is not zero, the method
+ ///
+ /// Reads up to len bytes of data from this input stream
+ /// into an array of bytes.If len is not zero, the method
/// blocks until some input is available; otherwise, no
- /// bytes are read and0
is returned.
- ///
- /// simulate java FilterInputStream
+ /// bytes are read and0 is returned.
+ ///
+ /// simulate java FilterInputStream
///
///
///
///
///
- public int Read(byte[] b, int off, int len)
+ public override int Read(byte[] b, int off, int len)
{
- return in1.Read(b, off, len);
+ return base.Read(b, off, len);
}
- private int readLimit = -1;
- private long markPos = -1;
- public void Mark(int readlimit)
+
+ public override void Mark(int readlimit)
{
this.readLimit = readlimit;
- this.markPos = in1.Position;
+ this.markPos = Position;
}
- public void Reset()
+ public override void Reset()
{
- in1.Seek(markPos - in1.Position, SeekOrigin.Current);
+ Seek(markPos - Position, SeekOrigin.Current);
}
- public long Skip(long n)
+ public override long Skip(long n)
{
- return in1.Seek(n, SeekOrigin.Current);
- }
- public int Available()
- {
- return (int)(in1.Length - in1.Position);
+ return Seek(n, SeekOrigin.Current);
}
- public LittleEndianInputStream(Stream is1)
+ public override int Available()
{
- in1 = is1;
+ return (int)(Length - Position);
}
- public int ReadByte()
+
+ public override int ReadByte()
{
return (byte)ReadUByte();
}
+
public int ReadUByte()
{
int ch;
try
{
- ch = in1.ReadByte();
+ ch = Read();
}
catch (IOException e)
{
@@ -106,10 +116,12 @@ public int ReadUByte()
CheckEOF(ch);
return ch;
}
+
public double ReadDouble()
{
return BitConverter.Int64BitsToDouble(ReadLong());
}
+
public int ReadInt()
{
int ch1;
@@ -118,10 +130,10 @@ public int ReadInt()
int ch4;
try
{
- ch1 = in1.ReadByte();
- ch2 = in1.ReadByte();
- ch3 = in1.ReadByte();
- ch4 = in1.ReadByte();
+ ch1 = Read();
+ ch2 = Read();
+ ch3 = Read();
+ ch4 = Read();
}
catch (IOException e)
{
@@ -149,14 +161,14 @@ public long ReadLong()
int b7;
try
{
- b0 = in1.ReadByte();
- b1 = in1.ReadByte();
- b2 = in1.ReadByte();
- b3 = in1.ReadByte();
- b4 = in1.ReadByte();
- b5 = in1.ReadByte();
- b6 = in1.ReadByte();
- b7 = in1.ReadByte();
+ b0 = Read();
+ b1 = Read();
+ b2 = Read();
+ b3 = Read();
+ b4 = Read();
+ b5 = Read();
+ b6 = Read();
+ b7 = Read();
}
catch (IOException e)
{
@@ -172,18 +184,20 @@ public long ReadLong()
(b1 << 8) +
(b0 << 0));
}
+
public short ReadShort()
{
return (short)ReadUShort();
}
+
public int ReadUShort()
{
int ch1;
int ch2;
try
{
- ch1 = in1.ReadByte();
- ch2 = in1.ReadByte();
+ ch1 = Read();
+ ch2 = Read();
}
catch (IOException e)
{
@@ -192,6 +206,7 @@ public int ReadUShort()
CheckEOF(ch1 | ch2);
return (ch2 << 8) + (ch1 << 0);
}
+
private static void CheckEOF(int value)
{
if (value < 0)
@@ -213,7 +228,7 @@ public void ReadFully(byte[] buf, int off, int len)
byte ch;
try
{
- ch = (byte)in1.ReadByte();
+ ch = (byte)Read();
}
catch (IOException e)
{
@@ -223,10 +238,5 @@ public void ReadFully(byte[] buf, int off, int len)
buf[i] = ch;
}
}
-
- internal void Close()
- {
- in1.Close();
- }
}
}
\ No newline at end of file
diff --git a/main/Util/RLEDecompressingInputStream.cs b/main/Util/RLEDecompressingInputStream.cs
index 1462938b2..bedbffcf7 100644
--- a/main/Util/RLEDecompressingInputStream.cs
+++ b/main/Util/RLEDecompressingInputStream.cs
@@ -20,17 +20,17 @@ namespace NPOI.Util
using System;
using System.IO;
- /**
- * Wrapper of InputStream which provides Run Length Encoding (RLE)
- * decompression on the fly. Uses MS-OVBA decompression algorithm. See
- * http://download.microsoft.com/download/2/4/8/24862317-78F0-4C4B-B355-C7B2C1D997DB/[MS-OVBA].pdf
- */
+ ///
+ /// Wrapper of InputStream which provides Run Length Encoding (RLE)
+ /// decompression on the fly. Uses MS-OVBA decompression algorithm. See
+ /// http://download.microsoft.com/download/2/4/8/24862317-78F0-4C4B-B355-C7B2C1D997DB/[MS-OVBA].pdf
+ ///
+ ///
public class RLEDecompressingInputStream : InputStream
{
-
- /**
- * Bitmasks for performance
- */
+ ///
+ /// Bitmasks for performance
+ ///
private static int[] POWER2 = new int[] {
0x0001, // 2^0
0x0002, // 2^1
@@ -50,46 +50,57 @@ public class RLEDecompressingInputStream : InputStream
0x8000 // 2^15
};
- /** the wrapped inputstream */
+ ///
+ /// the wrapped inputstream
+ ///
private Stream input;
- /** a byte buffer with size 4096 for storing a single chunk */
+ ///
+ /// a byte buffer with size 4096 for storing a single chunk
+ ///
private byte[] buf;
- /** the current position in the byte buffer for Reading */
+ ///
+ /// the current position in the byte buffer for Reading
+ ///
private int pos;
- /** the number of bytes in the byte buffer */
+ ///
+ /// the number of bytes in the byte buffer
+ ///
private int len;
- public override bool CanRead
+ public override bool CanRead
{
get {return input.CanRead; }
}
+
public override bool CanSeek
{
get {return input.CanSeek; }
}
+
public override bool CanWrite
{
get {return input.CanWrite; }
}
+
public override long Length
{
get {return input.Length; }
}
+
public override long Position
{
get { return input.Position; }
set { input.Position = value;}
}
- /**
- * Creates a new wrapper RLE Decompression InputStream.
- *
- * @param in The stream to wrap with the RLE Decompression
- * @throws IOException
- */
+ ///
+ /// Creates a new wrapper RLE Decompression InputStream.
+ ///
+ ///in The stream to wrap with the RLE Decompression
+ ///
public RLEDecompressingInputStream(Stream input)
{
this.input = input;
@@ -103,7 +114,6 @@ public RLEDecompressingInputStream(Stream input)
len = ReadChunk();
}
-
public override int Read()
{
if (len == -1)
@@ -120,18 +130,16 @@ public override int Read()
return buf[pos++];
}
-
public override int Read(byte[] b)
{
return Read(b, 0, b.Length);
}
-
public override int Read(byte[] b, int off, int l)
{
if (len == -1)
{
- return -1;
+ return 0;
}
int offset = off;
int length = l;
@@ -141,7 +149,7 @@ public override int Read(byte[] b, int off, int l)
{
if ((len = ReadChunk()) == -1)
{
- return offset > off ? offset - off : -1;
+ return offset > off ? offset - off : 0;
}
}
int c = Math.Min(length, len - pos);
@@ -153,7 +161,6 @@ public override int Read(byte[] b, int off, int l)
return l;
}
-
public override long Skip(long n)
{
long length = n;
@@ -173,24 +180,22 @@ public override long Skip(long n)
return n;
}
-
public override int Available()
{
return (len > 0 ? len - pos : 0);
}
-
public override void Close()
{
input.Close();
}
- /**
- * Reads a single chunk from the underlying inputstream.
- *
- * @return number of bytes that were Read, or -1 if the end of the stream was reached.
- * @throws IOException
- */
+ ///
+ /// Reads a single chunk from the underlying inputstream.
+ ///
+ ///number of bytes that were Read, or -1 if the end of the stream was reached.
+ ///@throws IOException
+ ///<
private int ReadChunk()
{
pos = 0;
@@ -267,12 +272,11 @@ private int ReadChunk()
}
}
- /**
- * Helper method to determine how many bits in the CopyToken are used for the CopyLength.
- *
- * @param offset
- * @return returns the number of bits in the copy token (a value between 4 and 12)
- */
+ ///
+ ///Helper method to determine how many bits in the CopyToken are used for the CopyLength.
+ ///
+ ///
+ ///returns the number of bits in the copy token (a value between 4 and 12)
static int GetCopyLenBits(int offset)
{
for (int n = 11; n >= 4; n--)
@@ -285,23 +289,21 @@ static int GetCopyLenBits(int offset)
return 12;
}
- /**
- * Convenience method for read a 2-bytes short in little endian encoding.
- *
- * @return short value from the stream, -1 if end of stream is reached
- * @throws IOException
- */
+ ///
+ ///Convenience method for read a 2-bytes short in little endian encoding.
+ ///
+ ///short value from the stream, -1 if end of stream is reached
+ ///
public int ReadShort()
{
return ReadShort(this);
}
- /**
- * Convenience method for read a 4-bytes int in little endian encoding.
- *
- * @return integer value from the stream, -1 if end of stream is reached
- * @throws IOException
- */
+ ///
+ ///Convenience method for read a 4-bytes int in little endian encoding.
+ ///
+ ///integer value from the stream, -1 if end of stream is reached
+ ///
public int ReadInt()
{
return ReadInt(this);
diff --git a/ooxml/POIFS/Crypt/Agile/AgileDecryptor.cs b/ooxml/POIFS/Crypt/Agile/AgileDecryptor.cs
index 869136d44..846dfa1d2 100644
--- a/ooxml/POIFS/Crypt/Agile/AgileDecryptor.cs
+++ b/ooxml/POIFS/Crypt/Agile/AgileDecryptor.cs
@@ -256,9 +256,9 @@ protected internal AgileDecryptor(AgileEncryptionInfoBuilder builder)
DocumentInputStream dis = dir.CreateDocumentInputStream(DEFAULT_POIFS_ENTRY);
_length = dis.ReadLong();
- ChunkedCipherInputStream cipherStream = new AgileCipherInputStream(dis, _length, builder, this);
- throw new NotImplementedException("AgileCipherInputStream should be derived from InputStream");
- //return cipherStream.GetStream();
+ var stream = new AgileCipherInputStream(dis, _length, builder, this);
+ stream.Position = 0;
+ return stream;
}
public override long GetLength() {
@@ -309,7 +309,7 @@ protected internal AgileDecryptor(AgileEncryptionInfoBuilder builder)
* that the StreamSize field of the EncryptedPackage field specifies the number of bytes of
* unencrypted data as specified in section 2.3.4.4.
*/
- private class AgileCipherInputStream : ChunkedCipherInputStream {
+ private sealed class AgileCipherInputStream : ChunkedCipherInputStream {
public AgileCipherInputStream(DocumentInputStream stream, long size,
IEncryptionInfoBuilder builder, AgileDecryptor decryptor)
From 51169e22df971ae720aeb01fb3ff23e6a94153fb Mon Sep 17 00:00:00 2001
From: KnyazSh
Date: Mon, 18 Jul 2022 15:34:31 +0300
Subject: [PATCH 2/2] Fix WorkbookFactory for encrypted OOXML
---
.../POIFS/FileSystem/DocumentFactoryHelper.cs | 201 +++++++++++++++++-
ooxml/SS/UserModel/WorkbookFactory.cs | 153 ++++++++-----
2 files changed, 300 insertions(+), 54 deletions(-)
diff --git a/main/POIFS/FileSystem/DocumentFactoryHelper.cs b/main/POIFS/FileSystem/DocumentFactoryHelper.cs
index 6fe8ab438..df4478991 100644
--- a/main/POIFS/FileSystem/DocumentFactoryHelper.cs
+++ b/main/POIFS/FileSystem/DocumentFactoryHelper.cs
@@ -20,6 +20,7 @@
using NPOI.Util;
using System;
using System.IO;
+using System.Text;
namespace NPOI.POIFS.FileSystem
{
@@ -105,6 +106,204 @@ public static bool HasOOXMLHeader(Stream inp)
);
}
- }
+ ///
+ /// Detects if a given office document is protected by a password or not.
+ /// Supported formats: Word, Excel and PowerPoint (both legacy and OpenXml).
+ ///
+ /// Path to an office document.
+ /// True if document is protected by a password, false otherwise.
+ public static bool IsPasswordProtected(string fileName)
+ {
+ using (var stream = File.OpenRead(fileName))
+ return IsPasswordProtected(stream);
+ }
+
+ ///
+ /// Detects if a given office document is protected by a password or not.
+ /// Supported formats: Word, Excel and PowerPoint (both legacy and OpenXml).
+ ///
+ /// Office document stream.
+ /// True if document is protected by a password, false otherwise.
+ public static bool IsPasswordProtected(Stream stream)
+ {
+ return GetPasswordProtected(stream) != OfficeProtectType.Other;
+ }
+
+ ///
+ /// Detects if a given office document is protected by a password or not.
+ /// Supported formats: Word, Excel and PowerPoint (both legacy and OpenXml).
+ ///
+ /// Office document stream.
+ /// True if document is protected by a password, false otherwise.
+ public static OfficeProtectType GetPasswordProtected(Stream stream)
+ {
+ // minimum file size for office file is 4k
+ if (stream.Length < 4096)
+ return OfficeProtectType.Other;
+ // read file header
+ stream.Seek(0, SeekOrigin.Begin);
+ var compObjHeader = new byte[0x20];
+ ReadFromStream(stream, compObjHeader);
+
+ // check if we have plain zip file
+ if (compObjHeader[0] == 0x50 && compObjHeader[1] == 0x4b && compObjHeader[2] == 0x03 && compObjHeader[4] == 0x04)
+ {
+ // this is a plain OpenXml document (not encrypted)
+ return OfficeProtectType.Other;
+ }
+
+ // check compound object magic bytes
+ if (compObjHeader[0] != 0xD0 || compObjHeader[1] != 0xCF)
+ {
+ // unknown document format
+ return OfficeProtectType.Other;
+ }
+
+ int sectionSizePower = compObjHeader[0x1E];
+ if (sectionSizePower < 8 || sectionSizePower > 16)
+ {
+ // invalid section size
+ return OfficeProtectType.Other;
+ }
+ int sectionSize = 2 << (sectionSizePower - 1);
+
+ const int defaultScanLength = 32768;
+ long scanLength = Math.Min(defaultScanLength, stream.Length);
+
+ // read header part for scan
+ stream.Seek(0, SeekOrigin.Begin);
+ var header = new byte[scanLength];
+ ReadFromStream(stream, header);
+
+ // check if we detected password protection
+
+ var protectType = ScanForPassword(stream, header, sectionSize);
+ if (protectType != OfficeProtectType.Other)
+ return protectType;
+
+ // if not, try to scan footer as well
+
+ // read footer part for scan
+ stream.Seek(-scanLength, SeekOrigin.End);
+ var footer = new byte[scanLength];
+ ReadFromStream(stream, footer);
+
+ // finally return the result
+ return ScanForPassword(stream, footer, sectionSize);
+ }
+
+ private static OfficeProtectType ScanForPassword(Stream stream, byte[] buffer, int sectionSize)
+ {
+ const string afterNamePadding = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+
+ try
+ {
+ string bufferString = Encoding.ASCII.GetString(buffer, 0, buffer.Length);
+
+ // try to detect password protection used in new OpenXml documents
+ // by searching for "EncryptedPackage" or "EncryptedSummary" streams
+ const string encryptedPackageName = "E\0n\0c\0r\0y\0p\0t\0e\0d\0P\0a\0c\0k\0a\0g\0e" + afterNamePadding;
+ const string encryptedSummaryName = "E\0n\0c\0r\0y\0p\0t\0e\0d\0S\0u\0m\0m\0a\0r\0y" + afterNamePadding;
+ if (bufferString.Contains(encryptedPackageName) ||
+ bufferString.Contains(encryptedSummaryName))
+ {
+ return OfficeProtectType.ProtectedOOXML;
+ }
+
+ // try to detect password protection for legacy Office documents
+ const int coBaseOffset = 0x200;
+ const int sectionIdOffset = 0x74;
+
+ // check for Word header
+ const string wordDocumentName = "W\0o\0r\0d\0D\0o\0c\0u\0m\0e\0n\0t" + afterNamePadding;
+ int headerOffset = bufferString.IndexOf(wordDocumentName, StringComparison.InvariantCulture);
+ int sectionId;
+ if (headerOffset >= 0)
+ {
+ sectionId = BitConverter.ToInt32(buffer, headerOffset + sectionIdOffset);
+ int sectionOffset = coBaseOffset + (sectionId * sectionSize);
+ const int fibScanSize = 0x10;
+
+ if (sectionOffset < 0 || sectionOffset + fibScanSize > stream.Length)
+ return OfficeProtectType.Other; // invalid document
+
+ var fibHeader = new byte[fibScanSize];
+ stream.Seek(sectionOffset, SeekOrigin.Begin);
+ ReadFromStream(stream, fibHeader);
+ short properties = BitConverter.ToInt16(fibHeader, 0x0A);
+ // check for fEncrypted FIB bit
+ const short fEncryptedBit = 0x0100;
+ if ((properties & fEncryptedBit) == fEncryptedBit)
+ {
+ return OfficeProtectType.ProtectedOffice;
+ }
+ else
+ {
+ return OfficeProtectType.Other;
+ }
+ }
+
+ // check for Excel header
+ const string workbookName = "W\0o\0r\0k\0b\0o\0o\0k" + afterNamePadding;
+ headerOffset = bufferString.IndexOf(workbookName, StringComparison.InvariantCulture);
+ if (headerOffset >= 0)
+ {
+ sectionId = BitConverter.ToInt32(buffer, headerOffset + sectionIdOffset);
+ int sectionOffset = coBaseOffset + (sectionId * sectionSize);
+ const int streamScanSize = 0x100;
+ if (sectionOffset < 0 || sectionOffset + streamScanSize > stream.Length)
+ return OfficeProtectType.Other; // invalid document
+ var workbookStream = new byte[streamScanSize];
+ stream.Seek(sectionOffset, SeekOrigin.Begin);
+ ReadFromStream(stream, workbookStream);
+ short record = BitConverter.ToInt16(workbookStream, 0);
+ short recordSize = BitConverter.ToInt16(workbookStream, sizeof(short));
+ const short bofMagic = 0x0809;
+ const short eofMagic = 0x000A;
+ const short filePassMagic = 0x002F;
+ if (record != bofMagic)
+ return OfficeProtectType.Other; // invalid BOF
+ // scan for FILEPASS record until the end of the buffer
+ int offset = (sizeof(short) * 2) + recordSize;
+ int recordsLeft = 16; // simple infinite loop check just in case
+ do
+ {
+ record = BitConverter.ToInt16(workbookStream, offset);
+ if (record == filePassMagic)
+ return OfficeProtectType.ProtectedOffice;
+ recordSize = BitConverter.ToInt16(workbookStream, sizeof(short) + offset);
+ offset += (sizeof(short) * 2) + recordSize;
+ recordsLeft--;
+ } while (record != eofMagic && recordsLeft > 0);
+ }
+ }
+ catch (Exception ex)
+ {
+ // BitConverter exceptions may be related to document format problems
+ // so we just treat them as "password not detected" result
+ if (ex is ArgumentException)
+ return OfficeProtectType.Other;
+ // respect all the rest exceptions
+ throw;
+ }
+
+ return OfficeProtectType.Other;
+ }
+
+ private static void ReadFromStream(Stream stream, byte[] buffer)
+ {
+ int bytesRead, count = buffer.Length;
+ while (count > 0 && (bytesRead = stream.Read(buffer, 0, count)) > 0)
+ count -= bytesRead;
+ if (count > 0) throw new EndOfStreamException();
+ }
+
+ public enum OfficeProtectType
+ {
+ ProtectedOOXML,
+ ProtectedOffice,
+ Other
+ }
+ }
}
diff --git a/ooxml/SS/UserModel/WorkbookFactory.cs b/ooxml/SS/UserModel/WorkbookFactory.cs
index 822e82296..9d3a2fb02 100644
--- a/ooxml/SS/UserModel/WorkbookFactory.cs
+++ b/ooxml/SS/UserModel/WorkbookFactory.cs
@@ -50,7 +50,7 @@ public enum ImportOption
/// Factory for creating the appropriate kind of Workbook
/// (be it HSSFWorkbook or XSSFWorkbook), from the given input
///
- public class WorkbookFactory
+ public static class WorkbookFactory
{
///
/// Creates an HSSFWorkbook from the given POIFSFileSystem
@@ -60,17 +60,22 @@ public static IWorkbook Create(POIFSFileSystem fs)
return new HSSFWorkbook(fs);
}
- /**
- * Creates an HSSFWorkbook from the given NPOIFSFileSystem
- */
+ ///
+ /// Creates an HSSFWorkbook from the given NPOIFSFileSystem
+ ///
+ ///
+ ///
public static IWorkbook Create(NPOIFSFileSystem fs)
{
return new HSSFWorkbook(fs.Root, true);
}
- /**
- * Creates a Workbook from the given NPOIFSFileSystem, which may
- * be password protected
- */
+
+ ///
+ /// Creates a Workbook from the given NPOIFSFileSystem, which may be password protected
+ ///
+ ///
+ ///
+ ///
private static IWorkbook Create(NPOIFSFileSystem fs, string password)
{
DirectoryNode root = fs.Root;
@@ -100,7 +105,6 @@ private static IWorkbook Create(NPOIFSFileSystem fs, string password)
}
}
-
///
/// Creates an XSSFWorkbook from the given OOXML Package
///
@@ -108,15 +112,15 @@ public static IWorkbook Create(OPCPackage pkg)
{
return new XSSFWorkbook(pkg);
}
+
///
/// Creates the appropriate HSSFWorkbook / XSSFWorkbook from
/// the given InputStream. The Stream is wraped inside a PushbackInputStream.
///
/// Input Stream of .xls or .xlsx file
- ///
/// IWorkbook depending on the input HSSFWorkbook or XSSFWorkbook is returned.
- // Your input stream MUST either support mark/reset, or
- // be wrapped as a {@link PushbackInputStream}!
+ /// Your input stream MUST either support mark/reset, or
+ /// be wrapped as a {@link PushbackInputStream}!
public static IWorkbook Create(Stream inputStream, bool bReadonly)
{
if (inputStream.Length == 0)
@@ -133,10 +137,32 @@ public static IWorkbook Create(Stream inputStream, bool bReadonly)
}
throw new InvalidFormatException("Your stream was neither an OLE2 stream, nor an OOXML stream.");
}
+
public static IWorkbook Create(Stream inputStream)
{
return Create(inputStream, false);
}
+
+ public static IWorkbook Create(string file, string password, bool readOnly)
+ {
+ if (!File.Exists(file))
+ {
+ throw new FileNotFoundException(file);
+ }
+
+ Stream inputStream = new FileStream(file, FileMode.Open, readOnly ? FileAccess.Read : FileAccess.ReadWrite);
+
+ try
+ {
+ return Create(inputStream, password, readOnly);
+ }
+ finally
+ {
+ if (inputStream != null)
+ inputStream.Dispose();
+ }
+ }
+
public static IWorkbook Create(string file)
{
if (!File.Exists(file))
@@ -148,11 +174,12 @@ public static IWorkbook Create(string file)
return Create(stream);
}
}
+
public static IWorkbook Create(string file, string password)
{
return Create(file, password, false);
}
-
+
///
/// Creates the appropriate HSSFWorkbook / XSSFWorkbook from
/// the given File, which must exist and be readable.
@@ -165,55 +192,77 @@ public static IWorkbook Create(string file, string password)
/// Note that for Workbooks opened this way, it is not possible
/// to explicitly close the underlying File resource.
///
- public static IWorkbook Create(string file, string password, bool readOnly)
+ public static IWorkbook Create(Stream inputStream, string password, bool readOnly)
{
- if (!File.Exists(file))
- {
- throw new FileNotFoundException(file);
- }
- FileInfo fInfo = new FileInfo(file);
+ inputStream = new PushbackStream(inputStream);
+
try
{
- NPOIFSFileSystem fs = new NPOIFSFileSystem(fInfo, readOnly);
- try
- {
- return Create(fs, password);
- }
- finally {
- // ensure that the file-handle is closed again
- if (fs!=null)
- fs.Close();
- }
-
- }
- catch (OfficeXmlFileException)
- {
- // opening as .xls failed => try opening as .xlsx
- OPCPackage pkg = OPCPackage.Open(file, readOnly ? PackageAccess.READ : PackageAccess.READ_WRITE);
- try
- {
- return new XSSFWorkbook(pkg);
- }
- catch (IOException ioe)
+ if (POIFSFileSystem.HasPOIFSHeader(inputStream))
{
- // ensure that file handles are closed (use revert() to not re-write the file)
- pkg.Revert();
- //pkg.close();
+ if (DocumentFactoryHelper.GetPasswordProtected(inputStream) == DocumentFactoryHelper.OfficeProtectType.ProtectedOOXML)
+ {
+ inputStream.Position = 0;
+ POIFSFileSystem fs = new POIFSFileSystem(inputStream);
+
+ var decriptedStream = DocumentFactoryHelper.GetDecryptedStream(fs, password);
+
+ return new XSSFWorkbook(decriptedStream);
+ }
+
+ inputStream.Position = 0;
+ NPOIFSFileSystem nfs = new NPOIFSFileSystem(inputStream);
- // rethrow exception
- throw ioe;
+ try
+ {
+ return Create(nfs, password);
+ }
+ finally
+ {
+ // ensure that the file-handle is closed again
+ if (nfs != null)
+ nfs.Close();
+ }
}
- catch (Exception ioe)
+
+ inputStream.Position = 0;
+ if (DocumentFactoryHelper.HasOOXMLHeader(inputStream))
{
- // ensure that file handles are closed (use revert() to not re-write the file)
- pkg.Revert();
- //pkg.close();
+ inputStream.Position = 0;
+ OPCPackage pkg = OPCPackage.Open(inputStream, readOnly);
+ try
+ {
+ return new XSSFWorkbook(pkg);
+ }
+ catch (IOException ioe)
+ {
+ // ensure that file handles are closed (use revert() to not re-write the file)
+ pkg.Revert();
+ //pkg.close();
+
+ // rethrow exception
+ throw ioe;
+ }
+ catch (Exception ioe)
+ {
+ // ensure that file handles are closed (use revert() to not re-write the file)
+ pkg.Revert();
+ //pkg.close();
- // rethrow exception
- throw ioe;
+ // rethrow exception
+ throw ioe;
+ }
}
}
+ finally
+ {
+ if (inputStream != null)
+ inputStream.Dispose();
+ }
+
+ throw new InvalidFormatException("Your stream was neither an OLE2 stream, nor an OOXML stream.");
}
+
///
/// Creates the appropriate HSSFWorkbook / XSSFWorkbook from
/// the given InputStream. The Stream is wraped inside a PushbackInputStream.
@@ -360,7 +409,5 @@ public static void SetImportOption(ImportOption importOption)
XSSFRelation.AddRelation(XSSFRelation.PRINTER_SETTINGS);
}
}
-
}
-
}
\ No newline at end of file