Skip to content

TUL/Browse links: carry only the parameters a browse page uses - #1453

Open
milanmajchrak wants to merge 3 commits into
customer/TULfrom
fix/tul-ssr-exclude-browse
Open

TUL/Browse links: carry only the parameters a browse page uses#1453
milanmajchrak wants to merge 3 commits into
customer/TULfrom
fix/tul-ssr-exclude-browse

Conversation

@milanmajchrak

@milanmajchrak milanmajchrak commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

References

Description

pr-1453-explained.html
Browse value links copied the whole current query string into themselves. They now carry only the parameters a browse page actually uses.

Instructions for Reviewers

A parameter nobody asked for used to come back in all 21 links on a browse page. A crawler that does not decode HTML entities then reads the escaped & as part of the next parameter name: ?zzz=1 comes back as &zzz=1, gets requested as amp;zzz=1, comes back as amp;amp;zzz=1, and so on without end, because every one of those is a valid 200 page with 21 fresh links. On TUL this reached 11.7M such requests against 7.7M real ones, chains 35 levels deep, and three days of backend at 130 % CPU.

List of changes in this PR:

  • browse-entry-list-element.component.html: dropped [queryParamsHandling]="'merge'". That was the only route by which an unknown parameter entered the link.
  • browse-entry-list-element.component.ts: getQueryParams() now also reads scope, bbm.rpp, bbm.sf and bbm.sd, the parameters merge used to bring along. Anything else in the URL is dropped.
  • spec: the RouteService mock answers per parameter name, and one test asserts on the rendered href with a real Router, since the reflection happened inside RouterLink rather than in the params object.

The list comes from the source, not from logs. Browse-by components read scope, startsWith, value and authority; PaginationService reads bbm.page, bbm.rpp, bbm.sf and bbm.sd, and bbm is the only pagination id under browse-by/. browseDefinition arrives through route.data via BrowseByGuard, not the query string. Every other query parameter in the app belongs to search, the comcol create form, submission import or access control, none of which sits on a browse route.

Upstream removed the same merge in DSpace#2735 but did not carry scope with it, which broke browsing inside a community or collection (DSpace#5209, open, present since 7.6.6). Keeping scope avoids repeating that.

How to test:

  1. curl -s 'https://<host>/browse/author?zzz=1' | grep -c 'zzz=1' prints 0 after the deploy. On an affected instance it is non-zero.
  2. Open a collection, browse by author, click a name. The URL must still carry ?scope=.
  3. Set 40 items per page and sort descending, then click a name. Both must survive the click.

Only the browse value link changed. Pagination and starts-with links keep their own merge, so anything legitimately in the URL still survives a click through those. Steps 2 and 3 have not been run in a browser yet, they need a deploy; everything else is covered by CI and the specs.

Considered and dropped: porting the SSR exclusion of /browse from 7.6.5 (DSpace#4332). It serves the page as a CSR shell, so the reflection stays and browse pages leave the index for crawlers that do not run JavaScript. Reasonable as a separate performance change, not as the fix for this.

Checklist

  • My PR is created against the main branch of code (against customer/TUL, which runs 7.5.0)
  • My PR is small in size (3 files, +61/-7)
  • My PR follows all coding best practices based on the Code Conventions Guide
  • My PR passes ESLint validation using npm run lint
  • My PR doesn't introduce circular dependencies (verified via npm run check-circ-deps)
  • My PR includes TypeDoc comments for all new (or modified) public methods and classes. It also includes TypeDoc for large or complex private methods.
  • My PR passes all specs/tests and includes new/updated specs or tests based on the Code Testing Guide.
  • My PR aligns with Accessibility guidelines if it makes changes to the user interface.
  • My PR uses i18n (internationalization) keys instead of hardcoded English text, to allow for translations. (no new text)
  • My PR includes details on how to test it. I've provided clear instructions to reviewers on how to successfully test this fix or feature.
  • If my PR includes new libraries/dependencies (in package.json), I've made sure their licenses align with the DSpace BSD License based on the Licensing of Contributions documentation. (none added)
  • If my PR includes new features or configurations, I've provided basic technical documentation in the PR itself. (no new configuration)
  • If my PR fixes an issue ticket, I've linked them together. (no issue ticket, reported through New Relic)

The browse entry link merged the whole current query string into every
link it generated. An unknown parameter therefore came back in 21 links
per page, and a crawler that does not decode HTML entities turned the
escaped separator into a longer amp;value on each pass - an unbounded
URL space that made up 52 % of all requests during the Aug 6-9 outage.

Build the parameters explicitly instead. scope, page size and sort are
carried over, everything else is dropped. Upstream removed the same
merge in DSpace#2735 but without carrying scope, which broke scoped browse
(DSpace#5209, still open).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@milanmajchrak
milanmajchrak force-pushed the fix/tul-ssr-exclude-browse branch from fc1e089 to 5ea7bb6 Compare August 17, 2026 07:21
@milanmajchrak milanmajchrak changed the title TUL/Exclude /browse, /search and admin paths from SSR (port of upstream #4332) TUL/Browse links: carry only the parameters a browse page uses Aug 17, 2026
@milanmajchrak
milanmajchrak requested a balanced review from Copilot August 17, 2026 13:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Restricts browse-entry links to supported browse parameters, preventing unbounded reflection of unknown query parameters.

Changes:

  • Explicitly preserves scope and pagination/sort parameters.
  • Removes query-parameter merging from browse-entry links.
  • Adds query-parameter unit tests.

Reviewed changes

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

File Description
browse-entry-list-element.component.ts Builds an explicit query-parameter allowlist.
browse-entry-list-element.component.html Disables query-parameter merging.
browse-entry-list-element.component.spec.ts Tests parameter projection behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

The previous test could not fail: the mock only answers for the names
getQueryParams() asks about, so an unknown key never reached the result.
The reflection happened in RouterLink, so the assertion has to be on the
rendered href with a real Router in place.
@milanmajchrak
milanmajchrak requested a review from vidiecan August 18, 2026 06:38
The previous version mocked RouteService and then navigated the router,
which made it look like the URL drove the assertions when it did not.
This suite uses the real RouteService, so scope, pagination and the
malformed parameter all reach the component the way they do in a browser.
Restoring queryParamsHandling merge makes it fail.

Copilot AI left a comment

Copy link
Copy Markdown

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants