diff --git a/MetadataExtractor/Formats/Apple/BplistReader.cs b/MetadataExtractor/Formats/Apple/BplistReader.cs index f5a2fe595..f59e0b3e3 100644 --- a/MetadataExtractor/Formats/Apple/BplistReader.cs +++ b/MetadataExtractor/Formats/Apple/BplistReader.cs @@ -1,7 +1,5 @@ // Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -using System.Buffers; - namespace MetadataExtractor.Formats.Apple; /// @@ -123,12 +121,9 @@ static object HandleInt(ref BufferReader reader, byte marker) static Dictionary HandleDict(ref BufferReader reader, byte count) { - var keyRefs = ArrayPool.Shared.Rent(count); + Span keyRefs = stackalloc byte[count]; // count is a byte (0-255) - for (int j = 0; j < count; j++) - { - keyRefs[j] = reader.GetByte(); - } + reader.GetBytes(keyRefs); Dictionary map = []; @@ -137,8 +132,6 @@ static Dictionary HandleDict(ref BufferReader reader, byte count) map.Add(keyRefs[j], reader.GetByte()); } - ArrayPool.Shared.Return(keyRefs); - return map; } diff --git a/MetadataExtractor/IO/BufferReader.Indexed.cs b/MetadataExtractor/IO/BufferReader.Indexed.cs index 398644f8e..0eb25066c 100644 --- a/MetadataExtractor/IO/BufferReader.Indexed.cs +++ b/MetadataExtractor/IO/BufferReader.Indexed.cs @@ -1,6 +1,5 @@ // Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -using System.Buffers; using System.Buffers.Binary; namespace MetadataExtractor.IO; @@ -178,33 +177,22 @@ public readonly double GetDouble64(int index) public readonly string GetString(int index, int bytesRequested, Encoding encoding) { + if (bytesRequested < 0) + throw new ArgumentOutOfRangeException(nameof(bytesRequested), "Must be zero or greater."); + // This check is important on .NET Framework if (bytesRequested is 0) { return ""; } - else if (bytesRequested < 256) - { - Span bytes = stackalloc byte[bytesRequested]; - - GetBytes(index, bytes); - - return encoding.GetString(bytes); - } - else - { - byte[] bytes = ArrayPool.Shared.Rent(bytesRequested); - - Span span = bytes.AsSpan(0, bytesRequested); - GetBytes(index, span); + using var buffer = bytesRequested <= ScopedBuffer.MaxStackBufferSize + ? new ScopedBuffer(stackalloc byte[bytesRequested]) + : new ScopedBuffer(bytesRequested); - var s = encoding.GetString(span); + GetBytes(index, buffer); - ArrayPool.Shared.Return(bytes); - - return s; - } + return encoding.GetString(buffer); } private readonly void ValidateIndex(int index, int bytesRequested) diff --git a/MetadataExtractor/IO/BufferReader.Sequential.cs b/MetadataExtractor/IO/BufferReader.Sequential.cs index 1231664c3..4c8bbc2da 100644 --- a/MetadataExtractor/IO/BufferReader.Sequential.cs +++ b/MetadataExtractor/IO/BufferReader.Sequential.cs @@ -114,17 +114,20 @@ public ulong GetUInt64() public string GetString(int bytesRequested, Encoding encoding) { + if (bytesRequested < 0) + throw new ArgumentOutOfRangeException(nameof(bytesRequested), "Must be zero or greater."); + // This check is important on .NET Framework if (bytesRequested is 0) return ""; - Span bytes = bytesRequested <= 256 - ? stackalloc byte[bytesRequested] - : new byte[bytesRequested]; + using var buffer = bytesRequested <= ScopedBuffer.MaxStackBufferSize + ? new ScopedBuffer(stackalloc byte[bytesRequested]) + : new ScopedBuffer(bytesRequested); - GetBytes(bytes); + GetBytes(buffer); - return encoding.GetString(bytes); + return encoding.GetString(buffer); } public StringValue GetNullTerminatedStringValue(int maxLengthBytes, Encoding? encoding = null, bool moveToMaxLength = false) diff --git a/MetadataExtractor/IO/IndexedReader.cs b/MetadataExtractor/IO/IndexedReader.cs index 9cedc8b6b..2d4a06502 100644 --- a/MetadataExtractor/IO/IndexedReader.cs +++ b/MetadataExtractor/IO/IndexedReader.cs @@ -1,7 +1,5 @@ // Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -using System.Buffers; using System.Buffers.Binary; namespace MetadataExtractor.IO @@ -320,33 +318,22 @@ public double GetDouble64(int index) /// public string GetString(int index, int bytesRequested, Encoding encoding) { + if (bytesRequested < 0) + throw new ArgumentOutOfRangeException(nameof(bytesRequested), "Must be zero or greater."); + // This check is important on .NET Framework if (bytesRequested is 0) { return ""; } - else if (bytesRequested < 256) - { - Span bytes = stackalloc byte[bytesRequested]; - GetBytes(index, bytes); + using var buffer = bytesRequested <= ScopedBuffer.MaxStackBufferSize + ? new ScopedBuffer(stackalloc byte[bytesRequested]) + : new ScopedBuffer(bytesRequested); - return encoding.GetString(bytes); - } - else - { - byte[] bytes = ArrayPool.Shared.Rent(bytesRequested); + GetBytes(index, buffer); - Span span = bytes.AsSpan(0, bytesRequested); - - GetBytes(index, span); - - var s = encoding.GetString(span); - - ArrayPool.Shared.Return(bytes); - - return s; - } + return encoding.GetString(buffer); } /// diff --git a/MetadataExtractor/IO/ScopedBuffer.cs b/MetadataExtractor/IO/ScopedBuffer.cs new file mode 100644 index 000000000..ee9358515 --- /dev/null +++ b/MetadataExtractor/IO/ScopedBuffer.cs @@ -0,0 +1,43 @@ +// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using System.Buffers; + +namespace MetadataExtractor.IO; + +internal ref struct ScopedBuffer +{ + public const int MaxStackBufferSize = 256; + + private byte[]? _rentedBuffer; + private readonly Span _span; + + public ScopedBuffer(int size) + { + _rentedBuffer = ArrayPool.Shared.Rent(size); + _span = _rentedBuffer.AsSpan(0, size); + } + + public ScopedBuffer(Span span) + { + _span = span; + } + + public readonly Span Span => _span; + + public static implicit operator Span(ScopedBuffer bufferScope) + { + return bufferScope._span; + } + + public static implicit operator ReadOnlySpan(ScopedBuffer bufferScope) + { + return bufferScope._span; + } + + public void Dispose() + { + if (_rentedBuffer is null) return; + + ArrayPool.Shared.Return(_rentedBuffer); + } +} diff --git a/MetadataExtractor/IO/SequentialReader.cs b/MetadataExtractor/IO/SequentialReader.cs index 29b4ab5fd..e0e509745 100644 --- a/MetadataExtractor/IO/SequentialReader.cs +++ b/MetadataExtractor/IO/SequentialReader.cs @@ -1,6 +1,5 @@ // Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -using System.Buffers; using System.Buffers.Binary; namespace MetadataExtractor.IO @@ -221,33 +220,22 @@ public float GetS15Fixed16() /// public string GetString(int bytesRequested, Encoding encoding) { + if (bytesRequested < 0) + throw new ArgumentOutOfRangeException(nameof(bytesRequested), "Must be zero or greater."); + // This check is important on .NET Framework if (bytesRequested is 0) { return ""; } - else if (bytesRequested < 256) - { - Span bytes = stackalloc byte[bytesRequested]; - - GetBytes(bytes); - - return encoding.GetString(bytes); - } - else - { - byte[] bytes = ArrayPool.Shared.Rent(bytesRequested); - - Span span = bytes.AsSpan(0, bytesRequested); - GetBytes(span); + using var buffer = bytesRequested <= ScopedBuffer.MaxStackBufferSize + ? new ScopedBuffer(stackalloc byte[bytesRequested]) + : new ScopedBuffer(bytesRequested); - var s = encoding.GetString(span); + GetBytes(buffer); - ArrayPool.Shared.Return(bytes); - - return s; - } + return encoding.GetString(buffer); } public StringValue GetStringValue(int bytesRequested, Encoding? encoding = null) diff --git a/MetadataExtractor/Util/DateUtil.cs b/MetadataExtractor/Util/DateUtil.cs index b29aae882..98d9fda3c 100644 --- a/MetadataExtractor/Util/DateUtil.cs +++ b/MetadataExtractor/Util/DateUtil.cs @@ -18,7 +18,7 @@ public static bool IsValidTime(int hours, int minutes, int seconds) minutes is >= 0 and < 60 && seconds is >= 0 and < 60; -#if NET8_0 || NETSTANDARD2_1 +#if NET8_0_OR_GREATER || NETSTANDARD2_1 private static readonly DateTime _unixEpoch = DateTime.UnixEpoch; #else private static readonly DateTime _unixEpoch = new(1970, 1, 1, 0, 0, 0);