[Files] Validate mime type when files are selected (#159503)

## Summary

Closes https://github.com/elastic/kibana/issues/155168


### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Vadim Kibana 2023-06-15 13:17:55 +02:00 committed by GitHub
parent 536a8d4194
commit 2f1c2fa3e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 60 additions and 15 deletions

View file

@ -39,4 +39,10 @@ export const i18nTexts = {
'File is too large. Maximum size is {expectedSize, plural, one {# byte} other {# bytes} }.',
values: { expectedSize },
}),
mimeTypeNotSupported: (mimeType: string, supportedMimeTypes: string) =>
i18n.translate('sharedUXPackages.fileUpload.mimeTypeNotSupportedErrorMessage', {
defaultMessage:
'File mime type "{mimeType}" is not supported. Supported mime types are: {supportedMimeTypes}.',
values: { mimeType, supportedMimeTypes },
}),
};

View file

@ -33,7 +33,12 @@ describe('UploadState', () => {
filesClient.create.mockReturnValue(of({ file: { id: 'test' } as FileJSON }) as any);
filesClient.upload.mockReturnValue(of(undefined) as any);
uploadState = new UploadState(
{ id: 'test', http: {}, maxSizeBytes: 1000 } as FileKindBrowser,
{
id: 'test',
http: {},
maxSizeBytes: 1000,
allowedMimeTypes: ['text/plain', 'image/png'],
} as FileKindBrowser,
filesClient,
{},
imageMetadataFactory
@ -73,8 +78,8 @@ describe('UploadState', () => {
it('uploads all provided files', async () => {
testScheduler.run(({ expectObservable, cold, flush }) => {
const file1 = { name: 'test', size: 1 } as File;
const file2 = { name: 'test 2', size: 1 } as File;
const file1 = { name: 'test', size: 1, type: 'text/plain' } as File;
const file2 = { name: 'test 2', size: 1, type: 'text/plain' } as File;
uploadState.setFiles([file1, file2]);
@ -120,7 +125,7 @@ describe('UploadState', () => {
filesClient.upload.mockReturnValue(of(undefined).pipe(delay(10)) as any);
filesClient.delete.mockReturnValue(of(undefined) as any);
const file1 = { name: 'test' } as File;
const file1 = { name: 'test', type: 'text/plain' } as File;
const file2 = { name: 'test 2.png', type: 'image/png' } as File;
uploadState.setFiles([file1, file2]);
@ -160,7 +165,7 @@ describe('UploadState', () => {
expect(filesClient.create).toHaveBeenNthCalledWith(1, {
kind: 'test',
meta: { myMeta: true },
mimeType: undefined,
mimeType: 'text/plain',
name: 'test',
});
expect(filesClient.create).toHaveBeenNthCalledWith(2, {
@ -192,6 +197,28 @@ describe('UploadState', () => {
});
});
it('throws for files, which mime-type is not supported', () => {
testScheduler.run(({ expectObservable }) => {
const file = {
name: 'script.sh',
size: 123,
type: 'text/x-sh',
} as File;
uploadState.setFiles([file]);
expectObservable(uploadState.files$).toBe('a', {
a: [
{
file,
status: 'idle',
error: new Error(
'File mime type "text/x-sh" is not supported. Supported mime types are: text/plain, image/png.'
),
},
],
});
});
});
it('option "allowRepeatedUploads" calls clear after upload is done', () => {
testScheduler.run(({ expectObservable, cold }) => {
uploadState = new UploadState(

View file

@ -97,15 +97,22 @@ export class UploadState {
return this.uploading$.getValue();
}
private validateFiles(files: File[]): undefined | string {
if (
this.fileKind.maxSizeBytes != null &&
files.some((file) => file.size > this.fileKind.maxSizeBytes!)
) {
return i18nTexts.fileTooLarge(String(this.fileKind.maxSizeBytes));
private readonly validateFile = (file: File): void => {
const fileKind = this.fileKind;
if (fileKind.maxSizeBytes != null && file.size > this.fileKind.maxSizeBytes!) {
const message = i18nTexts.fileTooLarge(String(this.fileKind.maxSizeBytes));
throw new Error(message);
}
return;
}
if (fileKind.allowedMimeTypes != null && !fileKind.allowedMimeTypes.includes(file.type)) {
const message = i18nTexts.mimeTypeNotSupported(
file.type,
fileKind.allowedMimeTypes.join(', ')
);
throw new Error(message);
}
};
public setFiles = (files: File[]): void => {
if (this.isUploading()) {
@ -117,14 +124,19 @@ export class UploadState {
this.error$.next(undefined);
}
const validationError = this.validateFiles(files);
let error: undefined | Error;
try {
files.forEach(this.validateFile);
} catch (err) {
error = err;
}
this.files$$.next(
files.map((file) =>
createStateSubject<FileState>({
file,
status: 'idle',
error: validationError ? new Error(validationError) : undefined,
error,
})
)
);