File: src\ArReader.cs
Web Access
Project: src\src\Microsoft.DotNet.Build.Tasks.Installers\Microsoft.DotNet.Build.Tasks.Installers.csproj (Microsoft.DotNet.Build.Tasks.Installers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
 
#nullable enable
 
namespace Microsoft.DotNet.Build.Tasks.Installers
{
    internal sealed class ArReader(Stream stream, bool leaveOpen) : IDisposable
    {
        private bool readMagic;
        public ArEntry? GetNextEntry()
        {
            if (!readMagic)
            {
                byte[] magic = new byte[8];
                ReadExactly(magic, 0, 8);
                readMagic = true;
                if (!magic.AsSpan().SequenceEqual("!<arch>\n"u8))
                {
                    throw new InvalidDataException("Invalid archive magic");
                }
            }
 
            if (stream.Position == stream.Length)
            {
                return null;
            }
 
            byte[] fileName = new byte[16];
            ReadExactly(fileName, 0, 16);
            string name = Encoding.ASCII.GetString(fileName).TrimEnd(' ');
            byte[] timestampBytes = new byte[12];
            ReadExactly(timestampBytes, 0, 12);
 
            ulong timestamp = ulong.Parse(Encoding.ASCII.GetString(timestampBytes).TrimEnd(' '));
 
            byte[] ownerIDBytes = new byte[6];
            ReadExactly(ownerIDBytes, 0, 6);
 
            ulong ownerID = ulong.Parse(Encoding.ASCII.GetString(ownerIDBytes).TrimEnd(' '));
 
            byte[] groupIDBytes = new byte[6];
            ReadExactly(groupIDBytes, 0, 6);
 
            ulong groupID = ulong.Parse(Encoding.ASCII.GetString(groupIDBytes).TrimEnd(' '));
 
            byte[] modeBytes = new byte[8];
            ReadExactly(modeBytes, 0, 8);
 
            uint mode = Convert.ToUInt32(Encoding.ASCII.GetString(modeBytes).TrimEnd(' '), 8);
 
            byte[] sizeBytes = new byte[10];
            ReadExactly(sizeBytes, 0, 10);
 
            ulong size = ulong.Parse(Encoding.ASCII.GetString(sizeBytes).TrimEnd(' '));
 
            byte[] footer = new byte[2];
            ReadExactly(footer, 0, 2);
            if (!footer.AsSpan().SequenceEqual("`\n"u8))
            {
                throw new InvalidDataException("Invalid archive magic");
            }
 
            byte[] data = new byte[size];
            ReadExactly(data, 0, checked((int)size));
 
            MemoryStream dataStream = new MemoryStream(data);
 
            if (size % 2 == 1)
            {
                // Skip the padding newline
                // for an odd-length entry
                _ = stream.ReadByte();
            }
 
            return new ArEntry(
                name,
                timestamp,
                ownerID,
                groupID,
                mode,
                dataStream);
        }
 
        public void Dispose()
        {
            if (!leaveOpen)
            {
                stream.Dispose();
            }
        }
 
#if !NET
        private void ReadExactly(byte[] buffer, int offset, int count)
        {
            while (count > 0)
            {
                int read = stream.Read(buffer, offset, count);
                if (read == 0)
                {
                    throw new InvalidOperationException("Unexpected end of stream");
                }
                offset += read;
                count -= read;
            }
        }
#else
        private void ReadExactly(byte[] buffer, int offset, int count)
        {
            stream.ReadExactly(buffer.AsSpan(offset, count));
        }
#endif
    }
}