Skip to content

Fix validated part uri equality - #131806

Merged
alinpahontu2912 merged 9 commits into
dotnet:mainfrom
alinpahontu2912:fix/validatedparturi-equality-118574
Aug 7, 2026
Merged

alinpahontu2912 merged 9 commits into
dotnet:mainfrom
alinpahontu2912:fix/validatedparturi-equality-118574

Conversation

@alinpahontu2912

Copy link
Copy Markdown
Member

Fixes #129927
PR #118574 added object.Equals/GetHashCode overrides to ValidatedPartUri (which derives from System.Uri) to support case-insensitive part-name matching. This broke the inherited Uri equality contract: Equals became asymmetric against a plain System.Uri of the same value, and GetHashCode became inconsistent with System.Uri.GetHashCode(). Consumers mixing ValidatedPartUri with plain System.Uri in HashSet/Dictionary<Uri,_> (e.g. the Open XML SDK) got incorrect lookup results.

Revert the Equals(object?)/GetHashCode() overrides, restoring the original CA1067 suppression. ValidatedPartUri once again inherits Uri's value-equality, matching plain System.Uri semantics.
Keep the case-insensitive Compare()/IComparable/ IEquatable logic, which is what makes Package.cs's SortedList<ValidatedPartUri, PackagePart> lookups case-insensitive (fixing #112783 for that path) and is unaffected by this change.
Add PackUriHelper.ValidatedPartUriEqualityComparer, an internal IEqualityComparer providing case-insensitive equality/hashing, and use it explicitly in ZipPackage's content-type override dictionary. Without this, removing GetHashCode() would leave that Dictionary<ValidatedPartUri,_> with a hash/equals mismatch (case-insensitive Equals via IEquatable, case-sensitive hash via inherited Uri.GetHashCode()), silently dropping parts whose zip entry name casing differs from their [Content_Types].xml Override PartName.
Add regression tests covering the Uri equality contract and the content-type override case-insensitivity behavior.

alinpahontu2912 and others added 2 commits August 4, 2026 15:55
PR dotnet#118574 added object.Equals/GetHashCode overrides to ValidatedPartUri
(which derives from System.Uri) to support case-insensitive part-name
matching. This broke the inherited Uri equality contract: Equals became
asymmetric against a plain System.Uri of the same value, and GetHashCode
became inconsistent with System.Uri.GetHashCode(). Consumers mixing
ValidatedPartUri with plain System.Uri in HashSet<Uri>/Dictionary<Uri,_>
(e.g. the Open XML SDK) got incorrect lookup results.

- Revert the Equals(object?)/GetHashCode() overrides, restoring the
  original CA1067 suppression. ValidatedPartUri once again inherits
  Uri's value-equality, matching plain System.Uri semantics.
- Add PackUriHelper.ValidatedPartUriEqualityComparer, an internal
  IEqualityComparer<ValidatedPartUri> providing case-insensitive
  equality/hashing, and use it explicitly in ZipPackage's content-type
  override dictionary. Without this, removing GetHashCode() would leave
  that Dictionary<ValidatedPartUri,_> with a hash/equals mismatch
  (case-insensitive Equals via IEquatable<T>, case-sensitive hash via
  inherited Uri.GetHashCode()), silently dropping parts whose zip entry
  name casing differs from their [Content_Types].xml Override PartName.
- Add regression tests covering the Uri equality contract and the
  content-type override case-insensitivity behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f1ae4251-6f11-4001-afb3-7cfdea3b64b5
…pare

PR dotnet#118574 changed Compare() to use StringComparison.OrdinalIgnoreCase
instead of string.CompareOrdinal. This is redundant: both operands are
NormalizedPartUriString, which is already case-folded via
ToUpperInvariant() before the comparison runs, so Ordinal and
OrdinalIgnoreCase comparisons produce identical results for realistic
part-name characters. Reverting to string.CompareOrdinal restores the
original code, minimizes the diff, and removes any residual risk from
divergent Unicode case-folding tables between ToUpperInvariant and
OrdinalIgnoreCase for non-ASCII edge cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f1ae4251-6f11-4001-afb3-7cfdea3b64b5
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@alinpahontu2912 alinpahontu2912 changed the title Fix/validatedparturi equality 118574 Fix validated part uri equality Aug 4, 2026
@alinpahontu2912 alinpahontu2912 added this to the 11.0.0 milestone Aug 4, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

@alinpahontu2912
alinpahontu2912 marked this pull request as draft August 4, 2026 14:03

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 restores PackUriHelper.ValidatedPartUri’s compatibility with System.Uri’s object.Equals/GetHashCode contract (so it can safely interoperate with plain Uri instances in hash-based collections), while keeping case-insensitive part-name semantics where needed via an explicit internal comparer.

Changes:

  • Remove ValidatedPartUri’s Equals(object?) / GetHashCode() overrides to re-align with base System.Uri equality/hashing behavior.
  • Introduce PackUriHelper.ValidatedPartUriEqualityComparer and use it for ZipPackage’s override content-type dictionary to preserve case-insensitive override lookups without changing Uri’s base contract.
  • Add regression tests covering (1) the Uri equality/hash contract and (2) case-insensitive content-type override resolution.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/libraries/System.IO.Packaging/tests/PartPieceTests.cs Adds regression tests for ValidatedPartUri equality/hash compatibility and for case-insensitive override content-type lookup.
src/libraries/System.IO.Packaging/src/System/IO/Packaging/ZipPackage.cs Updates the override dictionary to use the new ValidatedPartUriEqualityComparer.
src/libraries/System.IO.Packaging/src/System/IO/Packaging/PackUriHelper.cs Removes Equals(object?)/GetHashCode() overrides from ValidatedPartUri and adds the internal equality comparer type.

Comment thread src/libraries/System.IO.Packaging/tests/PartPieceTests.cs Outdated
Comment thread src/libraries/System.IO.Packaging/src/System/IO/Packaging/PackUriHelper.cs Outdated
Comment thread src/libraries/System.IO.Packaging/src/System/IO/Packaging/ZipPackage.cs Outdated
Copilot AI review requested due to automatic review settings August 4, 2026 14:04

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

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/libraries/System.IO.Packaging/src/System/IO/Packaging/ZipPackage.cs:949

  • EnsureOverrideDictionary() now uses ValidatedPartUriEqualityComparer, but the comment still says comparison is done using PartUriComparer. This is misleading when debugging override lookups and should be updated to reflect the actual comparer in use.
                // The part Uris are stored in the Override Dictionary in their original form , but they are compared
                // in a normalized manner using the PartUriComparer

src/libraries/System.IO.Packaging/tests/PartPieceTests.cs:377

  • The symmetry assertion can pass even if both directions return false. Since this test is specifically about the equality contract for two URIs that should be value-equal, it should assert both directions are true (not just equal).
            // object.Equals must be symmetric.
            Assert.Equal(a.Equals(b), b.Equals(a));

src/libraries/System.IO.Packaging/src/System/IO/Packaging/PackUriHelper.cs:607

  • The CA1067 suppression comment is self-contradictory ("Override Equals...; not overriding...") and doesn’t explain the real intent (preserving System.Uri’s equality/hash contract). This is easy to misread during future maintenance.
#pragma warning disable CA1067 // Override Equals because it implements IEquatable<T>; not overriding to avoid possible regressions in code that's working
        internal sealed class ValidatedPartUri : Uri, IComparable<ValidatedPartUri>, IEquatable<ValidatedPartUri>
#pragma warning restore CA1067

src/libraries/System.IO.Packaging/src/System/IO/Packaging/PackUriHelper.cs:908

  • ValidatedPartUri.NormalizedPartUriString is already case-folded to upper invariant (see GetNormalizedPartUriString), so using OrdinalIgnoreCase hashing here is unnecessary and slightly more expensive/misleading. Using ordinal hashing better matches the comparer’s actual semantics (ordinal compare on normalized strings).
            public int GetHashCode(ValidatedPartUri obj)
                => StringComparer.OrdinalIgnoreCase.GetHashCode(obj.NormalizedPartUriString);

@alinpahontu2912
alinpahontu2912 marked this pull request as ready for review August 5, 2026 11:16
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@alinpahontu2912 alinpahontu2912 assigned MihaZupan and rzikm and unassigned MihaZupan and rzikm Aug 5, 2026

@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.

Some copilot suggestions seem relevant, otherwise LGTM if tests pass

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 5, 2026 12:50

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

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/libraries/System.IO.Packaging/src/System/IO/Packaging/PackUriHelper.cs:907

  • ValidatedPartUri.NormalizedPartUriString is already case-folded to upper-invariant (see GetNormalizedPartUriString), so using StringComparer.OrdinalIgnoreCase here does extra work without changing behavior. Using StringComparer.Ordinal keeps equality/hash consistency while reducing hashing cost.
            public int GetHashCode(ValidatedPartUri obj)
                => StringComparer.OrdinalIgnoreCase.GetHashCode(obj.NormalizedPartUriString);

@MihaZupan MihaZupan 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.

So besides the hash code inconsistency, it seems that this type is also breaking invariants in other ways?

E.g. a.Equals(b) may not mean that a.Equals((object)b)
or a.Compare(b) == 0 may not mean that a == b?

These seem like pre-existing issues though

Comment thread src/libraries/System.IO.Packaging/src/System/IO/Packaging/PackUriHelper.cs Outdated
Copilot AI review requested due to automatic review settings August 6, 2026 11:12

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

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/libraries/System.IO.Packaging/src/System/IO/Packaging/PackUriHelper.cs:605

  • The CA1067 suppression comment is contradictory (it says "Override Equals" while the code intentionally does not override object.Equals/GetHashCode). This makes the rationale unclear for future maintainers.
#pragma warning disable CA1067 // Override Equals because it implements IEquatable<T>; not overriding to avoid possible regressions in code that's working

Copilot AI review requested due to automatic review settings August 6, 2026 12:06

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

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings August 7, 2026 11:47

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

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/libraries/System.IO.Packaging/src/System/IO/Packaging/PackUriHelper.cs:907

  • ValidatedPartUri.NormalizedPartUriString is always upper-cased (see GetNormalizedPartUriString()), so using StringComparer.OrdinalIgnoreCase here is redundant and adds extra work on every hash computation. Using StringComparer.Ordinal is sufficient and keeps the comparer consistent with the normalization strategy.
            public int GetHashCode(ValidatedPartUri obj)
                => StringComparer.OrdinalIgnoreCase.GetHashCode(obj.NormalizedPartUriString);

Copilot AI review requested due to automatic review settings August 7, 2026 12:59

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

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@alinpahontu2912
alinpahontu2912 enabled auto-merge (squash) August 7, 2026 14:17
@alinpahontu2912
alinpahontu2912 merged commit 675fd37 into dotnet:main Aug 7, 2026
83 of 85 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot modified the milestones: 11.0.0, 11.0-rc1 Aug 7, 2026
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Aug 11, 2026
Fixes dotnet#129927
PR dotnet#118574 added
object.Equals/GetHashCode overrides to ValidatedPartUri (which derives
from System.Uri) to support case-insensitive part-name matching. This
broke the inherited Uri equality contract: Equals became asymmetric
against a plain System.Uri of the same value, and GetHashCode became
inconsistent with System.Uri.GetHashCode(). Consumers mixing
ValidatedPartUri with plain System.Uri in HashSet/Dictionary<Uri,_>
(e.g. the Open XML SDK) got incorrect lookup results.

Revert the Equals(object?)/GetHashCode() overrides, restoring the
original CA1067 suppression. ValidatedPartUri once again inherits Uri's
value-equality, matching plain System.Uri semantics.
Keep the case-insensitive Compare()/IComparable/ IEquatable logic, which
is what makes Package.cs's SortedList<ValidatedPartUri, PackagePart>
lookups case-insensitive (fixing
dotnet#112783 for that path) and is
unaffected by this change.
Add PackUriHelper.ValidatedPartUriEqualityComparer, an internal
IEqualityComparer providing case-insensitive equality/hashing, and use
it explicitly in ZipPackage's content-type override dictionary. Without
this, removing GetHashCode() would leave that
Dictionary<ValidatedPartUri,_> with a hash/equals mismatch
(case-insensitive Equals via IEquatable, case-sensitive hash via
inherited Uri.GetHashCode()), silently dropping parts whose zip entry
name casing differs from their [Content_Types].xml Override PartName.
Add regression tests covering the Uri equality contract and the
content-type override case-insensitivity behavior.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot-Session: f1ae4251-6f11-4001-afb3-7cfdea3b64b5
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System.IO.Packaging: ValidatedPartUri (#118574) violates Equals/GetHashCode contract vs System.Uri, breaking hash-based Uri collections

4 participants