Files
NodeBB/src/file.js

199 lines
5.2 KiB
JavaScript
Raw Normal View History

2017-02-18 01:56:23 -07:00
'use strict';
2014-04-17 13:11:53 -04:00
2019-09-23 22:30:17 -04:00
const fs = require('fs');
const nconf = require('nconf');
const path = require('path');
const winston = require('winston');
const { mkdirp } = require('mkdirp');
2019-09-23 22:30:17 -04:00
const mime = require('mime');
const graceful = require('graceful-fs');
const sanitizeHtml = require('sanitize-html');
2019-09-23 22:30:17 -04:00
const slugify = require('./slugify');
graceful.gracefulify(fs);
2019-09-23 22:30:17 -04:00
const file = module.exports;
2019-09-23 22:30:17 -04:00
file.saveFileToLocal = async function (filename, folder, tempPath) {
/*
* remarkable doesn't allow spaces in hyperlinks, once that's fixed, remove this.
*/
filename = filename.split('.').map(name => slugify(name)).join('.');
2019-09-23 22:30:17 -04:00
const uploadPath = path.join(nconf.get('upload_path'), folder, filename);
if (!uploadPath.startsWith(nconf.get('upload_path'))) {
throw new Error('[[error:invalid-path]]');
}
2014-02-13 20:43:19 -05:00
2021-02-03 23:59:08 -07:00
winston.verbose(`Saving file ${filename} to : ${uploadPath}`);
2020-01-31 14:10:00 -05:00
await mkdirp(path.dirname(uploadPath));
if (filename.endsWith('.svg')) {
await sanitizeSvg(tempPath);
}
2020-08-14 00:05:03 -04:00
await fs.promises.copyFile(tempPath, uploadPath);
2019-09-23 22:30:17 -04:00
return {
2021-02-03 23:59:08 -07:00
url: `/assets/uploads/${folder ? `${folder}/` : ''}${filename}`,
2019-09-23 22:30:17 -04:00
path: uploadPath,
};
2014-04-17 13:11:53 -04:00
};
2014-02-13 20:43:19 -05:00
2019-09-23 22:30:17 -04:00
file.base64ToLocal = async function (imageData, uploadPath) {
const buffer = Buffer.from(imageData.slice(imageData.indexOf('base64') + 7), 'base64');
uploadPath = path.join(nconf.get('upload_path'), uploadPath);
2020-08-14 00:05:03 -04:00
await fs.promises.writeFile(uploadPath, buffer, {
2017-02-17 19:31:21 -07:00
encoding: 'base64',
});
2019-09-23 22:30:17 -04:00
return uploadPath;
};
// https://stackoverflow.com/a/31205878/583363
file.appendToFileName = function (filename, string) {
2019-09-23 22:30:17 -04:00
const dotIndex = filename.lastIndexOf('.');
if (dotIndex === -1) {
return filename + string;
}
return filename.substring(0, dotIndex) + string + filename.substring(dotIndex);
};
file.allowedExtensions = function () {
2019-09-23 22:30:17 -04:00
const meta = require('./meta');
let allowedExtensions = (meta.config.allowedFileExtensions || '').trim();
if (!allowedExtensions) {
return [];
}
allowedExtensions = allowedExtensions.split(',');
2021-02-04 00:01:39 -07:00
allowedExtensions = allowedExtensions.filter(Boolean).map((extension) => {
extension = extension.trim();
if (!extension.startsWith('.')) {
2021-02-03 23:59:08 -07:00
extension = `.${extension}`;
}
2017-05-01 20:58:34 -04:00
return extension.toLowerCase();
});
if (allowedExtensions.includes('.jpg') && !allowedExtensions.includes('.jpeg')) {
allowedExtensions.push('.jpeg');
}
return allowedExtensions;
};
2019-09-23 22:30:17 -04:00
file.exists = async function (path) {
try {
2020-08-14 00:05:03 -04:00
await fs.promises.stat(path);
2019-09-23 22:30:17 -04:00
} catch (err) {
if (err.code === 'ENOENT') {
return false;
2017-01-02 22:23:17 -07:00
}
2019-09-23 22:30:17 -04:00
throw err;
}
return true;
2015-09-29 18:22:41 -04:00
};
file.existsSync = function (path) {
2015-09-29 18:22:41 -04:00
try {
2017-01-02 22:23:17 -07:00
fs.statSync(path);
} catch (err) {
if (err.code === 'ENOENT') {
return false;
}
throw err;
2015-09-29 18:22:41 -04:00
}
2017-01-02 22:23:17 -07:00
return true;
2015-09-29 18:22:41 -04:00
};
2019-09-23 22:30:17 -04:00
file.delete = async function (path) {
if (!path) {
2019-09-23 22:30:17 -04:00
return;
2017-05-24 00:02:30 -04:00
}
2019-09-23 22:30:17 -04:00
try {
2020-08-14 00:05:03 -04:00
await fs.promises.unlink(path);
2019-09-23 22:30:17 -04:00
} catch (err) {
if (err.code === 'ENOENT') {
winston.verbose(`[file] Attempted to delete non-existent file: ${path}`);
return;
}
2019-09-23 22:30:17 -04:00
winston.warn(err);
}
2019-09-23 22:30:17 -04:00
};
2019-09-23 22:30:17 -04:00
file.link = async function link(filePath, destPath, relative) {
if (relative && process.platform !== 'win32') {
filePath = path.relative(path.dirname(destPath), filePath);
}
if (process.platform === 'win32') {
2020-08-14 00:05:03 -04:00
await fs.promises.link(filePath, destPath);
} else {
2020-08-14 00:05:03 -04:00
await fs.promises.symlink(filePath, destPath, 'file');
}
};
2019-09-23 22:30:17 -04:00
file.linkDirs = async function linkDirs(sourceDir, destDir, relative) {
if (relative && process.platform !== 'win32') {
sourceDir = path.relative(path.dirname(destDir), sourceDir);
}
2019-09-23 22:30:17 -04:00
const type = (process.platform === 'win32') ? 'junction' : 'dir';
2020-08-14 00:05:03 -04:00
await fs.promises.symlink(sourceDir, destDir, type);
};
2017-02-17 19:42:02 +00:00
file.typeToExtension = function (type) {
2019-09-23 22:30:17 -04:00
let extension = '';
2017-02-17 19:42:02 +00:00
if (type) {
2021-02-03 23:59:08 -07:00
extension = `.${mime.getExtension(type)}`;
2017-02-17 19:42:02 +00:00
}
return extension;
};
// Adapted from http://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search
2019-09-23 22:30:17 -04:00
file.walk = async function (dir) {
2020-08-14 00:05:03 -04:00
const subdirs = await fs.promises.readdir(dir);
2019-09-23 22:30:17 -04:00
const files = await Promise.all(subdirs.map(async (subdir) => {
const res = path.resolve(dir, subdir);
2020-08-14 00:05:03 -04:00
return (await fs.promises.stat(res)).isDirectory() ? file.walk(res) : res;
2019-09-23 22:30:17 -04:00
}));
2019-09-23 22:31:55 -04:00
return files.reduce((a, f) => a.concat(f), []);
};
Async refactor in place (#7736) * feat: allow both callback&and await * feat: ignore async key * feat: callbackify and promisify in same file * Revert "feat: callbackify and promisify in same file" This reverts commit cea206a9b8e6d8295310074b18cc82a504487862. * feat: no need to store .callbackify * feat: change getTopics to async * feat: remove .async * fix: byScore * feat: rewrite topics/index and social with async/await * fix: rewrite topics/data.js fix issue with async.waterfall, only pass result if its not undefined * feat: add callbackify to redis/psql * feat: psql use await * fix: redis :volcano: * feat: less returns * feat: more await rewrite * fix: redis tests * feat: convert sortedSetAdd rewrite psql transaction to async/await * feat: :dog: * feat: test * feat: log client and query * feat: log bind * feat: more logs * feat: more logs * feat: check perform * feat: dont callbackify transaction * feat: remove logs * fix: main functions * feat: more logs * fix: increment * fix: rename * feat: remove cls * fix: remove console.log * feat: add deprecation message to .async usage * feat: update more dbal methods * fix: redis :voodoo: * feat: fix redis zrem, convert setObject * feat: upgrade getObject methods * fix: psql getObjectField * fix: redis tests * feat: getObjectKeys * feat: getObjectValues * feat: isObjectField * fix: add missing return * feat: delObjectField * feat: incrObjectField * fix: add missing await * feat: remove exposed helpers * feat: list methods * feat: flush/empty * feat: delete * fix: redis delete all * feat: get/set * feat: incr/rename * feat: type * feat: expire * feat: setAdd * feat: setRemove * feat: isSetMember * feat: getSetMembers * feat: setCount, setRemoveRandom * feat: zcard,zcount * feat: sortedSetRank * feat: isSortedSetMember * feat: zincrby * feat: sortedSetLex * feat: processSortedSet * fix: add mising await * feat: debug psql * fix: psql test * fix: test * fix: another test * fix: test fix * fix: psql tests * feat: remove logs * feat: user arrow func use builtin async promises * feat: topic bookmarks * feat: topic.delete * feat: topic.restore * feat: topics.purge * feat: merge * feat: suggested * feat: topics/user.js * feat: topics modules * feat: topics/follow * fix: deprecation msg * feat: fork * feat: topics/posts * feat: sorted/recent * feat: topic/teaser * feat: topics/tools * feat: topics/unread * feat: add back node versions disable deprecation notice wrap async controllers in try/catch * feat: use db directly * feat: promisify in place * fix: redis/psql * feat: deprecation message logs for psql * feat: more logs * feat: more logs * feat: logs again * feat: more logs * fix: call release * feat: restore travis, remove logs * fix: loops * feat: remove .async. usage
2019-07-09 12:46:49 -04:00
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);
}
Async refactor in place (#7736) * feat: allow both callback&and await * feat: ignore async key * feat: callbackify and promisify in same file * Revert "feat: callbackify and promisify in same file" This reverts commit cea206a9b8e6d8295310074b18cc82a504487862. * feat: no need to store .callbackify * feat: change getTopics to async * feat: remove .async * fix: byScore * feat: rewrite topics/index and social with async/await * fix: rewrite topics/data.js fix issue with async.waterfall, only pass result if its not undefined * feat: add callbackify to redis/psql * feat: psql use await * fix: redis :volcano: * feat: less returns * feat: more await rewrite * fix: redis tests * feat: convert sortedSetAdd rewrite psql transaction to async/await * feat: :dog: * feat: test * feat: log client and query * feat: log bind * feat: more logs * feat: more logs * feat: check perform * feat: dont callbackify transaction * feat: remove logs * fix: main functions * feat: more logs * fix: increment * fix: rename * feat: remove cls * fix: remove console.log * feat: add deprecation message to .async usage * feat: update more dbal methods * fix: redis :voodoo: * feat: fix redis zrem, convert setObject * feat: upgrade getObject methods * fix: psql getObjectField * fix: redis tests * feat: getObjectKeys * feat: getObjectValues * feat: isObjectField * fix: add missing return * feat: delObjectField * feat: incrObjectField * fix: add missing await * feat: remove exposed helpers * feat: list methods * feat: flush/empty * feat: delete * fix: redis delete all * feat: get/set * feat: incr/rename * feat: type * feat: expire * feat: setAdd * feat: setRemove * feat: isSetMember * feat: getSetMembers * feat: setCount, setRemoveRandom * feat: zcard,zcount * feat: sortedSetRank * feat: isSortedSetMember * feat: zincrby * feat: sortedSetLex * feat: processSortedSet * fix: add mising await * feat: debug psql * fix: psql test * fix: test * fix: another test * fix: test fix * fix: psql tests * feat: remove logs * feat: user arrow func use builtin async promises * feat: topic bookmarks * feat: topic.delete * feat: topic.restore * feat: topics.purge * feat: merge * feat: suggested * feat: topics/user.js * feat: topics modules * feat: topics/follow * fix: deprecation msg * feat: fork * feat: topics/posts * feat: sorted/recent * feat: topic/teaser * feat: topics/tools * feat: topics/unread * feat: add back node versions disable deprecation notice wrap async controllers in try/catch * feat: use db directly * feat: promisify in place * fix: redis/psql * feat: deprecation message logs for psql * feat: more logs * feat: more logs * feat: logs again * feat: more logs * fix: call release * feat: restore travis, remove logs * fix: loops * feat: remove .async. usage
2019-07-09 12:46:49 -04:00
require('./promisify')(file);