Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { APP_CONFIG, AppConfig } from '../config/app-config.interface';
import { StoreDevModules } from '../config/store/devtools';
import { RootModule } from './root.module';
import { ScriptLoaderService } from './clarin-navbar-top/script-loader-service';
import { UrlSerializer } from '@angular/router';
import { BitstreamUrlSerializer } from './core/url-serializer/bitstream-url-serializer';

export function getConfig() {
return environment;
Expand Down Expand Up @@ -105,6 +107,7 @@ const PROVIDERS = [
useClass: LogInterceptor,
multi: true
},
{ provide: UrlSerializer, useClass: BitstreamUrlSerializer },
// register the dynamic matcher used by form. MUST be provided by the app module
...DYNAMIC_MATCHER_PROVIDERS,
];
Expand Down
3 changes: 2 additions & 1 deletion src/app/bitstream-page/legacy-bitstream-url.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Bitstream } from '../core/shared/bitstream.model';
import { getFirstCompletedRemoteData } from '../core/shared/operators';
import { hasNoValue } from '../shared/empty.util';
import { BitstreamDataService } from '../core/data/bitstream-data.service';
import { encodeRFC3986URIComponent } from '../shared/clarin-shared-util';

/**
* This class resolves a bitstream based on the DSpace 6 XMLUI or JSPUI bitstream download URLs
Expand Down Expand Up @@ -40,7 +41,7 @@ export class LegacyBitstreamUrlResolver implements Resolve<RemoteData<Bitstream>
return this.bitstreamDataService.findByItemHandle(
`${prefix}/${suffix}`,
sequenceId,
filename,
encodeRFC3986URIComponent(filename),
).pipe(
getFirstCompletedRemoteData()
);
Expand Down
43 changes: 43 additions & 0 deletions src/app/core/url-serializer/bitstream-url-serializer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { TestBed } from '@angular/core/testing';
import { BitstreamUrlSerializer } from './bitstream-url-serializer';
import { DefaultUrlSerializer, UrlTree } from '@angular/router';

describe('BitstreamUrlSerializer', () => {
let serializer: BitstreamUrlSerializer;

beforeEach(() => {
TestBed.configureTestingModule({
providers: [BitstreamUrlSerializer]
});
serializer = TestBed.inject(BitstreamUrlSerializer);
});

it('should be created', () => {
expect(serializer).toBeTruthy();
});

it('should not modify URLs that do not start with /bitstream/', () => {
const url = '/some/other/path/file.pdf';
const result = serializer.parse(url);
const expected = new DefaultUrlSerializer().parse(url);
expect(result).toEqual(expected);
});

it('should encode special characters in the filename in /bitstream/ URLs', () => {
const originalUrl = '/bitstream/id/123/456/some file(name)[v1].pdf';
const expectedEncodedFilename = 'some%20file%28name%29%5Bv1%5D.pdf';
const expectedUrl = `/bitstream/id/123/456/${expectedEncodedFilename}`;

const result: UrlTree = serializer.parse(originalUrl);

const resultUrl = new DefaultUrlSerializer().serialize(result);
expect(resultUrl).toBe(expectedUrl);
});

it('should not modify /bitstream/ URL if there is no filename', () => {
const url = '/bitstream/id/123/456';
const result = serializer.parse(url);
const expected = new DefaultUrlSerializer().parse(url);
expect(result).toEqual(expected);
});
});
27 changes: 27 additions & 0 deletions src/app/core/url-serializer/bitstream-url-serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
import { DefaultUrlSerializer, UrlTree } from '@angular/router';
import { encodeRFC3986URIComponent } from '../../shared/clarin-shared-util';

/**
* This class intercepts the parsing of URLs to ensure that the filename in the URL is properly encoded.
* But it only does this for URLs that start with '/bitstream/'.
*/
@Injectable({ providedIn: 'root' })
export class BitstreamUrlSerializer extends DefaultUrlSerializer {
FILENAME_INDEX = 5;
// Intercept parsing of every URL
parse(url: string): UrlTree {
if (url.startsWith('/bitstream/')) {
// Split the URL to isolate the filename
const parts = url.split('/');
if (parts.length > this.FILENAME_INDEX) {
// Fetch the filename from the URL
const filename = parts.slice(this.FILENAME_INDEX).join();
const encodedFilename = encodeRFC3986URIComponent(filename);
// Reconstruct the URL with the encoded filename
url = [...parts.slice(0, this.FILENAME_INDEX), encodedFilename].join('/');
}
}
return super.parse(url);
}
}
13 changes: 13 additions & 0 deletions src/app/shared/clarin-shared-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,16 @@ export function makeLinks(text: string): string {
const regex = /(?:https?|ftp):\/\/[^\s)]+|www\.[^\s)]+/g;
return text?.replace(regex, (url) => `<a href="${url}" target="_blank">${url}</a>`);
}

/**
* Encode special characters in a URI part to ensure it is safe for use in a URL.
* The special characters are `()[]` and the space character.
* @param uriPart
*/
export function encodeRFC3986URIComponent(uriPart: string) {
// Decode the filename to handle any encoded characters
const decodedFileName = decodeURIComponent(uriPart);
// Encode special characters in the filename
return encodeURIComponent(decodedFileName)
.replace(/[()]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase());
}