From ac6b48254fe0913c79b7475c1734a710ca9ce8d6 Mon Sep 17 00:00:00 2001 From: Lagicrus Date: Sun, 16 Dec 2018 04:34:06 +0000 Subject: [PATCH 1/2] Updated requirements --- .gitignore | 2 ++ package.json | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 20e34d5..d254c3a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ *.bak node_modules npm-debug.log + +/webpages diff --git a/package.json b/package.json index 4c929ea..5bcb2a7 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "dependencies": { "express": "^4.16.2", "multer": "^1.3.0", - "mysql2": "^1.5.1" + "mysql2": "^1.5.1", + "file-type": "^10.6.0", + "read-chunk": "^3.0.0" } } From 80464efedac781f693c26b838bd74f839c5638ca Mon Sep 17 00:00:00 2001 From: Lagicrus Date: Sun, 16 Dec 2018 04:38:15 +0000 Subject: [PATCH 2/2] Added backend functionality to be able to detect and reject invalid images on upload --- .gitignore | 4 ++- jstagram/config.js | 3 ++ jstagram/model-inmemory.js | 6 ++-- jstagram/server.js | 60 ++++++++++++++++++++++++++++++++++++-- package-lock.json | 44 +++++++++++++++++++++++++--- 5 files changed, 107 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index d254c3a..2809ec2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ node_modules npm-debug.log -/webpages +**/webpages +**.idea +**uploads diff --git a/jstagram/config.js b/jstagram/config.js index c0f8088..c95ba9a 100644 --- a/jstagram/config.js +++ b/jstagram/config.js @@ -12,6 +12,9 @@ module.exports.mysql = { // socketPath: '/tmp/mysql.sock', // uncomment this when testing with local non-networked mysql }; +// accepted file types of saving images +module.exports.supported_file_types = ["png", "jpeg", "webm", "gif", "apng"]; + // constants for directories module.exports.webpages = path.join(__dirname, '/webpages/'); module.exports.localimg = module.exports.webpages + 'img/'; diff --git a/jstagram/model-inmemory.js b/jstagram/model-inmemory.js index 430d0be..bedf10f 100644 --- a/jstagram/model-inmemory.js +++ b/jstagram/model-inmemory.js @@ -114,10 +114,12 @@ module.exports.deletePicture = async (id) => { }; -module.exports.uploadPicture = async (reqFile, title) => { +module.exports.uploadPicture = async (reqFile, title, fileExt) => { // move the file where we want it - const fileExt = reqFile.mimetype.split('/')[1] || 'png'; + //const fileExt = reqFile.mimetype.split('/')[1] || 'png'; + //console.log(fileExt); const newFilename = reqFile.filename + '.' + fileExt; + console.log(newFilename); try { await renameAsync(reqFile.path, config.localimg + newFilename); diff --git a/jstagram/server.js b/jstagram/server.js index 3bdbd3d..0568d6e 100644 --- a/jstagram/server.js +++ b/jstagram/server.js @@ -6,6 +6,11 @@ const express = require('express'); const multer = require('multer'); +const readChunk = require("read-chunk"); +const fileType = require("file-type"); +const { promisify } = require('util'); +const fs = require('fs'); +const unlinkAsync = promisify(fs.unlink); const db = require('./model-inmemory'); const config = require('./config'); @@ -35,7 +40,7 @@ app.use('/', (req, res, next) => { console.log(new Date(), req.method, req.url); // DELETE /api/pictures/x - returns http status code only app.get('/api/pictures', sendPictures); -app.post('/api/pictures', uploader.single('picfile'), uploadPicture); +app.post('/api/pictures', uploader.single('picfile'), pictureBackend); app.delete('/api/pictures/:id', deletePicture); @@ -84,9 +89,10 @@ async function deletePicture(req, res) { } } -async function uploadPicture(req, res) { +async function uploadPicture(req, res, fileExt) { try { - const retval = await db.uploadPicture(req.file, req.body.title); + const retval = await db.uploadPicture(req.file, req.body.title, fileExt); + console.log(retval); if (req.accepts('html')) { // browser should go to the listing of pictures res.redirect(303, '/#' + retval.id); @@ -99,6 +105,54 @@ async function uploadPicture(req, res) { } } +async function checkIfPicture(req, res){ + const buffer = readChunk.sync(res.req.file.path, 0, fileType.minimumBytes); + const file_type = fileType(buffer); + try{ + return [config.supported_file_types.includes(file_type.ext), file_type.ext]; + } + catch (TypError){ + return [false, ""]; + } + +} + +async function pictureBackend(req, res){ + if (res.req.file === undefined){ + res.status(415).send({error: "File required"}); + return + } + + if (res.req.body.title === undefined){ + res.status(400).send({error: "Title required"}); + return + } + + if (res.req.file.mimetype === undefined){ + res.status(415).send({error: "Unsupported mimetype"}); + return + } + else if (res.req.file.mimetype.split("/")[0] !== "image"){ + if (res.req.file.mimetype !== "application/octet-stream"){ + res.status(415).send({error: `'${res.req.file.mimetype.split("/")[1]}' is a unsupported mimetype`}); + return + } + console.log(`${res.req.file.mimetype} but ignoring`) + } + + const is_picture = await checkIfPicture(req, res); + console.log(`Picture test: ${is_picture}`); + + if (is_picture[0]){ + await uploadPicture(req, res, is_picture[1]) + } + else{ + await unlinkAsync(res.req.file.path); + res.status(415).send({error: "Unsupported media type"}); + return + } +} + function error(res, msg) { res.sendStatus(500); console.error(msg); diff --git a/package-lock.json b/package-lock.json index bb67da0..3cee517 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1434,6 +1434,11 @@ } } }, + "file-type": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.6.0.tgz", + "integrity": "sha512-GNOg09GC+rZzxetGZFoL7QOnWXRqvWuEdKURIJlr0d6MW107Iwy6voG1PPOrm5meG6ls59WkBmBMAZdVSVajRQ==" + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -3214,8 +3219,7 @@ "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-limit": { "version": "1.3.0", @@ -3337,8 +3341,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, "pinkie": { "version": "2.0.4", @@ -3484,6 +3487,22 @@ } } }, + "read-chunk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-3.0.0.tgz", + "integrity": "sha512-8lBUVPjj9TC5bKLBacB+rpexM03+LWiYbv6ma3BeWmUYXGxqA1WNNgIZHq/iIsCrbFMzPhFbkOqdsyOFRnuoXg==", + "requires": { + "pify": "^4.0.0", + "with-open-file": "^0.1.3" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + } + } + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -4450,6 +4469,23 @@ "string-width": "^2.1.1" } }, + "with-open-file": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/with-open-file/-/with-open-file-0.1.4.tgz", + "integrity": "sha512-BswUwq/x/BYtNFMr4Uw9V+P2uroc9/tcDpZ2RdDHehzwCKJFF1PSIjJmBNfpUE3UQyJmVINRLOW49WTXQMEnvg==", + "requires": { + "p-finally": "^1.0.0", + "p-try": "^2.0.0", + "pify": "^3.0.0" + }, + "dependencies": { + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" + } + } + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",