FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix(http): preserve immutability of materialized clones by SkyZeroZx · Pull Request #69762 · angular/angular · GitHub

fix(http): preserve immutability of materialized clones - #69762

Merged
alxhub merged 2 commits into
angular:mainfrom
SkyZeroZx:fix-http-inmutability
Jul 29, 2026
Merged

fix(http): preserve immutability of materialized clones#69762
alxhub merged 2 commits into
angular:mainfrom
SkyZeroZx:fix-http-inmutability

Conversation

Copy link
Copy Markdown
Contributor

Prevent lazy HttpHeaders and HttpParams clones from reusing value arrays owned by a materialized source. Append and value-specific delete operations previously mutated those shared arrays, violating the immutable API contract and allowing request metadata to bleed into later requests.

Copy each value array during clone materialization and cover the affected append and delete paths with regression tests that materialize the source first.

More context https://issuetracker.google.com/issues/533607148

pullapprove Bot requested a review from atscott July 13, 2026 22:10
angular-robot Bot added the area: common/http Issues related to HTTP and HTTP Client label Jul 13, 2026
ngbot Bot added this to the Backlog milestone Jul 13, 2026
JeanMeche requested review from JeanMeche and alan-agius4 and removed request for atscott July 14, 2026 09:42
expect(fourth.has('foo')).toEqual(false);
});

it('should delete only the exact matching string value', () => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Looking into HttpHeaders a bit more, I found some cases that weren't being handled correctly, so I added another commit to address this.

fix(http): match header values exactly when deleting

JeanMeche commented Jul 21, 2026
edited
Loading

Copy link
Copy Markdown
Member

I believe this test should be passing, but doesn't.

    it('should delete only the exact matching string value, not substrings', () => {
      const headers = new HttpHeaders({
        'X-Scopes': ['tenant:alpha', 'tenant:alpha:archive'],
      });

      // We only want to delete the 'tenant:alpha:archive' value
      const updated = headers.delete('X-Scopes', 'tenant:alpha:archive');

      expect(updated.getAll('X-Scopes')).toEqual(['tenant:alpha']);
    });

Potentially we should also have this one to pass:

it('should treat an empty string as a specific value to delete', () => {
  const headers = new HttpHeaders({ foo: ['', 'bar'] });
  // We only want to delete the empty string
  const updated = headers.delete('foo', '');

  // causing the entire 'foo' header key to be wiped out.
  expect(updated.getAll('foo')).toEqual(['bar']);
});

SkyZeroZx commented Jul 21, 2026
edited
Loading

Copy link
Copy Markdown
Contributor Author

I believe this test should be passing, but doesn't.

    it('should delete only the exact matching string value, not substrings', () => {
      const headers = new HttpHeaders({
        'X-Scopes': ['tenant:alpha', 'tenant:alpha:archive'],
      });

      // We only want to delete the 'tenant:alpha:archive' value
      const updated = headers.delete('X-Scopes', 'tenant:alpha:archive');

      expect(updated.getAll('X-Scopes')).toEqual(['tenant:alpha']);
    });

Potentially we should also have this one to pass:

it('should treat an empty string as a specific value to delete', () => {
  const headers = new HttpHeaders({ foo: ['', 'bar'] });
  // We only want to delete the empty string
  const updated = headers.delete('foo', '');

  // causing the entire 'foo' header key to be wiped out.
  expect(updated.getAll('foo')).toEqual(['bar']);
});

These tests are actually here in this commit 7290fe9 (this commit) and they pass in local
Or do you mean something else?

alan-agius4 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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

LGTM.

Not sure why this was logged with GVP.

alan-agius4 added action: review The PR is still awaiting reviews from at least one requested reviewer target: patch This PR is targeted for the next patch release and removed area: common/http Issues related to HTTP and HTTP Client labels Jul 22, 2026
ngbot Bot removed this from the Backlog milestone Jul 22, 2026
pkozlowski-opensource added the area: common/http Issues related to HTTP and HTTP Client label Jul 22, 2026
ngbot Bot added this to the Backlog milestone Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Not sure why this was logged with GVP.

Since the immutability contract isn't respected during SSR, headers can be mutated unexpectedly, adding or removing scopes. One possible case is TransferCache excluding a sensitive header, but because the contract isn't honored, it isn't recognized and ends up in the final HTML.

I can't confidently assess the severity, but given this, I think it might at least be worth backporting ?

SkyZeroZx force-pushed the fix-http-inmutability branch from 7290fe9 to 2e2a2b9 Compare July 23, 2026 22:35

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

AGENT: Thanks for this fix! I have left a minor inline suggestion for your consideration.

Comment thread packages/common/http/src/params.ts Outdated
SkyZeroZx force-pushed the fix-http-inmutability branch from 2e2a2b9 to c1b9178 Compare July 26, 2026 14:26

JeanMeche commented Jul 26, 2026
edited
Loading

Copy link
Copy Markdown
Member

AGENT: I have re-reviewed this PR and found two issues regarding the implementation of the fix:

Issue 1: Inconsistent deletion behavior between HttpHeaders and HttpParams

The PR correctly refactors HttpHeaders to fix a substring matching bug when deleting values. By using filter(), it successfully removes all occurrences of the targeted value if it was added multiple times.
However, the deletion logic in HttpParams was not updated and still uses indexOf and splice(idx, 1). Because it only splices once, HttpParams will only remove the first occurrence of a matching value. This creates an inconsistent API behavior between the two classes (for reference, the standard URLSearchParams.delete(name, value) deletes all matching occurrences).

Issue 2: Eager O(N) memory allocation (Performance Regression)

To preserve immutability, the PR fixes the shared array mutation bug by eagerly slicing every single array in the map during init():

for (const [key, values] of other.headers.entries()) {
  this.headers.set(key, values.slice());
  // ...
}

This forces an O(N) copy of the entire header/parameter map into memory upon initialization, regardless of how many keys are actually being modified. A more performant copy-on-write approach would be to leave the arrays shared initially, and only invoke .slice() locally inside applyUpdate for the specific array that is actively being mutated (e.g., before base.push() or base.splice()).

Prevent lazy HttpHeaders and HttpParams clones from reusing value arrays owned by a materialized source. Append and value-specific delete operations previously mutated those shared arrays, violating the immutable API contract and allowing request metadata to bleed into later requests.

Share value arrays until an update mutates a specific header or parameter, then copy only that array. Cover the affected append and delete paths with regression tests that materialize the source first.
Normalize value-specific HttpHeaders deletions before filtering. The string overload previously used String#indexOf and removed shorter values contained within the requested deletion value, potentially widening outgoing request metadata.

Preserve delete-all behavior only when no value is supplied, and cover string, array, and empty-string deletion.
SkyZeroZx force-pushed the fix-http-inmutability branch from c1b9178 to 3d7dab7 Compare July 26, 2026 15:35
const body = new HttpParams({fromString: 'a=false&a=true&a=false'});
const mutated = body.delete('a', false);
expect(mutated.getAll('a')).toEqual(['true', 'false']);
expect(mutated.getAll('a')).toEqual(['true']);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Issue 1: Inconsistent deletion behavior between HttpHeaders and HttpParams

Apparently, we previously had a test that considered this expected; I just updated it.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

if we had a test I would prefer that we revert the change. The agent might have missed that detail.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Done , I just removed it, although this is going to be a bit strange. Should we possibly open an issue to map this behavior?

SkyZeroZx force-pushed the fix-http-inmutability branch from 3d7dab7 to 9ee1c57 Compare July 26, 2026 16:43
JeanMeche added action: merge The PR is ready for merge by the caretaker and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Jul 27, 2026
alxhub merged commit f33ee95 into angular:main Jul 29, 2026
24 checks passed

alxhub commented Jul 29, 2026

Copy link
Copy Markdown
Member

This PR was merged into the repository. The changes were merged into the following branches:

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action: merge The PR is ready for merge by the caretaker area: common/http Issues related to HTTP and HTTP Client target: patch This PR is targeted for the next patch release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants


Back | FazBrowse Home | New Git URL