Skip to content

Commit 215f747

Browse files
committed
Use SemaphoreSlim to prevent concurrent basestream access
1 parent 1b9fcfc commit 215f747

File tree

1 file changed

+64
-28
lines changed

1 file changed

+64
-28
lines changed

src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
using System.IO;
1010
using System.Security.Cryptography;
1111
using System.Text;
12+
using System.Threading;
13+
using System.Threading.Tasks;
1214

1315
namespace ICSharpCode.SharpZipLib.Zip
1416
{
@@ -1079,8 +1081,10 @@ private enum HeaderTest
10791081
/// <returns>The offset of the entries data in the file</returns>
10801082
private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
10811083
{
1082-
lock (baseStream_)
1084+
try
10831085
{
1086+
this.baseStreamSemaphore.Wait();
1087+
10841088
bool testHeader = (tests & HeaderTest.Header) != 0;
10851089
bool testData = (tests & HeaderTest.Extract) != 0;
10861090

@@ -1350,6 +1354,10 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
13501354
int extraLength = storedNameLength + extraDataLength;
13511355
return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength;
13521356
}
1357+
finally
1358+
{
1359+
this.baseStreamSemaphore.Release();
1360+
}
13531361
}
13541362

13551363
#endregion Archive Testing
@@ -3349,11 +3357,21 @@ private void DisposeInternal(bool disposing)
33493357
isDisposed_ = true;
33503358
entries_ = Empty.Array<ZipEntry>();
33513359

3352-
if (IsStreamOwner && (baseStream_ != null))
3360+
if (disposing)
33533361
{
3354-
lock (baseStream_)
3362+
try
33553363
{
3356-
baseStream_.Dispose();
3364+
this.baseStreamSemaphore.Wait();
3365+
3366+
if (IsStreamOwner)
3367+
{
3368+
baseStream_?.Dispose();
3369+
}
3370+
}
3371+
finally
3372+
{
3373+
this.baseStreamSemaphore.Release();
3374+
this.baseStreamSemaphore.Dispose();
33573375
}
33583376
}
33593377

@@ -3787,6 +3805,7 @@ private static void WriteEncryptionHeader(Stream stream, long crcValue)
37873805
private ZipEntry[] entries_;
37883806
private byte[] key;
37893807
private bool isNewArchive_;
3808+
private readonly SemaphoreSlim baseStreamSemaphore = new SemaphoreSlim(1, 1);
37903809

37913810
// Default is dynamic which is not backwards compatible and can cause problems
37923811
// with XP's built in compression which cant read Zip64 archives.
@@ -4156,16 +4175,14 @@ public PartialInputStream(ZipFile zipFile, long start, long length)
41564175
// uses reader here....
41574176
zipFile_ = zipFile;
41584177
baseStream_ = zipFile_.baseStream_;
4178+
this.semaphore = zipFile.baseStreamSemaphore;
41594179
readPos_ = start;
41604180
end_ = start + length;
41614181
}
41624182

41634183
#endregion Constructors
41644184

4165-
/// <summary>
4166-
/// Read a byte from this stream.
4167-
/// </summary>
4168-
/// <returns>Returns the byte read or -1 on end of stream.</returns>
4185+
/// <inheritdoc/>
41694186
public override int ReadByte()
41704187
{
41714188
if (readPos_ >= end_)
@@ -4174,32 +4191,40 @@ public override int ReadByte()
41744191
return -1;
41754192
}
41764193

4177-
lock (baseStream_)
4194+
try
41784195
{
4196+
this.semaphore.Wait();
4197+
41794198
baseStream_.Seek(readPos_++, SeekOrigin.Begin);
41804199
return baseStream_.ReadByte();
41814200
}
4201+
finally
4202+
{
4203+
this.semaphore.Release();
4204+
}
41824205
}
41834206

4184-
/// <summary>
4185-
/// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
4186-
/// </summary>
4187-
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param>
4188-
/// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param>
4189-
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
4190-
/// <returns>
4191-
/// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
4192-
/// </returns>
4193-
/// <exception cref="System.ArgumentException">The sum of offset and count is larger than the buffer length. </exception>
4194-
/// <exception cref="System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
4195-
/// <exception cref="System.NotSupportedException">The stream does not support reading. </exception>
4196-
/// <exception cref="System.ArgumentNullException">buffer is null. </exception>
4197-
/// <exception cref="System.IO.IOException">An I/O error occurs. </exception>
4198-
/// <exception cref="System.ArgumentOutOfRangeException">offset or count is negative. </exception>
4207+
/// <inheritdoc/>
41994208
public override int Read(byte[] buffer, int offset, int count)
42004209
{
4201-
lock (baseStream_)
4210+
return ReadAsyncCore(buffer, offset, count, false, default).GetAwaiter().GetResult();
4211+
}
4212+
4213+
/// <inheritdoc/>
4214+
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
4215+
{
4216+
return ReadAsyncCore(buffer, offset, count, true, cancellationToken);
4217+
}
4218+
4219+
private async Task<int> ReadAsyncCore(byte[] buffer, int offset, int count, bool useAsync, CancellationToken cancellationToken)
4220+
{
4221+
try
42024222
{
4223+
if (useAsync)
4224+
await this.semaphore.WaitAsync(cancellationToken);
4225+
else
4226+
this.semaphore.Wait();
4227+
42034228
if (count > end_ - readPos_)
42044229
{
42054230
count = (int)(end_ - readPos_);
@@ -4208,19 +4233,29 @@ public override int Read(byte[] buffer, int offset, int count)
42084233
return 0;
42094234
}
42104235
}
4236+
42114237
// Protect against Stream implementations that throw away their buffer on every Seek
42124238
// (for example, Mono FileStream)
42134239
if (baseStream_.Position != readPos_)
42144240
{
42154241
baseStream_.Seek(readPos_, SeekOrigin.Begin);
42164242
}
4217-
int readCount = baseStream_.Read(buffer, offset, count);
4243+
4244+
var readCount = useAsync ?
4245+
await baseStream_.ReadAsync(buffer, offset, count, cancellationToken) :
4246+
baseStream_.Read(buffer, offset, count);
4247+
42184248
if (readCount > 0)
42194249
{
42204250
readPos_ += readCount;
42214251
}
4252+
42224253
return readCount;
42234254
}
4255+
finally
4256+
{
4257+
this.semaphore.Release();
4258+
}
42244259
}
42254260

42264261
/// <summary>
@@ -4386,12 +4421,13 @@ public override bool CanTimeout
43864421

43874422
#region Instance Fields
43884423

4389-
private ZipFile zipFile_;
4390-
private Stream baseStream_;
4424+
private readonly ZipFile zipFile_;
4425+
private readonly Stream baseStream_;
43914426
private readonly long start_;
43924427
private readonly long length_;
43934428
private long readPos_;
43944429
private readonly long end_;
4430+
private readonly SemaphoreSlim semaphore;
43954431

43964432
#endregion Instance Fields
43974433
}

0 commit comments

Comments
 (0)