Skip to content

Commit f129cd3

Browse files
authored
Merge branch 'master' into release/v1.4.0
2 parents 7f28639 + 519ed73 commit f129cd3

File tree

13 files changed

+271
-70
lines changed

13 files changed

+271
-70
lines changed

src/ICSharpCode.SharpZipLib/Encryption/PkzipClassic.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace ICSharpCode.SharpZipLib.Encryption
66
{
77
/// <summary>
88
/// PkzipClassic embodies the classic or original encryption facilities used in Pkzip archives.
9-
/// While it has been superceded by more recent and more powerful algorithms, its still in use and
9+
/// While it has been superseded by more recent and more powerful algorithms, its still in use and
1010
/// is viable for preventing casual snooping
1111
/// </summary>
1212
public abstract class PkzipClassic : SymmetricAlgorithm

src/ICSharpCode.SharpZipLib/Encryption/ZipAESTransform.cs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ public ZipAESTransform(string key, byte[] saltBytes, int blockSize, bool writeMo
5050
_encrPos = ENCRYPT_BLOCK;
5151

5252
// Performs the equivalent of derive_key in Dr Brian Gladman's pwd2key.c
53+
#if NET472_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_0_OR_GREATER
54+
var pdb = new Rfc2898DeriveBytes(key, saltBytes, KEY_ROUNDS, HashAlgorithmName.SHA1);
55+
#else
5356
var pdb = new Rfc2898DeriveBytes(key, saltBytes, KEY_ROUNDS);
57+
#endif
5458
var rm = Aes.Create();
5559
rm.Mode = CipherMode.ECB; // No feedback from cipher for CTR mode
5660
_counterNonce = new byte[_blockSize];
@@ -121,23 +125,9 @@ public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, b
121125
/// </summary>
122126
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
123127
{
124-
var buffer = Array.Empty<byte>();
125-
126-
// FIXME: When used together with `ZipAESStream`, the final block handling is done inside of it instead
127-
// This should not be necessary anymore, and the entire `ZipAESStream` class should be replaced with a plain `CryptoStream`
128-
if (inputCount != 0) {
129-
if (inputCount > ZipAESStream.AUTH_CODE_LENGTH)
130-
{
131-
// At least one byte of data is preceeding the auth code
132-
int finalBlock = inputCount - ZipAESStream.AUTH_CODE_LENGTH;
133-
buffer = new byte[finalBlock];
134-
TransformBlock(inputBuffer, inputOffset, finalBlock, buffer, 0);
135-
}
136-
else if (inputCount < ZipAESStream.AUTH_CODE_LENGTH)
137-
throw new Zip.ZipException("Auth code missing from input stream");
138-
139-
// Read the authcode from the last 10 bytes
140-
_authCode = _hmacsha1.GetHashAndReset();
128+
if (inputCount > 0)
129+
{
130+
throw new NotImplementedException("TransformFinalBlock is not implemented and inputCount is greater than 0");
141131
}
142132

143133

src/ICSharpCode.SharpZipLib/Tar/TarArchive.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -356,8 +356,7 @@ public string RootPath
356356
{
357357
throw new ObjectDisposedException("TarArchive");
358358
}
359-
// Convert to forward slashes for matching. Trim trailing / for correct final path
360-
rootPath = value.Replace('\\', '/').TrimEnd('/');
359+
rootPath = value.ToTarArchivePath().TrimEnd('/');
361360
}
362361
}
363362

@@ -660,7 +659,9 @@ private void ExtractEntry(string destDir, TarEntry entry, bool allowParentTraver
660659
string destFile = Path.Combine(destDir, name);
661660
var destFileDir = Path.GetDirectoryName(Path.GetFullPath(destFile)) ?? "";
662661

663-
if (!allowParentTraversal && !destFileDir.StartsWith(destDir, StringComparison.InvariantCultureIgnoreCase))
662+
var isRootDir = entry.IsDirectory && entry.Name == "";
663+
664+
if (!allowParentTraversal && !isRootDir && !destFileDir.StartsWith(destDir, StringComparison.InvariantCultureIgnoreCase))
664665
{
665666
throw new InvalidNameException("Parent traversal in paths is not allowed");
666667
}

src/ICSharpCode.SharpZipLib/Tar/TarEntry.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -372,15 +372,10 @@ public void GetFileTarHeader(TarHeader header, string file)
372372
}
373373
*/
374374

375-
name = name.Replace(Path.DirectorySeparatorChar, '/');
376-
377375
// No absolute pathnames
378376
// Windows (and Posix?) paths can start with UNC style "\\NetworkDrive\",
379377
// so we loop on starting /'s.
380-
while (name.StartsWith("/", StringComparison.Ordinal))
381-
{
382-
name = name.Substring(1);
383-
}
378+
name = name.ToTarArchivePath();
384379

385380
header.LinkName = String.Empty;
386381
header.Name = name;

src/ICSharpCode.SharpZipLib/Tar/TarExtendedHeaderReader.cs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Text;
34

45
namespace ICSharpCode.SharpZipLib.Tar
@@ -26,7 +27,10 @@ public class TarExtendedHeaderReader
2627

2728
private int state = LENGTH;
2829

29-
private static readonly byte[] StateNext = new[] { (byte)' ', (byte)'=', (byte)'\n' };
30+
private int currHeaderLength;
31+
private int currHeaderRead;
32+
33+
private static readonly byte[] StateNext = { (byte)' ', (byte)'=', (byte)'\n' };
3034

3135
/// <summary>
3236
/// Creates a new <see cref="TarExtendedHeaderReader"/>.
@@ -46,23 +50,46 @@ public void Read(byte[] buffer, int length)
4650
for (int i = 0; i < length; i++)
4751
{
4852
byte next = buffer[i];
53+
54+
var foundStateEnd = state == VALUE
55+
? currHeaderRead == currHeaderLength -1
56+
: next == StateNext[state];
4957

50-
if (next == StateNext[state])
58+
if (foundStateEnd)
5159
{
5260
Flush();
5361
headerParts[state] = sb.ToString();
5462
sb.Clear();
55-
63+
5664
if (++state == END)
5765
{
58-
headers.Add(headerParts[KEY], headerParts[VALUE]);
66+
if (!headers.ContainsKey(headerParts[KEY]))
67+
{
68+
headers.Add(headerParts[KEY], headerParts[VALUE]);
69+
}
70+
5971
headerParts = new string[3];
72+
currHeaderLength = 0;
73+
currHeaderRead = 0;
6074
state = LENGTH;
6175
}
76+
else
77+
{
78+
currHeaderRead++;
79+
}
80+
81+
82+
if (state != VALUE) continue;
83+
84+
if (int.TryParse(headerParts[LENGTH], out var vl))
85+
{
86+
currHeaderLength = vl;
87+
}
6288
}
6389
else
6490
{
6591
byteBuffer[bbIndex++] = next;
92+
currHeaderRead++;
6693
if (bbIndex == 4)
6794
Flush();
6895
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.IO;
2+
using ICSharpCode.SharpZipLib.Core;
3+
4+
namespace ICSharpCode.SharpZipLib.Tar
5+
{
6+
internal static class TarStringExtension
7+
{
8+
public static string ToTarArchivePath(this string s)
9+
{
10+
return PathUtils.DropPathRoot(s).Replace(Path.DirectorySeparatorChar, '/');
11+
}
12+
}
13+
}

src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs

Lines changed: 51 additions & 20 deletions
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

src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -521,9 +521,10 @@ internal void PutNextEntry(Stream stream, ZipEntry entry, long streamOffset = 0,
521521
public async Task PutNextEntryAsync(ZipEntry entry, CancellationToken ct = default)
522522
{
523523
if (curEntry != null) await CloseEntryAsync(ct);
524+
var position = CanPatchEntries ? baseOutputStream_.Position : -1;
524525
await baseOutputStream_.WriteProcToStreamAsync(s =>
525526
{
526-
PutNextEntry(s, entry, baseOutputStream_.Position);
527+
PutNextEntry(s, entry, position);
527528
}, ct);
528529

529530
if (!entry.IsCrypted) return;
@@ -807,11 +808,11 @@ public override void Write(byte[] buffer, int offset, int count)
807808

808809
private void CopyAndEncrypt(byte[] buffer, int offset, int count)
809810
{
810-
const int CopyBufferSize = 4096;
811-
byte[] localBuffer = new byte[CopyBufferSize];
811+
const int copyBufferSize = 4096;
812+
byte[] localBuffer = new byte[copyBufferSize];
812813
while (count > 0)
813814
{
814-
int bufferCount = (count < CopyBufferSize) ? count : CopyBufferSize;
815+
int bufferCount = (count < copyBufferSize) ? count : copyBufferSize;
815816

816817
Array.Copy(buffer, offset, localBuffer, 0, bufferCount);
817818
EncryptBlock(localBuffer, 0, bufferCount);

test/ICSharpCode.SharpZipLib.Tests/Tar/TarTests.cs

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ public void ParseHeaderWithEncoding(int length, string encodingName)
833833
reparseHeader.ParseBuffer(headerbytes, enc);
834834
Assert.AreEqual(name, reparseHeader.Name);
835835
// top 100 bytes are name field in tar header
836-
for (int i = 0;i < encodedName.Length;i++)
836+
for (int i = 0; i < encodedName.Length; i++)
837837
{
838838
Assert.AreEqual(encodedName[i], headerbytes[i]);
839839
}
@@ -852,9 +852,9 @@ public async Task StreamWithJapaneseNameAsync(int length, string encodingName)
852852
var entryName = new string((char)0x3042, length);
853853
var data = new byte[32];
854854
var encoding = Encoding.GetEncoding(encodingName);
855-
using(var memoryStream = new MemoryStream())
855+
using (var memoryStream = new MemoryStream())
856856
{
857-
using(var tarOutput = new TarOutputStream(memoryStream, encoding))
857+
using (var tarOutput = new TarOutputStream(memoryStream, encoding))
858858
{
859859
var entry = TarEntry.CreateTarEntry(entryName);
860860
entry.Size = 32;
@@ -874,5 +874,47 @@ public async Task StreamWithJapaneseNameAsync(int length, string encodingName)
874874
File.WriteAllBytes(Path.Combine(Path.GetTempPath(), $"jpnametest_{length}_{encodingName}.tar"), memoryStream.ToArray());
875875
}
876876
}
877+
/// <summary>
878+
/// This test could be considered integration test. it creates a tar archive with the root directory specified
879+
/// Then extracts it and compares the two folders. This used to fail on unix due to issues with root folder handling
880+
/// in the tar archive.
881+
/// </summary>
882+
[Test]
883+
[Category("Tar")]
884+
public void RootPathIsRespected()
885+
{
886+
using (var extractDirectory = new TempDir())
887+
using (var tarFileName = new TempFile())
888+
using (var tempDirectory = new TempDir())
889+
{
890+
tempDirectory.CreateDummyFile();
891+
892+
using (var tarFile = File.Open(tarFileName.FullName, FileMode.Create))
893+
{
894+
using (var tarOutputStream = TarArchive.CreateOutputTarArchive(tarFile))
895+
{
896+
tarOutputStream.RootPath = tempDirectory.FullName;
897+
var entry = TarEntry.CreateEntryFromFile(tempDirectory.FullName);
898+
tarOutputStream.WriteEntry(entry, true);
899+
}
900+
}
901+
902+
using (var file = File.OpenRead(tarFileName.FullName))
903+
{
904+
using (var archive = TarArchive.CreateInputTarArchive(file, Encoding.UTF8))
905+
{
906+
archive.ExtractContents(extractDirectory.FullName);
907+
}
908+
}
909+
910+
var expectationDirectory = new DirectoryInfo(tempDirectory.FullName);
911+
foreach (var checkFile in expectationDirectory.GetFiles("", SearchOption.AllDirectories))
912+
{
913+
var relativePath = checkFile.FullName.Substring(expectationDirectory.FullName.Length + 1);
914+
FileAssert.Exists(Path.Combine(extractDirectory.FullName, relativePath));
915+
FileAssert.AreEqual(checkFile.FullName, Path.Combine(extractDirectory.FullName, relativePath));
916+
}
917+
}
918+
}
877919
}
878920
}

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,15 @@ public class MemoryStreamWithoutSeek : TrackedMemoryStream
177177
/// </summary>
178178
/// <value></value>
179179
/// <returns>true if the stream is open.</returns>
180-
public override bool CanSeek
180+
public override bool CanSeek => false;
181+
182+
/// <inheritdoc />
183+
public override long Position
181184
{
182-
get
183-
{
184-
return false;
185-
}
185+
get => throw new NotSupportedException("Getting position is not supported");
186+
set => throw new NotSupportedException("Setting position is not supported");
186187
}
188+
187189
}
188190

189191
/// <summary>

0 commit comments

Comments
 (0)