Multiple File Upload and rename by multer in node.js - node.js

I intended to use multer to upload multiple file and then rename them back to their original names. The below are the sample code:
var express = require('express');
var app = express();
var fs = require("fs");
var multer = require('multer');
app.use(express.static('public'));
var upload = multer({ dest: './upload/' });
app.get('/index.html', function (req, res) {
res.sendFile(__dirname + "/" + "index.html");
})
app.post('/file_upload', upload.array('theFile', 2), function (req, res, next) {
var errorcode = 0;
for (var i = 0; i < req.files.length; i++) {
fs.rename(req.files[i].path, req.files[i].destination + req.files[i].originalname, function (err) {
errorcode = err;
}(i));
}
if (errorcode != 0) {
console.log("errorcode is " + errorcode);
res.sendStatus(500);
return;
} else {
res.json({
message: 'File uploaded successfully',
});
}
})
var server = app.listen(8089, function () {
var host = server.address().address
var port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)
})
I'm testing the above code on a windows server. And my observation is that the files can be uploaded successfully but the fs.rename() keeps returning error "1". And the renamed files in the targeted folder are always 1Kb. It seems that the rename function intends to fetch the files which might be still uploading. I'm not sure whether my understanding is correct. If so, is there a way to determine whether the files have been uploaded completely? Any suggestion for my problem?

Why not use Multer's built-in renaming functionality?
Adapted from the documentation:
var storage = multer.diskStorage({
destination: '/path/to/uploads/folder',
filename: function (req, file, cb) {
// Here we specify the file name to save it as
cb(null, file.originalname);
}
})
// And we can use it for example like this:
app.post('/upload', upload.single('image'), function (req, res, next) {
// req.file is the `image` file
// req.body will hold the text fields, if there were any
})
However, there are a couple things you should be aware of if you take this approach:
The client can send any type of file, with any (potentially incorrect) extension. This is a potential security risk.
If two files are uploaded with the same name, the second file will overwrite the first.
If you serve these files to other users, the security risk greatly increases. An attacker could create a script or HTML page and upload it, possibly giving it a different file name extension. There are several ways it could be run, such as if the user tries to open it in a new tab because an image didn't show up. The full implications of this, and how to deal with it, are a topic of their own.
Finally, make very, very, sure that the user cannot write to a directory other than the uploads folder. What happens when file.originalname is something like ../../index.js? It may be better to convert the file name to a slug.

Related

Renaming files using multer

I have configured multer as;
var storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, '../images/profile');
},
filename: function(req, file, cb) {
cb(null, req.body.username + '.jpeg'); // file does not get renamed
}
});
var upload = multer({storage: storage});
// Route that uses multer
router.post('/auth/signup/upload', upload.single('image'), function(req, res) {
console.log(req.body.username); // contains value
res.send();
});
Although req.body.username has a value, the file does not get renamed.
Wht am i missing here ?
From the multer manual:
Note that req.body might not have been fully populated yet. It depends on the order that the client transmits fields and files to the server.
Sadly, I don't believe there's a nice way to solve this. You could try switching the order of the fields in your HTML form, but this probably won't lead to consistent behaviours across browsers. You could also send the username on the query string instead (i.e. POST the file to http://foo.bar?username=me). You could also manually move the file afterwards, or store the mappings between usernames and files elsewhere.

Combine multer and tinypng API in node

does anyone know how to use tinyPNG's API with multer? The docs seem deceptively simple:
var source = tinify.fromFile("unoptimized.jpg");
source.toFile("optimized.jpg");
though there's no clear indication of where this is meant to go, especially in something as convoluted as this:
var storage = multer.diskStorage(
{
destination: function (req, file, callback) {
callback(null, './uploads');
},
filename: function (req, file, callback) {
//use date to guarantee name uniqueness
callback(null, file.originalname + '-' + Date.now());
}
}
);
//.any() allows multiple file uploads
var upload = multer({ storage : storage}).any()
app.post('/api/photo', function(req,res){
upload(req,res,function(err) {
if(err) {
return res.end("Error uploading file.");
}
res.end("File is uploaded");
});
});
Where am I meant to "intercept" the file uploaded by multer so that I can compress it with tinyPNG?
Thanks in advance for the help!
Use following basic sample that changes uploaded photo/gallery files:
// Import express and multer.
var express = require('express');
var multer = require('multer');
// Setup upload.
var upload = multer({ dest: 'uploads/' });
var multipleFiles = upload.fields([{ name: 'photo', maxCount: 1 },
{ name: 'gallery', maxCount: 8 }]);
// Setup tinify.
var tinify = require("tinify");
tinify.key = "YOUR_API_KEY";
// Get request handler for '/' path.
var app = express();
app.get('/', function (req, res) {
res.setHeader("Content-Type", "text/html");
res.end(
"<form action='/api/photo' method='post' enctype='multipart/form-data'>" +
"<input type='file' name='photo' />" +
"<input type='file' name='gallery' multiple/>" +
"<input type='submit' />" +
"</form>"
);
});
// Upload file handler with '/api/photo' path.
app.post('/api/photo', multipleFiles, function (req, res) {
req.files['gallery'].forEach(function(file) {
// Your logic with tinify here.
var source = tinify.fromFile(file.path);
source.toFile(file.path + "_optimized.jpg");
});
res.end("UPLOAD COMPLETED!");
});
Feel free to change express middleware how you need it, just make sure you use upload.fields and authenticate using tinify.key = "YOUR_API_KEY";
https://github.com/expressjs/multer
https://tinypng.com/developers/reference/nodejs#compressing-images
I recently worked out a similar problem for myself using the tinify package and found the docs to be somewhat lacking.
I have a Vue front end collecting file uploads from the user using vue2dropzone. These are sent to a node / Express back end.
I have a need to compress the file and upload it to an S3 instance without storing on disk. That means using multer memory storage.
As a result there won’t be an ability to use tinify.fromFile() as there is no file stored locally.
In my images middleware:
Const multer = require(“multer”);
const tinify = require("tinify");
tinify.key = "your_key";
exports.singleFile = multer({ storage: multer.memoryStorage() }).fields([{ name: "file", maxCount: 1 }]);
exports.uploadCompImage = async (req, res, next) => {
try {
const fileName = `${req.params.name}${path.extname(req.files.file[0].originalname)}`;
const source = tinify.fromBuffer(req.files.file[0].buffer);
source.store({
service: "s3",
aws_access_key_id: "your_id",
aws_secret_access_key: "your_key
region: "your_region",
headers: {
"Cache-Control": "public"
},
path: `your_bucket/your_folder/${fileName}`
});
return res.status(200).send(`path_to_file/${fileName}`)
} catch (err) {
console.log(err);
next(err);
}
}
Then in my routes file:
Const images = require(“../middleware/images”);
// skipped several lines for brevity
productRouter
.route("/images/:name")
.post(images.singleFile, images.uploadCompImage)
This process creates a multer singleFile upload to memoryStorage, making the file available at req.files.file[0] (req.files[“file”] because I specified “file” as the name in multer fields, loop through this array if uploading multiple).
After setting that up I get the file name, set the source by using tinify to read from req.files.file[0].buffer as a buffer.
Then I set the source to my s3 instance and send back a public link to the file.
Hopefully this answer helps you. I could definitely see altering the process to change where the file goes or even write it to disk by altering the multer options.

Node / Express - Is there a way to access static files from a controller method?

Inside the controller method, is there a way to access static files? I am using mailgun to send emails and need to send an html file in my request that is located in /public.
app.post('/', function(req, res) {
var html = getHtml(); // Need to get the html here to pass to mailgun
}
Is there a way to access the static file dir using some handy device provided by Express? No, not that I'm aware of.
Is there a way to get what you're trying to do done? Sure. Read it with the fs module. I like to use the path module, too, to generate my path to the file.
const path = require("path");
const fs = require("fs");
// Do however you like to build paths.
// I like to use resolve so I always get an absolute path.
const publicPath = path.resolve(__dirname, "public");
const htmlPath = path.join(publicPath, "thefile.html");
app.post('/', function (req, res, next) {
fs.readFile(htmlPath, "utf8", onFile);
function onFile (err, html) {
if (err) return next(err); // assuming you're using an error handler, like you probably should be
mailgunThatStuff(html, mgDone);
}
function mgDone (err) {
if (err) return next(err);
res.end("OK mailgun'd that thing");
}
}
That's a little wordy, maybe. Make sense?
you can try this
(app.js) you just mention static folder in app.js
var express = require('express');
var app = express();
app.use(express.static('public'));
index.html page in local flower in ./public/pages/
app.all('/', function (req, res) {
res.sendFile('index.html', {root: './public/pages/'});
});
try this its working fine

Stream uploaded file to Azure blob storage with Node

Using Express with Node, I can upload a file successfully and pass it to Azure storage in the following block of code.
app.get('/upload', function (req, res) {
res.send(
'<form action="/upload" method="post" enctype="multipart/form-data">' +
'<input type="file" name="snapshot" />' +
'<input type="submit" value="Upload" />' +
'</form>'
);
});
app.post('/upload', function (req, res) {
var path = req.files.snapshot.path;
var bs= azure.createBlobService();
bs.createBlockBlobFromFile('c', 'test.png', path, function (error) { });
res.send("OK");
});
This works just fine, but Express creates a temporary file and stores the image first, then I upload it to Azure from the file. This seems like an inefficient and unnecessary step in the process and I end up having to manage cleanup of the temp file directory.
I should be able to stream the file directly to Azure storage using the blobService.createBlockBlobFromStream method in the Azure SDK, but I am not familiar enough with Node or Express to understand how to access the stream data.
app.post('/upload', function (req, res) {
var stream = /// WHAT GOES HERE ?? ///
var bs= azure.createBlobService();
bs.createBlockBlobFromStream('c', 'test.png', stream, function (error) { });
res.send("OK");
});
I have found the following blog which indicates that there may be a way to do so, and certainly Express is grabbing the stream data and parsing and saving it to the file system as well. http://blog.valeryjacobs.com/index.php/streaming-media-from-url-to-blob-storage/
vjacobs code is actually downloading a file from another site and passing that stream to Azure, so I'm not sure if it can be adapted to work in my situation.
How can I access and pass the uploaded files stream directly to Azure using Node?
SOLUTION (based on discussion with #danielepolencic)
Using Multiparty(npm install multiparty), a fork of Formidable, we can access the multipart data if we disable the bodyparser() middleware from Express (see their notes on doing this for more information). Unlike Formidable, Multiparty will not stream the file to disk unless you tell it to.
app.post('/upload', function (req, res) {
var blobService = azure.createBlobService();
var form = new multiparty.Form();
form.on('part', function(part) {
if (part.filename) {
var size = part.byteCount - part.byteOffset;
var name = part.filename;
blobService.createBlockBlobFromStream('c', name, part, size, function(error) {
if (error) {
res.send({ Grrr: error });
}
});
} else {
form.handlePart(part);
}
});
form.parse(req);
res.send('OK');
});
Props to #danielepolencic for helping to find the solution to this.
As you can read from the connect middleware documentation, bodyparser automagically handles the form for you. In your particular case, it parses the incoming multipart data and store it somewhere else then exposes the saved file in a nice format (i.e. req.files).
Unfortunately, we do not need (and necessary like) black magic primarily because we want to be able to stream the incoming data to azure directly without hitting the disk (i.e. req.pipe(res)). Therefore, we can turn off bodyparser middleware and handle the incoming request ourselves. Under the hood, bodyparser uses node-formidable, so it may be a good idea to reuse it in our implementation.
var express = require('express');
var formidable = require('formidable');
var app = express();
// app.use(express.bodyParser({ uploadDir: 'temp' }));
app.get('/', function(req, res){
res.send('hello world');
});
app.get('/upload', function (req, res) {
res.send(
'<form action="/upload" method="post" enctype="multipart/form-data">' +
'<input type="file" name="snapshot" />' +
'<input type="submit" value="Upload" />' +
'</form>'
);
});
app.post('/upload', function (req, res) {
var bs = azure.createBlobService();
var form = new formidable.IncomingForm();
form.onPart = function(part){
bs.createBlockBlobFromStream('taskcontainer', 'task1', part, 11, function(error){
if(!error){
// Blob uploaded
}
});
};
form.parse(req);
res.send('OK');
});
app.listen(3000);
The core idea is that we can leverage node streams so that we don't need to load in memory the full file before we can send it to azure, but we can transfer it as it comes along. The node-formidable module supports streams, hence piping the stream to azure will achieve our objective.
You can easily test the code locally without hitting azure by replacing the post route with:
app.post('/upload', function (req, res) {
var form = new formidable.IncomingForm();
form.onPart = function(part){
part.pipe(res);
};
form.parse(req);
});
Here, we're simply piping the request from the input to the output. You can read more about bodyParser here.
There are different options for uploading binary data (e.g. images) via Azure Storage SDK for Node, not using multipart.
Based on the Buffer and Stream definitions in Node and manipulating them, these could be handled using almost all the methods for BLOB upload: createWriteStreamToBlockBlob, createBlockBlobFromStream, createBlockBlobFromText.
References could be found here: Upload a binary data from request body to Azure BLOB storage in Node.js [restify]
People having trouble with .createBlockBlobFromStream trying to implement the solutions, note that this method has been changed slightly in newer versions
Old version:
createBlockBlobFromStream(containerName, blobName, part, size, callback)
New version
createBlockBlobFromStream(containerName, blobName, part, size, options, callback)
(if you don't care about options, try an empty array) for the parameter.
Oddly enough, "options" is supposed to be optional, but for whatever reason, mine fails if I leave it out.

Express and uploading files

I am trying to upload files using express and formidable (eventualy forwarding to MongoDB and GridFS). I am starting by creating a form with a field of type file. On the action of that field I use the following route....
exports.addItem = function(req, res, next){
var form = new formidable.IncomingForm(),
files = [],
fields = [];
form
.on('file', function(field, file) {
console.log(field, file);
})
.on('end', function() {
console.log('-> upload done');
});
}
Everything runs fine but when I post I don't see anything in the console and it hangs.
The route looks like the following...
app.post('/item/add', routes.addItem, routes.getPlaylist, routes.index)
Any ideas?
UPDATE
Here is an example of grabbing the file, however, this still doesn't include formidable...
https://gist.github.com/2963261
The reason it is hanging is because you need to call next() to tell Express to continue.
Also use the bodyParser() middleware in express (included by default) to get the files. Something like this:
exports.addItem = function(req, res, next){
if(req.files.length > 0)
{
// process upload
console.log(req.files);
}
next();
}

Resources