From 29f7f25f387200856bde9fcfcd46f6e548b99be2 Mon Sep 17 00:00:00 2001 From: tycrek Date: Fri, 1 Oct 2021 10:40:50 -0600 Subject: [PATCH 1/3] Added check for duplicate ID's (closes #75) --- routers/upload.js | 130 ++++++++++++++++++++++++++-------------------- 1 file changed, 74 insertions(+), 56 deletions(-) diff --git a/routers/upload.js b/routers/upload.js index 875cbbf..1383fd9 100644 --- a/routers/upload.js +++ b/routers/upload.js @@ -64,62 +64,80 @@ router.post('/', (req, res, next) => { // Fix spaces in originalname req.file.originalname = req.file.originalname.replace(/\s/g, spaceReplace === '!' ? '' : spaceReplace); - // Save the file information - const resourceId = generateId(generator, resourceIdSize, req.headers['x-ass-gfycat'] || gfyIdSize, req.file.originalname); - log.debug('Saving data', data.name); - data.put(resourceId.split('.')[0], req.file).then(() => { - // Log the upload - const logInfo = `${req.file.originalname} (${req.file.mimetype}, ${formatBytes(req.file.size)})`; - log.success('File uploaded', logInfo, `uploaded by ${users[req.token] ? users[req.token].username : ''}`); - - // Build the URLs - const resourceUrl = `${getTrueHttp()}${trueDomain}/${resourceId}`; - const thumbnailUrl = `${getTrueHttp()}${trueDomain}/${resourceId}/thumbnail`; - const deleteUrl = `${getTrueHttp()}${trueDomain}/${resourceId}/delete/${req.file.deleteId}`; - - // Send the response - res.type('json').send({ resource: resourceUrl, thumbnail: thumbnailUrl, delete: deleteUrl }) - .on('finish', () => { - log.debug('Upload response sent'); - - // After we have sent the user the response, also send a Webhook to Discord (if headers are present) - if (req.headers['x-ass-webhook-client'] && req.headers['x-ass-webhook-token']) { - - // Build the webhook - const hook = new Webhook(req.headers['x-ass-webhook-url']); - hook.setUsername(req.headers['x-ass-webhook-username'] || 'ass'); - hook.setAvatar(req.headers['x-ass-webhook-avatar'] || ASS_LOGO); - - // Build the embed - const embed = new MessageBuilder() - .setTitle(logInfo) - .setURL(resourceUrl) - .setDescription(`**Size:** \`${formatBytes(req.file.size)}\`\n**[Delete](${deleteUrl})**`) - .setThumbnail(thumbnailUrl) - .setColor(req.file.vibrant) - .setTimestamp(); - - // Send the embed to the webhook, then delete the client after to free resources - log.debug('Sending embed to webhook'); - hook.send(embed) - .then(() => log.debug('Webhook sent')) - .catch((err) => log.error('Webhook error').err(err)); - } - - // Also update the users upload count - if (!users[req.token]) { - const generateUsername = () => generateId('random', 20, null); // skipcq: JS-0074 - let username = generateUsername(); - while (Object.values(users).findIndex((user) => user.username === username) !== -1) // skipcq: JS-0073 - username = generateUsername(); - users[req.token] = { username, count: 0 }; - } - users[req.token].count += 1; - fs.writeJsonSync(path('auth.json'), { users }, { spaces: 4 }); - - log.debug('Upload request flow completed', ''); - }); - }).catch(next); + // Generate a unique resource ID + let resourceId; + + // Function to call to generate a fresh ID. Used for multiple attempts in case an ID is already taken + const gen = () => generateId(generator, resourceIdSize, req.headers['x-ass-gfycat'] || gfyIdSize, req.file.originalname); + + // Called by a promise, this will recursively resolve itself until a unique ID is found + // TODO: Add max attempts in case all available ID's are taken + function genCheckId(resolve, reject) { + let uniqueId = gen(); + data.has(uniqueId) + .then((exists) => (log.debug('ID check', exists ? 'Taken' : 'Available'), exists ? genCheckId(resolve, reject) : resolve(uniqueId))) + .catch(reject); + } + + new Promise((resolve, reject) => genCheckId(resolve, reject)) + .then((uniqueId) => { + resourceId = uniqueId; + log.debug('Saving data', data.name); + }) + .then(() => data.put(resourceId.split('.')[0], req.file)) + .then(() => { + // Log the upload + const logInfo = `${req.file.originalname} (${req.file.mimetype}, ${formatBytes(req.file.size)})`; + log.success('File uploaded', logInfo, `uploaded by ${users[req.token] ? users[req.token].username : ''}`); + + // Build the URLs + const resourceUrl = `${getTrueHttp()}${trueDomain}/${resourceId}`; + const thumbnailUrl = `${getTrueHttp()}${trueDomain}/${resourceId}/thumbnail`; + const deleteUrl = `${getTrueHttp()}${trueDomain}/${resourceId}/delete/${req.file.deleteId}`; + + // Send the response + res.type('json').send({ resource: resourceUrl, thumbnail: thumbnailUrl, delete: deleteUrl }) + .on('finish', () => { + log.debug('Upload response sent'); + + // After we have sent the user the response, also send a Webhook to Discord (if headers are present) + if (req.headers['x-ass-webhook-client'] && req.headers['x-ass-webhook-token']) { + + // Build the webhook + const hook = new Webhook(req.headers['x-ass-webhook-url']); + hook.setUsername(req.headers['x-ass-webhook-username'] || 'ass'); + hook.setAvatar(req.headers['x-ass-webhook-avatar'] || ASS_LOGO); + + // Build the embed + const embed = new MessageBuilder() + .setTitle(logInfo) + .setURL(resourceUrl) + .setDescription(`**Size:** \`${formatBytes(req.file.size)}\`\n**[Delete](${deleteUrl})**`) + .setThumbnail(thumbnailUrl) + .setColor(req.file.vibrant) + .setTimestamp(); + + // Send the embed to the webhook, then delete the client after to free resources + log.debug('Sending embed to webhook'); + hook.send(embed) + .then(() => log.debug('Webhook sent')) + .catch((err) => log.error('Webhook error').err(err)); + } + + // Also update the users upload count + if (!users[req.token]) { + const generateUsername = () => generateId('random', 20, null); // skipcq: JS-0074 + let username = generateUsername(); + while (Object.values(users).findIndex((user) => user.username === username) !== -1) // skipcq: JS-0073 + username = generateUsername(); + users[req.token] = { username, count: 0 }; + } + users[req.token].count += 1; + fs.writeJsonSync(path('auth.json'), { users }, { spaces: 4 }); + + log.debug('Upload request flow completed', ''); + }); + }).catch(next); }); module.exports = router; From 31a973196de1a720c97ccd0c47f47e5af6e48423 Mon Sep 17 00:00:00 2001 From: tycrek Date: Fri, 1 Oct 2021 10:47:50 -0600 Subject: [PATCH 2/3] Fix DeepSource issues --- routers/upload.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/routers/upload.js b/routers/upload.js index 1383fd9..4d17d88 100644 --- a/routers/upload.js +++ b/routers/upload.js @@ -65,7 +65,7 @@ router.post('/', (req, res, next) => { req.file.originalname = req.file.originalname.replace(/\s/g, spaceReplace === '!' ? '' : spaceReplace); // Generate a unique resource ID - let resourceId; + let resourceId = ''; // Function to call to generate a fresh ID. Used for multiple attempts in case an ID is already taken const gen = () => generateId(generator, resourceIdSize, req.headers['x-ass-gfycat'] || gfyIdSize, req.file.originalname); @@ -73,9 +73,9 @@ router.post('/', (req, res, next) => { // Called by a promise, this will recursively resolve itself until a unique ID is found // TODO: Add max attempts in case all available ID's are taken function genCheckId(resolve, reject) { - let uniqueId = gen(); + const uniqueId = gen(); data.has(uniqueId) - .then((exists) => (log.debug('ID check', exists ? 'Taken' : 'Available'), exists ? genCheckId(resolve, reject) : resolve(uniqueId))) + .then((exists) => (log.debug('ID check', exists ? 'Taken' : 'Available'), exists ? genCheckId(resolve, reject) : resolve(uniqueId))) // skipcq: JS-0090 .catch(reject); } From 3cb2ca135d9724e85c4ba2451bcb951eadc25eb7 Mon Sep 17 00:00:00 2001 From: tycrek Date: Fri, 1 Oct 2021 10:55:34 -0600 Subject: [PATCH 3/3] Added max attempts to avoid infinite hang --- routers/upload.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/routers/upload.js b/routers/upload.js index 4d17d88..6cd87c7 100644 --- a/routers/upload.js +++ b/routers/upload.js @@ -70,12 +70,21 @@ router.post('/', (req, res, next) => { // Function to call to generate a fresh ID. Used for multiple attempts in case an ID is already taken const gen = () => generateId(generator, resourceIdSize, req.headers['x-ass-gfycat'] || gfyIdSize, req.file.originalname); + // Keeps track of the number of attempts in case all ID's are taken + const attempts = { + count: 0, + max: 50 + }; + // Called by a promise, this will recursively resolve itself until a unique ID is found - // TODO: Add max attempts in case all available ID's are taken function genCheckId(resolve, reject) { const uniqueId = gen(); + attempts.count++; data.has(uniqueId) - .then((exists) => (log.debug('ID check', exists ? 'Taken' : 'Available'), exists ? genCheckId(resolve, reject) : resolve(uniqueId))) // skipcq: JS-0090 + .then((exists) => { + log.debug('ID check', exists ? 'Taken' : 'Available'); + return attempts.count - 1 >= attempts.max ? reject(new Error('No ID\'s remaining')) : exists ? genCheckId(resolve, reject) : resolve(uniqueId); + }) .catch(reject); }