Using route parameters

Now that we've written more than 100 lines of code, let's take a step back and look at it. There's certainly space for optimizations. Let's use another awesome feature of Express – route parameters. They allow you to preprocess any route that uses a parameter and check whether it's valid, and do all kinds of stuff such as fetching additional information from a database or another server.

We'll use it to validate our image name for now:

app.param("image", (req, res, next, image) => {
if (!image.match(/.(png|jpg)$/i)) {
return res.status(req.method == "POST" ? 403 : 404).end();
}

req.image = image;
req.localpath = path.join(__dirname, "uploads", req.image);

return next();
});

We start by checking whether the name matches a PNG or JPG image and, if not, we immediately reply with a 403 (for POST requests) or 404 error code (for anything else). If the name is acceptable, we store the image and the expected local path in the request object, for consulting it later.

We can now rewrite our routes, so let's start with our upload route:

app.post("/uploads/:image", bodyparser.raw({
limit : "10mb",
type : "image/*"
}), (req, res) => {
let fd = fs.createWriteStream(req.localpath, {
flags : "w+",
encoding : "binary"
});

fd.end(req.body);

fd.on("close", () => {
res.send({ status : "ok", size: req.body.length });
});
});

Our initial check was completely removed since the parameter was pre-validated. Also, we can now use req.localpath. I also optimized code by removing an unnecessary variable allocation (len) and just using .end() by avoiding .write().

app.head("/uploads/:image", (req, res) => {
fs.access(req.localpath, fs.constants.R_OK , (err) => {
res.status(err ? 404 : 200).end();
});
});

Our image check route looks almost exactly the same. This route had no security, so at least we introduced the pre-validation automatically. It's still far from acceptable for production, but it's a good start:

app.get("/uploads/:image", (req, res) => {
let fd = fs.createReadStream(req.localpath);

fd.on("error", (e) => {
res.status(e.code == "ENOENT" ? 404 : 500).end();
});

res.setHeader("Content-Type", "image/" + path.extname(req.image).substr(1));

fd.pipe(res);
});

Our image download route got a bit smaller. I removed the fancy error to keep the service clean as it will most probably be used by other services and programs, not users directly.

The change we introduced centralizes the image parameter check. We can go a little further and see if the name has special characters that would allow a malicious user to fetch a file from a location other than our local uploads folder. Let's save that for the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset