From 44f0fcc763b88fc1291c2c4fcbf2e946a9fe34b9 Mon Sep 17 00:00:00 2001 From: Johann Studanski Date: Mon, 27 Jan 2025 18:06:12 +0100 Subject: [PATCH] More cleanup, handle binary parsing more cleanly --- plist-cil.test/ValuePreprocessorTests.cs | 134 +++++++++++++---------- plist-cil/BinaryPropertyListParser.cs | 4 +- plist-cil/NSNumber.cs | 6 +- plist-cil/PropertyListParser.cs | 6 +- plist-cil/ValuePreprocessor.cs | 51 +++++++-- 5 files changed, 129 insertions(+), 72 deletions(-) diff --git a/plist-cil.test/ValuePreprocessorTests.cs b/plist-cil.test/ValuePreprocessorTests.cs index 349631b..12869d8 100644 --- a/plist-cil.test/ValuePreprocessorTests.cs +++ b/plist-cil.test/ValuePreprocessorTests.cs @@ -1,28 +1,3 @@ -// plist-cil - An open source library to Parse and generate property lists for .NET -// Copyright (C) 2015 Natalia Portillo -// -// This code is based on: -// plist - An open source library to Parse and generate property lists -// Copyright (C) 2014 Daniel Dreibrodt -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - using System; using System.Linq; using Claunia.PropertyList; @@ -30,63 +5,110 @@ using Xunit; namespace plistcil.test { - public static class ValuePreprocessorTests { + // lock tests to make sure temporarily added / replaced preprocessors don't interfere with the other tests in this suite + private static readonly object _testLock = new(); + [Fact] public static void TestPassiveDefaultPreprocessorsRegistered() { - Assert.Equal(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL), true); - Assert.Equal(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL), false); - Assert.Equal(ValuePreprocessor.Preprocess("true", ValuePreprocessor.Type.BOOL), "true"); - Assert.Equal(ValuePreprocessor.Preprocess("42",ValuePreprocessor.Type.INTEGER), "42"); - Assert.Equal(ValuePreprocessor.Preprocess("3.14159",ValuePreprocessor.Type.FLOATING_POINT), "3.14159"); - Assert.Equal(ValuePreprocessor.Preprocess("2.71828", ValuePreprocessor.Type.UNDEFINED_NUMBER), "2.71828"); - Assert.Equal(ValuePreprocessor.Preprocess("TestString",ValuePreprocessor.Type.STRING), "TestString"); - Assert.Equal(ValuePreprocessor.Preprocess("TestData",ValuePreprocessor.Type.DATA), "TestData"); - byte[] value = { 0x1, 0x2, 0x4, 0x8 }; - Assert.Equal(ValuePreprocessor.Preprocess(value,ValuePreprocessor.Type.DATA), value); - Assert.Equal(ValuePreprocessor.Preprocess("01.02.1903",ValuePreprocessor.Type.DATE), "01.02.1903"); - Assert.Equal(ValuePreprocessor.Preprocess(23.0, ValuePreprocessor.Type.DATE), 23.0); + Assert.Equal(true, ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); + Assert.Equal(false, ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); + Assert.Equal("true", ValuePreprocessor.Preprocess("true", ValuePreprocessor.Type.BOOL)); + Assert.Equal("42", ValuePreprocessor.Preprocess("42", ValuePreprocessor.Type.INTEGER)); + Assert.Equal("3.14159", ValuePreprocessor.Preprocess("3.14159", ValuePreprocessor.Type.FLOATING_POINT)); + Assert.Equal("2.71828", ValuePreprocessor.Preprocess("2.71828", ValuePreprocessor.Type.UNDEFINED_NUMBER)); + Assert.Equal("TestString", ValuePreprocessor.Preprocess("TestString", ValuePreprocessor.Type.STRING)); + Assert.Equal("TestData", ValuePreprocessor.Preprocess("TestData", ValuePreprocessor.Type.DATA)); + byte[] value = [0x1, 0x2, 0x4, 0x8]; + Assert.Equal(value, ValuePreprocessor.Preprocess(value, ValuePreprocessor.Type.DATA)); + Assert.Equal("01.02.1903", ValuePreprocessor.Preprocess("01.02.1903", ValuePreprocessor.Type.DATE)); + Assert.Equal(23.0, ValuePreprocessor.Preprocess(23.0, ValuePreprocessor.Type.DATE)); } [Fact] public static void TestRegisterPreprocessor() { - Func examplePreprocessor = value => new string(value.Reverse().ToArray()); - string testString = "TestString"; - string expected = "gnirtStseT"; + lock(_testLock) + { + Func examplePreprocessor = value => new string(value.Reverse().ToArray()); + string testString = "TestString"; + string expected = "gnirtStseT"; - ValuePreprocessor.Register(examplePreprocessor, ValuePreprocessor.Type.STRING); - string actual = ValuePreprocessor.Preprocess(testString, ValuePreprocessor.Type.STRING); + var testType = (ValuePreprocessor.Type)42; - Assert.Equal(actual, expected); + ValuePreprocessor.Set(examplePreprocessor, testType); + string actual = ValuePreprocessor.Preprocess(testString, testType); - ValuePreprocessor.Unregister(ValuePreprocessor.Type.STRING); + Assert.Equal(actual, expected); + + ValuePreprocessor.Unset(testType); + } } [Fact] - public static void TestRegisteredPreprocessorSelection() + public static void TestRegisteredPreprocessorSelection1() { - Func examplePreprocessor = value => new string(value.Reverse().ToArray()); - string testString = "TestString"; + lock(_testLock) + { + Func examplePreprocessor = value => (short)(value - 1); + short testShort = 42; + string testString = "TestString"; - ValuePreprocessor.Register(examplePreprocessor, ValuePreprocessor.Type.STRING); - string actual = ValuePreprocessor.Preprocess(testString, ValuePreprocessor.Type.DATA); + var testType = (ValuePreprocessor.Type)42; - // assert unchanged, since the selected preprocessor != registered preprocessor - Assert.Equal(actual, testString); + // correct value type, differing data type + ValuePreprocessor.Set(examplePreprocessor, testType); + ValuePreprocessor.Set(ValuePreprocessor.GetDefault(), testType); - ValuePreprocessor.Unregister(ValuePreprocessor.Type.STRING); + string actual1 = ValuePreprocessor.Preprocess(testString, testType); + short actual2 = ValuePreprocessor.Preprocess(testShort, testType); + + // assert unchanged, since the selected preprocessor != tested preprocessor + Assert.Equal(actual1, testString); + Assert.NotEqual(actual2, testShort); + + ValuePreprocessor.Remove(testType); + ValuePreprocessor.Remove(testType); + } } [Fact] - public static void TestUnregisterdPreprocessorThrows() + public static void TestRegisteredPreprocessorSelection2() { - byte[] testArray = { 0x1, 0x2, 0x4, 0x8 }; + lock(_testLock) + { + Func examplePreprocessor = value => new string(value.Reverse().ToArray()); + byte[] testByteArray = [0x42,]; + string testString = "TestString"; + + var testType = (ValuePreprocessor.Type)42; + + // correct value type, differing data type + ValuePreprocessor.Set(examplePreprocessor, testType); + ValuePreprocessor.Set(ValuePreprocessor.GetDefault(), testType); + + string actual1 = ValuePreprocessor.Preprocess(testString, testType); + byte[] actual2 = ValuePreprocessor.Preprocess(testByteArray, testType); + + Assert.NotEqual(actual1, testString); + + // assert unchanged, since the selected preprocessor != tested preprocessor + Assert.Equal(actual2, testByteArray); + + ValuePreprocessor.Unset(testType); + ValuePreprocessor.Remove(testType); + } + } + + [Fact] + public static void TestUnregisteredPreprocessorThrows() + { + byte[] testArray = [0x1, 0x2, 0x4, 0x8]; // there's no registered preprocessor for byte array arguments for STRING Assert.Throws(() => ValuePreprocessor.Preprocess(testArray, ValuePreprocessor.Type.STRING)); } } -} +} \ No newline at end of file diff --git a/plist-cil/BinaryPropertyListParser.cs b/plist-cil/BinaryPropertyListParser.cs index 947459c..308b582 100644 --- a/plist-cil/BinaryPropertyListParser.cs +++ b/plist-cil/BinaryPropertyListParser.cs @@ -243,14 +243,14 @@ namespace Claunia.PropertyList //integer int length = 1 << objInfo; - return new NSNumber(bytes.Slice(offset + 1, length), NSNumber.INTEGER); + return new NSNumber(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, length).ToArray(), ValuePreprocessor.Type.INTEGER), NSNumber.INTEGER); } case 0x2: { //real int length = 1 << objInfo; - return new NSNumber(bytes.Slice(offset + 1, length), NSNumber.REAL); + return new NSNumber(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, length).ToArray(), ValuePreprocessor.Type.FLOATING_POINT), NSNumber.REAL); } case 0x3: { diff --git a/plist-cil/NSNumber.cs b/plist-cil/NSNumber.cs index 018f875..380530a 100644 --- a/plist-cil/NSNumber.cs +++ b/plist-cil/NSNumber.cs @@ -69,13 +69,13 @@ namespace Claunia.PropertyList switch(type) { case INTEGER: - doubleValue = longValue = ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseLong(bytes), ValuePreprocessor.Type.INTEGER); + doubleValue = longValue = BinaryPropertyListParser.ParseLong(bytes); break; case REAL: - doubleValue = ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseDouble(bytes), ValuePreprocessor.Type.FLOATING_POINT); - longValue = ValuePreprocessor.Preprocess((long)Math.Round(doubleValue), ValuePreprocessor.Type.INTEGER); + doubleValue = BinaryPropertyListParser.ParseDouble(bytes); + longValue = (long)Math.Round(doubleValue); break; diff --git a/plist-cil/PropertyListParser.cs b/plist-cil/PropertyListParser.cs index 6cfc071..688de80 100644 --- a/plist-cil/PropertyListParser.cs +++ b/plist-cil/PropertyListParser.cs @@ -147,11 +147,11 @@ namespace Claunia.PropertyList return type; } - /// Register preprocessing functions for for plist values. + /// Set up preprocessing functions for plist values. /// A function that preprocesses the passed string and returns the adjusted value. /// The type of value preprocessor to use. - public static void RegisterValuePreprocessor(Func preprocessor, ValuePreprocessor.Type type) => - ValuePreprocessor.Register(preprocessor, type); + public static void SetValuePreprocessor(Func preprocessor, ValuePreprocessor.Type type) => + ValuePreprocessor.Set(preprocessor, type); /// Reads all bytes from an Stream and stores them in an array, up to a maximum count. /// The Stream pointing to the data that should be stored in the array. diff --git a/plist-cil/ValuePreprocessor.cs b/plist-cil/ValuePreprocessor.cs index f6153de..436fa3f 100644 --- a/plist-cil/ValuePreprocessor.cs +++ b/plist-cil/ValuePreprocessor.cs @@ -25,7 +25,7 @@ namespace Claunia.PropertyList /// private static T NullPreprocessor(T value) => value; - private record struct TypeIdentifier(Type ValueType, System.Type ProcessingType); + private record struct TypeIdentifier(Type ValueType, System.Type DataType); /// /// Default preprocessors for all the standard cases. @@ -35,7 +35,9 @@ namespace Claunia.PropertyList { new TypeIdentifier(Type.BOOL, typeof(bool)), NullPreprocessor }, { new TypeIdentifier(Type.BOOL, typeof(string)), NullPreprocessor }, { new TypeIdentifier(Type.INTEGER, typeof(string)), NullPreprocessor }, + { new TypeIdentifier(Type.INTEGER, typeof(byte[])), NullPreprocessor }, { new TypeIdentifier(Type.FLOATING_POINT, typeof(string)), NullPreprocessor }, + { new TypeIdentifier(Type.FLOATING_POINT, typeof(byte[])), NullPreprocessor }, { new TypeIdentifier(Type.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor }, { new TypeIdentifier(Type.STRING, typeof(string)), NullPreprocessor }, { new TypeIdentifier(Type.DATA, typeof(string)), NullPreprocessor }, @@ -45,16 +47,32 @@ namespace Claunia.PropertyList }; /// - /// Register a custom preprocessor. + /// Get a default preprocessor. /// - public static void Register(Func preprocessor, Type type) => - _preprocessors[new(type, typeof(T))] = preprocessor; + public static Func GetDefault() => NullPreprocessor; /// - /// Unregister a specific preprocessor--replaces it with a null-implementation + /// Set up a custom preprocessor. + /// + public static void Set(Func preprocessor, Type type) => + _preprocessors[new(type, typeof(T))] = preprocessor; + + + /// + /// Unset a specific preprocessor--replaces it with a null-implementation /// to prevent argument errors. /// - public static void Unregister(Type type) => _preprocessors[new(type, typeof(T))] = NullPreprocessor; + /// If no appropriate preprocessor--not even a default null-implementation--was set up. + public static void Unset(Type type) => + _preprocessors[GetValidTypeIdentifier(type)] = NullPreprocessor; + + /// + /// Completely unregister a specific preprocessor--remove it instead of + /// replacing it with a null-implementation. + /// + /// If no appropriate preprocessor--not even a default null-implementation--was registered. + public static void Remove(Type type) => + _preprocessors.Remove(GetValidTypeIdentifier(type)); /// /// Preprocess the supplied data using the appropriate registered implementation. @@ -65,7 +83,8 @@ namespace Claunia.PropertyList : throw new ArgumentException($"Failed to find a preprocessor for value '{value}'."); /// - /// Gets the appropriate registered implementation--or null--and casts it back to the required type. + /// Gets the appropriate registered implementation--or null--and casts it back to + /// the required type. /// private static bool TryGetPreprocessor(Type type, out Func preprocess) { @@ -80,5 +99,21 @@ namespace Claunia.PropertyList return false; } + + /// + /// Gets a type identifier if a preprocessor exists for it. + /// + /// If no appropriate preprocessor--not even a default null-implementation--was set up. + private static TypeIdentifier GetValidTypeIdentifier(Type type) + { + var identifier = new TypeIdentifier(type, typeof(T)); + + if(!_preprocessors.ContainsKey(identifier)) + { + throw new ArgumentException($"Failed to find a valid preprocessor type identifier."); + } + + return identifier; + } } -} \ No newline at end of file +}