< Summary

Line coverage
75%
Covered lines: 133
Uncovered lines: 44
Coverable lines: 177
Total lines: 339
Line coverage: 75.1%
Branch coverage
66%
Covered branches: 36
Total branches: 54
Branch coverage: 66.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\ZipHelper.Async.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Diagnostics;
 6using System.Threading;
 7using System.Threading.Tasks;
 8
 9namespace System.IO.Compression;
 10
 11internal static partial class ZipHelper
 12{
 13    // Asynchronously assumes all bytes of signatureToFind are non zero, looks backwards from current position in stream
 14    // assumes maxBytesToRead is positive, ensures to not read beyond the provided max number of bytes,
 15    // if the signature is found then returns true and positions stream at first byte of signature
 16    // if the signature is not found, returns false
 17    internal static async Task<bool> SeekBackwardsToSignatureAsync(Stream stream, ReadOnlyMemory<byte> signatureToFind, 
 77218    {
 77219        cancellationToken.ThrowIfCancellationRequested();
 20
 77221        Debug.Assert(signatureToFind.Length != 0);
 77222        Debug.Assert(maxBytesToRead > 0);
 23
 24        // This method reads blocks of BackwardsSeekingBufferSize bytes, searching each block for signatureToFind.
 25        // A simple LastIndexOf(signatureToFind) doesn't account for cases where signatureToFind is split, starting in
 26        // one block and ending in another.
 27        // To account for this, we read blocks of BackwardsSeekingBufferSize bytes, but seek backwards by
 28        // [BackwardsSeekingBufferSize - signatureToFind.Length] bytes. This guarantees that signatureToFind will not be
 29        // split between two consecutive blocks, at the cost of reading [signatureToFind.Length] duplicate bytes in each
 77230        int bufferPointer = 0;
 77231        byte[] buffer = ArrayPool<byte>.Shared.Rent(BackwardsSeekingBufferSize);
 77232        Memory<byte> bufferMemory = buffer.AsMemory(0, BackwardsSeekingBufferSize);
 33
 34        try
 77235        {
 77236            bool outOfBytes = false;
 77237            bool signatureFound = false;
 38
 77239            int totalBytesRead = 0;
 40
 78341            while (!signatureFound && !outOfBytes && totalBytesRead < maxBytesToRead)
 77242            {
 77243                int overlap = totalBytesRead == 0 ? 0 : signatureToFind.Length;
 44
 77245                if (maxBytesToRead - totalBytesRead + overlap < bufferMemory.Length)
 6946                {
 47                    // If we have less than a full buffer left to read, we adjust the buffer size.
 6948                    bufferMemory = bufferMemory.Slice(0, maxBytesToRead - totalBytesRead + overlap);
 6949                }
 50
 77251                int bytesRead = await SeekBackwardsAndReadAsync(stream, bufferMemory, overlap, cancellationToken).Config
 52
 77253                outOfBytes = bytesRead < bufferMemory.Length;
 77254                if (bytesRead < bufferMemory.Length)
 70355                {
 70356                    bufferMemory = bufferMemory.Slice(0, bytesRead);
 70357                }
 58
 77259                bufferPointer = bufferMemory.Span.LastIndexOf(signatureToFind.Span);
 77260                Debug.Assert(bufferPointer < bufferMemory.Length);
 61
 77262                totalBytesRead += bytesRead - overlap;
 63
 77264                if (bufferPointer != -1)
 76165                {
 76166                    signatureFound = true;
 76167                    break;
 68                }
 1169            }
 70
 77271            if (!signatureFound)
 1172            {
 1173                return false;
 74            }
 75            else
 76176            {
 76177                stream.Seek(bufferPointer, SeekOrigin.Current);
 76178                return true;
 79            }
 80        }
 81        finally
 77282        {
 77283            ArrayPool<byte>.Shared.Return(buffer);
 77284        }
 77285    }
 86
 87    // Asynchronously returns the number of bytes actually read.
 88    // Allows successive buffers to overlap by a number of bytes. This handles cases where
 89    // the value being searched for straddles buffers (i.e. where the first buffer ends with the
 90    // first X bytes being searched for, and the second buffer begins with the remaining bytes.)
 91    private static async Task<int> SeekBackwardsAndReadAsync(Stream stream, Memory<byte> buffer, int overlap, Cancellati
 77292    {
 77293        cancellationToken.ThrowIfCancellationRequested();
 94
 95        int bytesRead;
 96
 77297        if (stream.Position >= buffer.Length)
 6998        {
 6999            Debug.Assert(overlap <= buffer.Length);
 69100            stream.Seek(-(buffer.Length - overlap), SeekOrigin.Current);
 69101            bytesRead = await stream.ReadAtLeastAsync(buffer, buffer.Length, throwOnEndOfStream: true, cancellationToken
 69102            stream.Seek(-buffer.Length, SeekOrigin.Current);
 69103        }
 104        else
 703105        {
 703106            int bytesToRead = (int)stream.Position;
 703107            stream.Seek(0, SeekOrigin.Begin);
 703108            bytesRead = await stream.ReadAtLeastAsync(buffer, bytesToRead, throwOnEndOfStream: true, cancellationToken).
 703109            stream.Seek(0, SeekOrigin.Begin);
 703110        }
 111
 772112        return bytesRead;
 772113    }
 114}

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\ZipHelper.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Diagnostics;
 6using System.Text;
 7
 8namespace System.IO.Compression;
 9
 10internal static partial class ZipHelper
 11{
 12    internal const uint Mask32Bit = 0xFFFFFFFF;
 13    internal const ushort Mask16Bit = 0xFFFF;
 14
 15    private const int BackwardsSeekingBufferSize = 4096;
 16
 17    internal const int ValidZipDate_YearMin = 1980;
 18    internal const int ValidZipDate_YearMax = 2107;
 19
 120    private static readonly DateTime s_invalidDateIndicator = new DateTime(ValidZipDate_YearMin, 1, 1, 0, 0, 0);
 21
 22    internal static Encoding GetEncoding(string text)
 023    {
 024        if (text.AsSpan().ContainsAnyExceptInRange((char)32, (char)126))
 025        {
 26            // The Zip Format uses code page 437 when the Unicode bit is not set. This format
 27            // is the same as ASCII for characters 32-126 but differs otherwise. If we can fit
 28            // the string into CP437 then we treat ASCII as acceptable.
 029            return Encoding.UTF8;
 30        }
 31
 032        return Encoding.ASCII;
 033    }
 34
 35    // will silently return InvalidDateIndicator if the uint is not a valid Dos DateTime
 36    internal static DateTime DosTimeToDateTime(uint dateTime)
 215637    {
 215638        if (dateTime == 0)
 50439        {
 50440            return s_invalidDateIndicator;
 41        }
 42
 43        // DosTime format 32 bits
 44        // Year: 7 bits, 0 is ValidZipDate_YearMin, unsigned (ValidZipDate_YearMin = 1980)
 45        // Month: 4 bits
 46        // Day: 5 bits
 47        // Hour: 5
 48        // Minute: 6 bits
 49        // Second: 5 bits
 50
 51        // do the bit shift as unsigned because the fields are unsigned, but
 52        // we can safely convert to int, because they won't be too big
 165253        int year = (int)(ValidZipDate_YearMin + (dateTime >> 25));
 165254        int month = (int)((dateTime >> 21) & 0xF);
 165255        int day = (int)((dateTime >> 16) & 0x1F);
 165256        int hour = (int)((dateTime >> 11) & 0x1F);
 165257        int minute = (int)((dateTime >> 5) & 0x3F);
 165258        int second = (int)((dateTime & 0x001F) * 2); // only 5 bits for second, so we only have a granularity of 2 sec.
 59
 60        try
 165261        {
 165262            return new DateTime(year, month, day, hour, minute, second, 0);
 63        }
 87664        catch (ArgumentOutOfRangeException)
 87665        {
 87666            return s_invalidDateIndicator;
 67        }
 068        catch (ArgumentException)
 069        {
 070            return s_invalidDateIndicator;
 71        }
 215672    }
 73
 74    // assume date time has passed IsConvertibleToDosTime
 75    internal static uint DateTimeToDosTime(DateTime dateTime)
 076    {
 77        // DateTime must be Convertible to DosTime:
 078        Debug.Assert(ValidZipDate_YearMin <= dateTime.Year && dateTime.Year <= ValidZipDate_YearMax);
 79
 080        int ret = ((dateTime.Year - ValidZipDate_YearMin) & 0x7F);
 081        ret = (ret << 4) + dateTime.Month;
 082        ret = (ret << 5) + dateTime.Day;
 083        ret = (ret << 5) + dateTime.Hour;
 084        ret = (ret << 6) + dateTime.Minute;
 085        ret = (ret << 5) + (dateTime.Second / 2); // only 5 bits for second, so we only have a granularity of 2 sec.
 086        return (uint)ret;
 087    }
 88
 89    // Assumes all bytes of signatureToFind are non zero, looks backwards from current position in stream,
 90    // assumes maxBytesToRead is positive, ensures to not read beyond the provided max number of bytes,
 91    // if the signature is found then returns true and positions stream at first byte of signature
 92    // if the signature is not found, returns false
 93    internal static bool SeekBackwardsToSignature(Stream stream, ReadOnlySpan<byte> signatureToFind, int maxBytesToRead)
 77294    {
 77295        Debug.Assert(signatureToFind.Length != 0);
 77296        Debug.Assert(maxBytesToRead > 0);
 97
 98        // This method reads blocks of BackwardsSeekingBufferSize bytes, searching each block for signatureToFind.
 99        // A simple LastIndexOf(signatureToFind) doesn't account for cases where signatureToFind is split, starting in
 100        // one block and ending in another.
 101        // To account for this, we read blocks of BackwardsSeekingBufferSize bytes, but seek backwards by
 102        // [BackwardsSeekingBufferSize - signatureToFind.Length] bytes. This guarantees that signatureToFind will not be
 103        // split between two consecutive blocks, at the cost of reading [signatureToFind.Length] duplicate bytes in each
 772104        int bufferPointer = 0;
 772105        byte[] buffer = ArrayPool<byte>.Shared.Rent(BackwardsSeekingBufferSize);
 772106        Span<byte> bufferSpan = buffer.AsSpan(0, BackwardsSeekingBufferSize);
 107
 108        try
 772109        {
 772110            bool outOfBytes = false;
 772111            bool signatureFound = false;
 112
 772113            int totalBytesRead = 0;
 114
 783115            while (!signatureFound && !outOfBytes && totalBytesRead < maxBytesToRead)
 772116            {
 772117                int overlap = totalBytesRead == 0 ? 0 : signatureToFind.Length;
 118
 772119                if (maxBytesToRead - totalBytesRead + overlap < bufferSpan.Length)
 69120                {
 121                    // If we have less than a full buffer left to read, we adjust the buffer size.
 69122                    bufferSpan = bufferSpan.Slice(0, maxBytesToRead - totalBytesRead + overlap);
 69123                }
 124
 772125                int bytesRead = SeekBackwardsAndRead(stream, bufferSpan, overlap);
 126
 772127                outOfBytes = bytesRead < bufferSpan.Length;
 772128                if (bytesRead < bufferSpan.Length)
 703129                {
 703130                    bufferSpan = bufferSpan.Slice(0, bytesRead);
 703131                }
 132
 772133                bufferPointer = bufferSpan.LastIndexOf(signatureToFind);
 772134                Debug.Assert(bufferPointer < bufferSpan.Length);
 135
 772136                totalBytesRead += bytesRead - overlap;
 137
 772138                if (bufferPointer != -1)
 761139                {
 761140                    signatureFound = true;
 761141                    break;
 142                }
 11143            }
 144
 772145            if (!signatureFound)
 11146            {
 11147                return false;
 148            }
 149            else
 761150            {
 761151                stream.Seek(bufferPointer, SeekOrigin.Current);
 761152                return true;
 153            }
 154        }
 155        finally
 772156        {
 772157            ArrayPool<byte>.Shared.Return(buffer);
 772158        }
 772159    }
 160
 161    // Returns the number of bytes actually read.
 162    // Allows successive buffers to overlap by a number of bytes. This handles cases where
 163    // the value being searched for straddles buffers (i.e. where the first buffer ends with the
 164    // first X bytes being searched for, and the second buffer begins with the remaining bytes.)
 165    private static int SeekBackwardsAndRead(Stream stream, Span<byte> buffer, int overlap)
 772166    {
 167        int bytesRead;
 168
 772169        if (stream.Position >= buffer.Length)
 69170        {
 69171            Debug.Assert(overlap <= buffer.Length);
 69172            stream.Seek(-(buffer.Length - overlap), SeekOrigin.Current);
 69173            bytesRead = stream.ReadAtLeast(buffer, buffer.Length, throwOnEndOfStream: true);
 69174            stream.Seek(-buffer.Length, SeekOrigin.Current);
 69175        }
 176        else
 703177        {
 703178            int bytesToRead = (int)stream.Position;
 703179            stream.Seek(0, SeekOrigin.Begin);
 703180            bytesRead = stream.ReadAtLeast(buffer, bytesToRead, throwOnEndOfStream: true);
 703181            stream.Seek(0, SeekOrigin.Begin);
 703182        }
 183
 772184        return bytesRead;
 772185    }
 186    // Converts the specified string into bytes using the optional specified encoding.
 187    // If the encoding null, then the encoding is calculated from the string itself.
 188    // If maxBytes is greater than zero, the returned string will be truncated to a total
 189    // number of characters whose bytes do not add up to more than that number.
 190    internal static byte[] GetEncodedTruncatedBytesFromString(string? text, Encoding? encoding, int maxBytes, out bool i
 0191    {
 0192        Debug.Assert(maxBytes >= 0, "maxBytes must be non-negative");
 193
 0194        if (string.IsNullOrEmpty(text))
 0195        {
 0196            isUTF8 = false;
 0197            return Array.Empty<byte>();
 198        }
 199
 0200        encoding ??= GetEncoding(text);
 0201        isUTF8 = encoding.CodePage == 65001;
 202
 0203        if (maxBytes == 0)
 0204        {
 0205            return encoding.GetBytes(text);
 206        }
 207
 0208        byte[] bytes = encoding.GetBytes(text);
 209
 0210        if (maxBytes < bytes.Length)
 0211        {
 0212            if (isUTF8)
 0213            {
 0214                while ((bytes[maxBytes] & 0xC0) == 0x80)
 0215                {
 0216                    maxBytes--;
 0217                }
 0218            }
 219
 0220            bytes = bytes[0..maxBytes];
 0221        }
 222
 0223        return bytes;
 0224    }
 225}