Skip to content

Commit

Permalink
Packaging: Port fix for GetParts from .NETFramework
Browse files Browse the repository at this point in the history
This addresses a bug in the `GetParts` method by porting a fix from .NETFramework, improving part URI handling and collision detection..

`src/libraries/System.IO.Packaging/src/System/IO/Packaging/Package.cs`: Added sorting of parts array, updated dictionary structure for part URIs, and implemented collision detection and handling.

`src/libraries/System.IO.Packaging/src/System.IO.Packaging.csproj`: Enabled package generation on build and updated servicing version.
  • Loading branch information
ericstj authored and carlossanlop committed Sep 9, 2024
1 parent 40d60a1 commit 741b045
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<NoWarn>$(NoWarn);CA1847</NoWarn>
<IsPackable>true</IsPackable>
<!-- If you enable GeneratePackageOnBuild for this package and bump ServicingVersion, make sure to also bump ServicingVersion in Microsoft.Windows.Compatibility.csproj once for the next release. -->
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<ServicingVersion>0</ServicingVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<ServicingVersion>1</ServicingVersion>
<PackageDescription>Provides classes that support storage of multiple data objects in a single container.</PackageDescription>
</PropertyGroup>

Expand Down Expand Up @@ -59,4 +59,4 @@
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
<Reference Include="WindowsBase" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -403,32 +403,63 @@ public PackagePartCollection GetParts()

PackUriHelper.ValidatedPartUri partUri;

var uriComparer = Comparer<PackUriHelper.ValidatedPartUri>.Default;

//Sorting the parts array which takes O(n log n) time.
Array.Sort(parts, Comparer<PackagePart>.Create((partA, partB) => uriComparer.Compare((PackUriHelper.ValidatedPartUri)partA.Uri, (PackUriHelper.ValidatedPartUri)partB.Uri)));

//We need this dictionary to detect any collisions that might be present in the
//list of parts that was given to us from the underlying physical layer, as more than one
//partnames can be mapped to the same normalized part.
//Note: We cannot use the _partList member variable, as that gets updated incrementally and so its
//not possible to find the collisions using that list.
//PackUriHelper.ValidatedPartUri implements the IComparable interface.
Dictionary<PackUriHelper.ValidatedPartUri, PackagePart> seenPartUris = new Dictionary<PackUriHelper.ValidatedPartUri, PackagePart>(parts.Length);
Dictionary<string, KeyValuePair<PackUriHelper.ValidatedPartUri, PackagePart>> partDictionary = new Dictionary<string, KeyValuePair<PackUriHelper.ValidatedPartUri, PackagePart>>(parts.Length);
List<string> partIndex = new List<string>(parts.Length);

for (int i = 0; i < parts.Length; i++)
{
partUri = (PackUriHelper.ValidatedPartUri)parts[i].Uri;

if (seenPartUris.ContainsKey(partUri))
string normalizedPartName = partUri.NormalizedPartUriString;

if (partDictionary.ContainsKey(normalizedPartName))
{
throw new FileFormatException(SR.BadPackageFormat);
}
else
{
// Add the part to the list of URIs that we have already seen
seenPartUris.Add(partUri, parts[i]);
//since we will arive to this line of code after the parts are already sorted
string? precedingPartName = null;

if (partIndex.Count > 0)
{
precedingPartName = (partIndex[partIndex.Count - 1]);
}

// Add the part to the dictionary
partDictionary.Add(normalizedPartName, new KeyValuePair<PackUriHelper.ValidatedPartUri, PackagePart>(partUri, parts[i]));

if (!_partList.ContainsKey(partUri))
if (precedingPartName != null
&& normalizedPartName.StartsWith(precedingPartName, StringComparison.Ordinal)
&& normalizedPartName.Length > precedingPartName.Length
&& normalizedPartName[precedingPartName.Length] == PackUriHelper.ForwardSlashChar)
{
// Add the part to the _partList if there is no prefix collision
AddIfNoPrefixCollisionDetected(partUri, parts[i]);
//Removing the invalid entry from the _partList.
partDictionary.Remove(normalizedPartName);

throw new InvalidOperationException(SR.PartNamePrefixExists);
}

//adding entry to partIndex to keep track of last element being added.
//since parts are already sorted, last element in partIndex list will point to preceeding element to the current.
partIndex.Add(partUri.NormalizedPartUriString);
}
}

//copying parts from partdictionary to partlist
CopyPartDicitonaryToPartList(partDictionary, partIndex);

_partCollection = new PackagePartCollection(_partList);
}
return _partCollection;
Expand Down Expand Up @@ -1186,6 +1217,23 @@ private PackageRelationshipCollection GetRelationshipsHelper(string? filterStrin
return new PackageRelationshipCollection(_relationships, filterString);
}

private void CopyPartDicitonaryToPartList(Dictionary<string, KeyValuePair<PackUriHelper.ValidatedPartUri, PackagePart>> partDictionary, List<string> partIndex)
{
//Clearing _partList before copying in new data. Reassigning the variable, assuming the previous object to be garbage collected.
//ideally addition to sortedlist takes O(n) but since we have sorted data and also we defined the size, it will take O(log n) per addition
//total time complexity for this function will be O(n log n)
_partList = new SortedList<PackUriHelper.ValidatedPartUri, PackagePart>(partDictionary.Count);

//Since partIndex is created from a sorted parts array we are sure that partIndex
//will have items in same order
foreach (var id in partIndex)
{
//retrieving object from partDictionary hashtable
var keyValue = partDictionary[id];
_partList.Add(keyValue.Key, keyValue.Value);
}
}

#endregion Private Methods

#region Private Members
Expand Down

0 comments on commit 741b045

Please sign in to comment.