< Summary

Line coverage
71%
Covered lines: 126
Uncovered lines: 51
Coverable lines: 177
Total lines: 339
Line coverage: 71.1%
Branch coverage
51%
Covered branches: 28
Total branches: 54
Branch coverage: 51.8%
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, 
 74718    {
 74719        cancellationToken.ThrowIfCancellationRequested();
 20
 74721        Debug.Assert(signatureToFind.Length != 0);
 74722        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
 74730        int bufferPointer = 0;
 74731        byte[] buffer = ArrayPool<byte>.Shared.Rent(BackwardsSeekingBufferSize);
 74732        Memory<byte> bufferMemory = buffer.AsMemory(0, BackwardsSeekingBufferSize);
 33
 34        try
 74735        {
 74736            bool outOfBytes = false;
 74737            bool signatureFound = false;
 38
 74739            int totalBytesRead = 0;
 40
 74741            while (!signatureFound && !outOfBytes && totalBytesRead < maxBytesToRead)
 74742            {
 74743                int overlap = totalBytesRead == 0 ? 0 : signatureToFind.Length;
 44
 74745                if (maxBytesToRead - totalBytesRead + overlap < bufferMemory.Length)
 046                {
 47                    // If we have less than a full buffer left to read, we adjust the buffer size.
 048                    bufferMemory = bufferMemory.Slice(0, maxBytesToRead - totalBytesRead + overlap);
 049                }
 50
 74751                int bytesRead = await SeekBackwardsAndReadAsync(stream, bufferMemory, overlap, cancellationToken).Config
 52
 74753                outOfBytes = bytesRead < bufferMemory.Length;
 74754                if (bytesRead < bufferMemory.Length)
 74755                {
 74756                    bufferMemory = bufferMemory.Slice(0, bytesRead);
 74757                }
 58
 74759                bufferPointer = bufferMemory.Span.LastIndexOf(signatureToFind.Span);
 74760                Debug.Assert(bufferPointer < bufferMemory.Length);
 61
 74762                totalBytesRead += bytesRead - overlap;
 63
 74764                if (bufferPointer != -1)
 74765                {
 74766                    signatureFound = true;
 74767                    break;
 68                }
 069            }
 70
 74771            if (!signatureFound)
 072            {
 073                return false;
 74            }
 75            else
 74776            {
 74777                stream.Seek(bufferPointer, SeekOrigin.Current);
 74778                return true;
 79            }
 80        }
 81        finally
 74782        {
 74783            ArrayPool<byte>.Shared.Return(buffer);
 74784        }
 74785    }
 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
 74792    {
 74793        cancellationToken.ThrowIfCancellationRequested();
 94
 95        int bytesRead;
 96
 74797        if (stream.Position >= buffer.Length)
 098        {
 099            Debug.Assert(overlap <= buffer.Length);
 0100            stream.Seek(-(buffer.Length - overlap), SeekOrigin.Current);
 0101            bytesRead = await stream.ReadAtLeastAsync(buffer, buffer.Length, throwOnEndOfStream: true, cancellationToken
 0102            stream.Seek(-buffer.Length, SeekOrigin.Current);
 0103        }
 104        else
 747105        {
 747106            int bytesToRead = (int)stream.Position;
 747107            stream.Seek(0, SeekOrigin.Begin);
 747108            bytesRead = await stream.ReadAtLeastAsync(buffer, bytesToRead, throwOnEndOfStream: true, cancellationToken).
 747109            stream.Seek(0, SeekOrigin.Begin);
 747110        }
 111
 747112        return bytesRead;
 747113    }
 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
 020    private static readonly DateTime s_invalidDateIndicator = new DateTime(ValidZipDate_YearMin, 1, 1, 0, 0, 0);
 21
 22    internal static Encoding GetEncoding(string text)
 149423    {
 149424        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
 149432        return Encoding.ASCII;
 149433    }
 34
 35    // will silently return InvalidDateIndicator if the uint is not a valid Dos DateTime
 36    internal static DateTime DosTimeToDateTime(uint dateTime)
 149437    {
 149438        if (dateTime == 0)
 039        {
 040            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
 149453        int year = (int)(ValidZipDate_YearMin + (dateTime >> 25));
 149454        int month = (int)((dateTime >> 21) & 0xF);
 149455        int day = (int)((dateTime >> 16) & 0x1F);
 149456        int hour = (int)((dateTime >> 11) & 0x1F);
 149457        int minute = (int)((dateTime >> 5) & 0x3F);
 149458        int second = (int)((dateTime & 0x001F) * 2); // only 5 bits for second, so we only have a granularity of 2 sec.
 59
 60        try
 149461        {
 149462            return new DateTime(year, month, day, hour, minute, second, 0);
 63        }
 064        catch (ArgumentOutOfRangeException)
 065        {
 066            return s_invalidDateIndicator;
 67        }
 068        catch (ArgumentException)
 069        {
 070            return s_invalidDateIndicator;
 71        }
 149472    }
 73
 74    // assume date time has passed IsConvertibleToDosTime
 75    internal static uint DateTimeToDosTime(DateTime dateTime)
 687676    {
 77        // DateTime must be Convertible to DosTime:
 687678        Debug.Assert(ValidZipDate_YearMin <= dateTime.Year && dateTime.Year <= ValidZipDate_YearMax);
 79
 687680        int ret = ((dateTime.Year - ValidZipDate_YearMin) & 0x7F);
 687681        ret = (ret << 4) + dateTime.Month;
 687682        ret = (ret << 5) + dateTime.Day;
 687683        ret = (ret << 5) + dateTime.Hour;
 687684        ret = (ret << 6) + dateTime.Minute;
 687685        ret = (ret << 5) + (dateTime.Second / 2); // only 5 bits for second, so we only have a granularity of 2 sec.
 687686        return (uint)ret;
 687687    }
 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)
 74794    {
 74795        Debug.Assert(signatureToFind.Length != 0);
 74796        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
 747104        int bufferPointer = 0;
 747105        byte[] buffer = ArrayPool<byte>.Shared.Rent(BackwardsSeekingBufferSize);
 747106        Span<byte> bufferSpan = buffer.AsSpan(0, BackwardsSeekingBufferSize);
 107
 108        try
 747109        {
 747110            bool outOfBytes = false;
 747111            bool signatureFound = false;
 112
 747113            int totalBytesRead = 0;
 114
 747115            while (!signatureFound && !outOfBytes && totalBytesRead < maxBytesToRead)
 747116            {
 747117                int overlap = totalBytesRead == 0 ? 0 : signatureToFind.Length;
 118
 747119                if (maxBytesToRead - totalBytesRead + overlap < bufferSpan.Length)
 0120                {
 121                    // If we have less than a full buffer left to read, we adjust the buffer size.
 0122                    bufferSpan = bufferSpan.Slice(0, maxBytesToRead - totalBytesRead + overlap);
 0123                }
 124
 747125                int bytesRead = SeekBackwardsAndRead(stream, bufferSpan, overlap);
 126
 747127                outOfBytes = bytesRead < bufferSpan.Length;
 747128                if (bytesRead < bufferSpan.Length)
 747129                {
 747130                    bufferSpan = bufferSpan.Slice(0, bytesRead);
 747131                }
 132
 747133                bufferPointer = bufferSpan.LastIndexOf(signatureToFind);
 747134                Debug.Assert(bufferPointer < bufferSpan.Length);
 135
 747136                totalBytesRead += bytesRead - overlap;
 137
 747138                if (bufferPointer != -1)
 747139                {
 747140                    signatureFound = true;
 747141                    break;
 142                }
 0143            }
 144
 747145            if (!signatureFound)
 0146            {
 0147                return false;
 148            }
 149            else
 747150            {
 747151                stream.Seek(bufferPointer, SeekOrigin.Current);
 747152                return true;
 153            }
 154        }
 155        finally
 747156        {
 747157            ArrayPool<byte>.Shared.Return(buffer);
 747158        }
 747159    }
 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)
 747166    {
 167        int bytesRead;
 168
 747169        if (stream.Position >= buffer.Length)
 0170        {
 0171            Debug.Assert(overlap <= buffer.Length);
 0172            stream.Seek(-(buffer.Length - overlap), SeekOrigin.Current);
 0173            bytesRead = stream.ReadAtLeast(buffer, buffer.Length, throwOnEndOfStream: true);
 0174            stream.Seek(-buffer.Length, SeekOrigin.Current);
 0175        }
 176        else
 747177        {
 747178            int bytesToRead = (int)stream.Position;
 747179            stream.Seek(0, SeekOrigin.Begin);
 747180            bytesRead = stream.ReadAtLeast(buffer, bytesToRead, throwOnEndOfStream: true);
 747181            stream.Seek(0, SeekOrigin.Begin);
 747182        }
 183
 747184        return bytesRead;
 747185    }
 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
 1494191    {
 1494192        Debug.Assert(maxBytes >= 0, "maxBytes must be non-negative");
 193
 1494194        if (string.IsNullOrEmpty(text))
 0195        {
 0196            isUTF8 = false;
 0197            return Array.Empty<byte>();
 198        }
 199
 1494200        encoding ??= GetEncoding(text);
 1494201        isUTF8 = encoding.CodePage == 65001;
 202
 1494203        if (maxBytes == 0)
 1494204        {
 1494205            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;
 1494224    }
 225}