Skip to content

Commit e93ea45

Browse files
authored
[ENG-3882] Prevent special chars and show error messages (#1569)
- Ticket: [ENG-3882] - Feature flag: n/a ## Purpose - Disallow special characters in new folders and file/folder rename modals ## Summary of Changes - Add logic to check against forbidden special chars for file rename and new folder modal - Add error messages to those modals
1 parent a9f2dab commit e93ea45

File tree

14 files changed

+186
-18
lines changed

14 files changed

+186
-18
lines changed

app/models/file.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export interface FileLinks extends BaseFileLinks {
2222
render?: Link;
2323
}
2424

25+
// Character that need to be excluded: ()<>~!@$&*:;,'"\|/?
26+
export const forbiddenFileNameCharacters = /[()<>~!@$&*:;,"\\|/?]/;
27+
2528
export default class FileModel extends BaseFileItem {
2629
@attr() links!: FileLinks;
2730
@attr('fixstring') name!: string;
Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { inject as service } from '@ember/service';
2+
import Intl from 'ember-intl/services/intl';
13
import Component from '@glimmer/component';
24
import { tracked } from '@glimmer/tracking';
3-
import { taskFor } from 'ember-concurrency-ts';
45

6+
import { forbiddenFileNameCharacters } from 'ember-osf-web/models/file';
57
import StorageManager from 'osf-components/components/storage-provider-manager/storage-manager/component';
68

79
interface Args {
@@ -10,9 +12,31 @@ interface Args {
1012
}
1113

1214
export default class CreateFolderModal extends Component<Args> {
15+
@service intl!: Intl;
1316
@tracked newFolderName = '';
1417

15-
get shouldDisable() {
16-
return taskFor(this.args.manager.createNewFolder).isRunning || !this.newFolderName;
18+
get isInvalid() {
19+
const trimmedName = this.newFolderName.trim();
20+
return !trimmedName || this.containsForbiddenChars || this.endsWithDot;
21+
}
22+
23+
get containsForbiddenChars() {
24+
return this.newFolderName && forbiddenFileNameCharacters.test(this.newFolderName);
25+
}
26+
27+
get endsWithDot() {
28+
return this.newFolderName && this.newFolderName.endsWith('.');
29+
}
30+
31+
get errorText() {
32+
let errorTextKey = 'osf-components.file-browser.create_folder.error_';
33+
if (this.containsForbiddenChars) {
34+
errorTextKey += 'forbidden_chars';
35+
} else if (this.endsWithDot) {
36+
errorTextKey += 'ends_with_dot';
37+
} else {
38+
errorTextKey += 'message';
39+
}
40+
return this.intl.t(errorTextKey);
1741
}
1842
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.RenameInput {
2+
min-width: 50vw;
3+
width: 100%;
4+
}
5+
6+
.ErrorText {
7+
color: $color-text-gray;
8+
font-style: italic;
9+
font-weight: 400;
10+
}

lib/osf-components/addon/components/file-browser/add-new/create-folder-modal/template.hbs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
@onClose={{queue (action (mut @isOpen) false) (action (mut this.newFolderName) '')}}
44
as |dialog|
55
>
6-
<dialog.heading>
6+
<dialog.heading data-test-create-folder-heading>
77
{{t 'osf-components.file-browser.create_folder.title'}}
88
</dialog.heading>
9-
<dialog.main>
9+
<dialog.main data-test-create-folder-main>
1010
<label>
1111
<p>{{t 'osf-components.file-browser.create_folder.new_folder_name'}}</p>
1212
<Input
@@ -16,13 +16,19 @@
1616
(action (mut this.newFolderName) '')
1717
}}
1818
@value={{this.newFolderName}}
19+
local-class='RenameInput'
1920
/>
21+
{{#if this.isInvalid}}
22+
<p local-class='ErrorText' data-test-new-folder-error>{{this.errorText}}</p>
23+
{{/if}}
2024
</label>
2125
</dialog.main>
2226
<dialog.footer>
2327
<Button
28+
data-test-create-folder-button
29+
data-analytics-name='Create folder'
2430
@type='create'
25-
disabled={{this.shouldDisable}}
31+
disabled={{or @manager.createNewFolder.isRunning this.isInvalid}}
2632
{{on 'click'
2733
(queue
2834
(perform @manager.createNewFolder this.newFolderName)

lib/osf-components/addon/components/file-browser/add-new/template.hbs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
as |dropdown|
77
>
88
<dropdown.trigger
9+
data-analytics-name='Add file or folder omnibutton'
910
data-test-add-new-trigger
1011
aria-label={{t 'osf-components.file-browser.add_button_aria'}}
1112
local-class='TriggerButton {{if dropdown.isOpen 'CloseButton'}}'>
@@ -18,12 +19,16 @@
1819
{{will-destroy (fn @setClickableElementId '')}}
1920
>
2021
<Button
22+
data-analytics-name='Open create folder modal'
23+
data-test-create-folder
2124
@layout='fake-link'
2225
{{on 'click' (fn (mut this.createFolderModalOpen) true)}}
2326
>
2427
{{t 'osf-components.file-browser.create_folder.title'}}
2528
</Button>
2629
<Button
30+
data-analytics-name='Upload file'
31+
data-test-upload-file
2732
@layout='fake-link'
2833
id={{uploadButtonId}}
2934
>

lib/osf-components/addon/components/file-browser/file-rename-modal/component.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import Toast from 'ember-toastr/services/toast';
66
import { restartableTask } from 'ember-concurrency';
77
import { waitFor } from '@ember/test-waiters';
88
import Intl from 'ember-intl/services/intl';
9+
10+
import { forbiddenFileNameCharacters } from 'ember-osf-web/models/file';
911
import File from 'ember-osf-web/packages/files/file';
1012
import StorageManager from 'osf-components/components/storage-provider-manager/storage-manager/component';
1113

@@ -28,12 +30,34 @@ export default class FileRenameModal extends Component<Args> {
2830
}
2931

3032
get isValid() {
31-
if(this.newFileName) {
33+
if(this.newFileName && !this.containsForbiddenChars && !this.endsWithDot) {
3234
return (this.newFileName.trim() !== this.originalFileName && this.newFileName.trim() !== '');
3335
}
3436
return false;
3537
}
3638

39+
get containsForbiddenChars() {
40+
return this.newFileName && forbiddenFileNameCharacters.test(this.newFileName);
41+
}
42+
43+
get endsWithDot() {
44+
return this.newFileName && this.newFileName.endsWith('.');
45+
}
46+
47+
get errorText() {
48+
let errorTextKey = 'osf-components.file-browser.file_rename_modal.error_';
49+
if (!this.newFileName) {
50+
errorTextKey += 'empty_name';
51+
} else if (this.endsWithDot) {
52+
errorTextKey += 'ends_with_dot';
53+
} else if (this.containsForbiddenChars) {
54+
errorTextKey += 'forbidden_chars';
55+
} else {
56+
errorTextKey += 'message';
57+
}
58+
return this.intl.t(errorTextKey);
59+
}
60+
3761
@action
3862
resetFileNameValue() {
3963
this.newFileName = this.originalFileName;

lib/osf-components/addon/components/file-browser/file-rename-modal/styles.scss

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,7 @@
2020
}
2121
}
2222

23-
23+
.ErrorText {
24+
color: $color-text-gray;
25+
font-style: italic;
26+
}

lib/osf-components/addon/components/file-browser/file-rename-modal/template.hbs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
{{t 'osf-components.file-browser.file_rename_modal.heading'}}
1010
</dialog.heading>
1111
<dialog.main>
12-
<div local-class='RenameInput'>
12+
<div data-test-rename-main local-class='RenameInput'>
1313
<Input
1414
data-test-user-input
1515
aria-label={{t 'osf-components.file-browser.file_rename_modal.input_aria'}}
@@ -20,6 +20,11 @@
2020
dialog.close
2121
}}
2222
/>
23+
{{#unless this.isValid}}
24+
<p local-class='ErrorText'>
25+
{{this.errorText}}
26+
</p>
27+
{{/unless}}
2328
</div>
2429
</dialog.main>
2530
<dialog.footer>

mirage/serializers/file-provider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export default class FileSerializer extends ApplicationSerializer<MirageFileProv
6262
return {
6363
...super.buildNormalLinks(model),
6464
upload: `${apiUrl}/v2/${pathName}/${model.targetId.id}/files/${model.name}/upload`,
65+
new_folder: `${apiUrl}/v2/${pathName}/${model.targetId.id}/files/${model.name}/upload/?kind=folder`,
6566
};
6667
}
6768
}

mirage/serializers/file.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export default class FileSerializer extends ApplicationSerializer<MirageFile> {
6868
const { id } = model;
6969
return {
7070
...super.buildNormalLinks(model),
71+
new_folder: model.kind === 'folder' ? `${apiUrl}/wb/files/${id}/upload/?kind=folder` : undefined,
7172
upload: `${apiUrl}/wb/files/${id}/upload/`,
7273
download: `${apiUrl}/wb/files/${id}/download/`,
7374
move: `${apiUrl}/wb/files/${id}/move/`,

0 commit comments

Comments
 (0)