Skip to content

Commit efe12f4

Browse files
author
Mike Patnode
committed
Move filename validation out of the Router and into the FilesAdaptor
1 parent f67f8db commit efe12f4

File tree

6 files changed

+70
-22
lines changed

6 files changed

+70
-22
lines changed

spec/FilesController.spec.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const mockAdapter = {
1414
deleteFile: () => {},
1515
getFileData: () => {},
1616
getFileLocation: () => 'xyz',
17+
validateFilename: () => {},
1718
};
1819

1920
// Small additional tests to improve overall coverage
@@ -118,4 +119,13 @@ describe('FilesController', () => {
118119

119120
done();
120121
});
122+
123+
it('should reject slashes in file names', done => {
124+
const gridStoreAdapter = new GridFSBucketAdapter(
125+
'mongodb://localhost:27017/parse'
126+
);
127+
const fileName = 'foo/randomFileName.pdf';
128+
expect(gridStoreAdapter.validateFilename(fileName)).not.toBe(null);
129+
done();
130+
});
121131
});

src/Adapters/Files/FilesAdapter.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
// * deleteFile(filename)
99
// * getFileData(filename)
1010
// * getFileLocation(config, filename)
11+
// Adapter classes should implement the following functions:
12+
// * validateFilename(filename)
1113
//
1214
// Default is GridFSBucketAdapter, which requires mongo
1315
// and for the API server to be using the DatabaseController with Mongo
1416
// database adapter.
1517

1618
import type { Config } from '../../Config';
19+
import Parse from 'parse/lib/node/Parse';
1720
/**
1821
* @module Adapters
1922
*/
@@ -56,6 +59,37 @@ export class FilesAdapter {
5659
* @return {string} Absolute URL
5760
*/
5861
getFileLocation(config: Config, filename: string): string {}
62+
63+
/** Validate a filename for this adaptor type (optional)1G
64+
*
65+
* @param {string} filename
66+
*
67+
* @returns {null|*|Parse.Error} null if there are no errors
68+
*/
69+
// TODO: Make this required once enough people have updated their adaptors
70+
// validateFilename(filename): ?Parse.Error {}
71+
}
72+
73+
/**
74+
* Default filename validate pulled out of FilesRouter. Mostly used for Mongo storage
75+
*
76+
* @param filename
77+
* @returns {null|*|Parse.Error|Parse.ParseError|ParseError|ParseError|Parse.ParseError}
78+
*/
79+
export function validateFilename(filename): ?Parse.Error {
80+
if (filename.length > 128) {
81+
return new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename too long.');
82+
}
83+
84+
// US/ASCII centric default
85+
const regx = /^[_a-zA-Z0-9][a-zA-Z0-9@. ~_-]*$/;
86+
if (!filename.match(regx)) {
87+
return new Parse.Error(
88+
Parse.Error.INVALID_FILE_NAME,
89+
'Filename contains invalid characters.'
90+
);
91+
}
92+
return null; // No errors
5993
}
6094

6195
export default FilesAdapter;

src/Adapters/Files/GridFSBucketAdapter.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
// @flow-disable-next
1010
import { MongoClient, GridFSBucket, Db } from 'mongodb';
11-
import { FilesAdapter } from './FilesAdapter';
11+
import { FilesAdapter, validateFilename } from './FilesAdapter';
1212
import defaults from '../../defaults';
1313

1414
export class GridFSBucketAdapter extends FilesAdapter {
@@ -139,6 +139,10 @@ export class GridFSBucketAdapter extends FilesAdapter {
139139
}
140140
return this._client.close(false);
141141
}
142+
143+
validateFilename(filename) {
144+
return validateFilename(filename);
145+
}
142146
}
143147

144148
export default GridFSBucketAdapter;

src/Adapters/Files/GridStoreAdapter.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
// @flow-disable-next
1111
import { MongoClient, GridStore, Db } from 'mongodb';
12-
import { FilesAdapter } from './FilesAdapter';
12+
import { FilesAdapter, validateFilename } from './FilesAdapter';
1313
import defaults from '../../defaults';
1414

1515
export class GridStoreAdapter extends FilesAdapter {
@@ -110,6 +110,10 @@ export class GridStoreAdapter extends FilesAdapter {
110110
}
111111
return this._client.close(false);
112112
}
113+
114+
validateFilename(filename) {
115+
return validateFilename(filename);
116+
}
113117
}
114118

115119
// handleRangeRequest is licensed under Creative Commons Attribution 4.0 International License (https://creativecommons.org/licenses/by/4.0/).

src/Controllers/FilesController.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// FilesController.js
22
import { randomHexString } from '../cryptoUtils';
33
import AdaptableController from './AdaptableController';
4-
import { FilesAdapter } from '../Adapters/Files/FilesAdapter';
4+
import { validateFilename, FilesAdapter } from '../Adapters/Files/FilesAdapter';
55
import path from 'path';
66
import mime from 'mime';
77

@@ -95,6 +95,13 @@ export class FilesController extends AdaptableController {
9595
handleFileStream(config, filename, req, res, contentType) {
9696
return this.adapter.handleFileStream(filename, req, res, contentType);
9797
}
98+
99+
validateFilename(filename) {
100+
if (typeof this.adapter.validateFilename === 'function') {
101+
return this.adapter.validateFilename(filename);
102+
}
103+
return validateFilename(filename);
104+
}
98105
}
99106

100107
export default FilesController;

src/Routers/FilesRouter.js

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -69,35 +69,24 @@ export class FilesRouter {
6969
}
7070

7171
createHandler(req, res, next) {
72+
const config = req.config;
73+
const filesController = config.filesController;
74+
const filename = req.params.filename;
75+
const contentType = req.get('Content-type');
76+
7277
if (!req.body || !req.body.length) {
7378
next(
7479
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.')
7580
);
7681
return;
7782
}
7883

79-
if (req.params.filename.length > 128) {
80-
next(
81-
new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename too long.')
82-
);
84+
const error = filesController.validateFilename(filename);
85+
if (error) {
86+
next(error);
8387
return;
8488
}
8589

86-
if (!req.params.filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
87-
next(
88-
new Parse.Error(
89-
Parse.Error.INVALID_FILE_NAME,
90-
'Filename contains invalid characters.'
91-
)
92-
);
93-
return;
94-
}
95-
96-
const filename = req.params.filename;
97-
const contentType = req.get('Content-type');
98-
const config = req.config;
99-
const filesController = config.filesController;
100-
10190
filesController
10291
.createFile(config, filename, req.body, contentType)
10392
.then(result => {

0 commit comments

Comments
 (0)