Skip to content

Commit 519ed73

Browse files
authored
fix(zip): handle iterating updated entries in ZipInputStream (#642)
1 parent bdec777 commit 519ed73

File tree

3 files changed

+132
-22
lines changed

3 files changed

+132
-22
lines changed

src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs

+51-20
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using ICSharpCode.SharpZipLib.Zip.Compression;
44
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
55
using System;
6+
using System.Diagnostics;
67
using System.IO;
78

89
namespace ICSharpCode.SharpZipLib.Zip
@@ -181,31 +182,12 @@ public ZipEntry GetNextEntry()
181182
CloseEntry();
182183
}
183184

184-
int header = inputBuffer.ReadLeInt();
185-
186-
if (header == ZipConstants.CentralHeaderSignature ||
187-
header == ZipConstants.EndOfCentralDirectorySignature ||
188-
header == ZipConstants.CentralHeaderDigitalSignature ||
189-
header == ZipConstants.ArchiveExtraDataSignature ||
190-
header == ZipConstants.Zip64CentralFileHeaderSignature)
185+
if (!SkipUntilNextEntry())
191186
{
192-
// No more individual entries exist
193187
Dispose();
194188
return null;
195189
}
196190

197-
// -jr- 07-Dec-2003 Ignore spanning temporary signatures if found
198-
// Spanning signature is same as descriptor signature and is untested as yet.
199-
if ((header == ZipConstants.SpanningTempSignature) || (header == ZipConstants.SpanningSignature))
200-
{
201-
header = inputBuffer.ReadLeInt();
202-
}
203-
204-
if (header != ZipConstants.LocalHeaderSignature)
205-
{
206-
throw new ZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header));
207-
}
208-
209191
var versionRequiredToExtract = (short)inputBuffer.ReadLeShort();
210192

211193
flags = inputBuffer.ReadLeShort();
@@ -303,6 +285,54 @@ public ZipEntry GetNextEntry()
303285
return entry;
304286
}
305287

288+
/// <summary>
289+
/// Reads bytes from the input stream until either a local file header signature, or another signature
290+
/// indicating that no more entries should be present, is found.
291+
/// </summary>
292+
/// <exception cref="ZipException">Thrown if the end of the input stream is reached without any signatures found</exception>
293+
/// <returns>Returns whether the found signature is for a local entry header</returns>
294+
private bool SkipUntilNextEntry()
295+
{
296+
// First let's skip all null bytes since it's the sane padding to add when updating an entry with smaller size
297+
var paddingSkipped = 0;
298+
while(inputBuffer.ReadLeByte() == 0) {
299+
paddingSkipped++;
300+
}
301+
302+
// Last byte read was not actually consumed, restore the offset
303+
inputBuffer.Available += 1;
304+
if(paddingSkipped > 0) {
305+
Debug.WriteLine("Skipped {0} null byte(s) before reading signature", paddingSkipped);
306+
}
307+
308+
var offset = 0;
309+
// Read initial header quad directly after the last entry
310+
var header = (uint)inputBuffer.ReadLeInt();
311+
do
312+
{
313+
switch (header)
314+
{
315+
case ZipConstants.CentralHeaderSignature:
316+
case ZipConstants.EndOfCentralDirectorySignature:
317+
case ZipConstants.CentralHeaderDigitalSignature:
318+
case ZipConstants.ArchiveExtraDataSignature:
319+
case ZipConstants.Zip64CentralFileHeaderSignature:
320+
Debug.WriteLine("Non-entry signature found at offset {0,2}: 0x{1:x8}", offset, header);
321+
// No more individual entries exist
322+
return false;
323+
324+
case ZipConstants.LocalHeaderSignature:
325+
Debug.WriteLine("Entry local header signature found at offset {0,2}: 0x{1:x8}", offset, header);
326+
return true;
327+
default:
328+
// Current header quad did not match any signature, shift in another byte
329+
header = (uint) (inputBuffer.ReadLeByte() << 24) | (header >> 8);
330+
offset++;
331+
break;
332+
}
333+
} while (true); // Loop until we either get an EOF exception or we find the next signature
334+
}
335+
306336
/// <summary>
307337
/// Read data descriptor at the end of compressed data.
308338
/// </summary>
@@ -400,6 +430,7 @@ public void CloseEntry()
400430

401431
if ((inputBuffer.Available > csize) && (csize >= 0))
402432
{
433+
// Buffer can contain entire entry data. Internally offsetting position inside buffer
403434
inputBuffer.Available = (int)((long)inputBuffer.Available - csize);
404435
}
405436
else

test/ICSharpCode.SharpZipLib.Tests/TestSupport/Utils.cs

+35-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using NUnit.Framework;
22
using System;
3+
using System.Diagnostics;
34
using System.IO;
5+
using System.Text;
6+
using ICSharpCode.SharpZipLib.Tests.Zip;
47
using System.Linq;
58
using System.Threading.Tasks;
69

@@ -11,7 +14,10 @@ namespace ICSharpCode.SharpZipLib.Tests.TestSupport
1114
/// </summary>
1215
public static class Utils
1316
{
17+
public static int DummyContentLength = 16;
18+
1419
internal const int DefaultSeed = 5;
20+
private static Random random = new Random(DefaultSeed);
1521

1622
/// <summary>
1723
/// Returns the system root for the current platform (usually c:\ for windows and / for others)
@@ -115,6 +121,30 @@ public static string GetDummyFileName()
115121
/// </summary>
116122
/// <returns></returns>
117123
public static TempFile GetTempFile() => new TempFile();
124+
125+
public static void PatchFirstEntrySize(Stream stream, int newSize)
126+
{
127+
using(stream)
128+
{
129+
var sizeBytes = BitConverter.GetBytes(newSize);
130+
131+
stream.Seek(18, SeekOrigin.Begin);
132+
stream.Write(sizeBytes, 0, 4);
133+
stream.Write(sizeBytes, 0, 4);
134+
}
135+
}
136+
}
137+
138+
public class TestTraceListener : TraceListener
139+
{
140+
private readonly TextWriter _writer;
141+
public TestTraceListener(TextWriter writer)
142+
{
143+
_writer = writer;
144+
}
145+
146+
public override void WriteLine(string message) => _writer.WriteLine(message);
147+
public override void Write(string message) => _writer.Write(message);
118148
}
119149

120150
public class TempFile : FileSystemInfo, IDisposable
@@ -137,6 +167,8 @@ public override void Delete()
137167
_fileInfo.Delete();
138168
}
139169

170+
public FileStream Open(FileMode mode, FileAccess access) => _fileInfo.Open(mode, access);
171+
public FileStream Open(FileMode mode) => _fileInfo.Open(mode);
140172
public FileStream Create() => _fileInfo.Create();
141173

142174
public static TempFile WithDummyData(int size, string dirPath = null, string filename = null, int seed = Utils.DefaultSeed)
@@ -182,9 +214,10 @@ public void Dispose()
182214
}
183215

184216
#endregion IDisposable Support
185-
186-
187217
}
218+
219+
220+
188221
public class TempDir : FileSystemInfo, IDisposable
189222
{
190223
public override string Name => Path.GetFileName(FullName);

test/ICSharpCode.SharpZipLib.Tests/Zip/StreamHandling.cs

+46
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
using ICSharpCode.SharpZipLib.Zip;
44
using NUnit.Framework;
55
using System;
6+
using System.Diagnostics;
67
using System.IO;
8+
using System.Linq;
9+
using System.Text;
710
using Does = ICSharpCode.SharpZipLib.Tests.TestSupport.Does;
811

912
namespace ICSharpCode.SharpZipLib.Tests.Zip
@@ -14,6 +17,12 @@ namespace ICSharpCode.SharpZipLib.Tests.Zip
1417
[TestFixture]
1518
public class StreamHandling : ZipBase
1619
{
20+
private TestTraceListener Listener;
21+
[SetUp]
22+
public void Init() => Trace.Listeners.Add(Listener = new TestTraceListener(TestContext.Out));
23+
[TearDown]
24+
public void Deinit() => Trace.Listeners.Remove(Listener);
25+
1726
private void MustFailRead(Stream s, byte[] buffer, int offset, int count)
1827
{
1928
bool exception = false;
@@ -540,5 +549,42 @@ public void ShouldThrowDescriptiveExceptionOnUncompressedDescriptorEntry()
540549
});
541550
}
542551
}
552+
553+
[Test]
554+
[Category("Zip")]
555+
public void IteratingOverEntriesInDirectUpdatedArchive([Values(0x0, 0x80)] byte padding)
556+
{
557+
using (var tempFile = new TempFile())
558+
{
559+
using (var zf = ZipFile.Create(tempFile))
560+
{
561+
zf.BeginUpdate();
562+
// Add a "large" file, where the bottom 1023 bytes will become padding
563+
var contentsAndPadding = Enumerable.Repeat(padding, count: 1024).ToArray();
564+
zf.Add(new MemoryDataSource(contentsAndPadding), "FirstFile", CompressionMethod.Stored);
565+
// Add a second file after the first one
566+
zf.Add(new StringMemoryDataSource("fileContents"), "SecondFile", CompressionMethod.Stored);
567+
zf.CommitUpdate();
568+
}
569+
570+
// Since ZipFile doesn't support UpdateCommand.Modify yet we'll have to simulate it by patching the header
571+
Utils.PatchFirstEntrySize(tempFile.Open(FileMode.Open), 1);
572+
573+
// Iterate updated entries
574+
using (var fs = File.OpenRead(tempFile))
575+
using (var zis = new ZipInputStream(fs))
576+
{
577+
var firstEntry = zis.GetNextEntry();
578+
Assert.NotNull(firstEntry);
579+
Assert.AreEqual(1, firstEntry.CompressedSize);
580+
Assert.AreEqual(1, firstEntry.Size);
581+
582+
var secondEntry = zis.GetNextEntry();
583+
Assert.NotNull(secondEntry, "Zip entry following padding not found");
584+
var contents = new StreamReader(zis, Encoding.UTF8, false, 128, true).ReadToEnd();
585+
Assert.AreEqual("fileContents", contents);
586+
}
587+
}
588+
}
543589
}
544590
}

0 commit comments

Comments
 (0)