-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
System.IO.Packaging.Package
provides a simple abstraction over a container to hold multiple objects in a single entity. This is commonly used in nupkg packages, vsix extensions, appx, and also Office documents. The package format makes heavy use of URIs to maintain relationships between various components. However, Office will allow a user to generate an invalid URI for one of these relationships, and then opening via System.IO.Packaging fails with no easy way of recovery besides manually updating the file, which necessitates a deeper understanding of the file format that should be abstracted by the library.
This is to work around a breaking change that occurred between 4.0->4.5. This proposal is to provide a way to work around that change.
Example
There are multiple issues filed on the OpenXml SDK project (which abstracts the Office format to strongly typed classes) and related projects where users have been hit by this. For example:
- Malformed mailto Hyperlink causes Exception on .NET 4.5+ Open-XML-SDK#38
- Invalid Hyperlink: Malformed URI is embedded as a hyperlink in the document. ClosedXML/ClosedXML#249
A simple Office document that will fail can be created by the following:
- Create a new Word document
- Add a hyperlink to an invalid URI (ie
mailto:one@
) - Try to load with
Package
:
using (var package = Package.Open(pathToDoc))
{
foreach (var part in package.GetParts())
{
part.GetRelationships();
}
}
This will fail with the following exception:
System.UriFormatException
HResult=0x80131537
Message=Invalid URI: The hostname could not be parsed.
Source=System.Private.Uri
StackTrace:
at System.Uri.CreateThis(String uri, Boolean dontEscape, UriKind uriKind)
at System.Uri..ctor(String uriString, UriKind uriKind)
at System.IO.Packaging.InternalRelationshipCollection.ProcessRelationshipAttributes(XmlCompatibilityReader reader)
at System.IO.Packaging.InternalRelationshipCollection.ParseRelationshipPart(PackagePart part)
at System.IO.Packaging.InternalRelationshipCollection..ctor(Package package, PackagePart part)
at System.IO.Packaging.PackagePart.EnsureRelationships()
at System.IO.Packaging.PackagePart.GetRelationshipsHelper(String filterString)
at PackagingExample.Program.Main(String[] args) in c:\users\tasou\source\repos\PackagingExample\PackagingExample\Program.cs:line 15
Proposed API
namespace System.IO.Packaging
{
public abstract class Package // Existing class
{
public static Package Open(string path, FileShare packageShare, PackageSettings settings);
public static Package Open(string path, PackageSettings settings);
public static Package Open(Stream stream, PackageSettings settings);
}
public class PackageSettings
{
public FileMode PackageMode { get; set; }
public FileAccess PackageAccess { get; set; }
public IUriProvider UriProvider { get; set; }
}
public interface IUriProvider
{
Uri CreateUri(string uriString, UriKind uriKind);
}
}
Details
- The
PackageSettings
class would allow for easily adding additional settings at a later point without adding more factory methods (i.e.Package.Open(...)
). Since theFileMode
andFileAccess
properties are used on all factory methods, these are added to the settings object. - The
Package.Open(...)
methods listed extend the current factory methods that take aStream
or a path with an optionalFileShare
. - The default implementations of
IUriProvider
would essentially be this line: https://source.dot.net/#System.IO.Packaging/System/IO/Packaging/InternalRelationshipCollection.cs,363