Skip to content

Fix case-insensitive URI matching - #118574

Merged
alinpahontu2912 merged 5 commits into
dotnet:mainfrom
alinpahontu2912:update_parts_names
Aug 28, 2025
Merged

alinpahontu2912 merged 5 commits into
dotnet:mainfrom
alinpahontu2912:update_parts_names

Conversation

@alinpahontu2912

Copy link
Copy Markdown
Member

The package lookup fails when part names differ only by ASCII case, because the comparer is doing a case-sensitive match.

This PR updates the URI comparer logic to validate equal case-insensitive strings.

Fixes #112783

Copilot AI review requested due to automatic review settings August 11, 2025 10:39
@github-actions github-actions Bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 11, 2025

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a bug where package part lookups failed when URI part names differed only by ASCII case. The fix updates the URI comparison logic to perform case-insensitive matching instead of case-sensitive matching.

Key changes:

  • Updates the ValidatedPartUri.Compare method to use case-insensitive string comparison
  • Adds a comprehensive test to verify that part names with different cases are treated as equivalent

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/libraries/System.IO.Packaging/src/System/IO/Packaging/PackUriHelper.cs Updates URI comparison logic from case-sensitive to case-insensitive using StringComparison.OrdinalIgnoreCase
src/libraries/System.IO.Packaging/tests/PartPieceTests.cs Adds test case to verify that parts with different case URIs are treated as the same part

Comment thread src/libraries/System.IO.Packaging/src/System/IO/Packaging/PackUriHelper.cs Outdated

@rzikm rzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try to verify with the original repro?

@MihaZupan MihaZupan added area-System.IO.Compression and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Aug 11, 2025

@rzikm rzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but I would prefer @ericstj to take a look as well.

@alinpahontu2912 did you also check all other usages of this type which may rely on equality, and validated the fix against the original repro from the issue?

Comment thread src/libraries/System.IO.Packaging/src/System/IO/Packaging/PackUriHelper.cs Outdated
@rzikm
rzikm requested a review from ericstj August 11, 2025 15:05
@alinpahontu2912

Copy link
Copy Markdown
Member Author

Hey @rzikm, the original repro case now works, but I haven't checked for other usages

@ericstj

ericstj commented Aug 12, 2025

Copy link
Copy Markdown
Member

The change looks ok - can you think of any side-effects of this that folks might notice? Anything where some behavior might have crept in while this bug was present that would be broken with the fix? The best I can think of is some zip that had entries that only differ in case and we somehow consumed that before and can't or will consume differently after the fix. Think through and either manually test or add tests. If you find a behavior change, consider documenting it.

We don't have a lot of testing of this component in dotnet/runtime - we try not to touch it too much. It might be interesting to try consuming it in WPF and/or OpenXML to see if it works well.

@rzikm
rzikm requested a review from a team August 14, 2025 09:18
@alinpahontu2912

alinpahontu2912 commented Aug 19, 2025 •

Copy link
Copy Markdown
Member Author

Hey @rzikm @ericstj can youcheck this again? I checked for other usages of the ValidatedUriPart class and there aren't that many, all are inside the packaging library. To me it looks like these changes are not affecting something else. I also read through the ECMA documentation, and found: 6.2.2.3 Part name equivalence and integrity in an abstract package Equivalence of part names shall be determined by ASCII case-insensitive matching. [...] EXAMPLE 1 If an abstract package contains a part named "/a", the name of another part in that abstract package cannot be "/a" or "/A"., so entries that differ only in case should not be allowed anyway. I added a new test to check for this.

@ericstj ericstj left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change looks good, thank you for adding the test.

One last case to consider - suppose a zip already has entries that differ only in case - what happens on read before and after the change?

Your added test proves that folks can't add a part through IO.Packaging API, but it's possible that the zip itself might have the problem.

Mainly interested in exploring potential changes/breaking behavior - even for a "non-compliant" package - as it could be interesting to document or tweak the implementation if it behaves poorly. For example - if a zip would load and work before, but now throws on open, we might want to address it. If it loads and just gives slightly different behavior when using the part API - we would probably just document "don't do that".

@alinpahontu2912

Copy link
Copy Markdown
Member Author

Hey @ericstj, thanks for the review and the comment. I checked locally, and non-compliant packages that previously worked will now throw errors when trying to open them. From what I understand, this throw is expected, since such packages shouldn’t work in the first place. So while this is technically a breaking change, should we just document that the behavior now aligns with the spec and that packages with parts differing only by case are not supported? Is it okay to merge these changes as-is, and if we document the behavior change, where do we write about it ?

@ericstj

ericstj commented Aug 20, 2025

Copy link
Copy Markdown
Member

Would those packages open before - I guess on .NET 5 - .NET 9? What happens on .NETFramework? Folks do weird things like misuse the Packaging API as a general purpose ZIP API. Very old code did this from before we added Zip support in .NETFramework 4.x. So while we might have a good argument for fixing this, the end user who is broken might not care about that argument.

Here are some things to consider:
Does it fail in a very predictable, deterministic way that users might discover our breaking change documentation from?
Would we have a workaround to give the user?
Do other OpenXML packaging APIs also reject the package? Consider .NETFramework, JS, python). A lot of our decisions around file formats involve ensuring that we're as compatible as other clients.

If we're comfortable with the answers to all these, we can take it as is and file a breaking change doc. I'm fine with that as an early change in .NET 11 to get this in previews and have plenty of bake time. If we were to take this to ask-mode for .NET 10, I would hope we have a very good case against this breaking many customers.

@ericstj ericstj added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label Aug 20, 2025
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Added needs-breaking-change-doc-created label because this PR has the breaking-change label.

When you commit this breaking change:

  1. Create and link to this PR and the issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.
  2. Ask a committer to mail the .NET Breaking Change Notification DL.

Tagging @dotnet/compat for awareness of the breaking change.

@dotnet-policy-service dotnet-policy-service Bot added the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label Aug 20, 2025
@alinpahontu2912

Copy link
Copy Markdown
Member Author

Hey, I checked how this behaves in .NET Framework and URI matching there is already case-insensitive, so this change aligns with that behavior. Also, if you try to create two parts that differ only by casing (/part and /PART) through the Packaging API, it already throws (Cannot add part for the specified URI because it is already in the package.), so such a package can’t be authored legitimately.
A ZipArchive can contain entries that differ only in case because it does not enforce OPC rules. Package.Open only treats a ZIP as a valid OPC package if it contains the required [Content_Types].xml at the root. Once recognized as such, it will enforces case-insensitive part names according to the specs.
The retrieval will now behave consistently and users won’t be able to get ambiguous results when asking for /part vs /PART. This brings .NET 5–9 in line with both .NET Framework and the OPC spec.

@ericstj

ericstj commented Aug 27, 2025 •

Copy link
Copy Markdown
Member

Did you test the opening of the bad zip in .NET Framework - the one that opened in net6.0-net9.0 but failed to open in latest? That was the case I was hoping to double check. As I mentioned before regardless of findings I approve of this change with a minor breaking change notice (indicating we'll now reject opening of non-compliant packages).

@alinpahontu2912

Copy link
Copy Markdown
Member Author

Hey @ericstj, thanks for the comment. Yep, I tested the bad ZIP in .NET Framework. Package.Open throws on the case-conflicting parts in all versions, while opening as a ZIP archive works since ZIP doesn’t enforce OPC rules. Opened a breaking change issue here: dotnet/docs#48174

@ericstj

ericstj commented Aug 28, 2025

Copy link
Copy Markdown
Member

That's good, that means "bad behavior" was only introduced on core where we have better APIs for folks to use. Much less likely that folks have expectation that this works. Thank you for following up.

@alinpahontu2912
alinpahontu2912 merged commit e49e456 into dotnet:main Aug 28, 2025
90 checks passed
@alinpahontu2912
alinpahontu2912 deleted the update_parts_names branch August 28, 2025 18:14
@jeffhandley

Copy link
Copy Markdown
Member

@alinpahontu2912 Just a reminder that the breaking change doc issue still needs to be filed for this. The automated comment has instructions for what's expected, and you can review other breaking change issues in the dotnet/docs repo for reference examples if needed.

Thanks and nice work!

@ericstj ericstj removed the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label Oct 2, 2025
@ericstj

ericstj commented Oct 2, 2025

Copy link
Copy Markdown
Member

Removing needs-breaking-change-doc-created label as this PR already has a documentation issue: dotnet/docs#48174

@github-actions github-actions Bot locked and limited conversation to collaborators Nov 2, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.IO.Compression breaking-change Issue or PR that represents a breaking API or functional change over a previous release.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System.IO.Packaging should perform case-insensitive matching when determining part media types

6 participants