Skip to content

Commit 523d888

Browse files
committed
Fixed GzipInputStream to complete reading gracefully if at least one block has been successfully processed. This allows for a compressed stream that may contain trailing garbage.
1 parent 4f2d664 commit 523d888

2 files changed

Lines changed: 80 additions & 8 deletions

File tree

ICSharpCode.SharpZipLib.Tests/GZip/GZipTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,58 @@ public void WriteAfterClose()
220220
}
221221
}
222222

223+
/// <summary>
224+
/// Verify that if a decompression was successful for at least one block we're exiting gracefully.
225+
/// </summary>
226+
[Test]
227+
public void TrailingGarbage()
228+
{
229+
/* ARRANGE */
230+
var ms = new MemoryStream();
231+
var outStream = new GZipOutputStream(ms);
232+
233+
// input buffer to be compressed
234+
byte[] buf = new byte[100000];
235+
var rnd = new Random();
236+
rnd.NextBytes(buf);
237+
238+
// compress input buffer
239+
outStream.Write(buf, 0, buf.Length);
240+
outStream.Flush();
241+
outStream.Finish();
242+
243+
// generate random trailing garbage and add to the compressed stream
244+
byte[] garbage = new byte[4096];
245+
rnd.NextBytes(garbage);
246+
ms.Write(garbage, 0, garbage.Length);
247+
248+
// rewind the concatenated stream
249+
ms.Seek(0, SeekOrigin.Begin);
250+
251+
252+
/* ACT */
253+
// decompress concatenated stream
254+
var inStream = new GZipInputStream(ms);
255+
byte[] buf2 = new byte[buf.Length];
256+
int currentIndex = 0;
257+
int count = buf2.Length;
258+
while (true) {
259+
int numRead = inStream.Read(buf2, currentIndex, count);
260+
if (numRead <= 0) {
261+
break;
262+
}
263+
currentIndex += numRead;
264+
count -= numRead;
265+
}
266+
267+
268+
/* ASSERT */
269+
Assert.AreEqual(0, count);
270+
for (int i = 0; i < buf.Length; ++i) {
271+
Assert.AreEqual(buf2[i], buf[i]);
272+
}
273+
}
274+
223275
// TODO: Fix This
224276
//[Test]
225277
//[Category("GZip")]

ICSharpCode.SharpZipLib/GZip/GzipInputStream.cs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ public class GZipInputStream : InflaterInputStream
4747
/// This is tracked per-block as the file is parsed.
4848
/// </summary>
4949
bool readGZIPHeader;
50+
51+
/// <summary>
52+
/// Flag to indicate if at least one block in a stream with concatenated blocks was read successfully.
53+
/// This allows us to exit gracefully if downstream data is not in gzip format.
54+
/// </summary>
55+
bool completedLastBlock;
5056
#endregion
5157

5258
#region Constructors
@@ -100,12 +106,23 @@ public override int Read(byte[] buffer, int offset, int count)
100106
// If we haven't read the header for this block, read it
101107
if (!readGZIPHeader) {
102108

103-
// Try to read header. If there is no header (0 bytes available), this is EOF. If there is
104-
// an incomplete header, this will throw an exception.
105-
if (!ReadHeader()) {
106-
return 0;
107-
}
108-
}
109+
// Try to read header. If there is no header (0 bytes available), this is EOF. If there is
110+
// an incomplete header, this will throw an exception.
111+
try
112+
{
113+
if (!ReadHeader())
114+
{
115+
return 0;
116+
}
117+
}
118+
catch (Exception ex) when (completedLastBlock && (ex is GZipException || ex is EndOfStreamException))
119+
{
120+
// if we completed the last block (i.e. we're in a stream that has multiple blocks concatenated
121+
// we want to return gracefully from any header parsing exceptions since sometimes there may
122+
// be trailing garbage on a stream
123+
return 0;
124+
}
125+
}
109126

110127
// Try to read compressed data
111128
int bytesRead = base.Read(buffer, offset, count);
@@ -151,14 +168,14 @@ bool ReadHeader()
151168

152169
headCRC.Update(magic);
153170
if (magic != (GZipConstants.GZIP_MAGIC >> 8)) {
154-
throw new GZipException("Error GZIP header, first magic byte doesn't match");
171+
throw new GZipException("Error GZIP header, first magic byte doesn't match");
155172
}
156173

157174
//magic = baseInputStream.ReadByte();
158175
magic = inputBuffer.ReadLeByte();
159176

160177
if (magic < 0) {
161-
throw new EndOfStreamException("EOS reading GZIP header");
178+
throw new EndOfStreamException("EOS reading GZIP header");
162179
}
163180

164181
if (magic != (GZipConstants.GZIP_MAGIC & 0xFF)) {
@@ -324,6 +341,9 @@ void ReadFooter()
324341

325342
// Mark header read as false so if another header exists, we'll continue reading through the file
326343
readGZIPHeader = false;
344+
345+
// Indicate that we succeeded on at least one block so we can exit gracefully if there is trailing garbage downstream
346+
completedLastBlock = true;
327347
}
328348
#endregion
329349
}

0 commit comments

Comments
 (0)