Break up parseURL middleware

This commit is contained in:
Michael Jackson
2018-05-19 08:34:36 -07:00
parent c6a6b1ffc9
commit 269b756aeb
8 changed files with 103 additions and 107 deletions

View File

@ -1,85 +0,0 @@
const validateNpmPackageName = require("validate-npm-package-name");
const parsePackageURL = require("../utils/parsePackageURL");
const createSearch = require("./utils/createSearch");
const knownQueryParams = {
main: true, // Deprecated, see #63
meta: true,
module: true
};
function isKnownQueryParam(param) {
return !!knownQueryParams[param];
}
function queryIsKnown(query) {
return Object.keys(query).every(isKnownQueryParam);
}
function sanitizeQuery(query) {
const saneQuery = {};
Object.keys(query).forEach(param => {
if (isKnownQueryParam(param)) saneQuery[param] = query[param];
});
return saneQuery;
}
/**
* Parse and validate the URL.
*/
function parseURL(req, res, next) {
// Permanently redirect /_meta/path to /path?meta.
if (req.path.match(/^\/_meta\//)) {
req.query.meta = "";
return res.redirect(301, req.path.substr(6) + createSearch(req.query));
}
// Permanently redirect /path?json => /path?meta
if (req.query.json != null) {
delete req.query.json;
req.query.meta = "";
return res.redirect(301, req.path + createSearch(req.query));
}
// Redirect requests with unknown query params to their equivalents
// with only known params so they can be served from the cache. This
// prevents people using random query params designed to bust the cache.
if (!queryIsKnown(req.query)) {
return res.redirect(302, req.path + createSearch(sanitizeQuery(req.query)));
}
const url = parsePackageURL(req.url);
// Disallow invalid URLs.
if (url == null) {
return res
.status(403)
.type("text")
.send(`Invalid URL: ${req.url}`);
}
const nameErrors = validateNpmPackageName(url.packageName).errors;
// Disallow invalid package names.
if (nameErrors) {
const reason = nameErrors.join(", ");
return res
.status(403)
.type("text")
.send(`Invalid package name "${url.packageName}" (${reason})`);
}
req.packageName = url.packageName;
req.packageVersion = url.packageVersion;
req.packageSpec = `${url.packageName}@${url.packageVersion}`;
req.pathname = url.pathname;
req.filename = url.filename;
req.search = url.search;
req.query = url.query;
next();
}
module.exports = parseURL;

View File

@ -0,0 +1,23 @@
const createSearch = require("./utils/createSearch");
/**
* Redirect old URLs that we no longer support.
*/
function redirectLegacyURLs(req, res, next) {
// Permanently redirect /_meta/path to /path?meta.
if (req.path.match(/^\/_meta\//)) {
req.query.meta = "";
return res.redirect(301, req.path.substr(6) + createSearch(req.query));
}
// Permanently redirect /path?json => /path?meta
if (req.query.json != null) {
delete req.query.json;
req.query.meta = "";
return res.redirect(301, req.path + createSearch(req.query));
}
next();
}
module.exports = redirectLegacyURLs;

View File

@ -0,0 +1,20 @@
const validateNpmPackageName = require("validate-npm-package-name");
/**
* Reject requests for invalid npm package names.
*/
function validatePackageName(req, res, next) {
const nameErrors = validateNpmPackageName(req.packageName).errors;
if (nameErrors) {
const reason = nameErrors.join(", ");
return res
.status(403)
.type("text")
.send(`Invalid package name "${req.packageName}" (${reason})`);
}
next();
}
module.exports = validatePackageName;

View File

@ -1,8 +1,8 @@
const parsePackageURL = require("../utils/parsePackageURL");
/**
* Adds various properties to the request object to do with the
* package/file being requested.
* Parse the URL and add various properties to the request object to
* do with the package/file being requested. Reject invalid URLs.
*/
function validatePackageURL(req, res, next) {
const url = parsePackageURL(req.url);
@ -14,7 +14,7 @@ function validatePackageURL(req, res, next) {
req.packageName = url.packageName;
req.packageVersion = url.packageVersion;
req.packageSpec = `${url.packageName}@${url.packageVersion}`;
req.pathname = url.pathname;
req.pathname = url.pathname; // TODO: remove
req.filename = url.filename;
req.search = url.search;
req.query = url.query;

View File

@ -0,0 +1,34 @@
const createSearch = require("./utils/createSearch");
const knownQueryParams = {
main: true, // Deprecated, see #63
meta: true,
module: true
};
function isKnownQueryParam(param) {
return !!knownQueryParams[param];
}
function sanitizeQuery(originalQuery) {
const query = {};
Object.keys(originalQuery).forEach(param => {
if (isKnownQueryParam(param)) query[param] = originalQuery[param];
});
return query;
}
/**
* Reject URLs with invalid query parameters to increase cache hit rates.
*/
function validateQuery(req, res, next) {
if (!Object.keys(req.query).every(isKnownQueryParam)) {
return res.redirect(302, req.path + createSearch(sanitizeQuery(req.query)));
}
next();
}
module.exports = validateQuery;