< 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, 
 301518    {
 301519        cancellationToken.ThrowIfCancellationRequested();
 20
 301521        Debug.Assert(signatureToFind.Length != 0);
 301522        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
 301530        int bufferPointer = 0;
 301531        byte[] buffer = ArrayPool<byte>.Shared.Rent(BackwardsSeekingBufferSize);
 301532        Memory<byte> bufferMemory = buffer.AsMemory(0, BackwardsSeekingBufferSize);
 33
 34        try
 301535        {
 301536            bool outOfBytes = false;
 301537            bool signatureFound = false;
 38
 301539            int totalBytesRead = 0;
 40
 421241            while (!signatureFound && !outOfBytes && totalBytesRead < maxBytesToRead)
 301542            {
 301543                int overlap = totalBytesRead == 0 ? 0 : signatureToFind.Length;
 44
 301545                if (maxBytesToRead - totalBytesRead + overlap < bufferMemory.Length)
 128046                {
 47                    // If we have less than a full buffer left to read, we adjust the buffer size.
 128048                    bufferMemory = bufferMemory.Slice(0, maxBytesToRead - totalBytesRead + overlap);
 128049                }
 50
 301551                int bytesRead = await SeekBackwardsAndReadAsync(stream, bufferMemory, overlap, cancellationToken).Config
 52
 301553                outOfBytes = bytesRead < bufferMemory.Length;
 301554                if (bytesRead < bufferMemory.Length)
 173455                {
 173456                    bufferMemory = bufferMemory.Slice(0, bytesRead);
 173457                }
 58
 301559                bufferPointer = bufferMemory.Span.LastIndexOf(signatureToFind.Span);
 301560                Debug.Assert(bufferPointer < bufferMemory.Length);
 61
 301562                totalBytesRead += bytesRead - overlap;
 63
 301564                if (bufferPointer != -1)
 181865                {
 181866                    signatureFound = true;
 181867                    break;
 68                }
 119769            }
 70
 301571            if (!signatureFound)
 119772            {
 119773                return false;
 74            }
 75            else
 181876            {
 181877                stream.Seek(bufferPointer, SeekOrigin.Current);
 181878                return true;
 79            }
 80        }
 81        finally
 301582        {
 301583            ArrayPool<byte>.Shared.Return(buffer);
 301584        }
 301585    }
 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
 301592    {
 301593        cancellationToken.ThrowIfCancellationRequested();
 94
 95        int bytesRead;
 96
 301597        if (stream.Position >= buffer.Length)
 128098        {
 128099            Debug.Assert(overlap <= buffer.Length);
 1280100            stream.Seek(-(buffer.Length - overlap), SeekOrigin.Current);
 1280101            bytesRead = await stream.ReadAtLeastAsync(buffer, buffer.Length, throwOnEndOfStream: true, cancellationToken
 1280102            stream.Seek(-buffer.Length, SeekOrigin.Current);
 1280103        }
 104        else
 1735105        {
 1735106            int bytesToRead = (int)stream.Position;
 1735107            stream.Seek(0, SeekOrigin.Begin);
 1735108            bytesRead = await stream.ReadAtLeastAsync(buffer, bytesToRead, throwOnEndOfStream: true, cancellationToken).
 1735109            stream.Seek(0, SeekOrigin.Begin);
 1735110        }
 111
 3015112        return bytesRead;
 3015113    }
 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)
 2975637    {
 2975638        if (dateTime == 0)
 444839        {
 444840            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
 2530853        int year = (int)(ValidZipDate_YearMin + (dateTime >> 25));
 2530854        int month = (int)((dateTime >> 21) & 0xF);
 2530855        int day = (int)((dateTime >> 16) & 0x1F);
 2530856        int hour = (int)((dateTime >> 11) & 0x1F);
 2530857        int minute = (int)((dateTime >> 5) & 0x3F);
 2530858        int second = (int)((dateTime & 0x001F) * 2); // only 5 bits for second, so we only have a granularity of 2 sec.
 59
 60        try
 2530861        {
 2530862            return new DateTime(year, month, day, hour, minute, second, 0);
 63        }
 1763664        catch (ArgumentOutOfRangeException)
 1763665        {
 1763666            return s_invalidDateIndicator;
 67        }
 068        catch (ArgumentException)
 069        {
 070            return s_invalidDateIndicator;
 71        }
 2975672    }
 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)
 301594    {
 301595        Debug.Assert(signatureToFind.Length != 0);
 301596        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
 3015104        int bufferPointer = 0;
 3015105        byte[] buffer = ArrayPool<byte>.Shared.Rent(BackwardsSeekingBufferSize);
 3015106        Span<byte> bufferSpan = buffer.AsSpan(0, BackwardsSeekingBufferSize);
 107
 108        try
 3015109        {
 3015110            bool outOfBytes = false;
 3015111            bool signatureFound = false;
 112
 3015113            int totalBytesRead = 0;
 114
 4212115            while (!signatureFound && !outOfBytes && totalBytesRead < maxBytesToRead)
 3015116            {
 3015117                int overlap = totalBytesRead == 0 ? 0 : signatureToFind.Length;
 118
 3015119                if (maxBytesToRead - totalBytesRead + overlap < bufferSpan.Length)
 1280120                {
 121                    // If we have less than a full buffer left to read, we adjust the buffer size.
 1280122                    bufferSpan = bufferSpan.Slice(0, maxBytesToRead - totalBytesRead + overlap);
 1280123                }
 124
 3015125                int bytesRead = SeekBackwardsAndRead(stream, bufferSpan, overlap);
 126
 3015127                outOfBytes = bytesRead < bufferSpan.Length;
 3015128                if (bytesRead < bufferSpan.Length)
 1734129                {
 1734130                    bufferSpan = bufferSpan.Slice(0, bytesRead);
 1734131                }
 132
 3015133                bufferPointer = bufferSpan.LastIndexOf(signatureToFind);
 3015134                Debug.Assert(bufferPointer < bufferSpan.Length);
 135
 3015136                totalBytesRead += bytesRead - overlap;
 137
 3015138                if (bufferPointer != -1)
 1818139                {
 1818140                    signatureFound = true;
 1818141                    break;
 142                }
 1197143            }
 144
 3015145            if (!signatureFound)
 1197146            {
 1197147                return false;
 148            }
 149            else
 1818150            {
 1818151                stream.Seek(bufferPointer, SeekOrigin.Current);
 1818152                return true;
 153            }
 154        }
 155        finally
 3015156        {
 3015157            ArrayPool<byte>.Shared.Return(buffer);
 3015158        }
 3015159    }
 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)
 3015166    {
 167        int bytesRead;
 168
 3015169        if (stream.Position >= buffer.Length)
 1280170        {
 1280171            Debug.Assert(overlap <= buffer.Length);
 1280172            stream.Seek(-(buffer.Length - overlap), SeekOrigin.Current);
 1280173            bytesRead = stream.ReadAtLeast(buffer, buffer.Length, throwOnEndOfStream: true);
 1280174            stream.Seek(-buffer.Length, SeekOrigin.Current);
 1280175        }
 176        else
 1735177        {
 1735178            int bytesToRead = (int)stream.Position;
 1735179            stream.Seek(0, SeekOrigin.Begin);
 1735180            bytesRead = stream.ReadAtLeast(buffer, bytesToRead, throwOnEndOfStream: true);
 1735181            stream.Seek(0, SeekOrigin.Begin);
 1735182        }
 183
 3015184        return bytesRead;
 3015185    }
 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}