Socket
Socket
Sign inDemoInstall

webpack-dev-middleware

Package Overview
Dependencies
82
Maintainers
4
Versions
113
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 5.3.3 to 5.3.4

16

dist/middleware.js

@@ -17,3 +17,4 @@ "use strict";

setStatusCode,
send
send,
sendError
} = require("./utils/compatibleAPI");

@@ -90,5 +91,7 @@

async function processRequest() {
/** @type {import("./utils/getFilenameFromUrl").Extra} */
const extra = {};
const filename = getFilenameFromUrl(context,
/** @type {string} */
req.url);
req.url, extra);

@@ -100,2 +103,11 @@ if (!filename) {

if (extra.errorCode) {
if (extra.errorCode === 403) {
context.logger.error(`Malicious path "${filename}".`);
}
sendError(req, res, extra.errorCode);
return;
}
let {

@@ -102,0 +114,0 @@ headers

@@ -164,3 +164,122 @@ "use strict";

}
/**
* @template {ServerResponse} Response
* @param {Response} res
*/
function clearHeadersForResponse(res) {
const headers = getHeaderNames(res);
for (let i = 0; i < headers.length; i++) {
res.removeHeader(headers[i]);
}
}
const matchHtmlRegExp = /["'&<>]/;
/**
* @param {string} string raw HTML
* @returns {string} escaped HTML
*/
function escapeHtml(string) {
const str = `${string}`;
const match = matchHtmlRegExp.exec(str);
if (!match) {
return str;
}
let escape;
let html = "";
let index = 0;
let lastIndex = 0;
for (({
index
} = match); index < str.length; index++) {
switch (str.charCodeAt(index)) {
// "
case 34:
escape = "&quot;";
break;
// &
case 38:
escape = "&amp;";
break;
// '
case 39:
escape = "&#39;";
break;
// <
case 60:
escape = "&lt;";
break;
// >
case 62:
escape = "&gt;";
break;
default:
// eslint-disable-next-line no-continue
continue;
}
if (lastIndex !== index) {
html += str.substring(lastIndex, index);
}
lastIndex = index + 1;
html += escape;
}
return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
}
/** @type {Record<number, string>} */
const statuses = {
400: "Bad Request",
403: "Forbidden",
404: "Not Found",
416: "Range Not Satisfiable",
500: "Internal Server Error"
};
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {Request} req response
* @param {Response} res response
* @param {number} status status
* @returns {void}
*/
function sendError(req, res, status) {
const content = statuses[status] || String(status);
const document = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>${escapeHtml(content)}</pre>
</body>
</html>`; // Clear existing headers
clearHeadersForResponse(res); // Send basic response
setStatusCode(res, status);
setHeaderForResponse(res, "Content-Type", "text/html; charset=utf-8");
setHeaderForResponse(res, "Content-Security-Policy", "default-src 'none'");
setHeaderForResponse(res, "X-Content-Type-Options", "nosniff");
const byteLength = Buffer.byteLength(document);
setHeaderForResponse(res, "Content-Length", byteLength);
res.end(document);
}
module.exports = {

@@ -172,3 +291,4 @@ getHeaderNames,

setStatusCode,
send
send,
sendError
};

@@ -19,10 +19,13 @@ "use strict";

/**
* @template T
* @param {Function} fn
* @param {{ cache?: Map<any, any> }} [cache]
* @param {{ cache?: Map<string, { data: T }> } | undefined} cache
* @param {(value: T) => T} callback
* @returns {any}
*/
// @ts-ignore
const mem = (fn, {
cache = new Map()
} = {}) => {
} = {}, callback) => {
/**

@@ -40,3 +43,4 @@ * @param {any} arguments_

const result = fn.apply(void 0, arguments_);
let result = fn.apply(void 0, arguments_);
result = callback(result);
cache.set(key, {

@@ -50,6 +54,33 @@ data: result

return memoized;
};
}; // eslint-disable-next-line no-undefined
const memoizedParse = mem(parse);
const memoizedParse = mem(parse, undefined, value => {
if (value.pathname) {
// eslint-disable-next-line no-param-reassign
value.pathname = decode(value.pathname);
}
return value;
});
const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
/**
* @typedef {Object} Extra
* @property {import("fs").Stats=} stats
* @property {number=} errorCode
*/
/**
* decodeURIComponent.
*
* Allows V8 to only deoptimize this fn instead of all of send().
*
* @param {string} input
* @returns {string}
*/
function decode(input) {
return querystring.unescape(input);
}
/**
* @template {IncomingMessage} Request

@@ -59,6 +90,8 @@ * @template {ServerResponse} Response

* @param {string} url
* @param {Extra=} extra
* @returns {string | undefined}
*/
function getFilenameFromUrl(context, url) {
function getFilenameFromUrl(context, url, extra = {}) {
const {

@@ -68,3 +101,7 @@ options

const paths = getPaths(context);
/** @type {string | undefined} */
let foundFilename;
/** @type {URL} */
let urlObject;

@@ -83,3 +120,6 @@

} of paths) {
/** @type {string | undefined} */
let filename;
/** @type {URL} */
let publicPathObject;

@@ -94,16 +134,33 @@

if (urlObject.pathname && urlObject.pathname.startsWith(publicPathObject.pathname)) {
filename = outputPath; // Strip the `pathname` property from the `publicPath` option from the start of requested url
const {
pathname
} = urlObject;
const {
pathname: publicPathPathname
} = publicPathObject;
if (pathname && pathname.startsWith(publicPathPathname)) {
// Null byte(s)
if (pathname.includes("\0")) {
// eslint-disable-next-line no-param-reassign
extra.errorCode = 400;
return;
} // ".." is malicious
if (UP_PATH_REGEXP.test(path.normalize(`./${pathname}`))) {
// eslint-disable-next-line no-param-reassign
extra.errorCode = 403;
return;
} // Strip the `pathname` property from the `publicPath` option from the start of requested url
// `/complex/foo.js` => `foo.js`
// and add outputPath
// `foo.js` => `/home/user/my-project/dist/foo.js`
const pathname = urlObject.pathname.slice(publicPathObject.pathname.length);
if (pathname) {
filename = path.join(outputPath, querystring.unescape(pathname));
}
filename = path.join(outputPath, pathname.slice(publicPathPathname.length));
let fsStats;
try {
fsStats =
// eslint-disable-next-line no-param-reassign
extra.stats =
/** @type {import("fs").statSync} */

@@ -116,6 +173,6 @@ context.outputFileSystem.statSync(filename);

if (fsStats.isFile()) {
if (extra.stats.isFile()) {
foundFilename = filename;
break;
} else if (fsStats.isDirectory() && (typeof options.index === "undefined" || options.index)) {
} else if (extra.stats.isDirectory() && (typeof options.index === "undefined" || options.index)) {
const indexValue = typeof options.index === "undefined" || typeof options.index === "boolean" ? "index.html" : options.index;

@@ -125,3 +182,4 @@ filename = path.join(filename, indexValue);

try {
fsStats =
// eslint-disable-next-line no-param-reassign
extra.stats =
/** @type {import("fs").statSync} */

@@ -134,3 +192,3 @@ context.outputFileSystem.statSync(filename);

if (fsStats.isFile()) {
if (extra.stats.isFile()) {
foundFilename = filename;

@@ -137,0 +195,0 @@ break;

2

package.json
{
"name": "webpack-dev-middleware",
"version": "5.3.3",
"version": "5.3.4",
"description": "A development middleware for webpack",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -87,1 +87,13 @@ /// <reference types="node" />

): void;
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {Request} req response
* @param {Response} res response
* @param {number} status status
* @returns {void}
*/
export function sendError<
Request_1 extends import("http").IncomingMessage,
Response_1 extends import("../index.js").ServerResponse
>(req: Request_1, res: Response_1, status: number): void;

@@ -8,2 +8,3 @@ /// <reference types="node" />

* @param {string} url
* @param {Extra=} extra
* @returns {string | undefined}

@@ -16,8 +17,13 @@ */

context: import("../index.js").Context<Request_1, Response_1>,
url: string
url: string,
extra?: Extra | undefined
): string | undefined;
declare namespace getFilenameFromUrl {
export { IncomingMessage, ServerResponse };
export { Extra, IncomingMessage, ServerResponse };
}
type Extra = {
stats?: import("fs").Stats | undefined;
errorCode?: number | undefined;
};
type IncomingMessage = import("../index.js").IncomingMessage;
type ServerResponse = import("../index.js").ServerResponse;
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc