From c1b349323d84e2a5e31fc912a501f55f485ad7c2 Mon Sep 17 00:00:00 2001 From: Paurikova2 Date: Tue, 9 Sep 2025 14:03:23 +0200 Subject: [PATCH 1/7] added missing prefix --- .../bitstream-page/legacy-bitstream-url-redirect.guard.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts b/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts index d8d8932c30d..58d52dd6f32 100644 --- a/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts +++ b/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts @@ -9,6 +9,7 @@ import { import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; +import { APP_CONFIG, AppConfig } from '../../config/app-config.interface'; import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths'; import { BitstreamDataService } from '../core/data/bitstream-data.service'; import { RemoteData } from '../core/data/remote-data'; @@ -30,6 +31,7 @@ export const legacyBitstreamURLRedirectGuard: CanActivateFn = ( bitstreamDataService: BitstreamDataService = inject(BitstreamDataService), serverHardRedirectService: HardRedirectService = inject(HardRedirectService), router: Router = inject(Router), + appConfig: AppConfig = inject(APP_CONFIG), ): Observable => { const prefix = route.params.prefix; const suffix = route.params.suffix; @@ -46,7 +48,10 @@ export const legacyBitstreamURLRedirectGuard: CanActivateFn = ( getFirstCompletedRemoteData(), map((rd: RemoteData) => { if (rd.hasSucceeded && !rd.hasNoContent) { - serverHardRedirectService.redirect(new URL(`/bitstreams/${rd.payload.uuid}/download`, serverHardRedirectService.getCurrentOrigin()).href, 301); + const nameSpace = appConfig.ui.nameSpace?.replace(/\/$/, '') || ''; + const redirectUrl = new URL(nameSpace + `/bitstreams/${rd.payload.uuid}/download`, serverHardRedirectService.getCurrentOrigin()).href; + console.log('Legacy bitstream URL redirecting to:', redirectUrl); + serverHardRedirectService.redirect(redirectUrl, 301); return false; } else { return router.createUrlTree([PAGE_NOT_FOUND_PATH]); From 3a83eaf2339a58df657bce949e4b38b8945a263a Mon Sep 17 00:00:00 2001 From: Paurikova2 Date: Tue, 9 Sep 2025 14:40:11 +0200 Subject: [PATCH 2/7] security: harden namespace handling in legacy bitstream redirect guard --- .../legacy-bitstream-url-redirect.guard.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts b/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts index 58d52dd6f32..8d6c7fce594 100644 --- a/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts +++ b/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts @@ -48,9 +48,15 @@ export const legacyBitstreamURLRedirectGuard: CanActivateFn = ( getFirstCompletedRemoteData(), map((rd: RemoteData) => { if (rd.hasSucceeded && !rd.hasNoContent) { - const nameSpace = appConfig.ui.nameSpace?.replace(/\/$/, '') || ''; - const redirectUrl = new URL(nameSpace + `/bitstreams/${rd.payload.uuid}/download`, serverHardRedirectService.getCurrentOrigin()).href; - console.log('Legacy bitstream URL redirecting to:', redirectUrl); + // Harden namespace handling: trim slashes, neutralize absolute-like values, enforce root-relative path + let nameSpace = (appConfig.ui.nameSpace || '').replace(/^\/+|\/+$/g, ''); + // Neutralize any absolute-like values (http:, https:, //) + if (nameSpace.startsWith('http:') || nameSpace.startsWith('https:') || nameSpace.startsWith('//')) { + nameSpace = ''; + } + // Always build path starting with / + const redirectPath = nameSpace ? `/${nameSpace}/bitstreams/${rd.payload.uuid}/download` : `/bitstreams/${rd.payload.uuid}/download`; + const redirectUrl = new URL(redirectPath, serverHardRedirectService.getCurrentOrigin()).href; serverHardRedirectService.redirect(redirectUrl, 301); return false; } else { From 40437e90f899a15f9f07e9f307522f52a5d44717 Mon Sep 17 00:00:00 2001 From: Paurikova2 Date: Tue, 9 Sep 2025 14:44:43 +0200 Subject: [PATCH 3/7] harden legacy bitstream redirect namespace --- .../legacy-bitstream-url-redirect.guard.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts b/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts index 8d6c7fce594..4a7d158252b 100644 --- a/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts +++ b/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts @@ -48,13 +48,19 @@ export const legacyBitstreamURLRedirectGuard: CanActivateFn = ( getFirstCompletedRemoteData(), map((rd: RemoteData) => { if (rd.hasSucceeded && !rd.hasNoContent) { - // Harden namespace handling: trim slashes, neutralize absolute-like values, enforce root-relative path let nameSpace = (appConfig.ui.nameSpace || '').replace(/^\/+|\/+$/g, ''); - // Neutralize any absolute-like values (http:, https:, //) - if (nameSpace.startsWith('http:') || nameSpace.startsWith('https:') || nameSpace.startsWith('//')) { + // Neutralize any absolute-like values (http:, https:, //, protocol-relative URLs, or invalid characters) + const allowedNamespaceRegex = /^[a-zA-Z0-9_-]+$/; + // Check for absolute URLs, protocol-relative URLs, or invalid characters + if ( + nameSpace.startsWith('http:') || + nameSpace.startsWith('https:') || + nameSpace.startsWith('//') || + nameSpace.match(/^\/[^\/]/) || // protocol-relative URL: single slash followed by non-slash + !allowedNamespaceRegex.test(nameSpace) + ) { nameSpace = ''; } - // Always build path starting with / const redirectPath = nameSpace ? `/${nameSpace}/bitstreams/${rd.payload.uuid}/download` : `/bitstreams/${rd.payload.uuid}/download`; const redirectUrl = new URL(redirectPath, serverHardRedirectService.getCurrentOrigin()).href; serverHardRedirectService.redirect(redirectUrl, 301); From 0f240e60873a3f73f8c6e8d848421e9b75c692c1 Mon Sep 17 00:00:00 2001 From: Paurikova2 Date: Tue, 9 Sep 2025 15:24:25 +0200 Subject: [PATCH 4/7] removed security control --- .../legacy-bitstream-url-redirect.guard.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts b/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts index 4a7d158252b..58d52dd6f32 100644 --- a/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts +++ b/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts @@ -48,21 +48,9 @@ export const legacyBitstreamURLRedirectGuard: CanActivateFn = ( getFirstCompletedRemoteData(), map((rd: RemoteData) => { if (rd.hasSucceeded && !rd.hasNoContent) { - let nameSpace = (appConfig.ui.nameSpace || '').replace(/^\/+|\/+$/g, ''); - // Neutralize any absolute-like values (http:, https:, //, protocol-relative URLs, or invalid characters) - const allowedNamespaceRegex = /^[a-zA-Z0-9_-]+$/; - // Check for absolute URLs, protocol-relative URLs, or invalid characters - if ( - nameSpace.startsWith('http:') || - nameSpace.startsWith('https:') || - nameSpace.startsWith('//') || - nameSpace.match(/^\/[^\/]/) || // protocol-relative URL: single slash followed by non-slash - !allowedNamespaceRegex.test(nameSpace) - ) { - nameSpace = ''; - } - const redirectPath = nameSpace ? `/${nameSpace}/bitstreams/${rd.payload.uuid}/download` : `/bitstreams/${rd.payload.uuid}/download`; - const redirectUrl = new URL(redirectPath, serverHardRedirectService.getCurrentOrigin()).href; + const nameSpace = appConfig.ui.nameSpace?.replace(/\/$/, '') || ''; + const redirectUrl = new URL(nameSpace + `/bitstreams/${rd.payload.uuid}/download`, serverHardRedirectService.getCurrentOrigin()).href; + console.log('Legacy bitstream URL redirecting to:', redirectUrl); serverHardRedirectService.redirect(redirectUrl, 301); return false; } else { From 62b74cec0c630b3bdc3a469b2d799324c38999af Mon Sep 17 00:00:00 2001 From: Paurikova2 Date: Tue, 9 Sep 2025 15:50:49 +0200 Subject: [PATCH 5/7] modified curl, download bitstreams based on the name as separeated api calling --- .../clarin-files-section/clarin-files-section.component.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/item-page/clarin-files-section/clarin-files-section.component.ts b/src/app/item-page/clarin-files-section/clarin-files-section.component.ts index 15820be9f68..b02d9e071f9 100644 --- a/src/app/item-page/clarin-files-section/clarin-files-section.component.ts +++ b/src/app/item-page/clarin-files-section/clarin-files-section.component.ts @@ -107,8 +107,10 @@ export class ClarinFilesSectionComponent implements OnInit { return file.name; }); - const url = `${this.halService.getRootHref()}/core/items/${this.item.id}/allzip?handleId=${this.itemHandle}`; - this.command = `curl -o "allzip.zip" "${url}"`; + // Generate curl command for individual bitstream downloads + const baseUrl = `${this.halService.getRootHref()}/bitstream/${this.itemHandle}`; + const fileNamesFormatted = fileNames.map((fileName, index) => `/${index}/${fileName}`).join(','); + this.command = `curl -O ${baseUrl}{${fileNamesFormatted}} -k`; } loadDownloadZipConfigProperties() { From f29708de0748ab0dd75368fba14640528a1d0d2d Mon Sep 17 00:00:00 2001 From: Paurikova2 Date: Wed, 10 Sep 2025 09:43:50 +0200 Subject: [PATCH 6/7] remove -k argument from curl --- .../clarin-files-section/clarin-files-section.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/item-page/clarin-files-section/clarin-files-section.component.ts b/src/app/item-page/clarin-files-section/clarin-files-section.component.ts index b02d9e071f9..08edfe7ac55 100644 --- a/src/app/item-page/clarin-files-section/clarin-files-section.component.ts +++ b/src/app/item-page/clarin-files-section/clarin-files-section.component.ts @@ -110,7 +110,7 @@ export class ClarinFilesSectionComponent implements OnInit { // Generate curl command for individual bitstream downloads const baseUrl = `${this.halService.getRootHref()}/bitstream/${this.itemHandle}`; const fileNamesFormatted = fileNames.map((fileName, index) => `/${index}/${fileName}`).join(','); - this.command = `curl -O ${baseUrl}{${fileNamesFormatted}} -k`; + this.command = `curl -O ${baseUrl}{${fileNamesFormatted}}`; } loadDownloadZipConfigProperties() { From d03e3e5864a62185eec3aac738d0ae614ffc77a4 Mon Sep 17 00:00:00 2001 From: Paurikova2 Date: Wed, 10 Sep 2025 09:59:31 +0200 Subject: [PATCH 7/7] removed debug console --- src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts b/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts index 58d52dd6f32..ba995c5e9f4 100644 --- a/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts +++ b/src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts @@ -50,7 +50,6 @@ export const legacyBitstreamURLRedirectGuard: CanActivateFn = ( if (rd.hasSucceeded && !rd.hasNoContent) { const nameSpace = appConfig.ui.nameSpace?.replace(/\/$/, '') || ''; const redirectUrl = new URL(nameSpace + `/bitstreams/${rd.payload.uuid}/download`, serverHardRedirectService.getCurrentOrigin()).href; - console.log('Legacy bitstream URL redirecting to:', redirectUrl); serverHardRedirectService.redirect(redirectUrl, 301); return false; } else {