XZBlock.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. using System.Collections.Generic;
  2. using System.IO;
  3. using System.Linq;
  4. using SharpCompress.Compressors.Xz.Filters;
  5. namespace SharpCompress.Compressors.Xz
  6. {
  7. internal sealed class XZBlock : XZReadOnlyStream
  8. {
  9. public int BlockHeaderSize => (_blockHeaderSizeByte + 1) * 4;
  10. public ulong? CompressedSize { get; private set; }
  11. public ulong? UncompressedSize { get; private set; }
  12. public Stack<BlockFilter> Filters { get; private set; } = new Stack<BlockFilter>();
  13. public bool HeaderIsLoaded { get; private set; }
  14. private CheckType _checkType;
  15. private readonly int _checkSize;
  16. private bool _streamConnected;
  17. private int _numFilters;
  18. private byte _blockHeaderSizeByte;
  19. private Stream _decomStream;
  20. private bool _endOfStream;
  21. private bool _paddingSkipped;
  22. private bool _crcChecked;
  23. private ulong _bytesRead;
  24. public XZBlock(Stream stream, CheckType checkType, int checkSize) : base(stream)
  25. {
  26. _checkType = checkType;
  27. _checkSize = checkSize;
  28. }
  29. public override int Read(byte[] buffer, int offset, int count)
  30. {
  31. int bytesRead = 0;
  32. if (!HeaderIsLoaded)
  33. LoadHeader();
  34. if (!_streamConnected)
  35. ConnectStream();
  36. if (!_endOfStream)
  37. bytesRead = _decomStream.Read(buffer, offset, count);
  38. if (bytesRead != count)
  39. _endOfStream = true;
  40. if (_endOfStream && !_paddingSkipped)
  41. SkipPadding();
  42. if (_endOfStream && !_crcChecked)
  43. CheckCrc();
  44. _bytesRead += (ulong)bytesRead;
  45. return bytesRead;
  46. }
  47. private void SkipPadding()
  48. {
  49. int bytes = (int)(BaseStream.Position % 4);
  50. if (bytes > 0)
  51. {
  52. byte[] paddingBytes = new byte[4 - bytes];
  53. BaseStream.Read(paddingBytes, 0, paddingBytes.Length);
  54. if (paddingBytes.Any(b => b != 0))
  55. throw new InvalidDataException("Padding bytes were non-null");
  56. }
  57. _paddingSkipped = true;
  58. }
  59. private void CheckCrc()
  60. {
  61. byte[] crc = new byte[_checkSize];
  62. BaseStream.Read(crc, 0, _checkSize);
  63. // Actually do a check (and read in the bytes
  64. // into the function throughout the stream read).
  65. _crcChecked = true;
  66. }
  67. private void ConnectStream()
  68. {
  69. _decomStream = BaseStream;
  70. while (Filters.Any())
  71. {
  72. var filter = Filters.Pop();
  73. filter.SetBaseStream(_decomStream);
  74. _decomStream = filter;
  75. }
  76. _streamConnected = true;
  77. }
  78. private void LoadHeader()
  79. {
  80. ReadHeaderSize();
  81. byte[] headerCache = CacheHeader();
  82. using (var cache = new MemoryStream(headerCache))
  83. using (var cachedReader = new BinaryReader(cache))
  84. {
  85. cachedReader.BaseStream.Position = 1; // skip the header size byte
  86. ReadBlockFlags(cachedReader);
  87. ReadFilters(cachedReader);
  88. }
  89. HeaderIsLoaded = true;
  90. }
  91. private void ReadHeaderSize()
  92. {
  93. _blockHeaderSizeByte = (byte)BaseStream.ReadByte();
  94. if (_blockHeaderSizeByte == 0)
  95. throw new XZIndexMarkerReachedException();
  96. }
  97. private byte[] CacheHeader()
  98. {
  99. byte[] blockHeaderWithoutCrc = new byte[BlockHeaderSize - 4];
  100. blockHeaderWithoutCrc[0] = _blockHeaderSizeByte;
  101. var read = BaseStream.Read(blockHeaderWithoutCrc, 1, BlockHeaderSize - 5);
  102. if (read != BlockHeaderSize - 5)
  103. throw new EndOfStreamException("Reached end of stream unexectedly");
  104. uint crc = BaseStream.ReadLittleEndianUInt32();
  105. uint calcCrc = Crc32.Compute(blockHeaderWithoutCrc);
  106. if (crc != calcCrc)
  107. throw new InvalidDataException("Block header corrupt");
  108. return blockHeaderWithoutCrc;
  109. }
  110. private void ReadBlockFlags(BinaryReader reader)
  111. {
  112. var blockFlags = reader.ReadByte();
  113. _numFilters = (blockFlags & 0x03) + 1;
  114. byte reserved = (byte)(blockFlags & 0x3C);
  115. if (reserved != 0)
  116. throw new InvalidDataException("Reserved bytes used, perhaps an unknown XZ implementation");
  117. bool compressedSizePresent = (blockFlags & 0x40) != 0;
  118. bool uncompressedSizePresent = (blockFlags & 0x80) != 0;
  119. if (compressedSizePresent)
  120. CompressedSize = reader.ReadXZInteger();
  121. if (uncompressedSizePresent)
  122. UncompressedSize = reader.ReadXZInteger();
  123. }
  124. private void ReadFilters(BinaryReader reader, long baseStreamOffset = 0)
  125. {
  126. int nonLastSizeChangers = 0;
  127. for (int i = 0; i < _numFilters; i++)
  128. {
  129. var filter = BlockFilter.Read(reader);
  130. if ((i + 1 == _numFilters && !filter.AllowAsLast)
  131. || (i + 1 < _numFilters && !filter.AllowAsNonLast))
  132. throw new InvalidDataException("Block Filters in bad order");
  133. if (filter.ChangesDataSize && i + 1 < _numFilters)
  134. nonLastSizeChangers++;
  135. filter.ValidateFilter();
  136. Filters.Push(filter);
  137. }
  138. if (nonLastSizeChangers > 2)
  139. throw new InvalidDataException("More than two non-last block filters cannot change stream size");
  140. int blockHeaderPaddingSize = BlockHeaderSize -
  141. (4 + (int)(reader.BaseStream.Position - baseStreamOffset));
  142. byte[] blockHeaderPadding = reader.ReadBytes(blockHeaderPaddingSize);
  143. if (!blockHeaderPadding.All(b => b == 0))
  144. throw new InvalidDataException("Block header contains unknown fields");
  145. }
  146. }
  147. }