< 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, 
 64618    {
 64619        cancellationToken.ThrowIfCancellationRequested();
 20
 64621        Debug.Assert(signatureToFind.Length != 0);
 64622        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
 64630        int bufferPointer = 0;
 64631        byte[] buffer = ArrayPool<byte>.Shared.Rent(BackwardsSeekingBufferSize);
 64632        Memory<byte> bufferMemory = buffer.AsMemory(0, BackwardsSeekingBufferSize);
 33
 34        try
 64635        {
 64636            bool outOfBytes = false;
 64637            bool signatureFound = false;
 38
 64639            int totalBytesRead = 0;
 40
 64641            while (!signatureFound && !outOfBytes && totalBytesRead < maxBytesToRead)
 64642            {
 64643                int overlap = totalBytesRead == 0 ? 0 : signatureToFind.Length;
 44
 64645                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
 64651                int bytesRead = await SeekBackwardsAndReadAsync(stream, bufferMemory, overlap, cancellationToken).Config
 52
 64653                outOfBytes = bytesRead < bufferMemory.Length;
 64654                if (bytesRead < bufferMemory.Length)
 64655                {
 64656                    bufferMemory = bufferMemory.Slice(0, bytesRead);
 64657                }
 58
 64659                bufferPointer = bufferMemory.Span.LastIndexOf(signatureToFind.Span);
 64660                Debug.Assert(bufferPointer < bufferMemory.Length);
 61
 64662                totalBytesRead += bytesRead - overlap;
 63
 64664                if (bufferPointer != -1)
 64665                {
 64666                    signatureFound = true;
 64667                    break;
 68                }
 069            }
 70
 64671            if (!signatureFound)
 072            {
 073                return false;
 74            }
 75            else
 64676            {
 64677                stream.Seek(bufferPointer, SeekOrigin.Current);
 64678                return true;
 79            }
 80        }
 81        finally
 64682        {
 64683            ArrayPool<byte>.Shared.Return(buffer);
 64684        }
 64685    }
 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
 64692    {
 64693        cancellationToken.ThrowIfCancellationRequested();
 94
 95        int bytesRead;
 96
 64697        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
 646105        {
 646106            int bytesToRead = (int)stream.Position;
 646107            stream.Seek(0, SeekOrigin.Begin);
 646108            bytesRead = await stream.ReadAtLeastAsync(buffer, bytesToRead, throwOnEndOfStream: true, cancellationToken).
 646109            stream.Seek(0, SeekOrigin.Begin);
 646110        }
 111
 646112        return bytesRead;
 646113    }
 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)
 129223    {
 129224        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
 129232        return Encoding.ASCII;
 129233    }
 34
 35    // will silently return InvalidDateIndicator if the uint is not a valid Dos DateTime
 36    internal static DateTime DosTimeToDateTime(uint dateTime)
 129237    {
 129238        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
 129253        int year = (int)(ValidZipDate_YearMin + (dateTime >> 25));
 129254        int month = (int)((dateTime >> 21) & 0xF);
 129255        int day = (int)((dateTime >> 16) & 0x1F);
 129256        int hour = (int)((dateTime >> 11) & 0x1F);
 129257        int minute = (int)((dateTime >> 5) & 0x3F);
 129258        int second = (int)((dateTime & 0x001F) * 2); // only 5 bits for second, so we only have a granularity of 2 sec.
 59
 60        try
 129261        {
 129262            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        }
 129272    }
 73
 74    // assume date time has passed IsConvertibleToDosTime
 75    internal static uint DateTimeToDosTime(DateTime dateTime)
 646076    {
 77        // DateTime must be Convertible to DosTime:
 646078        Debug.Assert(ValidZipDate_YearMin <= dateTime.Year && dateTime.Year <= ValidZipDate_YearMax);
 79
 646080        int ret = ((dateTime.Year - ValidZipDate_YearMin) & 0x7F);
 646081        ret = (ret << 4) + dateTime.Month;
 646082        ret = (ret << 5) + dateTime.Day;
 646083        ret = (ret << 5) + dateTime.Hour;
 646084        ret = (ret << 6) + dateTime.Minute;
 646085        ret = (ret << 5) + (dateTime.Second / 2); // only 5 bits for second, so we only have a granularity of 2 sec.
 646086        return (uint)ret;
 646087    }
 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)
 64694    {
 64695        Debug.Assert(signatureToFind.Length != 0);
 64696        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
 646104        int bufferPointer = 0;
 646105        byte[] buffer = ArrayPool<byte>.Shared.Rent(BackwardsSeekingBufferSize);
 646106        Span<byte> bufferSpan = buffer.AsSpan(0, BackwardsSeekingBufferSize);
 107
 108        try
 646109        {
 646110            bool outOfBytes = false;
 646111            bool signatureFound = false;
 112
 646113            int totalBytesRead = 0;
 114
 646115            while (!signatureFound && !outOfBytes && totalBytesRead < maxBytesToRead)
 646116            {
 646117                int overlap = totalBytesRead == 0 ? 0 : signatureToFind.Length;
 118
 646119                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
 646125                int bytesRead = SeekBackwardsAndRead(stream, bufferSpan, overlap);
 126
 646127                outOfBytes = bytesRead < bufferSpan.Length;
 646128                if (bytesRead < bufferSpan.Length)
 646129                {
 646130                    bufferSpan = bufferSpan.Slice(0, bytesRead);
 646131                }
 132
 646133                bufferPointer = bufferSpan.LastIndexOf(signatureToFind);
 646134                Debug.Assert(bufferPointer < bufferSpan.Length);
 135
 646136                totalBytesRead += bytesRead - overlap;
 137
 646138                if (bufferPointer != -1)
 646139                {
 646140                    signatureFound = true;
 646141                    break;
 142                }
 0143            }
 144
 646145            if (!signatureFound)
 0146            {
 0147                return false;
 148            }
 149            else
 646150            {
 646151                stream.Seek(bufferPointer, SeekOrigin.Current);
 646152                return true;
 153            }
 154        }
 155        finally
 646156        {
 646157            ArrayPool<byte>.Shared.Return(buffer);
 646158        }
 646159    }
 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)
 646166    {
 167        int bytesRead;
 168
 646169        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
 646177        {
 646178            int bytesToRead = (int)stream.Position;
 646179            stream.Seek(0, SeekOrigin.Begin);
 646180            bytesRead = stream.ReadAtLeast(buffer, bytesToRead, throwOnEndOfStream: true);
 646181            stream.Seek(0, SeekOrigin.Begin);
 646182        }
 183
 646184        return bytesRead;
 646185    }
 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
 1292191    {
 1292192        Debug.Assert(maxBytes >= 0, "maxBytes must be non-negative");
 193
 1292194        if (string.IsNullOrEmpty(text))
 0195        {
 0196            isUTF8 = false;
 0197            return Array.Empty<byte>();
 198        }
 199
 1292200        encoding ??= GetEncoding(text);
 1292201        isUTF8 = encoding.CodePage == 65001;
 202
 1292203        if (maxBytes == 0)
 1292204        {
 1292205            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;
 1292224    }
 225}