mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-28 09:36:16 +01:00
fix: apply sanitizeSvg to regular uploads and uploads from manage uploads acp page
This commit is contained in:
@@ -4,7 +4,6 @@ const path = require('path');
|
|||||||
const nconf = require('nconf');
|
const nconf = require('nconf');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const winston = require('winston');
|
const winston = require('winston');
|
||||||
const sanitizeHtml = require('sanitize-html');
|
|
||||||
|
|
||||||
const meta = require('../../meta');
|
const meta = require('../../meta');
|
||||||
const posts = require('../../posts');
|
const posts = require('../../posts');
|
||||||
@@ -157,50 +156,11 @@ uploadsController.uploadCategoryPicture = async function (req, res, next) {
|
|||||||
return next(new Error('[[error:invalid-json]]'));
|
return next(new Error('[[error:invalid-json]]'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uploadedFile.path.endsWith('.svg')) {
|
|
||||||
await sanitizeSvg(uploadedFile.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
await validateUpload(uploadedFile, allowedImageTypes);
|
await validateUpload(uploadedFile, allowedImageTypes);
|
||||||
const filename = `category-${params.cid}${path.extname(uploadedFile.name)}`;
|
const filename = `category-${params.cid}${path.extname(uploadedFile.name)}`;
|
||||||
await uploadImage(filename, 'category', uploadedFile, req, res, next);
|
await uploadImage(filename, 'category', uploadedFile, req, res, next);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function sanitizeSvg(filePath) {
|
|
||||||
const dirty = await fs.promises.readFile(filePath, 'utf8');
|
|
||||||
const clean = sanitizeHtml(dirty, {
|
|
||||||
allowedTags: [
|
|
||||||
'svg', 'g', 'defs', 'linearGradient', 'radialGradient', 'stop',
|
|
||||||
'circle', 'ellipse', 'polygon', 'polyline', 'path', 'rect',
|
|
||||||
'line', 'text', 'tspan', 'use', 'symbol', 'clipPath', 'mask', 'pattern',
|
|
||||||
'filter', 'feGaussianBlur', 'feOffset', 'feBlend', 'feColorMatrix', 'feMerge', 'feMergeNode',
|
|
||||||
],
|
|
||||||
allowedAttributes: {
|
|
||||||
'*': [
|
|
||||||
// Geometry
|
|
||||||
'x', 'y', 'x1', 'x2', 'y1', 'y2', 'cx', 'cy', 'r', 'rx', 'ry',
|
|
||||||
'width', 'height', 'd', 'points', 'viewBox', 'transform',
|
|
||||||
|
|
||||||
// Presentation
|
|
||||||
'fill', 'stroke', 'stroke-width', 'opacity',
|
|
||||||
'stop-color', 'stop-opacity', 'offset', 'style', 'class',
|
|
||||||
|
|
||||||
// Text
|
|
||||||
'text-anchor', 'font-size', 'font-family',
|
|
||||||
|
|
||||||
// Misc
|
|
||||||
'id', 'clip-path', 'mask', 'filter', 'gradientUnits', 'gradientTransform',
|
|
||||||
'xmlns', 'preserveAspectRatio',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
parser: {
|
|
||||||
lowerCaseTags: false,
|
|
||||||
lowerCaseAttributeNames: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await fs.promises.writeFile(filePath, clean);
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadsController.uploadFavicon = async function (req, res, next) {
|
uploadsController.uploadFavicon = async function (req, res, next) {
|
||||||
const uploadedFile = req.files.files[0];
|
const uploadedFile = req.files.files[0];
|
||||||
const allowedTypes = ['image/x-icon', 'image/vnd.microsoft.icon'];
|
const allowedTypes = ['image/x-icon', 'image/vnd.microsoft.icon'];
|
||||||
@@ -296,10 +256,6 @@ uploadsController.uploadOgImage = async function (req, res, next) {
|
|||||||
async function upload(name, req, res, next) {
|
async function upload(name, req, res, next) {
|
||||||
const uploadedFile = req.files.files[0];
|
const uploadedFile = req.files.files[0];
|
||||||
|
|
||||||
if (uploadedFile.path.endsWith('.svg')) {
|
|
||||||
await sanitizeSvg(uploadedFile.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
await validateUpload(uploadedFile, allowedImageTypes);
|
await validateUpload(uploadedFile, allowedImageTypes);
|
||||||
const filename = name + path.extname(uploadedFile.name);
|
const filename = name + path.extname(uploadedFile.name);
|
||||||
await uploadImage(filename, 'system', uploadedFile, req, res, next);
|
await uploadImage(filename, 'system', uploadedFile, req, res, next);
|
||||||
|
|||||||
40
src/file.js
40
src/file.js
@@ -7,6 +7,7 @@ const winston = require('winston');
|
|||||||
const { mkdirp } = require('mkdirp');
|
const { mkdirp } = require('mkdirp');
|
||||||
const mime = require('mime');
|
const mime = require('mime');
|
||||||
const graceful = require('graceful-fs');
|
const graceful = require('graceful-fs');
|
||||||
|
const sanitizeHtml = require('sanitize-html');
|
||||||
|
|
||||||
const slugify = require('./slugify');
|
const slugify = require('./slugify');
|
||||||
|
|
||||||
@@ -27,6 +28,10 @@ file.saveFileToLocal = async function (filename, folder, tempPath) {
|
|||||||
|
|
||||||
winston.verbose(`Saving file ${filename} to : ${uploadPath}`);
|
winston.verbose(`Saving file ${filename} to : ${uploadPath}`);
|
||||||
await mkdirp(path.dirname(uploadPath));
|
await mkdirp(path.dirname(uploadPath));
|
||||||
|
if (tempPath.endsWith('.svg')) {
|
||||||
|
await sanitizeSvg(tempPath);
|
||||||
|
}
|
||||||
|
|
||||||
await fs.promises.copyFile(tempPath, uploadPath);
|
await fs.promises.copyFile(tempPath, uploadPath);
|
||||||
return {
|
return {
|
||||||
url: `/assets/uploads/${folder ? `${folder}/` : ''}${filename}`,
|
url: `/assets/uploads/${folder ? `${folder}/` : ''}${filename}`,
|
||||||
@@ -155,4 +160,39 @@ file.walk = async function (dir) {
|
|||||||
return files.reduce((a, f) => a.concat(f), []);
|
return files.reduce((a, f) => a.concat(f), []);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function sanitizeSvg(filePath) {
|
||||||
|
const dirty = await fs.promises.readFile(filePath, 'utf8');
|
||||||
|
const clean = sanitizeHtml(dirty, {
|
||||||
|
allowedTags: [
|
||||||
|
'svg', 'g', 'defs', 'linearGradient', 'radialGradient', 'stop',
|
||||||
|
'circle', 'ellipse', 'polygon', 'polyline', 'path', 'rect',
|
||||||
|
'line', 'text', 'tspan', 'use', 'symbol', 'clipPath', 'mask', 'pattern',
|
||||||
|
'filter', 'feGaussianBlur', 'feOffset', 'feBlend', 'feColorMatrix', 'feMerge', 'feMergeNode',
|
||||||
|
],
|
||||||
|
allowedAttributes: {
|
||||||
|
'*': [
|
||||||
|
// Geometry
|
||||||
|
'x', 'y', 'x1', 'x2', 'y1', 'y2', 'cx', 'cy', 'r', 'rx', 'ry',
|
||||||
|
'width', 'height', 'd', 'points', 'viewBox', 'transform',
|
||||||
|
|
||||||
|
// Presentation
|
||||||
|
'fill', 'stroke', 'stroke-width', 'opacity',
|
||||||
|
'stop-color', 'stop-opacity', 'offset', 'style', 'class',
|
||||||
|
|
||||||
|
// Text
|
||||||
|
'text-anchor', 'font-size', 'font-family',
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
'id', 'clip-path', 'mask', 'filter', 'gradientUnits', 'gradientTransform',
|
||||||
|
'xmlns', 'preserveAspectRatio',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
parser: {
|
||||||
|
lowerCaseTags: false,
|
||||||
|
lowerCaseAttributeNames: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await fs.promises.writeFile(filePath, clean);
|
||||||
|
}
|
||||||
|
|
||||||
require('./promisify')(file);
|
require('./promisify')(file);
|
||||||
|
|||||||
4
test/files/dirty.svg
Normal file
4
test/files/dirty.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="10" y="10" width="80" height="80" fill="red" stroke="black" stroke-width="4"/>
|
||||||
|
</svg>
|
||||||
|
<script>alert('foo');</script>
|
||||||
|
After Width: | Height: | Size: 192 B |
@@ -338,6 +338,15 @@ describe('Upload Controllers', () => {
|
|||||||
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/category/category-1.png`);
|
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/category/category-1.png`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should upload svg as category image after cleaning it up', async () => {
|
||||||
|
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/category/uploadpicture`, path.join(__dirname, '../test/files/dirty.svg'), { params: JSON.stringify({ cid: cid }) }, jar, csrf_token);
|
||||||
|
assert.equal(response.statusCode, 200);
|
||||||
|
assert(Array.isArray(body));
|
||||||
|
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/category/category-1.svg`);
|
||||||
|
const svgContents = await fs.readFile(path.join(__dirname, '../test/uploads/category/category-1.svg'), 'utf-8');
|
||||||
|
assert.strictEqual(svgContents.includes('<script>'), false);
|
||||||
|
});
|
||||||
|
|
||||||
it('should upload default avatar', async () => {
|
it('should upload default avatar', async () => {
|
||||||
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadDefaultAvatar`, path.join(__dirname, '../test/files/test.png'), { }, jar, csrf_token);
|
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadDefaultAvatar`, path.join(__dirname, '../test/files/test.png'), { }, jar, csrf_token);
|
||||||
assert.equal(response.statusCode, 200);
|
assert.equal(response.statusCode, 200);
|
||||||
|
|||||||
Reference in New Issue
Block a user