-
Notifications
You must be signed in to change notification settings - Fork 598
Fix missing rels content-type registration causing null MainDocumentPart #2090
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,9 @@ | |
| using System; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.IO; | ||
| #if !NETFRAMEWORK | ||
| using System.IO.Compression; | ||
| #endif | ||
| using System.IO.Packaging; | ||
|
|
||
| namespace DocumentFormat.OpenXml.Features; | ||
|
|
@@ -128,6 +131,7 @@ private void InitializePackage(FileMode? mode = default, FileAccess? access = de | |
|
|
||
| try | ||
| { | ||
| _stream = RepairRelsContentType(_stream); | ||
| _package = Package.Open(_stream, mode.Value, access.Value); | ||
| } | ||
| catch (ArgumentException ex) | ||
|
|
@@ -170,6 +174,107 @@ public void Dispose() | |
| GC.SuppressFinalize(this); | ||
| } | ||
|
|
||
| // Some OOXML producers omit the required <Default Extension="rels" .../> entry from | ||
| // [Content_Types].xml (a known OPC spec deviation). System.IO.Packaging is strict: without | ||
| // that registration it cannot resolve .rels parts, so package-level relationships are never | ||
| // loaded and MainDocumentPart (and equivalents) come back null. Repair the stream in-memory | ||
| // before handing it to Package.Open so the SDK behaves like Word Desktop and the MIP SDK, | ||
| // both of which recover silently from this deviation. | ||
| // | ||
| // Gated on !NETFRAMEWORK because ZipArchive requires net45+ and the old System.IO.Packaging | ||
| // on net35/net40/net46 is already lenient about missing content-type registrations. | ||
| #if !NETFRAMEWORK | ||
| private static Stream RepairRelsContentType(Stream input) | ||
| { | ||
| const string Marker = "Extension=\"rels\""; | ||
| const string Closing = "</Types>"; | ||
| const string Injection = | ||
| "<Default Extension=\"rels\" " + | ||
| "ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>"; | ||
|
|
||
| if (!input.CanRead || !input.CanSeek) | ||
| { | ||
| return input; | ||
| } | ||
|
|
||
| long originalPosition = input.Position; | ||
|
|
||
| try | ||
| { | ||
| string ctXml; | ||
| using (var readZip = new ZipArchive(input, ZipArchiveMode.Read, leaveOpen: true)) | ||
| { | ||
| var ctEntry = readZip.GetEntry("[Content_Types].xml"); | ||
| if (ctEntry is null) | ||
| { | ||
| input.Position = originalPosition; | ||
| return input; | ||
| } | ||
|
|
||
| using var reader = new StreamReader(ctEntry.Open()); | ||
| ctXml = reader.ReadToEnd(); | ||
| } | ||
|
|
||
| #if NET6_0_OR_GREATER | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of checking for the element and adding it if it's missing, check for |
||
| if (ctXml.Contains(Marker, StringComparison.Ordinal)) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hey @rakeshbabuseva, Thanks very much for this PR. I think this is a good catch. I do have some thoughts on this and please take a look and see what you think.
Adding Word does call this out to the user and while it does offer to repair and will use Finally, I think that throwing the exception would be good since it's apparent that the package integrity is not good and this would allow the Open XML producing app, library or tool to correct the situation rather than mask it. What do you think? We can discuss further if you'd like. |
||
| { | ||
| input.Position = originalPosition; | ||
| return input; | ||
| } | ||
|
|
||
| int closingIdx = ctXml.IndexOf(Closing, StringComparison.Ordinal); | ||
| string patched = closingIdx >= 0 | ||
| ? string.Concat(ctXml.AsSpan(0, closingIdx), Injection, ctXml.AsSpan(closingIdx)) | ||
| : ctXml + Injection; | ||
| #else | ||
| if (ctXml.IndexOf(Marker, StringComparison.Ordinal) >= 0) | ||
| { | ||
| input.Position = originalPosition; | ||
| return input; | ||
| } | ||
|
|
||
| int closingIdx = ctXml.IndexOf(Closing, StringComparison.Ordinal); | ||
| string patched = closingIdx >= 0 | ||
| ? ctXml.Substring(0, closingIdx) + Injection + ctXml.Substring(closingIdx) | ||
| : ctXml + Injection; | ||
| #endif | ||
|
|
||
| input.Position = originalPosition; | ||
| var output = new MemoryStream(); | ||
| using (var inZip = new ZipArchive(input, ZipArchiveMode.Read, leaveOpen: false)) | ||
| using (var outZip = new ZipArchive(output, ZipArchiveMode.Create, leaveOpen: true)) | ||
| { | ||
| foreach (var entry in inZip.Entries) | ||
| { | ||
| var outEntry = outZip.CreateEntry(entry.FullName, CompressionLevel.Fastest); | ||
| outEntry.LastWriteTime = entry.LastWriteTime; | ||
| using var inStream = entry.Open(); | ||
| using var outStream = outEntry.Open(); | ||
| if (entry.FullName == "[Content_Types].xml") | ||
| { | ||
| using var sw = new StreamWriter(outStream); | ||
| sw.Write(patched); | ||
| } | ||
| else | ||
| { | ||
| inStream.CopyTo(outStream); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| output.Position = 0; | ||
| return output; | ||
| } | ||
| catch | ||
| { | ||
| input.Position = originalPosition; | ||
| return input; | ||
| } | ||
| } | ||
| #else | ||
| private static Stream RepairRelsContentType(Stream input) => input; | ||
| #endif | ||
|
|
||
| protected override void Register(IFeatureCollection features) | ||
| { | ||
| base.Register(features); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, the extension is not required to be
rels. Instead check forapplication/vnd.openxmlformats-package.relationships+xmlbecause the content type won't change