From 23a741ab3a254e08f534f4ed51109d58c19bd5e1 Mon Sep 17 00:00:00 2001 From: jm Date: Tue, 13 Oct 2026 09:14:52 +0200 Subject: [PATCH 1/2] Keep the full query string and fragment in BitstreamUrlSerializer Split the URL at the first raw '?' or '#' instead of destructuring url.split('?'), which dropped everything after a second raw '?' in the query and encoded a '#fragment' into the filename. Co-Authored-By: Claude Opus 5.5 --- .../bitstream-url-serializer.spec.ts | 14 ++++++++++++++ .../url-serializer/bitstream-url-serializer.ts | 10 ++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/app/core/url-serializer/bitstream-url-serializer.spec.ts b/src/app/core/url-serializer/bitstream-url-serializer.spec.ts index a66e47b3cd5..d7416b674e3 100644 --- a/src/app/core/url-serializer/bitstream-url-serializer.spec.ts +++ b/src/app/core/url-serializer/bitstream-url-serializer.spec.ts @@ -51,4 +51,18 @@ describe('BitstreamUrlSerializer', () => { const resultUrl = new DefaultUrlSerializer().serialize(result); expect(resultUrl).toBe(expectedUrl); }); + + it('should keep the whole query string when a query value contains a raw "?"', () => { + const result: UrlTree = serializer.parse('/bitstream/handle/123/456/file.pdf?isAllowed=y?x&sequenceId=2'); + + expect(result.root.children.primary.segments[4].path).toBe('file.pdf'); + expect(result.queryParams).toEqual({ isAllowed: 'y?x', sequenceId: '2' }); + }); + + it('should not encode a fragment into the filename', () => { + const result: UrlTree = serializer.parse('/bitstream/handle/123/456/file.pdf#page=2'); + + expect(result.root.children.primary.segments[4].path).toBe('file.pdf'); + expect(result.fragment).toBe('page=2'); + }); }); diff --git a/src/app/core/url-serializer/bitstream-url-serializer.ts b/src/app/core/url-serializer/bitstream-url-serializer.ts index 7872732e46a..24b6dfcb171 100644 --- a/src/app/core/url-serializer/bitstream-url-serializer.ts +++ b/src/app/core/url-serializer/bitstream-url-serializer.ts @@ -12,8 +12,10 @@ export class BitstreamUrlSerializer extends DefaultUrlSerializer { // Intercept parsing of every URL parse(url: string): UrlTree { if (url.startsWith('/bitstream/')) { - // Separate the path from the query string - const [path, query] = url.split('?'); + // Separate the path from the query string and fragment (everything from the first raw '?' or '#') + const queryIndex = url.search(/[?#]/); + const path = queryIndex === -1 ? url : url.substring(0, queryIndex); + const queryAndFragment = queryIndex === -1 ? '' : url.substring(queryIndex); // Split the path to isolate the filename const parts = path.split('/'); @@ -24,8 +26,8 @@ export class BitstreamUrlSerializer extends DefaultUrlSerializer { // Reconstruct the path with the encoded filename const newPath = [...parts.slice(0, this.FILENAME_INDEX), encodedFilename].join('/'); - // Reattach query string if present - url = query ? `${newPath}?${query}` : newPath; + // Reattach the query string and fragment unchanged + url = newPath + queryAndFragment; } } return super.parse(url); From d982cf9369e79f228b3cbae23491db6dd8b3fb00 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Mon, 19 Oct 2026 17:05:19 +0200 Subject: [PATCH 2/2] Assert the full path segments in the serializer tests Co-Authored-By: Claude Fable 5.1 --- .../core/url-serializer/bitstream-url-serializer.spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/core/url-serializer/bitstream-url-serializer.spec.ts b/src/app/core/url-serializer/bitstream-url-serializer.spec.ts index d7416b674e3..a8d5a903f2f 100644 --- a/src/app/core/url-serializer/bitstream-url-serializer.spec.ts +++ b/src/app/core/url-serializer/bitstream-url-serializer.spec.ts @@ -5,6 +5,10 @@ import { DefaultUrlSerializer, UrlTree } from '@angular/router'; describe('BitstreamUrlSerializer', () => { let serializer: BitstreamUrlSerializer; + function pathSegments(tree: UrlTree): string[] { + return tree.root.children.primary.segments.map((segment) => segment.path); + } + beforeEach(() => { TestBed.configureTestingModule({ providers: [BitstreamUrlSerializer] @@ -55,14 +59,14 @@ describe('BitstreamUrlSerializer', () => { it('should keep the whole query string when a query value contains a raw "?"', () => { const result: UrlTree = serializer.parse('/bitstream/handle/123/456/file.pdf?isAllowed=y?x&sequenceId=2'); - expect(result.root.children.primary.segments[4].path).toBe('file.pdf'); + expect(pathSegments(result)).toEqual(['bitstream', 'handle', '123', '456', 'file.pdf']); expect(result.queryParams).toEqual({ isAllowed: 'y?x', sequenceId: '2' }); }); it('should not encode a fragment into the filename', () => { const result: UrlTree = serializer.parse('/bitstream/handle/123/456/file.pdf#page=2'); - expect(result.root.children.primary.segments[4].path).toBe('file.pdf'); + expect(pathSegments(result)).toEqual(['bitstream', 'handle', '123', '456', 'file.pdf']); expect(result.fragment).toBe('page=2'); }); });