Merge pull request #36 from quamotion/fixes/array-length

Add support for Span<byte>
This commit is contained in:
2018-06-19 17:18:04 +01:00
committed by GitHub
4 changed files with 133 additions and 49 deletions

View File

@@ -92,7 +92,35 @@ namespace Claunia.PropertyList
/// <exception cref="FormatException">When an error occurs during parsing.</exception>
public static NSObject Parse(byte[] bytes)
{
return Parse(bytes.AsSpan());
}
/// <summary>
/// Parses an ASCII property list from a byte array.
/// </summary>
/// <param name="bytes">The ASCII property list data.</param>
/// <param name="count">The offset at which to start reading the property list.</param>
/// <param name="offset">The length of the property list.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="FormatException">When an error occurs during parsing.</exception>
public static NSObject Parse(byte[] bytes, int offset, int count)
{
return Parse(bytes.AsSpan(offset, count));
}
/// <summary>
/// Parses an ASCII property list from a byte span.
/// </summary>
/// <param name="bytes">The ASCII property list data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="FormatException">When an error occurs during parsing.</exception>
public static NSObject Parse(ReadOnlySpan<byte> bytes)
{
#if NATIVE_SPAN
return ParseString(Encoding.UTF8.GetString(bytes));
#else
return ParseString(Encoding.UTF8.GetString(bytes.ToArray()));
#endif
}
/// <summary>

View File

@@ -53,11 +53,6 @@ namespace Claunia.PropertyList
/// </summary>
int minorVersion;
/// <summary>
/// Property list in bytes
/// </summary>
byte[] bytes;
/// <summary>
/// Length of an object reference in bytes
/// </summary>
@@ -84,6 +79,30 @@ namespace Claunia.PropertyList
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception>
public static NSObject Parse(byte[] data)
{
return Parse(data.AsSpan());
}
/// <summary>
/// Parses a binary property list from a byte array.
/// </summary>
/// <param name="data">The binary property list's data.</param>
/// <param name="offset">The length of the property list.</param>
/// <param name="count">The offset at which to start reading the property list.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception>
public static NSObject Parse(byte[] data, int offset, int length)
{
return Parse(data.AsSpan(offset, length));
}
/// <summary>
/// Parses a binary property list from a byte span.
/// </summary>
/// <param name="data">The binary property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception>
public static NSObject Parse(ReadOnlySpan<byte> data)
{
BinaryPropertyListParser parser = new BinaryPropertyListParser();
return parser.DoParse(data);
@@ -93,11 +112,10 @@ namespace Claunia.PropertyList
/// Parses a binary property list from a byte array.
/// </summary>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <param name="data">The binary property list's data.</param>
/// <param name="bytes">The binary property list's data.</param>
/// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception>
NSObject DoParse(byte[] data)
private NSObject DoParse(ReadOnlySpan<byte> bytes)
{
bytes = data;
string magic = Encoding.ASCII.GetString(CopyOfRange(bytes, 0, 8));
if (!magic.StartsWith("bplist", StringComparison.Ordinal))
{
@@ -142,7 +160,7 @@ namespace Claunia.PropertyList
offsetTable[i] = (int)ParseUnsignedInt(offsetBytes);
}
return ParseObject(topObject);
return ParseObject(bytes, topObject);
}
/// <summary>
@@ -180,7 +198,7 @@ namespace Claunia.PropertyList
/// <returns>The parsed object.</returns>
/// <param name="obj">The object ID.</param>
/// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception>
NSObject ParseObject(int obj)
NSObject ParseObject(ReadOnlySpan<byte> bytes, int obj)
{
int offset = offsetTable[obj];
byte type = bytes[offset];
@@ -258,7 +276,7 @@ namespace Claunia.PropertyList
case 0x4:
{
//Data
int[] lengthAndOffset = ReadLengthAndOffset(objInfo, offset);
int[] lengthAndOffset = ReadLengthAndOffset(bytes, objInfo, offset);
int length = lengthAndOffset[0];
int dataoffset = lengthAndOffset[1];
@@ -267,7 +285,7 @@ namespace Claunia.PropertyList
case 0x5:
{
//ASCII String
int[] lengthAndOffset = ReadLengthAndOffset(objInfo, offset);
int[] lengthAndOffset = ReadLengthAndOffset(bytes, objInfo, offset);
int length = lengthAndOffset[0]; //Each character is 1 byte
int stroffset = lengthAndOffset[1];
@@ -276,7 +294,7 @@ namespace Claunia.PropertyList
case 0x6:
{
//UTF-16-BE String
int[] lengthAndOffset = ReadLengthAndOffset(objInfo, offset);
int[] lengthAndOffset = ReadLengthAndOffset(bytes, objInfo, offset);
int length = lengthAndOffset[0];
int stroffset = lengthAndOffset[1];
@@ -288,7 +306,7 @@ namespace Claunia.PropertyList
case 0x7:
{
//UTF-8 string (v1.0 and later)
int[] lengthAndOffset = ReadLengthAndOffset(objInfo, offset);
int[] lengthAndOffset = ReadLengthAndOffset(bytes, objInfo, offset);
int strOffset = lengthAndOffset[1];
int characters = lengthAndOffset[0];
//UTF-8 characters can have variable length, so we need to calculate the byte length dynamically
@@ -305,7 +323,7 @@ namespace Claunia.PropertyList
case 0xA:
{
//Array
int[] lengthAndOffset = ReadLengthAndOffset(objInfo, offset);
int[] lengthAndOffset = ReadLengthAndOffset(bytes, objInfo, offset);
int length = lengthAndOffset[0];
int arrayOffset = lengthAndOffset[1];
@@ -315,7 +333,7 @@ namespace Claunia.PropertyList
int objRef = (int)ParseUnsignedInt(CopyOfRange(bytes,
offset + arrayOffset + i * objectRefSize,
offset + arrayOffset + (i + 1) * objectRefSize));
array.Add(ParseObject(objRef));
array.Add(ParseObject(bytes, objRef));
}
return array;
@@ -323,7 +341,7 @@ namespace Claunia.PropertyList
case 0xB:
{
//Ordered set (v1.0 and later)
int[] lengthAndOffset = ReadLengthAndOffset(objInfo, offset);
int[] lengthAndOffset = ReadLengthAndOffset(bytes, objInfo, offset);
int length = lengthAndOffset[0];
int contentOffset = lengthAndOffset[1];
@@ -333,14 +351,14 @@ namespace Claunia.PropertyList
int objRef = (int)ParseUnsignedInt(CopyOfRange(bytes,
offset + contentOffset + i * objectRefSize,
offset + contentOffset + (i + 1) * objectRefSize));
set.AddObject(ParseObject(objRef));
set.AddObject(ParseObject(bytes, objRef));
}
return set;
}
case 0xC:
{
//Set (v1.0 and later)
int[] lengthAndOffset = ReadLengthAndOffset(objInfo, offset);
int[] lengthAndOffset = ReadLengthAndOffset(bytes, objInfo, offset);
int length = lengthAndOffset[0];
int contentOffset = lengthAndOffset[1];
@@ -350,14 +368,14 @@ namespace Claunia.PropertyList
int objRef = (int)ParseUnsignedInt(CopyOfRange(bytes,
offset + contentOffset + i * objectRefSize,
offset + contentOffset + (i + 1) * objectRefSize));
set.AddObject(ParseObject(objRef));
set.AddObject(ParseObject(bytes, objRef));
}
return set;
}
case 0xD:
{
//Dictionary
int[] lengthAndOffset = ReadLengthAndOffset(objInfo, offset);
int[] lengthAndOffset = ReadLengthAndOffset(bytes, objInfo, offset);
int length = lengthAndOffset[0];
int contentOffset = lengthAndOffset[1];
@@ -371,8 +389,8 @@ namespace Claunia.PropertyList
int valRef = (int)ParseUnsignedInt(CopyOfRange(bytes,
offset + contentOffset + (length * objectRefSize) + i * objectRefSize,
offset + contentOffset + (length * objectRefSize) + (i + 1) * objectRefSize));
NSObject key = ParseObject(keyRef);
NSObject val = ParseObject(valRef);
NSObject key = ParseObject(bytes, keyRef);
NSObject val = ParseObject(bytes, valRef);
dict.Add(key.ToString(), val);
}
return dict;
@@ -392,7 +410,7 @@ namespace Claunia.PropertyList
/// <returns>An array with the length two. First entry is the length, second entry the offset at which the content starts.</returns>
/// <param name="objInfo">Object information byte.</param>
/// <param name="offset">Offset in the byte array at which the object is located.</param>
int[] ReadLengthAndOffset(int objInfo, int offset)
int[] ReadLengthAndOffset(ReadOnlySpan<byte> bytes, int objInfo, int offset)
{
int lengthValue = objInfo;
int offsetValue = 1;
@@ -434,7 +452,7 @@ namespace Claunia.PropertyList
/// <param name="bytes">Array containing the UTF-8 string.</param>
/// <param name="offset">Offset in the array where the UTF-8 string resides.</param>
/// <param name="numCharacters">How many UTF-8 characters are in the string.</param>
int CalculateUtf8StringLength(byte[] bytes, int offset, int numCharacters)
int CalculateUtf8StringLength(ReadOnlySpan<byte> bytes, int offset, int numCharacters)
{
int length = 0;
for (int i = 0; i < numCharacters; i++)
@@ -603,16 +621,15 @@ namespace Claunia.PropertyList
/// <param name="src">The source array.</param>
/// <param name="startIndex">The index from which to start copying.</param>
/// <param name="endIndex">The index until which to copy.</param>
public static byte[] CopyOfRange(byte[] src, int startIndex, int endIndex)
public static byte[] CopyOfRange(ReadOnlySpan<byte> src, int startIndex, int endIndex)
{
int length = endIndex - startIndex;
if (length < 0)
{
throw new ArgumentOutOfRangeException("startIndex (" + startIndex + ")" + " > endIndex (" + endIndex + ")");
}
byte[] dest = new byte[length];
Array.Copy(src, startIndex, dest, 0, length);
return dest;
return src.Slice(startIndex, endIndex - startIndex).ToArray();
}
}
}

View File

@@ -72,7 +72,7 @@ namespace Claunia.PropertyList
/// </summary>
/// <returns>The very first bytes of data of the property list (minus any whitespace)</returns>
/// <param name="bytes">The type of the property list</param>
static int DetermineType(byte[] bytes)
static int DetermineType(ReadOnlySpan<byte> bytes)
{
if (bytes.Length == 0)
return TYPE_ERROR_BLANK;
@@ -88,7 +88,13 @@ namespace Claunia.PropertyList
{
offset++;
}
return DetermineType(Encoding.ASCII.GetString(bytes, offset, Math.Min(8, bytes.Length - offset)));
var header = bytes.Slice(offset, Math.Min(8, bytes.Length - offset));
#if NATIVE_SPAN
return DetermineType(Encoding.ASCII.GetString(header));
#else
return DetermineType(Encoding.ASCII.GetString(header.ToArray(), 0, header.Length));
#endif
}
/// <summary>
@@ -194,6 +200,38 @@ namespace Claunia.PropertyList
}
}
/// <summary>
/// Parses a property list from a byte array.
/// </summary>
/// <param name="bytes">The property list data as a byte array.</param>
/// <param name="offset">The length of the property list.</param>
/// <param name="count">The offset at which to start reading the property list.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(byte[] bytes, int offset, int length)
{
return Parse(bytes.AsSpan(offset, length));
}
/// <summary>
/// Parses a property list from a byte span.
/// </summary>
/// <param name="bytes">The property list data as a byte array.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(ReadOnlySpan<byte> bytes)
{
switch (DetermineType(bytes))
{
case TYPE_BINARY:
return BinaryPropertyListParser.Parse(bytes);
case TYPE_XML:
return XmlPropertyListParser.Parse(bytes.ToArray());
case TYPE_ASCII:
return ASCIIPropertyListParser.Parse(bytes);
default:
throw new PropertyListFormatException("The given data is not a property list of a supported format.");
}
}
/// <summary>
/// Parses a property list from an Stream.
/// </summary>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp1.0;netstandard1.3;netstandard1.4;netstandard1.6;netcoreapp2.0;netstandard2.0</TargetFrameworks>
<TargetFrameworks>netcoreapp1.0;netstandard1.3;netstandard1.4;netstandard1.6;netcoreapp2.0;netstandard2.0;netcoreapp2.1</TargetFrameworks>
<Version>1.16</Version>
<Authors>Natalia Portillo</Authors>
<Company>Claunia.com</Company>
@@ -12,22 +12,23 @@
<RepositoryUrl>http://www.github.com/claunia/plist-cil</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>apple propertylist property list gnustep plist</PackageTags>
<PackageReleaseNotes>Added support for .NETStandard1.4.
Enhanced binary compatibility with Apple.
Correct opening of plists with unescaped UTF8 strings.
Added support for parsing hex numbers.
Added explicit casts for NSNumber, NSData, NSDate and NSString.
Added examples to README.</PackageReleaseNotes>
<PackageReleaseNotes>
Added support for .NETStandard1.4.
Enhanced binary compatibility with Apple.
Correct opening of plists with unescaped UTF8 strings.
Added support for parsing hex numbers.
Added explicit casts for NSNumber, NSData, NSDate and NSString.
Added examples to README.
</PackageReleaseNotes>
<NeutralLanguage>en-US</NeutralLanguage>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>plist-cil.snk</AssemblyOriginatorKeyFile>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
</PropertyGroup>
<PropertyGroup Condition="'$(OS)' == 'Windows_NT'">
<TargetFrameworks>$(TargetFrameworks);net45;net40</TargetFrameworks>
<TargetFrameworks>$(TargetFrameworks);net45</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@@ -42,22 +43,18 @@ Added examples to README.</PackageReleaseNotes>
<DefineConstants>$(DefineConstants);NETCORE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netcoreapp1.0|AnyCPU'">
<DocumentationFile>bin\Release\netcoreapp1.0\plist-cil.xml</DocumentationFile>
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<DefineConstants>$(DefineConstants);NATIVE_SPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net40|AnyCPU'">
<DocumentationFile>bin\Release\net40\plist-cil.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net40' Or '$(TargetFramework)' == 'net45'">
<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
<Reference Include="System" />
<Reference Include="System.Numerics" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith('netcoreapp')) Or $(TargetFramework.StartsWith('netstandard'))">
<ItemGroup Condition="$(TargetFramework.StartsWith('netstandard1.')) Or $(TargetFramework.StartsWith('netcoreapp1.'))">
<PackageReference Include="System.Diagnostics.Debug" Version="4.3.0" />
<PackageReference Include="System.Linq" Version="4.3.0" />
<PackageReference Include="System.Globalization" Version="4.3.0" />
@@ -74,6 +71,10 @@ Added examples to README.</PackageReleaseNotes>
<PackageReference Include="System.Runtime.Numerics" Version="4.3.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp2.1'">
<PackageReference Include="System.Memory" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<None Include="..\LICENSE">
<Link>LICENSE</Link>