Skip to content

Commit 6786c29

Browse files
committed
UI: html entities not being rendered on object version page
Fixes: https://github.com/aquarist-labs/s3gw/issues/839 Signed-off-by: Volker Theile <[email protected]>
1 parent fa8e1b8 commit 6786c29

File tree

6 files changed

+91
-11
lines changed

6 files changed

+91
-11
lines changed

src/frontend/src/app/functions.helper.spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
basename,
33
bytesToSize,
4+
decodeURIComponents,
45
extractErrorCode,
56
extractErrorDescription,
67
extractErrorMessage,
@@ -65,6 +66,22 @@ describe('functions.helper', () => {
6566
);
6667
});
6768

69+
it('should format a string [3]', () => {
70+
expect(format('{{ foo | basename }}', { foo: 'foo/bar/baz' })).toBe('baz');
71+
});
72+
73+
it('should format a string [4]', () => {
74+
expect(format('{{ foo | decodeUriComponent }}', { foo: encodeURIComponent('foo & baz') })).toBe(
75+
'foo &amp; baz'
76+
);
77+
});
78+
79+
it('should format a string [5]', () => {
80+
expect(
81+
format('{{ foo | decodeUriComponent | safe }}', { foo: encodeURIComponent('foo & baz') })
82+
).toBe('foo & baz');
83+
});
84+
6885
it('should isEqualOrUndefined [1]', () => {
6986
expect(isEqualOrUndefined('foo', undefined)).toBeTruthy();
7087
});
@@ -205,4 +222,32 @@ describe('functions.helper', () => {
205222
it('should check object version ID [5]', () => {
206223
expect(isObjectVersionID(1234)).toBeFalsy();
207224
});
225+
226+
it('should decode URI components [1]', () => {
227+
let data: Record<string, any> = {
228+
foo: encodeURIComponent('foo & foo'),
229+
bar: encodeURIComponent('a=10'),
230+
baz: 10,
231+
xyz: encodeURIComponent('xyz & xyz')
232+
};
233+
data = decodeURIComponents(data);
234+
expect(data['foo']).toBe('foo & foo');
235+
expect(data['bar']).toBe('a=10');
236+
expect(data['baz']).toBe(10);
237+
expect(data['xyz']).toBe('xyz & xyz');
238+
});
239+
240+
it('should decode URI components [2]', () => {
241+
let data: Record<string, any> = {
242+
foo: encodeURIComponent('foo & foo'),
243+
bar: encodeURIComponent('a=10'),
244+
baz: 10,
245+
xyz: encodeURIComponent('xyz & xyz')
246+
};
247+
data = decodeURIComponents(data, ['foo', 'bar', 'baz']);
248+
expect(data['foo']).toBe('foo & foo');
249+
expect(data['bar']).toBe('a=10');
250+
expect(data['baz']).toBe(10);
251+
expect(data['xyz']).toBe(encodeURIComponent('xyz & xyz'));
252+
});
208253
});

src/frontend/src/app/functions.helper.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ export const bytesToSize = (value: undefined | null | number | string): string =
4949
* mustache style for that seems to be the better approach than using
5050
* the ES string interpolate style.
5151
*
52+
* Note, output with dangerous characters is escaped automatically.
53+
*
5254
* Example:
5355
* format('Hello {{ username }}', {username: 'foo'})
5456
*
@@ -175,6 +177,35 @@ export const isObjectVersionID = (value: any, excludeNull = false): boolean => {
175177
return _.isString(value) && !invalidValues.includes(value);
176178
};
177179

180+
/**
181+
* Decode all specified Uniform Resource Identifier (URI) components
182+
* previously created by encodeURIComponent() or by a similar routine
183+
* in the specified object.
184+
*
185+
* @param data The object containing the encoded components to be
186+
* processed.
187+
* @param encodedURIComponents An optional list of encoded components of
188+
* Uniform Resource Identifiers. If not set, the all keys of the given
189+
* data are used.
190+
* @return Returns a new object containing the decoded versions of the
191+
* given encoded Uniform Resource Identifier (URI) components.
192+
*/
193+
export const decodeURIComponents = (
194+
data: Record<string, any>,
195+
encodedURIComponents?: string[]
196+
): Record<string, any> => {
197+
const newData: Record<string, any> = _.cloneDeep(data);
198+
if (!_.isArray(encodedURIComponents)) {
199+
encodedURIComponents = _.keys(data);
200+
}
201+
_.forEach(encodedURIComponents, (encodedURIComponent: string) => {
202+
if (encodedURIComponent in newData && _.isString(newData[encodedURIComponent])) {
203+
newData[encodedURIComponent] = decodeURIComponent(newData[encodedURIComponent]);
204+
}
205+
});
206+
return newData;
207+
};
208+
178209
/**
179210
* Append various Nunjucks filter.
180211
*/

src/frontend/src/app/pages/admin/user/user-key-form-page/user-key-form-page.component.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Component, OnInit, ViewChild } from '@angular/core';
22
import { ActivatedRoute, Params, Router } from '@angular/router';
33
import { marker as TEXT } from '@ngneat/transloco-keys-manager/marker';
44

5+
import { decodeURIComponents } from '~/app/functions.helper';
56
import { DeclarativeFormComponent } from '~/app/shared/components/declarative-form/declarative-form.component';
67
import { PageStatus } from '~/app/shared/components/page-wrapper/page-wrapper.component';
78
import { DeclarativeFormConfig } from '~/app/shared/models/declarative-form-config.type';
@@ -33,9 +34,10 @@ export class UserKeyFormPageComponent implements OnInit, IsDirty {
3334

3435
ngOnInit(): void {
3536
this.route.params.subscribe((value: Params) => {
37+
value = decodeURIComponents(value);
3638
this.pageStatus = PageStatus.ready;
37-
this.uid = decodeURIComponent(value['uid']);
38-
this.user = decodeURIComponent(value['user']);
39+
this.uid = value['uid'];
40+
this.user = value['user'];
3941
this.createForm();
4042
});
4143
}

src/frontend/src/app/pages/user/object/object-version-datatable-page/object-version-datatable-page.component.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as _ from 'lodash';
55
import { Subscription } from 'rxjs';
66
import { finalize } from 'rxjs/operators';
77

8-
import { format } from '~/app/functions.helper';
8+
import { decodeURIComponents, format } from '~/app/functions.helper';
99
import { Unsubscribe } from '~/app/functions.helper';
1010
import { PageStatus } from '~/app/shared/components/page-wrapper/page-wrapper.component';
1111
import { Icon } from '~/app/shared/enum/icon.enum';
@@ -61,8 +61,9 @@ export class ObjectVersionDatatablePageComponent implements OnInit {
6161
this.pageStatus = PageStatus.ready;
6262
return;
6363
}
64-
this.bid = decodeURIComponent(value['bid']);
65-
this.key = decodeURIComponent(value['key']);
64+
value = decodeURIComponents(value, ['bid', 'key']);
65+
this.bid = value['bid'];
66+
this.key = value['key'];
6667
this.loadData();
6768
});
6869
this.datatableActions = [

src/frontend/src/app/pages/user/user-pages-routing.module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const routes: Routes = [
5656
{
5757
path: 'versions/:key',
5858
data: {
59-
subTitle: '{{ bid }}/{{ key | decodeUriComponent }} - Versions',
59+
subTitle: '{{ bid }}/{{ key | safe }} - Versions',
6060
title: TEXT('Object:'),
6161
url: '../..'
6262
},

src/frontend/src/app/shared/components/page-title/page-title.component.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Component } from '@angular/core';
22
import { Title } from '@angular/platform-browser';
33
import { ActivatedRoute, Params } from '@angular/router';
44

5-
import { format } from '~/app/functions.helper';
5+
import { decodeURIComponents, format } from '~/app/functions.helper';
66
import { AppConfigService } from '~/app/shared/services/app-config.service';
77

88
@Component({
@@ -20,15 +20,16 @@ export class PageTitleComponent {
2020
private appConfigService: AppConfigService,
2121
private titleService: Title
2222
) {
23-
this.activatedRoute.params.subscribe((params: Params) => {
23+
this.activatedRoute.params.subscribe((value: Params) => {
24+
value = decodeURIComponents(value);
2425
this.subTitle = this.activatedRoute.snapshot.data?.['subTitle']
25-
? format(this.activatedRoute.snapshot.data['subTitle'], params)
26+
? format(this.activatedRoute.snapshot.data['subTitle'], value)
2627
: undefined;
2728
this.title = this.activatedRoute.snapshot.data?.['title']
28-
? format(this.activatedRoute.snapshot.data['title'], params)
29+
? format(this.activatedRoute.snapshot.data['title'], value)
2930
: undefined;
3031
this.url = this.activatedRoute.snapshot.data?.['url']
31-
? format(this.activatedRoute.snapshot.data['url'], params)
32+
? format(this.activatedRoute.snapshot.data['url'], value)
3233
: undefined;
3334
if (this.title) {
3435
let newTitle = `${this.appConfigService.config?.title} - ${this.title}`;

0 commit comments

Comments
 (0)