Refactor dictionary size validation to eliminate code duplication

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-05 09:10:28 +00:00
parent 258f9b9bd8
commit f230f24109

View File

@@ -37,12 +37,21 @@ public class LzmaStream : Stream, IStreamStack
private readonly long _outputSize;
private readonly bool _leaveOpen;
// Maximum dictionary size to prevent OutOfMemoryException: 256MB
// Maximum dictionary size to prevent OutOfMemoryException: 512MB
// This is a reasonable limit that balances memory usage with decompression capabilities.
// The LZMA spec allows up to 4GB, but such large dictionaries are rarely necessary and
// can cause OutOfMemoryException in memory-constrained environments (e.g., containers).
// Archives with larger dictionary sizes should be recreated with a smaller setting.
private const int MaxDictionarySize = 256 * 1024 * 1024; // 256MB
//
// Most 7z archives use much smaller dictionary sizes:
// - Default: 32MB for solid archives, 1MB for non-solid
// - Common maximum: 64-128MB
// - Archives requiring >512MB dictionary are extremely rare
//
// If you encounter this limit with a legitimate archive, consider:
// - Recreating the archive with a smaller dictionary size (if you control creation)
// - Increasing available memory for the application
// - Filing an issue to request a configurable limit option
private const int MaxDictionarySize = 512 * 1024 * 1024; // 512MB
private readonly int _dictionarySize;
private readonly OutWindow _outWindow = new();
@@ -64,6 +73,27 @@ public class LzmaStream : Stream, IStreamStack
private readonly Encoder _encoder;
private bool _isDisposed;
private static void ValidateDictionarySize(int dictionarySize)
{
// Validate dictionary size
if (dictionarySize < 0)
{
throw new InvalidOperationException(
$"Invalid dictionary size ({dictionarySize}). The archive may be corrupted or use an unsupported format."
);
}
// Cap dictionary size to prevent OutOfMemoryException
if (dictionarySize > MaxDictionarySize)
{
throw new InvalidOperationException(
$"Dictionary size ({dictionarySize} bytes, {dictionarySize / (1024.0 * 1024.0):F2} MB) exceeds maximum allowed size ({MaxDictionarySize} bytes, {MaxDictionarySize / (1024.0 * 1024.0):F2} MB). "
+ "This archive may have been created with an extremely large dictionary size setting. "
+ "Consider recreating the archive with a smaller dictionary size."
);
}
}
public LzmaStream(byte[] properties, Stream inputStream, bool leaveOpen = false)
: this(properties, inputStream, -1, -1, null, properties.Length < 5, leaveOpen) { }
@@ -110,24 +140,7 @@ public class LzmaStream : Stream, IStreamStack
if (!isLzma2)
{
_dictionarySize = BinaryPrimitives.ReadInt32LittleEndian(properties.AsSpan(1));
// Validate dictionary size
if (_dictionarySize < 0)
{
throw new InvalidOperationException(
$"Invalid dictionary size ({_dictionarySize}). The archive may be corrupted or use an unsupported format."
);
}
// Cap dictionary size to prevent OutOfMemoryException
if (_dictionarySize > MaxDictionarySize)
{
throw new InvalidOperationException(
$"Dictionary size ({_dictionarySize} bytes, {_dictionarySize / (1024.0 * 1024.0):F2} MB) exceeds maximum allowed size ({MaxDictionarySize} bytes, {MaxDictionarySize / (1024.0 * 1024.0):F2} MB). "
+ "This archive may have been created with an extremely large dictionary size setting. "
+ "Consider recreating the archive with a smaller dictionary size."
);
}
ValidateDictionarySize(_dictionarySize);
_outWindow.Create(_dictionarySize);
if (presetDictionary != null)
@@ -148,24 +161,7 @@ public class LzmaStream : Stream, IStreamStack
{
_dictionarySize = 2 | (properties[0] & 1);
_dictionarySize <<= (properties[0] >> 1) + 11;
// Validate dictionary size (check for overflow to negative)
if (_dictionarySize < 0)
{
throw new InvalidOperationException(
$"Invalid dictionary size ({_dictionarySize}). The archive may be corrupted or use an unsupported format."
);
}
// Cap dictionary size to prevent OutOfMemoryException
if (_dictionarySize > MaxDictionarySize)
{
throw new InvalidOperationException(
$"Dictionary size ({_dictionarySize} bytes, {_dictionarySize / (1024.0 * 1024.0):F2} MB) exceeds maximum allowed size ({MaxDictionarySize} bytes, {MaxDictionarySize / (1024.0 * 1024.0):F2} MB). "
+ "This archive may have been created with an extremely large dictionary size setting. "
+ "Consider recreating the archive with a smaller dictionary size."
);
}
ValidateDictionarySize(_dictionarySize);
_outWindow.Create(_dictionarySize);
if (presetDictionary != null)