File: System\Reflection\PortableExecutable\PEBinaryReader.cs
Web Access
Project: src\src\libraries\System.Reflection.Metadata\src\System.Reflection.Metadata.csproj (System.Reflection.Metadata)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.IO;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Text;
 
namespace System.Reflection.PortableExecutable
{
    /// <summary>
    /// Simple BinaryReader wrapper to:
    ///
    ///  1) throw BadImageFormat instead of EndOfStream or ArgumentOutOfRange.
    ///  2) limit reads to a subset of the base stream.
    ///
    /// Only methods that are needed to read PE headers are implemented.
    /// </summary>
    internal readonly struct PEBinaryReader
    {
        private readonly long _startOffset;
        private readonly long _maxOffset;
        private readonly BinaryReader _reader;
 
        public PEBinaryReader(Stream stream, int size)
        {
            Debug.Assert(size >= 0 && size <= (stream.Length - stream.Position));
 
            _startOffset = stream.Position;
            _maxOffset = _startOffset + size;
            _reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
        }
 
        public int CurrentOffset
        {
            get { return (int)(_reader.BaseStream.Position - _startOffset); }
        }
 
        public void Seek(int offset)
        {
            CheckBounds(_startOffset, offset);
            _reader.BaseStream.Seek(offset, SeekOrigin.Begin);
        }
 
        public byte[] ReadBytes(int count)
        {
            CheckBounds(_reader.BaseStream.Position, count);
            return _reader.ReadBytes(count);
        }
 
        public byte ReadByte()
        {
            CheckBounds(sizeof(byte));
            return _reader.ReadByte();
        }
 
        public short ReadInt16()
        {
            CheckBounds(sizeof(short));
            return _reader.ReadInt16();
        }
 
        public ushort ReadUInt16()
        {
            CheckBounds(sizeof(ushort));
            return _reader.ReadUInt16();
        }
 
        public int ReadInt32()
        {
            CheckBounds(sizeof(int));
            return _reader.ReadInt32();
        }
 
        public uint ReadUInt32()
        {
            CheckBounds(sizeof(uint));
            return _reader.ReadUInt32();
        }
 
        public ulong ReadUInt64()
        {
            CheckBounds(sizeof(ulong));
            return _reader.ReadUInt64();
        }
 
        /// <summary>
        /// Reads a fixed-length byte block as a null-padded UTF-8 encoded string.
        /// The padding is not included in the returned string.
        ///
        /// Note that it is legal for UTF-8 strings to contain NUL; if NUL occurs
        /// between non-NUL codepoints, it is not considered to be padding and
        /// is included in the result.
        /// </summary>
        public string ReadNullPaddedUTF8(int byteCount)
        {
            byte[] bytes = ReadBytes(byteCount);
            int nonPaddedLength = 0;
            for (int i = bytes.Length; i > 0; --i)
            {
                if (bytes[i - 1] != 0)
                {
                    nonPaddedLength = i;
                    break;
                }
            }
            return Encoding.UTF8.GetString(bytes, 0, nonPaddedLength);
        }
 
        private void CheckBounds(uint count)
        {
            Debug.Assert(count <= sizeof(long));  // Error message assumes we're trying to read constant small number of bytes.
            Debug.Assert(_reader.BaseStream.Position >= 0 && _maxOffset >= 0);
 
            // Add cannot overflow because the worst case is (ulong)long.MaxValue + uint.MaxValue < ulong.MaxValue.
            if ((ulong)_reader.BaseStream.Position + count > (ulong)_maxOffset)
            {
                Throw.ImageTooSmall();
            }
        }
 
        private void CheckBounds(long startPosition, int count)
        {
            Debug.Assert(startPosition >= 0 && _maxOffset >= 0);
 
            // Add cannot overflow because the worst case is (ulong)long.MaxValue + uint.MaxValue < ulong.MaxValue.
            // Negative count is handled by overflow to greater than maximum size = int.MaxValue.
            if ((ulong)startPosition + unchecked((uint)count) > (ulong)_maxOffset)
            {
                Throw.ImageTooSmallOrContainsInvalidOffsetOrCount();
            }
        }
    }
}