< 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, 
 8618    {
 8619        cancellationToken.ThrowIfCancellationRequested();
 20
 8621        Debug.Assert(signatureToFind.Length != 0);
 8622        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
 8630        int bufferPointer = 0;
 8631        byte[] buffer = ArrayPool<byte>.Shared.Rent(BackwardsSeekingBufferSize);
 8632        Memory<byte> bufferMemory = buffer.AsMemory(0, BackwardsSeekingBufferSize);
 33
 34        try
 8635        {
 8636            bool outOfBytes = false;
 8637            bool signatureFound = false;
 38
 8639            int totalBytesRead = 0;
 40
 8641            while (!signatureFound && !outOfBytes && totalBytesRead < maxBytesToRead)
 8642            {
 8643                int overlap = totalBytesRead == 0 ? 0 : signatureToFind.Length;
 44
 8645                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
 8651                int bytesRead = await SeekBackwardsAndReadAsync(stream, bufferMemory, overlap, cancellationToken).Config
 52
 8653                outOfBytes = bytesRead < bufferMemory.Length;
 8654                if (bytesRead < bufferMemory.Length)
 8655                {
 8656                    bufferMemory = bufferMemory.Slice(0, bytesRead);
 8657                }
 58
 8659                bufferPointer = bufferMemory.Span.LastIndexOf(signatureToFind.Span);
 8660                Debug.Assert(bufferPointer < bufferMemory.Length);
 61
 8662                totalBytesRead += bytesRead - overlap;
 63
 8664                if (bufferPointer != -1)
 8665                {
 8666                    signatureFound = true;
 8667                    break;
 68                }
 069            }
 70
 8671            if (!signatureFound)
 072            {
 073                return false;
 74            }
 75            else
 8676            {
 8677                stream.Seek(bufferPointer, SeekOrigin.Current);
 8678                return true;
 79            }
 80        }
 81        finally
 8682        {
 8683            ArrayPool<byte>.Shared.Return(buffer);
 8684        }
 8685    }
 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
 8692    {
 8693        cancellationToken.ThrowIfCancellationRequested();
 94
 95        int bytesRead;
 96
 8697        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
 86105        {
 86106            int bytesToRead = (int)stream.Position;
 86107            stream.Seek(0, SeekOrigin.Begin);
 86108            bytesRead = await stream.ReadAtLeastAsync(buffer, bytesToRead, throwOnEndOfStream: true, cancellationToken).
 86109            stream.Seek(0, SeekOrigin.Begin);
 86110        }
 111
 86112        return bytesRead;
 86113    }
 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)
 17223    {
 17224        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
 17232        return Encoding.ASCII;
 17233    }
 34
 35    // will silently return InvalidDateIndicator if the uint is not a valid Dos DateTime
 36    internal static DateTime DosTimeToDateTime(uint dateTime)
 17237    {
 17238        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
 17253        int year = (int)(ValidZipDate_YearMin + (dateTime >> 25));
 17254        int month = (int)((dateTime >> 21) & 0xF);
 17255        int day = (int)((dateTime >> 16) & 0x1F);
 17256        int hour = (int)((dateTime >> 11) & 0x1F);
 17257        int minute = (int)((dateTime >> 5) & 0x3F);
 17258        int second = (int)((dateTime & 0x001F) * 2); // only 5 bits for second, so we only have a granularity of 2 sec.
 59
 60        try
 17261        {
 17262            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        }
 17272    }
 73
 74    // assume date time has passed IsConvertibleToDosTime
 75    internal static uint DateTimeToDosTime(DateTime dateTime)
 34476    {
 77        // DateTime must be Convertible to DosTime:
 34478        Debug.Assert(ValidZipDate_YearMin <= dateTime.Year && dateTime.Year <= ValidZipDate_YearMax);
 79
 34480        int ret = ((dateTime.Year - ValidZipDate_YearMin) & 0x7F);
 34481        ret = (ret << 4) + dateTime.Month;
 34482        ret = (ret << 5) + dateTime.Day;
 34483        ret = (ret << 5) + dateTime.Hour;
 34484        ret = (ret << 6) + dateTime.Minute;
 34485        ret = (ret << 5) + (dateTime.Second / 2); // only 5 bits for second, so we only have a granularity of 2 sec.
 34486        return (uint)ret;
 34487    }
 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)
 8694    {
 8695        Debug.Assert(signatureToFind.Length != 0);
 8696        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
 86104        int bufferPointer = 0;
 86105        byte[] buffer = ArrayPool<byte>.Shared.Rent(BackwardsSeekingBufferSize);
 86106        Span<byte> bufferSpan = buffer.AsSpan(0, BackwardsSeekingBufferSize);
 107
 108        try
 86109        {
 86110            bool outOfBytes = false;
 86111            bool signatureFound = false;
 112
 86113            int totalBytesRead = 0;
 114
 86115            while (!signatureFound && !outOfBytes && totalBytesRead < maxBytesToRead)
 86116            {
 86117                int overlap = totalBytesRead == 0 ? 0 : signatureToFind.Length;
 118
 86119                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
 86125                int bytesRead = SeekBackwardsAndRead(stream, bufferSpan, overlap);
 126
 86127                outOfBytes = bytesRead < bufferSpan.Length;
 86128                if (bytesRead < bufferSpan.Length)
 86129                {
 86130                    bufferSpan = bufferSpan.Slice(0, bytesRead);
 86131                }
 132
 86133                bufferPointer = bufferSpan.LastIndexOf(signatureToFind);
 86134                Debug.Assert(bufferPointer < bufferSpan.Length);
 135
 86136                totalBytesRead += bytesRead - overlap;
 137
 86138                if (bufferPointer != -1)
 86139                {
 86140                    signatureFound = true;
 86141                    break;
 142                }
 0143            }
 144
 86145            if (!signatureFound)
 0146            {
 0147                return false;
 148            }
 149            else
 86150            {
 86151                stream.Seek(bufferPointer, SeekOrigin.Current);
 86152                return true;
 153            }
 154        }
 155        finally
 86156        {
 86157            ArrayPool<byte>.Shared.Return(buffer);
 86158        }
 86159    }
 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)
 86166    {
 167        int bytesRead;
 168
 86169        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
 86177        {
 86178            int bytesToRead = (int)stream.Position;
 86179            stream.Seek(0, SeekOrigin.Begin);
 86180            bytesRead = stream.ReadAtLeast(buffer, bytesToRead, throwOnEndOfStream: true);
 86181            stream.Seek(0, SeekOrigin.Begin);
 86182        }
 183
 86184        return bytesRead;
 86185    }
 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
 172191    {
 172192        Debug.Assert(maxBytes >= 0, "maxBytes must be non-negative");
 193
 172194        if (string.IsNullOrEmpty(text))
 0195        {
 0196            isUTF8 = false;
 0197            return Array.Empty<byte>();
 198        }
 199
 172200        encoding ??= GetEncoding(text);
 172201        isUTF8 = encoding.CodePage == 65001;
 202
 172203        if (maxBytes == 0)
 172204        {
 172205            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;
 172224    }
 225}