// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : OffsetStream.cs // Author(s) : Natalia Portillo // // Component : Filters. // // --[ Description ] ---------------------------------------------------------- // // Provides a stream that's a subset of another stream. // // --[ License ] -------------------------------------------------------------- // // This library is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation; either version 2.1 of the // License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, see . // // ---------------------------------------------------------------------------- // Copyright © 2011-2025 Natalia Portillo // ****************************************************************************/ using System; using System.Diagnostics.CodeAnalysis; using System.IO; using Microsoft.Win32.SafeHandles; #if !NETSTANDARD2_0 #endif namespace Aaru.Helpers.IO; /// Creates a stream that is a subset of another stream. /// [SuppressMessage("ReSharper", "UnusedMember.Global")] public sealed class OffsetStream : Stream { readonly Stream _baseStream; readonly long _streamEnd; readonly long _streamStart; /// /// /// Initializes a stream that only allows reading from to of the /// specified stream, both inclusive. /// /// Base stream /// Start position /// Last readable position /// Invalid range public OffsetStream(Stream stream, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = stream; if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// /// /// Initializes a stream that only allows reading from to of the /// specified file, both inclusive. /// /// A relative or absolute path for the file that the stream will encapsulate. /// One of the enumeration values that determines how to open or create the file. /// /// A bitwise combination of the enumeration values that determines how the file can be accessed by a /// object. /// /// /// A bitwise combination of the enumeration values that determines how the file will be shared by /// processes. /// /// /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is /// 4096. /// /// A bitwise combination of the enumeration values that specifies additional file options. /// Start position /// Last readable position /// Invalid range public OffsetStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = new FileStream(path, mode, access, share, bufferSize, options); if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// /// /// Initializes a stream that only allows reading from to of the /// specified file, both inclusive. /// /// A file handle for the file that the stream will encapsulate. /// /// A bitwise combination of the enumeration values that determines how the file can be accessed by a /// object. /// /// Start position /// Last readable position /// Invalid range public OffsetStream(SafeFileHandle handle, FileAccess access, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = new FileStream(handle, access); if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// /// /// Initializes a stream that only allows reading from to of the /// specified file, both inclusive. /// /// A file handle for the file that the stream will encapsulate. /// /// A bitwise combination of the enumeration values that determines how the file can be accessed by a /// object. /// /// /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is /// 4096. /// /// Start position /// Last readable position /// Invalid range public OffsetStream(SafeFileHandle handle, FileAccess access, int bufferSize, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = new FileStream(handle, access, bufferSize); if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// /// /// Initializes a stream that only allows reading from to of the /// specified file, both inclusive. /// /// A file handle for the file that the stream will encapsulate. /// /// A bitwise combination of the enumeration values that determines how the file can be accessed by a /// object. /// /// /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is /// 4096. /// /// Specifies whether to use asynchronous I/O or synchronous I/O. /// Start position /// Last readable position /// Invalid range public OffsetStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = new FileStream(handle, access, bufferSize, isAsync); if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// /// /// Initializes a stream that only allows reading from to of the /// specified file, both inclusive. /// /// A relative or absolute path for the file that the stream will encapsulate. /// One of the enumeration values that determines how to open or create the file. /// /// A bitwise combination of the enumeration values that determines how the file can be accessed by a /// object. /// /// /// A bitwise combination of the enumeration values that determines how the file will be shared by /// processes. /// /// /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is /// 4096. /// /// Specifies whether to use asynchronous I/O or synchronous I/O. /// Start position /// Last readable position /// Invalid range public OffsetStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = new FileStream(path, mode, access, share, bufferSize, useAsync); if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// /// /// Initializes a stream that only allows reading from to of the /// specified file, both inclusive. /// /// A relative or absolute path for the file that the stream will encapsulate. /// One of the enumeration values that determines how to open or create the file. /// /// A bitwise combination of the enumeration values that determines how the file can be accessed by a /// object. /// /// /// A bitwise combination of the enumeration values that determines how the file will be shared by /// processes. /// /// /// A positive Int32 value greater than 0 indicating the buffer size. The default buffer size is /// 4096. /// /// Start position /// Last readable position /// Invalid range public OffsetStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = new FileStream(path, mode, access, share, bufferSize); if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// /// /// Initializes a stream that only allows reading from to of the /// specified file, both inclusive. /// /// A relative or absolute path for the file that the stream will encapsulate. /// One of the enumeration values that determines how to open or create the file. /// /// A bitwise combination of the enumeration values that determines how the file can be accessed by a /// object. /// /// /// A bitwise combination of the enumeration values that determines how the file will be shared by /// processes. /// /// Start position /// Last readable position /// Invalid range public OffsetStream(string path, FileMode mode, FileAccess access, FileShare share, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = new FileStream(path, mode, access, share); if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// /// /// Initializes a stream that only allows reading from to of the /// specified file, both inclusive. /// /// A relative or absolute path for the file that the stream will encapsulate. /// One of the enumeration values that determines how to open or create the file. /// /// A bitwise combination of the enumeration values that determines how the file can be accessed by a /// object. /// /// Start position /// Last readable position /// Invalid range public OffsetStream(string path, FileMode mode, FileAccess access, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = new FileStream(path, mode, access); if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// /// /// Initializes a stream that only allows reading from to of the /// specified file, both inclusive. /// /// A relative or absolute path for the file that the stream will encapsulate. /// One of the enumeration values that determines how to open or create the file. /// Start position /// Last readable position /// Invalid range public OffsetStream(string path, FileMode mode, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = new FileStream(path, mode); if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// /// /// Initializes a stream that only allows reading from to of the /// specified byte array, both inclusive. /// /// The array of unsigned bytes to add at the end of this stream. /// The index into at which the stream begins. /// The length in bytes to add to the end of the current stream. /// The setting of the CanWrite property, currently ignored. /// Currently ignored. /// Start position /// Last readable position /// Invalid range public OffsetStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = new MemoryStream(buffer, index, count, writable, publiclyVisible); if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// /// /// Initializes a stream that only allows reading from to of the /// specified byte array, both inclusive. /// /// The array of unsigned bytes to add at the end of this stream. /// The index into at which the stream begins. /// The length in bytes to add to the end of the current stream. /// The setting of the CanWrite property, currently ignored. /// Start position /// Last readable position /// Invalid range public OffsetStream(byte[] buffer, int index, int count, bool writable, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = new MemoryStream(buffer, index, count, writable); if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// /// /// Initializes a stream that only allows reading from to of the /// specified byte array, both inclusive. /// /// The array of unsigned bytes to add at the end of this stream. /// The index into at which the stream begins. /// The length in bytes to add to the end of the current stream. /// Start position /// Last readable position /// Invalid range public OffsetStream(byte[] buffer, int index, int count, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = new MemoryStream(buffer, index, count); if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// /// /// Initializes a stream that only allows reading from to of the /// specified byte array, both inclusive. /// /// The array of unsigned bytes to add at the end of this stream. /// The setting of the CanWrite property, currently ignored. /// Start position /// Last readable position /// Invalid range public OffsetStream(byte[] buffer, bool writable, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = new MemoryStream(buffer, writable); if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// /// /// Initializes a stream that only allows reading from to of the /// specified byte array, both inclusive. /// /// The array of unsigned bytes to add at the end of this stream. /// Start position /// Last readable position /// Invalid range public OffsetStream(byte[] buffer, long start, long end) { if(start < 0) throw new ArgumentOutOfRangeException(nameof(start), Localization.Start_cant_be_a_negative_number); if(end < 0) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_cant_be_a_negative_number); _streamStart = start; _streamEnd = end; _baseStream = new MemoryStream(buffer); if(end > _baseStream.Length) throw new ArgumentOutOfRangeException(nameof(end), Localization.End_is_after_stream_end); _baseStream.Position = start; } /// public override bool CanRead => _baseStream.CanRead; /// public override bool CanSeek => _baseStream.CanSeek; /// public override bool CanWrite => _baseStream.CanWrite; /// public override long Length => _streamEnd - _streamStart + 1; /// public override long Position { get => _baseStream.Position - _streamStart; set { if(value + _streamStart > _streamEnd) throw new IOException(Localization.Cannot_set_position_past_stream_end); _baseStream.Position = value + _streamStart; } } ~OffsetStream() { _baseStream.Close(); _baseStream.Dispose(); } /// public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { if(_baseStream.Position + count > _streamEnd) count = (int)(_streamEnd - _baseStream.Position); return _baseStream.BeginRead(buffer, offset, count, callback, state); } /// public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { if(_baseStream.Position + count > _streamEnd) throw new IOException(Localization.Cannot_write_past_stream_end); return _baseStream.BeginWrite(buffer, offset, count, callback, state); } /// public override void Close() => _baseStream.Close(); /// public override int EndRead(IAsyncResult asyncResult) => _baseStream.EndRead(asyncResult); /// public override void EndWrite(IAsyncResult asyncResult) => _baseStream.EndWrite(asyncResult); /// public override int ReadByte() => _baseStream.Position == _streamEnd + 1 ? -1 : _baseStream.ReadByte(); /// public override void WriteByte(byte value) { if(_baseStream.Position + 1 > _streamEnd) throw new IOException(Localization.Cannot_write_past_stream_end); _baseStream.WriteByte(value); } /// public override void Flush() => _baseStream.Flush(); /// public override int Read(byte[] buffer, int offset, int count) { if(_baseStream.Position + count > _streamEnd + 1) count = (int)(_streamEnd - _baseStream.Position); return _baseStream.EnsureRead(buffer, offset, count); } /// public override long Seek(long offset, SeekOrigin origin) { switch(origin) { case SeekOrigin.Begin: if(offset + _streamStart > _streamEnd) throw new IOException(Localization.Cannot_seek_after_stream_end); return _baseStream.Seek(offset + _streamStart, SeekOrigin.Begin) - _streamStart; case SeekOrigin.End: if(offset - (_baseStream.Length - _streamEnd) < _streamStart) throw new IOException(Localization.Cannot_seek_before_stream_start); return _baseStream.Seek(offset - (_baseStream.Length - _streamEnd), SeekOrigin.End) - _streamStart; default: if(offset + _baseStream.Position > _streamEnd) throw new IOException(Localization.Cannot_seek_after_stream_end); return _baseStream.Seek(offset, SeekOrigin.Current) - _streamStart; } } /// public override void SetLength(long value) => throw new NotSupportedException(Localization.Growing_OffsetStream_is_not_supported); /// public override void Write(byte[] buffer, int offset, int count) { if(_baseStream.Position + count > _streamEnd) throw new IOException(Localization.Cannot_write_past_stream_end); _baseStream.Write(buffer, offset, count); } }