diff --git a/Directory.Packages.props b/Directory.Packages.props index 7559f05e..39c5815f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ - + diff --git a/src/SharpCompress/Common/ArchiveEncodingExtensions.cs b/src/SharpCompress/Common/ArchiveEncodingExtensions.cs index 88dc35b4..08e64329 100644 --- a/src/SharpCompress/Common/ArchiveEncodingExtensions.cs +++ b/src/SharpCompress/Common/ArchiveEncodingExtensions.cs @@ -24,14 +24,6 @@ public enum EncodingType /// public static class ArchiveEncodingExtensions { -#if !NETFRAMEWORK - /// - /// Registers the code pages encoding provider. - /// - static ArchiveEncodingExtensions() => - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); -#endif - extension(IArchiveEncoding encoding) { /// diff --git a/src/SharpCompress/Common/EncodingProviderRegistration.cs b/src/SharpCompress/Common/EncodingProviderRegistration.cs new file mode 100644 index 00000000..351b0231 --- /dev/null +++ b/src/SharpCompress/Common/EncodingProviderRegistration.cs @@ -0,0 +1,52 @@ +using System.Runtime.CompilerServices; +using System.Text; + +namespace SharpCompress.Common; + +/// +/// Registers so that legacy code pages (e.g. 437, 866) +/// used by archive headers are resolvable via . +/// +/// +/// +/// This runs from a module initializer rather than a static constructor. Registration must happen +/// before any encoding lookup, including lookups a caller performs itself while building an +/// — for example Encoding.GetEncoding(866). A static constructor +/// only fires when its own type is first touched, which made registration order-dependent and caused +/// for callers that resolved a code page before touching +/// any other SharpCompress type. +/// +/// +/// .NET Framework resolves these code pages natively, so registration is only needed elsewhere. +/// +/// +internal static class EncodingProviderRegistration +{ +#if !NETFRAMEWORK + // CA2255 discourages [ModuleInitializer] in libraries because load-time work can surprise consumers. + // Registering an encoding provider is the exception it does not account for: the registration has to be + // in place before the first Encoding.GetEncoding call, and that call may be made by the consumer before + // it touches any SharpCompress type. Every lazier trigger reintroduces the ordering bug. Callers who need + // registration to be explicit (e.g. to keep it trimmable) can call RegisterCodePagesProvider themselves. +#pragma warning disable CA2255 + [ModuleInitializer] +#pragma warning restore CA2255 + internal static void Initialize() => RegisterCodePagesProvider(); + + private static bool _registered; + + /// + /// Registers the code pages provider if it has not already been registered. Idempotent. + /// + internal static void RegisterCodePagesProvider() + { + if (_registered) + { + return; + } + + _registered = true; + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } +#endif +} diff --git a/src/SharpCompress/Compressors/LZMA/Log.cs b/src/SharpCompress/Compressors/LZMA/Log.cs deleted file mode 100644 index e0c9bcd9..00000000 --- a/src/SharpCompress/Compressors/LZMA/Log.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using SharpCompress.Common; - -namespace SharpCompress.Compressors.LZMA; - -internal static class Log -{ - private static readonly Stack INDENT = new(); - private static bool NEEDS_INDENT = true; - - static Log() => INDENT.Push(""); - - public static void PushIndent(string indent = " ") => INDENT.Push(INDENT.Peek() + indent); - - public static void PopIndent() - { - if (INDENT.Count == 1) - { - throw new ArchiveOperationException(); - } - - INDENT.Pop(); - } - - private static void EnsureIndent() - { - if (NEEDS_INDENT) - { - NEEDS_INDENT = false; - } - } - - public static void Write(object value) - { - EnsureIndent(); - } - - public static void Write(string text) - { - EnsureIndent(); - } - - public static void Write(string format, params object[] args) - { - EnsureIndent(); - } - - public static void WriteLine() - { - NEEDS_INDENT = true; - } - - public static void WriteLine(object value) - { - EnsureIndent(); - NEEDS_INDENT = true; - } - - public static void WriteLine(string text) - { - EnsureIndent(); - NEEDS_INDENT = true; - } - - public static void WriteLine(string format, params object[] args) - { - EnsureIndent(); - NEEDS_INDENT = true; - } -} diff --git a/src/SharpCompress/IO/IStreamStack.cs b/src/SharpCompress/IO/IStreamStack.cs index c24a9c90..f6baa199 100644 --- a/src/SharpCompress/IO/IStreamStack.cs +++ b/src/SharpCompress/IO/IStreamStack.cs @@ -36,20 +36,16 @@ public static class StreamStackExtensions } /// - /// Gets the root underlying stream at the bottom of the stack. - /// This is useful for seeking when the intermediate streams don't support it. + /// Rewinds by bytes within the buffered region of the nearest + /// in the stack. /// - public static Stream GetRootStream(this IStreamStack stack) - { - var current = stack.BaseStream(); - while (current is IStreamStack streamStack) - { - current = streamStack.BaseStream(); - } - return current; - } - - internal static void Rewind(this IStreamStack stream, int count) + /// + /// Named distinctly from and + /// , which rewind to the recording anchor instead. + /// A single Rewind name across both would let Rewind(4) and Rewind(true) select + /// unrelated semantics on the same variable with no compiler complaint. + /// + internal static void RewindBytes(this IStreamStack stream, int count) { IStreamStack? current = stream; diff --git a/src/SharpCompress/Polyfills/ModuleInitializerAttribute.cs b/src/SharpCompress/Polyfills/ModuleInitializerAttribute.cs new file mode 100644 index 00000000..a129cdbe --- /dev/null +++ b/src/SharpCompress/Polyfills/ModuleInitializerAttribute.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// This file is required for [ModuleInitializer] to work on older target frameworks +// (.NET Framework 4.8, .NET Standard 2.0/2.1). The attribute is recognised by the C# compiler +// by name, so supplying our own definition is enough to enable module initializers there. + +#if NETFRAMEWORK || NETSTANDARD2_0 || NETSTANDARD2_1 +using System.ComponentModel; + +namespace System.Runtime.CompilerServices; + +/// +/// Used to indicate to the compiler that a method should be called in its containing module's initializer. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +[AttributeUsage(AttributeTargets.Method, Inherited = false)] +internal sealed class ModuleInitializerAttribute : Attribute { } +#endif diff --git a/tests/SharpCompress.Test/Tar/TarWriterAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarWriterAsyncTests.cs index 3a516a04..31fa98fa 100644 --- a/tests/SharpCompress.Test/Tar/TarWriterAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarWriterAsyncTests.cs @@ -13,14 +13,6 @@ namespace SharpCompress.Test.Tar; public class TarWriterAsyncTests : WriterTests { - static TarWriterAsyncTests() - { -#if !NETFRAMEWORK - //fix issue where these tests could not be ran in isolation - System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); -#endif - } - public TarWriterAsyncTests() : base(ArchiveType.Tar) => UseExtensionInsteadOfNameToVerify = true; diff --git a/tests/SharpCompress.Test/Tar/TarWriterTests.cs b/tests/SharpCompress.Test/Tar/TarWriterTests.cs index b4cada92..45660128 100644 --- a/tests/SharpCompress.Test/Tar/TarWriterTests.cs +++ b/tests/SharpCompress.Test/Tar/TarWriterTests.cs @@ -11,14 +11,6 @@ namespace SharpCompress.Test.Tar; public class TarWriterTests : WriterTests { - static TarWriterTests() - { -#if !NETFRAMEWORK - //fix issue where these tests could not be ran in isolation - System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); -#endif - } - public TarWriterTests() : base(ArchiveType.Tar) => UseExtensionInsteadOfNameToVerify = true; diff --git a/tests/SharpCompress.Test/packages.lock.json b/tests/SharpCompress.Test/packages.lock.json index 2f685ff1..ed267ec1 100644 --- a/tests/SharpCompress.Test/packages.lock.json +++ b/tests/SharpCompress.Test/packages.lock.json @@ -4,9 +4,9 @@ ".NETFramework,Version=v4.8": { "AwesomeAssertions": { "type": "Direct", - "requested": "[9.4.0, )", - "resolved": "9.4.0", - "contentHash": "dJxkWiQ8D+xT6Gr2sSL83+Mar+Vpy2JTcUPxFcckpPJ8VYBfSgnk+zqpS6t7kcGnjz8NLyF14qfuoL4bKzzoew==", + "requested": "[9.5.0, )", + "resolved": "9.5.0", + "contentHash": "F92MsjoF8B7IdcWETru4QGZx6VAl38qQPKNGS3kwPwlLB5lb0O4bhFqG8a8sz2estQJWkKmZkVy1r19HTaFcpg==", "dependencies": { "System.Threading.Tasks.Extensions": "4.5.4" } @@ -331,9 +331,9 @@ "net10.0": { "AwesomeAssertions": { "type": "Direct", - "requested": "[9.4.0, )", - "resolved": "9.4.0", - "contentHash": "dJxkWiQ8D+xT6Gr2sSL83+Mar+Vpy2JTcUPxFcckpPJ8VYBfSgnk+zqpS6t7kcGnjz8NLyF14qfuoL4bKzzoew==" + "requested": "[9.5.0, )", + "resolved": "9.5.0", + "contentHash": "F92MsjoF8B7IdcWETru4QGZx6VAl38qQPKNGS3kwPwlLB5lb0O4bhFqG8a8sz2estQJWkKmZkVy1r19HTaFcpg==" }, "Microsoft.NET.Test.Sdk": { "type": "Direct",