From f449444fe215c5b0d23b93b7416ffb88e726d229 Mon Sep 17 00:00:00 2001
From: emilANS
Date: Sun, 15 Mar 2026 00:27:23 -0500
Subject: [PATCH 01/10] Fix issue #5995: cookies encryption added
---
examples/cookies/index.js | 73 ++++--
lib/response.js | 533 +++++++++++++++++++++++---------------
package.json | 1 +
3 files changed, 373 insertions(+), 234 deletions(-)
diff --git a/examples/cookies/index.js b/examples/cookies/index.js
index 0620cb40e45..5a9d7da688f 100644
--- a/examples/cookies/index.js
+++ b/examples/cookies/index.js
@@ -1,53 +1,86 @@
-'use strict'
+"use strict";
/**
* Module dependencies.
*/
-var express = require('../../');
-var app = module.exports = express();
-var logger = require('morgan');
-var cookieParser = require('cookie-parser');
+var express = require("../../");
+var app = (module.exports = express());
+var logger = require("morgan");
+var cookieParser = require("cookie-parser");
+var crypto = require("node:crypto");
// custom log format
-if (process.env.NODE_ENV !== 'test') app.use(logger(':method :url'))
+if (process.env.NODE_ENV !== "test") app.use(logger(":method :url"));
// parses request cookies, populating
// req.cookies and req.signedCookies
// when the secret is passed, used
// for signing the cookies.
-app.use(cookieParser('my secret here'));
+app.use(cookieParser("my secret here"));
+
+app.use(express.json());
// parses x-www-form-urlencoded
-app.use(express.urlencoded())
+app.use(express.urlencoded());
-app.get('/', function(req, res){
+app.get("/", function (req, res) {
if (req.cookies.remember) {
- res.send('Remembered :). Click to forget!.');
+ res.send(
+ 'Remembered :). Click to forget!.
',
+ );
} else {
- res.send('');
+ res.send(
+ '",
+ );
}
});
-app.get('/forget', function(req, res){
- res.clearCookie('remember');
- res.redirect(req.get('Referrer') || '/');
+app.get("/forget", function (req, res) {
+ res.clearCookie("remember");
+ res.redirect(req.get("Referrer") || "/");
});
-app.post('/', function(req, res){
+app.post("/", function (req, res) {
var minute = 60000;
if (req.body && req.body.remember) {
- res.cookie('remember', 1, { maxAge: minute })
+ res.cookie("remember", 1, { maxAge: minute });
}
- res.redirect(req.get('Referrer') || '/');
+ res.redirect(req.get("Referrer") || "/");
+});
+
+app.post("/encryptCookies", function (req, res) {
+ const iv = crypto.randomBytes(16);
+
+ const encryptionAlgorithm = "aes-256-cbc";
+
+ const hashAlgorithm = "sha256";
+
+ res.cookie(
+ "encryptedCookie",
+ "i like to hide my cookies under the sofa",
+ {signed: false},
+ { encryptionAlgorithm, hashAlgorithm, iv },
+ );
+
+ res.send("cookie encrypted");
+});
+
+app.post("/decryptCookies", function (req, res) {
+ const encryptedCookie = req.cookies.encryptedCookie;
+
+ const decryptedCookie = res.decryptCookie(encryptedCookie);
+
+ res.send(decryptedCookie);
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
- console.log('Express started on port 3000');
+ console.log("Express started on port 3000");
}
diff --git a/lib/response.js b/lib/response.js
index f965e539dd2..fe83c48efd6 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -5,48 +5,48 @@
* MIT Licensed
*/
-'use strict';
+"use strict";
/**
* Module dependencies.
* @private
*/
-var contentDisposition = require('content-disposition');
-var createError = require('http-errors')
-var deprecate = require('depd')('express');
-var encodeUrl = require('encodeurl');
-var escapeHtml = require('escape-html');
-var http = require('node:http');
-var onFinished = require('on-finished');
-var mime = require('mime-types')
-var path = require('node:path');
-var pathIsAbsolute = require('node:path').isAbsolute;
-var statuses = require('statuses')
-var sign = require('cookie-signature').sign;
-var normalizeType = require('./utils').normalizeType;
-var normalizeTypes = require('./utils').normalizeTypes;
-var setCharset = require('./utils').setCharset;
-var cookie = require('cookie');
-var send = require('send');
+var contentDisposition = require("content-disposition");
+var createError = require("http-errors");
+var deprecate = require("depd")("express");
+var encodeUrl = require("encodeurl");
+var escapeHtml = require("escape-html");
+var http = require("node:http");
+var onFinished = require("on-finished");
+var mime = require("mime-types");
+var path = require("node:path");
+var pathIsAbsolute = require("node:path").isAbsolute;
+var statuses = require("statuses");
+var sign = require("cookie-signature").sign;
+var normalizeType = require("./utils").normalizeType;
+var normalizeTypes = require("./utils").normalizeTypes;
+var setCharset = require("./utils").setCharset;
+var cookie = require("cookie");
+var send = require("send");
var extname = path.extname;
var resolve = path.resolve;
-var vary = require('vary');
-const { Buffer } = require('node:buffer');
-
+var vary = require("vary");
+var crypto = require("crypto");
+const { Buffer } = require("node:buffer");
/**
* Response prototype.
* @public
*/
-var res = Object.create(http.ServerResponse.prototype)
+var res = Object.create(http.ServerResponse.prototype);
/**
* Module exports.
* @public
*/
-module.exports = res
+module.exports = res;
/**
* Set the HTTP status code for the response.
@@ -64,11 +64,15 @@ module.exports = res
res.status = function status(code) {
// Check if the status code is not an integer
if (!Number.isInteger(code)) {
- throw new TypeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be an integer.`);
+ throw new TypeError(
+ `Invalid status code: ${JSON.stringify(code)}. Status code must be an integer.`,
+ );
}
// Check if the status code is outside of Node's valid range
if (code < 100 || code > 999) {
- throw new RangeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be greater than 99 and less than 1000.`);
+ throw new RangeError(
+ `Invalid status code: ${JSON.stringify(code)}. Status code must be greater than 99 and less than 1000.`,
+ );
}
this.statusCode = code;
@@ -94,19 +98,27 @@ res.status = function status(code) {
* @public
*/
-res.links = function(links) {
- var link = this.get('Link') || '';
- if (link) link += ', ';
- return this.set('Link', link + Object.keys(links).map(function(rel) {
- // Allow multiple links if links[rel] is an array
- if (Array.isArray(links[rel])) {
- return links[rel].map(function (singleLink) {
- return `<${singleLink}>; rel="${rel}"`;
- }).join(', ');
- } else {
- return `<${links[rel]}>; rel="${rel}"`;
- }
- }).join(', '));
+res.links = function (links) {
+ var link = this.get("Link") || "";
+ if (link) link += ", ";
+ return this.set(
+ "Link",
+ link +
+ Object.keys(links)
+ .map(function (rel) {
+ // Allow multiple links if links[rel] is an array
+ if (Array.isArray(links[rel])) {
+ return links[rel]
+ .map(function (singleLink) {
+ return `<${singleLink}>; rel="${rel}"`;
+ })
+ .join(", ");
+ } else {
+ return `<${links[rel]}>; rel="${rel}"`;
+ }
+ })
+ .join(", "),
+ );
};
/**
@@ -132,24 +144,24 @@ res.send = function send(body) {
switch (typeof chunk) {
// string defaulting to html
- case 'string':
- encoding = 'utf8';
- const type = this.get('Content-Type');
+ case "string":
+ encoding = "utf8";
+ const type = this.get("Content-Type");
- if (typeof type === 'string') {
- this.set('Content-Type', setCharset(type, 'utf-8'));
+ if (typeof type === "string") {
+ this.set("Content-Type", setCharset(type, "utf-8"));
} else {
- this.type('html');
+ this.type("html");
}
break;
- case 'boolean':
- case 'number':
- case 'object':
+ case "boolean":
+ case "number":
+ case "object":
if (chunk === null) {
- chunk = '';
+ chunk = "";
} else if (ArrayBuffer.isView(chunk)) {
- if (!this.get('Content-Type')) {
- this.type('bin');
+ if (!this.get("Content-Type")) {
+ this.type("bin");
}
} else {
return this.json(chunk);
@@ -158,33 +170,33 @@ res.send = function send(body) {
}
// determine if ETag should be generated
- var etagFn = app.get('etag fn')
- var generateETag = !this.get('ETag') && typeof etagFn === 'function'
+ var etagFn = app.get("etag fn");
+ var generateETag = !this.get("ETag") && typeof etagFn === "function";
// populate Content-Length
- var len
+ var len;
if (chunk !== undefined) {
if (Buffer.isBuffer(chunk)) {
// get length of Buffer
- len = chunk.length
+ len = chunk.length;
} else if (!generateETag && chunk.length < 1000) {
// just calculate length when no ETag + small chunk
- len = Buffer.byteLength(chunk, encoding)
+ len = Buffer.byteLength(chunk, encoding);
} else {
// convert chunk to Buffer and calculate
- chunk = Buffer.from(chunk, encoding)
+ chunk = Buffer.from(chunk, encoding);
encoding = undefined;
- len = chunk.length
+ len = chunk.length;
}
- this.set('Content-Length', len);
+ this.set("Content-Length", len);
}
// populate ETag
var etag;
if (generateETag && len !== undefined) {
if ((etag = etagFn(chunk, encoding))) {
- this.set('ETag', etag);
+ this.set("ETag", etag);
}
}
@@ -193,20 +205,20 @@ res.send = function send(body) {
// strip irrelevant headers
if (204 === this.statusCode || 304 === this.statusCode) {
- this.removeHeader('Content-Type');
- this.removeHeader('Content-Length');
- this.removeHeader('Transfer-Encoding');
- chunk = '';
+ this.removeHeader("Content-Type");
+ this.removeHeader("Content-Length");
+ this.removeHeader("Transfer-Encoding");
+ chunk = "";
}
// alter headers for 205
if (this.statusCode === 205) {
- this.set('Content-Length', '0')
- this.removeHeader('Transfer-Encoding')
- chunk = ''
+ this.set("Content-Length", "0");
+ this.removeHeader("Transfer-Encoding");
+ chunk = "";
}
- if (req.method === 'HEAD') {
+ if (req.method === "HEAD") {
// skip body for HEAD
this.end();
} else {
@@ -232,14 +244,14 @@ res.send = function send(body) {
res.json = function json(obj) {
// settings
var app = this.app;
- var escape = app.get('json escape')
- var replacer = app.get('json replacer');
- var spaces = app.get('json spaces');
- var body = stringify(obj, replacer, spaces, escape)
+ var escape = app.get("json escape");
+ var replacer = app.get("json replacer");
+ var spaces = app.get("json spaces");
+ var body = stringify(obj, replacer, spaces, escape);
// content-type
- if (!this.get('Content-Type')) {
- this.set('Content-Type', 'application/json');
+ if (!this.get("Content-Type")) {
+ this.set("Content-Type", "application/json");
}
return this.send(body);
@@ -260,16 +272,16 @@ res.json = function json(obj) {
res.jsonp = function jsonp(obj) {
// settings
var app = this.app;
- var escape = app.get('json escape')
- var replacer = app.get('json replacer');
- var spaces = app.get('json spaces');
- var body = stringify(obj, replacer, spaces, escape)
- var callback = this.req.query[app.get('jsonp callback name')];
+ var escape = app.get("json escape");
+ var replacer = app.get("json replacer");
+ var spaces = app.get("json spaces");
+ var body = stringify(obj, replacer, spaces, escape);
+ var callback = this.req.query[app.get("jsonp callback name")];
// content-type
- if (!this.get('Content-Type')) {
- this.set('X-Content-Type-Options', 'nosniff');
- this.set('Content-Type', 'application/json');
+ if (!this.get("Content-Type")) {
+ this.set("X-Content-Type-Options", "nosniff");
+ this.set("Content-Type", "application/json");
}
// fixup callback
@@ -278,26 +290,31 @@ res.jsonp = function jsonp(obj) {
}
// jsonp
- if (typeof callback === 'string' && callback.length !== 0) {
- this.set('X-Content-Type-Options', 'nosniff');
- this.set('Content-Type', 'text/javascript');
+ if (typeof callback === "string" && callback.length !== 0) {
+ this.set("X-Content-Type-Options", "nosniff");
+ this.set("Content-Type", "text/javascript");
// restrict callback charset
- callback = callback.replace(/[^\[\]\w$.]/g, '');
+ callback = callback.replace(/[^\[\]\w$.]/g, "");
if (body === undefined) {
// empty argument
- body = ''
- } else if (typeof body === 'string') {
+ body = "";
+ } else if (typeof body === "string") {
// replace chars not allowed in JavaScript that are in JSON
- body = body
- .replace(/\u2028/g, '\\u2028')
- .replace(/\u2029/g, '\\u2029')
+ body = body.replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
}
// the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
// the typeof check is just to reduce client error noise
- body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
+ body =
+ "/**/ typeof " +
+ callback +
+ " === 'function' && " +
+ callback +
+ "(" +
+ body +
+ ");";
}
return this.send(body);
@@ -319,10 +336,10 @@ res.jsonp = function jsonp(obj) {
*/
res.sendStatus = function sendStatus(statusCode) {
- var body = statuses.message[statusCode] || String(statusCode)
+ var body = statuses.message[statusCode] || String(statusCode);
this.status(statusCode);
- this.type('txt');
+ this.type("txt");
return this.send(body);
};
@@ -376,37 +393,39 @@ res.sendFile = function sendFile(path, options, callback) {
var opts = options || {};
if (!path) {
- throw new TypeError('path argument is required to res.sendFile');
+ throw new TypeError("path argument is required to res.sendFile");
}
- if (typeof path !== 'string') {
- throw new TypeError('path must be a string to res.sendFile')
+ if (typeof path !== "string") {
+ throw new TypeError("path must be a string to res.sendFile");
}
// support function as second arg
- if (typeof options === 'function') {
+ if (typeof options === "function") {
done = options;
opts = {};
}
if (!opts.root && !pathIsAbsolute(path)) {
- throw new TypeError('path must be absolute or specify root to res.sendFile');
+ throw new TypeError(
+ "path must be absolute or specify root to res.sendFile",
+ );
}
// create file stream
var pathname = encodeURI(path);
// wire application etag option to send
- opts.etag = this.app.enabled('etag');
+ opts.etag = this.app.enabled("etag");
var file = send(req, pathname, opts);
// transfer
sendfile(res, file, opts, function (err) {
if (done) return done(err);
- if (err && err.code === 'EISDIR') return next();
+ if (err && err.code === "EISDIR") return next();
// next() all but write errors
- if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
+ if (err && err.code !== "ECONNABORTED" && err.syscall !== "write") {
next(err);
}
});
@@ -430,55 +449,55 @@ res.sendFile = function sendFile(path, options, callback) {
* @public
*/
-res.download = function download (path, filename, options, callback) {
+res.download = function download(path, filename, options, callback) {
var done = callback;
var name = filename;
- var opts = options || null
+ var opts = options || null;
// support function as second or third arg
- if (typeof filename === 'function') {
+ if (typeof filename === "function") {
done = filename;
name = null;
- opts = null
- } else if (typeof options === 'function') {
- done = options
- opts = null
+ opts = null;
+ } else if (typeof options === "function") {
+ done = options;
+ opts = null;
}
// support optional filename, where options may be in it's place
- if (typeof filename === 'object' &&
- (typeof options === 'function' || options === undefined)) {
- name = null
- opts = filename
+ if (
+ typeof filename === "object" &&
+ (typeof options === "function" || options === undefined)
+ ) {
+ name = null;
+ opts = filename;
}
// set Content-Disposition when file is sent
var headers = {
- 'Content-Disposition': contentDisposition(name || path)
+ "Content-Disposition": contentDisposition(name || path),
};
// merge user-provided headers
if (opts && opts.headers) {
- var keys = Object.keys(opts.headers)
+ var keys = Object.keys(opts.headers);
for (var i = 0; i < keys.length; i++) {
- var key = keys[i]
- if (key.toLowerCase() !== 'content-disposition') {
- headers[key] = opts.headers[key]
+ var key = keys[i];
+ if (key.toLowerCase() !== "content-disposition") {
+ headers[key] = opts.headers[key];
}
}
}
// merge user-provided options
- opts = Object.create(opts)
- opts.headers = headers
+ opts = Object.create(opts);
+ opts.headers = headers;
// Resolve the full path for sendFile
- var fullPath = !opts.root
- ? resolve(path)
- : path
+ var fullPath = !opts.root ? resolve(path) : path;
// send file
- return this.sendFile(fullPath, opts, done)
+ return this.sendFile(fullPath, opts, done);
};
/**
@@ -500,13 +519,13 @@ res.download = function download (path, filename, options, callback) {
* @public
*/
-res.contentType =
-res.type = function contentType(type) {
- var ct = type.indexOf('/') === -1
- ? (mime.contentType(type) || 'application/octet-stream')
- : type;
+res.contentType = res.type = function contentType(type) {
+ var ct =
+ type.indexOf("/") === -1
+ ? mime.contentType(type) || "application/octet-stream"
+ : type;
- return this.set('Content-Type', ct);
+ return this.set("Content-Type", ct);
};
/**
@@ -566,28 +585,31 @@ res.type = function contentType(type) {
* @public
*/
-res.format = function(obj){
+res.format = function (obj) {
var req = this.req;
var next = req.next;
- var keys = Object.keys(obj)
- .filter(function (v) { return v !== 'default' })
+ var keys = Object.keys(obj).filter(function (v) {
+ return v !== "default";
+ });
- var key = keys.length > 0
- ? req.accepts(keys)
- : false;
+ var key = keys.length > 0 ? req.accepts(keys) : false;
this.vary("Accept");
if (key) {
- this.set('Content-Type', normalizeType(key).value);
+ this.set("Content-Type", normalizeType(key).value);
obj[key](req, this, next);
} else if (obj.default) {
- obj.default(req, this, next)
+ obj.default(req, this, next);
} else {
- next(createError(406, {
- types: normalizeTypes(keys).map(function (o) { return o.value })
- }))
+ next(
+ createError(406, {
+ types: normalizeTypes(keys).map(function (o) {
+ return o.value;
+ }),
+ }),
+ );
}
return this;
@@ -606,7 +628,7 @@ res.attachment = function attachment(filename) {
this.type(extname(filename));
}
- this.set('Content-Disposition', contentDisposition(filename));
+ this.set("Content-Disposition", contentDisposition(filename));
return this;
};
@@ -632,9 +654,11 @@ res.append = function append(field, val) {
if (prev) {
// concat the new and prev vals
- value = Array.isArray(prev) ? prev.concat(val)
- : Array.isArray(val) ? [prev].concat(val)
- : [prev, val]
+ value = Array.isArray(prev)
+ ? prev.concat(val)
+ : Array.isArray(val)
+ ? [prev].concat(val)
+ : [prev, val];
}
return this.set(field, value);
@@ -661,19 +685,16 @@ res.append = function append(field, val) {
* @public
*/
-res.set =
-res.header = function header(field, val) {
+res.set = res.header = function header(field, val) {
if (arguments.length === 2) {
- var value = Array.isArray(val)
- ? val.map(String)
- : String(val);
+ var value = Array.isArray(val) ? val.map(String) : String(val);
// add charset to content-type
- if (field.toLowerCase() === 'content-type') {
+ if (field.toLowerCase() === "content-type") {
if (Array.isArray(value)) {
- throw new TypeError('Content-Type cannot be set to an Array');
+ throw new TypeError("Content-Type cannot be set to an Array");
}
- value = mime.contentType(value)
+ value = mime.contentType(value);
}
this.setHeader(field, value);
@@ -693,7 +714,7 @@ res.header = function header(field, val) {
* @public
*/
-res.get = function(field){
+res.get = function (field) {
return this.getHeader(field);
};
@@ -708,11 +729,11 @@ res.get = function(field){
res.clearCookie = function clearCookie(name, options) {
// Force cookie expiration by setting expires to the past
- const opts = { path: '/', ...options, expires: new Date(1)};
+ const opts = { path: "/", ...options, expires: new Date(1) };
// ensure maxAge is not passed
- delete opts.maxAge
+ delete opts.maxAge;
- return this.cookie(name, '', opts);
+ return this.cookie(name, "", opts);
};
/**
@@ -732,14 +753,24 @@ res.clearCookie = function clearCookie(name, options) {
* // same as above
* res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
*
+ * Encrypt:
+ * - `encryptionAlgorithm` encryption algorithm you will use
+ * - `hashAlgorithm` hash algorithm you will use
+ * - `iv` Initialization Vector used for encryption is recommended you create a entropied random value
+ *
+ * Examples:
+ * // Create an encrypted cookie
+ * res.cookie('encryptedCookie', 'secret thing to be encrypted', {signed = false}, {encryptionAlgorithm: 'aes-256-cbc', hashAlgorithm: 'sha256', iv: crypto.randomBytes(16) })
+ *
* @param {String} name
* @param {String|Object} value
* @param {Object} [options]
+ * @param {{encryptionAlgorithm: string, hashAlgorithm: string, iv: Buffer}} encrypt
* @return {ServerResponse} for chaining
* @public
*/
-res.cookie = function (name, value, options) {
+res.cookie = function (name, value, options, encrypt) {
var opts = { ...options };
var secret = this.req.secret;
var signed = opts.signed;
@@ -748,32 +779,96 @@ res.cookie = function (name, value, options) {
throw new Error('cookieParser("secret") required for signed cookies');
}
- var val = typeof value === 'object'
- ? 'j:' + JSON.stringify(value)
- : String(value);
+ var val =
+ typeof value === "object" ? "j:" + JSON.stringify(value) : String(value);
+
+ if (signed && !encrypt) {
+ val = "s:" + sign(val, secret);
+ }
+
+ if (!signed && encrypt) {
+ var encryptionAlgorithm = encrypt.encryptionAlgorithm;
+
+ var hashAlgorithm = encrypt.hashAlgorithm;
+
+ var iv = encrypt.iv;
+
+ const plainText = Buffer.from(JSON.stringify(value), "utf8");
+
+ const key = crypto
+ .createHash(hashAlgorithm)
+ .update(String(secret))
+ .digest()
+ .subarray(0, 32);
+
+ let cipher = crypto.createCipheriv(encryptionAlgorithm, key, iv);
+
+ const encryptedText = Buffer.concat([
+ cipher.update(plainText),
+ cipher.final(),
+ ]);
+
+ const encryptedTextObject = {
+ encryptedText: encryptedText,
+ encryptionAlgorithm: encryptionAlgorithm,
+ hashAlgorithm: hashAlgorithm,
+ iv: iv,
+ key: key,
+ };
- if (signed) {
- val = 's:' + sign(val, secret);
+ val = JSON.stringify(encryptedTextObject);
+ }
+
+ if (signed && encrypt) {
+ throw new Error(
+ "You should decide between a signed cookie or a encrypted cookie, you have signed = true and passing to encrypt parameters",
+ );
}
if (opts.maxAge != null) {
- var maxAge = opts.maxAge - 0
+ var maxAge = opts.maxAge - 0;
if (!isNaN(maxAge)) {
- opts.expires = new Date(Date.now() + maxAge)
- opts.maxAge = Math.floor(maxAge / 1000)
+ opts.expires = new Date(Date.now() + maxAge);
+ opts.maxAge = Math.floor(maxAge / 1000);
}
}
if (opts.path == null) {
- opts.path = '/';
+ opts.path = "/";
}
- this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
+ this.append("Set-Cookie", cookie.serialize(name, String(val), opts));
return this;
};
+/**
+ * @param {String} encryptedCookie
+ * @return {String}
+ * @public
+ **/
+
+res.decryptCookie = function decryptCookie(encryptedCookie) {
+ let { encryptedText, encryptionAlgorithm, iv, key } =
+ JSON.parse(encryptedCookie);
+
+ iv = Buffer.from(iv);
+
+ key = Buffer.from(key);
+
+ encryptedText = Buffer.from(encryptedText);
+
+ const decipher = crypto.createDecipheriv(encryptionAlgorithm, key, iv);
+
+ const plainText = Buffer.concat([
+ decipher.update(encryptedText),
+ decipher.final(),
+ ]);
+
+ return plainText.toString("utf8");
+};
+
/**
* Set the location header to `url`.
*
@@ -792,7 +887,7 @@ res.cookie = function (name, value, options) {
*/
res.location = function location(url) {
- return this.set('Location', encodeUrl(url));
+ return this.set("Location", encodeUrl(url));
};
/**
@@ -816,47 +911,54 @@ res.redirect = function redirect(url) {
// allow status / url
if (arguments.length === 2) {
- status = arguments[0]
- address = arguments[1]
+ status = arguments[0];
+ address = arguments[1];
}
if (!address) {
- deprecate('Provide a url argument');
+ deprecate("Provide a url argument");
}
- if (typeof address !== 'string') {
- deprecate('Url must be a string');
+ if (typeof address !== "string") {
+ deprecate("Url must be a string");
}
- if (typeof status !== 'number') {
- deprecate('Status must be a number');
+ if (typeof status !== "number") {
+ deprecate("Status must be a number");
}
// Set location header
- address = this.location(address).get('Location');
+ address = this.location(address).get("Location");
// Support text/{plain,html} by default
this.format({
- text: function(){
- body = statuses.message[status] + '. Redirecting to ' + address
+ text: function () {
+ body = statuses.message[status] + ". Redirecting to " + address;
},
- html: function(){
+ html: function () {
var u = escapeHtml(address);
- body = '' + statuses.message[status] + ''
- + '' + statuses.message[status] + '. Redirecting to ' + u + '
'
+ body =
+ "" +
+ statuses.message[status] +
+ "" +
+ "" +
+ statuses.message[status] +
+ ". Redirecting to " +
+ u +
+ "
";
},
- default: function(){
- body = '';
- }
+ default: function () {
+ body = "";
+ },
});
// Respond
this.status(status);
- this.set('Content-Length', Buffer.byteLength(body));
+ this.set("Content-Length", Buffer.byteLength(body));
- if (this.req.method === 'HEAD') {
+ if (this.req.method === "HEAD") {
this.end();
} else {
this.end(body);
@@ -872,7 +974,7 @@ res.redirect = function redirect(url) {
* @public
*/
-res.vary = function(field){
+res.vary = function (field) {
vary(this, field);
return this;
@@ -899,7 +1001,7 @@ res.render = function render(view, options, callback) {
var self = this;
// support callback function as second arg
- if (typeof options === 'function') {
+ if (typeof options === "function") {
done = options;
opts = {};
}
@@ -908,10 +1010,12 @@ res.render = function render(view, options, callback) {
opts._locals = self.locals;
// default callback to respond
- done = done || function (err, str) {
- if (err) return req.next(err);
- self.send(str);
- };
+ done =
+ done ||
+ function (err, str) {
+ if (err) return req.next(err);
+ self.send(str);
+ };
// render
app.render(view, opts, done);
@@ -927,8 +1031,8 @@ function sendfile(res, file, options, callback) {
if (done) return;
done = true;
- var err = new Error('Request aborted');
- err.code = 'ECONNABORTED';
+ var err = new Error("Request aborted");
+ err.code = "ECONNABORTED";
callback(err);
}
@@ -937,8 +1041,8 @@ function sendfile(res, file, options, callback) {
if (done) return;
done = true;
- var err = new Error('EISDIR, read');
- err.code = 'EISDIR';
+ var err = new Error("EISDIR, read");
+ err.code = "EISDIR";
callback(err);
}
@@ -963,7 +1067,7 @@ function sendfile(res, file, options, callback) {
// finished
function onfinish(err) {
- if (err && err.code === 'ECONNRESET') return onaborted();
+ if (err && err.code === "ECONNRESET") return onaborted();
if (err) return onerror(err);
if (done) return;
@@ -984,16 +1088,16 @@ function sendfile(res, file, options, callback) {
streaming = true;
}
- file.on('directory', ondirectory);
- file.on('end', onend);
- file.on('error', onerror);
- file.on('file', onfile);
- file.on('stream', onstream);
+ file.on("directory", ondirectory);
+ file.on("end", onend);
+ file.on("error", onerror);
+ file.on("file", onfile);
+ file.on("stream", onstream);
onFinished(res, onfinish);
if (options.headers) {
// set headers on successful transfer
- file.on('headers', function headers(res) {
+ file.on("headers", function headers(res) {
var obj = options.headers;
var keys = Object.keys(obj);
@@ -1020,28 +1124,29 @@ function sendfile(res, file, options, callback) {
* @private
*/
-function stringify (value, replacer, spaces, escape) {
+function stringify(value, replacer, spaces, escape) {
// v8 checks arguments.length for optimizing simple call
// https://bugs.chromium.org/p/v8/issues/detail?id=4730
- var json = replacer || spaces
- ? JSON.stringify(value, replacer, spaces)
- : JSON.stringify(value);
+ var json =
+ replacer || spaces
+ ? JSON.stringify(value, replacer, spaces)
+ : JSON.stringify(value);
- if (escape && typeof json === 'string') {
+ if (escape && typeof json === "string") {
json = json.replace(/[<>&]/g, function (c) {
switch (c.charCodeAt(0)) {
case 0x3c:
- return '\\u003c'
+ return "\\u003c";
case 0x3e:
- return '\\u003e'
+ return "\\u003e";
case 0x26:
- return '\\u0026'
+ return "\\u0026";
/* istanbul ignore next: unreachable default */
default:
- return c
+ return c;
}
- })
+ });
}
- return json
+ return json;
}
diff --git a/package.json b/package.json
index aa2afbcb543..4d283e0792e 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,7 @@
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
+ "nodemon": "^3.1.14",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
From 142eba937fb8e25499e16f353824311a7d07b15d Mon Sep 17 00:00:00 2001
From: emilANS
Date: Sun, 15 Mar 2026 00:43:41 -0500
Subject: [PATCH 02/10] fix issue #5995: encrypted cookies added
---
examples/cookies/index.js | 54 ++++----
lib/response.js | 274 +++++++++++++++++++-------------------
2 files changed, 161 insertions(+), 167 deletions(-)
diff --git a/examples/cookies/index.js b/examples/cookies/index.js
index 5a9d7da688f..6eec9fcc955 100644
--- a/examples/cookies/index.js
+++ b/examples/cookies/index.js
@@ -1,77 +1,71 @@
-"use strict";
+'use strict';
/**
* Module dependencies.
*/
-var express = require("../../");
+var express = require('../../');
var app = (module.exports = express());
-var logger = require("morgan");
-var cookieParser = require("cookie-parser");
-var crypto = require("node:crypto");
+var logger = require('morgan');
+var cookieParser = require('cookie-parser');
// custom log format
-if (process.env.NODE_ENV !== "test") app.use(logger(":method :url"));
+if (process.env.NODE_ENV !== 'test') app.use(logger(':method :url'));
// parses request cookies, populating
// req.cookies and req.signedCookies
// when the secret is passed, used
// for signing the cookies.
-app.use(cookieParser("my secret here"));
-
-app.use(express.json());
+app.use(cookieParser('my secret here'));
// parses x-www-form-urlencoded
app.use(express.urlencoded());
-app.get("/", function (req, res) {
+app.get('/', function (req, res) {
if (req.cookies.remember) {
- res.send(
- 'Remembered :). Click to forget!.
',
- );
+ res.send('Remembered :). Click to forget!.');
} else {
res.send(
'",
+ '.
',
);
}
});
-app.get("/forget", function (req, res) {
- res.clearCookie("remember");
- res.redirect(req.get("Referrer") || "/");
+app.get('/forget', function (req, res) {
+ res.clearCookie('remember');
+ res.redirect(req.get('Referrer') || '/');
});
-app.post("/", function (req, res) {
+app.post('/', function (req, res) {
var minute = 60000;
if (req.body && req.body.remember) {
- res.cookie("remember", 1, { maxAge: minute });
+ res.cookie('remember', 1, { maxAge: minute });
}
- res.redirect(req.get("Referrer") || "/");
+ res.redirect(req.get('Referrer') || '/');
});
-app.post("/encryptCookies", function (req, res) {
+app.post('/encryptCookies', function (req, res) {
const iv = crypto.randomBytes(16);
- const encryptionAlgorithm = "aes-256-cbc";
+ const encryptionAlgorithm = 'aes-256-cbc';
- const hashAlgorithm = "sha256";
+ const hashAlgorithm = 'sha256';
res.cookie(
- "encryptedCookie",
- "i like to hide my cookies under the sofa",
- {signed: false},
+ 'encryptedCookie',
+ 'i like to hide my cookies under the sofa',
+ { signed: false },
{ encryptionAlgorithm, hashAlgorithm, iv },
);
- res.send("cookie encrypted");
+ res.send('cookie encrypted');
});
-app.post("/decryptCookies", function (req, res) {
+app.post('/decryptCookies', function (req, res) {
const encryptedCookie = req.cookies.encryptedCookie;
const decryptedCookie = res.decryptCookie(encryptedCookie);
@@ -82,5 +76,5 @@ app.post("/decryptCookies", function (req, res) {
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
- console.log("Express started on port 3000");
+ console.log('Express started on port 3000');
}
diff --git a/lib/response.js b/lib/response.js
index fe83c48efd6..0eab2e77846 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -5,35 +5,35 @@
* MIT Licensed
*/
-"use strict";
+'use strict';
/**
* Module dependencies.
* @private
*/
-var contentDisposition = require("content-disposition");
-var createError = require("http-errors");
-var deprecate = require("depd")("express");
-var encodeUrl = require("encodeurl");
-var escapeHtml = require("escape-html");
-var http = require("node:http");
-var onFinished = require("on-finished");
-var mime = require("mime-types");
-var path = require("node:path");
-var pathIsAbsolute = require("node:path").isAbsolute;
-var statuses = require("statuses");
-var sign = require("cookie-signature").sign;
-var normalizeType = require("./utils").normalizeType;
-var normalizeTypes = require("./utils").normalizeTypes;
-var setCharset = require("./utils").setCharset;
-var cookie = require("cookie");
-var send = require("send");
+var contentDisposition = require('content-disposition');
+var createError = require('http-errors');
+var deprecate = require('depd')('express');
+var encodeUrl = require('encodeurl');
+var escapeHtml = require('escape-html');
+var http = require('node:http');
+var onFinished = require('on-finished');
+var mime = require('mime-types');
+var path = require('node:path');
+var pathIsAbsolute = require('node:path').isAbsolute;
+var statuses = require('statuses');
+var sign = require('cookie-signature').sign;
+var normalizeType = require('./utils').normalizeType;
+var normalizeTypes = require('./utils').normalizeTypes;
+var setCharset = require('./utils').setCharset;
+var cookie = require('cookie');
+var send = require('send');
var extname = path.extname;
var resolve = path.resolve;
-var vary = require("vary");
-var crypto = require("crypto");
-const { Buffer } = require("node:buffer");
+var vary = require('vary');
+var crypto = require('crypto');
+const { Buffer } = require('node:buffer');
/**
* Response prototype.
* @public
@@ -99,10 +99,10 @@ res.status = function status(code) {
*/
res.links = function (links) {
- var link = this.get("Link") || "";
- if (link) link += ", ";
+ var link = this.get('Link') || '';
+ if (link) link += ', ';
return this.set(
- "Link",
+ 'Link',
link +
Object.keys(links)
.map(function (rel) {
@@ -112,12 +112,12 @@ res.links = function (links) {
.map(function (singleLink) {
return `<${singleLink}>; rel="${rel}"`;
})
- .join(", ");
+ .join(', ');
} else {
return `<${links[rel]}>; rel="${rel}"`;
}
})
- .join(", "),
+ .join(', '),
);
};
@@ -144,24 +144,24 @@ res.send = function send(body) {
switch (typeof chunk) {
// string defaulting to html
- case "string":
- encoding = "utf8";
- const type = this.get("Content-Type");
+ case 'string':
+ encoding = 'utf8';
+ const type = this.get('Content-Type');
- if (typeof type === "string") {
- this.set("Content-Type", setCharset(type, "utf-8"));
+ if (typeof type === 'string') {
+ this.set('Content-Type', setCharset(type, 'utf-8'));
} else {
- this.type("html");
+ this.type('html');
}
break;
- case "boolean":
- case "number":
- case "object":
+ case 'boolean':
+ case 'number':
+ case 'object':
if (chunk === null) {
- chunk = "";
+ chunk = '';
} else if (ArrayBuffer.isView(chunk)) {
- if (!this.get("Content-Type")) {
- this.type("bin");
+ if (!this.get('Content-Type')) {
+ this.type('bin');
}
} else {
return this.json(chunk);
@@ -170,8 +170,8 @@ res.send = function send(body) {
}
// determine if ETag should be generated
- var etagFn = app.get("etag fn");
- var generateETag = !this.get("ETag") && typeof etagFn === "function";
+ var etagFn = app.get('etag fn');
+ var generateETag = !this.get('ETag') && typeof etagFn === 'function';
// populate Content-Length
var len;
@@ -189,14 +189,14 @@ res.send = function send(body) {
len = chunk.length;
}
- this.set("Content-Length", len);
+ this.set('Content-Length', len);
}
// populate ETag
var etag;
if (generateETag && len !== undefined) {
if ((etag = etagFn(chunk, encoding))) {
- this.set("ETag", etag);
+ this.set('ETag', etag);
}
}
@@ -205,20 +205,20 @@ res.send = function send(body) {
// strip irrelevant headers
if (204 === this.statusCode || 304 === this.statusCode) {
- this.removeHeader("Content-Type");
- this.removeHeader("Content-Length");
- this.removeHeader("Transfer-Encoding");
- chunk = "";
+ this.removeHeader('Content-Type');
+ this.removeHeader('Content-Length');
+ this.removeHeader('Transfer-Encoding');
+ chunk = '';
}
// alter headers for 205
if (this.statusCode === 205) {
- this.set("Content-Length", "0");
- this.removeHeader("Transfer-Encoding");
- chunk = "";
+ this.set('Content-Length', '0');
+ this.removeHeader('Transfer-Encoding');
+ chunk = '';
}
- if (req.method === "HEAD") {
+ if (req.method === 'HEAD') {
// skip body for HEAD
this.end();
} else {
@@ -244,14 +244,14 @@ res.send = function send(body) {
res.json = function json(obj) {
// settings
var app = this.app;
- var escape = app.get("json escape");
- var replacer = app.get("json replacer");
- var spaces = app.get("json spaces");
+ var escape = app.get('json escape');
+ var replacer = app.get('json replacer');
+ var spaces = app.get('json spaces');
var body = stringify(obj, replacer, spaces, escape);
// content-type
- if (!this.get("Content-Type")) {
- this.set("Content-Type", "application/json");
+ if (!this.get('Content-Type')) {
+ this.set('Content-Type', 'application/json');
}
return this.send(body);
@@ -272,16 +272,16 @@ res.json = function json(obj) {
res.jsonp = function jsonp(obj) {
// settings
var app = this.app;
- var escape = app.get("json escape");
- var replacer = app.get("json replacer");
- var spaces = app.get("json spaces");
+ var escape = app.get('json escape');
+ var replacer = app.get('json replacer');
+ var spaces = app.get('json spaces');
var body = stringify(obj, replacer, spaces, escape);
- var callback = this.req.query[app.get("jsonp callback name")];
+ var callback = this.req.query[app.get('jsonp callback name')];
// content-type
- if (!this.get("Content-Type")) {
- this.set("X-Content-Type-Options", "nosniff");
- this.set("Content-Type", "application/json");
+ if (!this.get('Content-Type')) {
+ this.set('X-Content-Type-Options', 'nosniff');
+ this.set('Content-Type', 'application/json');
}
// fixup callback
@@ -290,31 +290,31 @@ res.jsonp = function jsonp(obj) {
}
// jsonp
- if (typeof callback === "string" && callback.length !== 0) {
- this.set("X-Content-Type-Options", "nosniff");
- this.set("Content-Type", "text/javascript");
+ if (typeof callback === 'string' && callback.length !== 0) {
+ this.set('X-Content-Type-Options', 'nosniff');
+ this.set('Content-Type', 'text/javascript');
// restrict callback charset
- callback = callback.replace(/[^\[\]\w$.]/g, "");
+ callback = callback.replace(/[^\[\]\w$.]/g, '');
if (body === undefined) {
// empty argument
- body = "";
- } else if (typeof body === "string") {
+ body = '';
+ } else if (typeof body === 'string') {
// replace chars not allowed in JavaScript that are in JSON
- body = body.replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
+ body = body.replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
}
// the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
// the typeof check is just to reduce client error noise
body =
- "/**/ typeof " +
+ '/**/ typeof ' +
callback +
" === 'function' && " +
callback +
- "(" +
+ '(' +
body +
- ");";
+ ');';
}
return this.send(body);
@@ -339,7 +339,7 @@ res.sendStatus = function sendStatus(statusCode) {
var body = statuses.message[statusCode] || String(statusCode);
this.status(statusCode);
- this.type("txt");
+ this.type('txt');
return this.send(body);
};
@@ -393,22 +393,22 @@ res.sendFile = function sendFile(path, options, callback) {
var opts = options || {};
if (!path) {
- throw new TypeError("path argument is required to res.sendFile");
+ throw new TypeError('path argument is required to res.sendFile');
}
- if (typeof path !== "string") {
- throw new TypeError("path must be a string to res.sendFile");
+ if (typeof path !== 'string') {
+ throw new TypeError('path must be a string to res.sendFile');
}
// support function as second arg
- if (typeof options === "function") {
+ if (typeof options === 'function') {
done = options;
opts = {};
}
if (!opts.root && !pathIsAbsolute(path)) {
throw new TypeError(
- "path must be absolute or specify root to res.sendFile",
+ 'path must be absolute or specify root to res.sendFile',
);
}
@@ -416,16 +416,16 @@ res.sendFile = function sendFile(path, options, callback) {
var pathname = encodeURI(path);
// wire application etag option to send
- opts.etag = this.app.enabled("etag");
+ opts.etag = this.app.enabled('etag');
var file = send(req, pathname, opts);
// transfer
sendfile(res, file, opts, function (err) {
if (done) return done(err);
- if (err && err.code === "EISDIR") return next();
+ if (err && err.code === 'EISDIR') return next();
// next() all but write errors
- if (err && err.code !== "ECONNABORTED" && err.syscall !== "write") {
+ if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
next(err);
}
});
@@ -455,19 +455,19 @@ res.download = function download(path, filename, options, callback) {
var opts = options || null;
// support function as second or third arg
- if (typeof filename === "function") {
+ if (typeof filename === 'function') {
done = filename;
name = null;
opts = null;
- } else if (typeof options === "function") {
+ } else if (typeof options === 'function') {
done = options;
opts = null;
}
// support optional filename, where options may be in it's place
if (
- typeof filename === "object" &&
- (typeof options === "function" || options === undefined)
+ typeof filename === 'object' &&
+ (typeof options === 'function' || options === undefined)
) {
name = null;
opts = filename;
@@ -475,7 +475,7 @@ res.download = function download(path, filename, options, callback) {
// set Content-Disposition when file is sent
var headers = {
- "Content-Disposition": contentDisposition(name || path),
+ 'Content-Disposition': contentDisposition(name || path),
};
// merge user-provided headers
@@ -483,7 +483,7 @@ res.download = function download(path, filename, options, callback) {
var keys = Object.keys(opts.headers);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
- if (key.toLowerCase() !== "content-disposition") {
+ if (key.toLowerCase() !== 'content-disposition') {
headers[key] = opts.headers[key];
}
}
@@ -521,11 +521,11 @@ res.download = function download(path, filename, options, callback) {
res.contentType = res.type = function contentType(type) {
var ct =
- type.indexOf("/") === -1
- ? mime.contentType(type) || "application/octet-stream"
+ type.indexOf('/') === -1
+ ? mime.contentType(type) || 'application/octet-stream'
: type;
- return this.set("Content-Type", ct);
+ return this.set('Content-Type', ct);
};
/**
@@ -590,15 +590,15 @@ res.format = function (obj) {
var next = req.next;
var keys = Object.keys(obj).filter(function (v) {
- return v !== "default";
+ return v !== 'default';
});
var key = keys.length > 0 ? req.accepts(keys) : false;
- this.vary("Accept");
+ this.vary('Accept');
if (key) {
- this.set("Content-Type", normalizeType(key).value);
+ this.set('Content-Type', normalizeType(key).value);
obj[key](req, this, next);
} else if (obj.default) {
obj.default(req, this, next);
@@ -628,7 +628,7 @@ res.attachment = function attachment(filename) {
this.type(extname(filename));
}
- this.set("Content-Disposition", contentDisposition(filename));
+ this.set('Content-Disposition', contentDisposition(filename));
return this;
};
@@ -690,9 +690,9 @@ res.set = res.header = function header(field, val) {
var value = Array.isArray(val) ? val.map(String) : String(val);
// add charset to content-type
- if (field.toLowerCase() === "content-type") {
+ if (field.toLowerCase() === 'content-type') {
if (Array.isArray(value)) {
- throw new TypeError("Content-Type cannot be set to an Array");
+ throw new TypeError('Content-Type cannot be set to an Array');
}
value = mime.contentType(value);
}
@@ -729,11 +729,11 @@ res.get = function (field) {
res.clearCookie = function clearCookie(name, options) {
// Force cookie expiration by setting expires to the past
- const opts = { path: "/", ...options, expires: new Date(1) };
+ const opts = { path: '/', ...options, expires: new Date(1) };
// ensure maxAge is not passed
delete opts.maxAge;
- return this.cookie(name, "", opts);
+ return this.cookie(name, '', opts);
};
/**
@@ -780,10 +780,10 @@ res.cookie = function (name, value, options, encrypt) {
}
var val =
- typeof value === "object" ? "j:" + JSON.stringify(value) : String(value);
+ typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value);
if (signed && !encrypt) {
- val = "s:" + sign(val, secret);
+ val = 's:' + sign(val, secret);
}
if (!signed && encrypt) {
@@ -793,7 +793,7 @@ res.cookie = function (name, value, options, encrypt) {
var iv = encrypt.iv;
- const plainText = Buffer.from(JSON.stringify(value), "utf8");
+ const plainText = Buffer.from(JSON.stringify(value), 'utf8');
const key = crypto
.createHash(hashAlgorithm)
@@ -821,7 +821,7 @@ res.cookie = function (name, value, options, encrypt) {
if (signed && encrypt) {
throw new Error(
- "You should decide between a signed cookie or a encrypted cookie, you have signed = true and passing to encrypt parameters",
+ 'You should decide between a signed cookie or a encrypted cookie, you have signed = true and passing to encrypt parameters',
);
}
@@ -835,10 +835,10 @@ res.cookie = function (name, value, options, encrypt) {
}
if (opts.path == null) {
- opts.path = "/";
+ opts.path = '/';
}
- this.append("Set-Cookie", cookie.serialize(name, String(val), opts));
+ this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
return this;
};
@@ -866,7 +866,7 @@ res.decryptCookie = function decryptCookie(encryptedCookie) {
decipher.final(),
]);
- return plainText.toString("utf8");
+ return plainText.toString('utf8');
};
/**
@@ -887,7 +887,7 @@ res.decryptCookie = function decryptCookie(encryptedCookie) {
*/
res.location = function location(url) {
- return this.set("Location", encodeUrl(url));
+ return this.set('Location', encodeUrl(url));
};
/**
@@ -916,49 +916,49 @@ res.redirect = function redirect(url) {
}
if (!address) {
- deprecate("Provide a url argument");
+ deprecate('Provide a url argument');
}
- if (typeof address !== "string") {
- deprecate("Url must be a string");
+ if (typeof address !== 'string') {
+ deprecate('Url must be a string');
}
- if (typeof status !== "number") {
- deprecate("Status must be a number");
+ if (typeof status !== 'number') {
+ deprecate('Status must be a number');
}
// Set location header
- address = this.location(address).get("Location");
+ address = this.location(address).get('Location');
// Support text/{plain,html} by default
this.format({
text: function () {
- body = statuses.message[status] + ". Redirecting to " + address;
+ body = statuses.message[status] + '. Redirecting to ' + address;
},
html: function () {
var u = escapeHtml(address);
body =
- "" +
+ '' +
statuses.message[status] +
- "" +
- "" +
+ '' +
+ '
' +
statuses.message[status] +
- ". Redirecting to " +
+ '. Redirecting to ' +
u +
- "
";
+ '';
},
default: function () {
- body = "";
+ body = '';
},
});
// Respond
this.status(status);
- this.set("Content-Length", Buffer.byteLength(body));
+ this.set('Content-Length', Buffer.byteLength(body));
- if (this.req.method === "HEAD") {
+ if (this.req.method === 'HEAD') {
this.end();
} else {
this.end(body);
@@ -1001,7 +1001,7 @@ res.render = function render(view, options, callback) {
var self = this;
// support callback function as second arg
- if (typeof options === "function") {
+ if (typeof options === 'function') {
done = options;
opts = {};
}
@@ -1031,8 +1031,8 @@ function sendfile(res, file, options, callback) {
if (done) return;
done = true;
- var err = new Error("Request aborted");
- err.code = "ECONNABORTED";
+ var err = new Error('Request aborted');
+ err.code = 'ECONNABORTED';
callback(err);
}
@@ -1041,8 +1041,8 @@ function sendfile(res, file, options, callback) {
if (done) return;
done = true;
- var err = new Error("EISDIR, read");
- err.code = "EISDIR";
+ var err = new Error('EISDIR, read');
+ err.code = 'EISDIR';
callback(err);
}
@@ -1067,7 +1067,7 @@ function sendfile(res, file, options, callback) {
// finished
function onfinish(err) {
- if (err && err.code === "ECONNRESET") return onaborted();
+ if (err && err.code === 'ECONNRESET') return onaborted();
if (err) return onerror(err);
if (done) return;
@@ -1088,16 +1088,16 @@ function sendfile(res, file, options, callback) {
streaming = true;
}
- file.on("directory", ondirectory);
- file.on("end", onend);
- file.on("error", onerror);
- file.on("file", onfile);
- file.on("stream", onstream);
+ file.on('directory', ondirectory);
+ file.on('end', onend);
+ file.on('error', onerror);
+ file.on('file', onfile);
+ file.on('stream', onstream);
onFinished(res, onfinish);
if (options.headers) {
// set headers on successful transfer
- file.on("headers", function headers(res) {
+ file.on('headers', function headers(res) {
var obj = options.headers;
var keys = Object.keys(obj);
@@ -1132,15 +1132,15 @@ function stringify(value, replacer, spaces, escape) {
? JSON.stringify(value, replacer, spaces)
: JSON.stringify(value);
- if (escape && typeof json === "string") {
+ if (escape && typeof json === 'string') {
json = json.replace(/[<>&]/g, function (c) {
switch (c.charCodeAt(0)) {
case 0x3c:
- return "\\u003c";
+ return '\\u003c';
case 0x3e:
- return "\\u003e";
+ return '\\u003e';
case 0x26:
- return "\\u0026";
+ return '\\u0026';
/* istanbul ignore next: unreachable default */
default:
return c;
From c636e8dc375a92e6b380ed94076fd30509b3fd9d Mon Sep 17 00:00:00 2001
From: emilANS
Date: Sun, 15 Mar 2026 01:07:09 -0500
Subject: [PATCH 03/10] fix issue #5995
---
examples/cookies/index.js | 26 ++--
lib/response.js | 256 +++++++++++++++++---------------------
2 files changed, 126 insertions(+), 156 deletions(-)
diff --git a/examples/cookies/index.js b/examples/cookies/index.js
index 6eec9fcc955..968d288b448 100644
--- a/examples/cookies/index.js
+++ b/examples/cookies/index.js
@@ -1,16 +1,18 @@
-'use strict';
+'use strict'
/**
* Module dependencies.
*/
var express = require('../../');
-var app = (module.exports = express());
+var app = module.exports = express();
var logger = require('morgan');
var cookieParser = require('cookie-parser');
+var crypto = require('node:crypto')
+
// custom log format
-if (process.env.NODE_ENV !== 'test') app.use(logger(':method :url'));
+if (process.env.NODE_ENV !== 'test') app.use(logger(':method :url'))
// parses request cookies, populating
// req.cookies and req.signedCookies
@@ -19,30 +21,28 @@ if (process.env.NODE_ENV !== 'test') app.use(logger(':method :url'));
app.use(cookieParser('my secret here'));
// parses x-www-form-urlencoded
-app.use(express.urlencoded());
+app.use(express.urlencoded())
-app.get('/', function (req, res) {
+app.get('/', function(req, res){
if (req.cookies.remember) {
res.send('Remembered :). Click to forget!.');
} else {
- res.send(
- '',
- );
+ res.send('');
}
});
-app.get('/forget', function (req, res) {
+app.get('/forget', function(req, res){
res.clearCookie('remember');
res.redirect(req.get('Referrer') || '/');
});
-app.post('/', function (req, res) {
+app.post('/', function(req, res){
var minute = 60000;
if (req.body && req.body.remember) {
- res.cookie('remember', 1, { maxAge: minute });
+ res.cookie('remember', 1, { maxAge: minute })
}
res.redirect(req.get('Referrer') || '/');
diff --git a/lib/response.js b/lib/response.js
index 0eab2e77846..fe4aefda8ab 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -13,16 +13,16 @@
*/
var contentDisposition = require('content-disposition');
-var createError = require('http-errors');
+var createError = require('http-errors')
var deprecate = require('depd')('express');
var encodeUrl = require('encodeurl');
var escapeHtml = require('escape-html');
var http = require('node:http');
var onFinished = require('on-finished');
-var mime = require('mime-types');
+var mime = require('mime-types')
var path = require('node:path');
var pathIsAbsolute = require('node:path').isAbsolute;
-var statuses = require('statuses');
+var statuses = require('statuses')
var sign = require('cookie-signature').sign;
var normalizeType = require('./utils').normalizeType;
var normalizeTypes = require('./utils').normalizeTypes;
@@ -32,21 +32,22 @@ var send = require('send');
var extname = path.extname;
var resolve = path.resolve;
var vary = require('vary');
-var crypto = require('crypto');
+const crypto = require('node:crypto')
const { Buffer } = require('node:buffer');
+
/**
* Response prototype.
* @public
*/
-var res = Object.create(http.ServerResponse.prototype);
+var res = Object.create(http.ServerResponse.prototype)
/**
* Module exports.
* @public
*/
-module.exports = res;
+module.exports = res
/**
* Set the HTTP status code for the response.
@@ -64,15 +65,11 @@ module.exports = res;
res.status = function status(code) {
// Check if the status code is not an integer
if (!Number.isInteger(code)) {
- throw new TypeError(
- `Invalid status code: ${JSON.stringify(code)}. Status code must be an integer.`,
- );
+ throw new TypeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be an integer.`);
}
// Check if the status code is outside of Node's valid range
if (code < 100 || code > 999) {
- throw new RangeError(
- `Invalid status code: ${JSON.stringify(code)}. Status code must be greater than 99 and less than 1000.`,
- );
+ throw new RangeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be greater than 99 and less than 1000.`);
}
this.statusCode = code;
@@ -98,27 +95,19 @@ res.status = function status(code) {
* @public
*/
-res.links = function (links) {
+res.links = function(links) {
var link = this.get('Link') || '';
if (link) link += ', ';
- return this.set(
- 'Link',
- link +
- Object.keys(links)
- .map(function (rel) {
- // Allow multiple links if links[rel] is an array
- if (Array.isArray(links[rel])) {
- return links[rel]
- .map(function (singleLink) {
- return `<${singleLink}>; rel="${rel}"`;
- })
- .join(', ');
- } else {
- return `<${links[rel]}>; rel="${rel}"`;
- }
- })
- .join(', '),
- );
+ return this.set('Link', link + Object.keys(links).map(function(rel) {
+ // Allow multiple links if links[rel] is an array
+ if (Array.isArray(links[rel])) {
+ return links[rel].map(function (singleLink) {
+ return `<${singleLink}>; rel="${rel}"`;
+ }).join(', ');
+ } else {
+ return `<${links[rel]}>; rel="${rel}"`;
+ }
+ }).join(', '));
};
/**
@@ -170,23 +159,23 @@ res.send = function send(body) {
}
// determine if ETag should be generated
- var etagFn = app.get('etag fn');
- var generateETag = !this.get('ETag') && typeof etagFn === 'function';
+ var etagFn = app.get('etag fn')
+ var generateETag = !this.get('ETag') && typeof etagFn === 'function'
// populate Content-Length
- var len;
+ var len
if (chunk !== undefined) {
if (Buffer.isBuffer(chunk)) {
// get length of Buffer
- len = chunk.length;
+ len = chunk.length
} else if (!generateETag && chunk.length < 1000) {
// just calculate length when no ETag + small chunk
- len = Buffer.byteLength(chunk, encoding);
+ len = Buffer.byteLength(chunk, encoding)
} else {
// convert chunk to Buffer and calculate
- chunk = Buffer.from(chunk, encoding);
+ chunk = Buffer.from(chunk, encoding)
encoding = undefined;
- len = chunk.length;
+ len = chunk.length
}
this.set('Content-Length', len);
@@ -213,9 +202,9 @@ res.send = function send(body) {
// alter headers for 205
if (this.statusCode === 205) {
- this.set('Content-Length', '0');
- this.removeHeader('Transfer-Encoding');
- chunk = '';
+ this.set('Content-Length', '0')
+ this.removeHeader('Transfer-Encoding')
+ chunk = ''
}
if (req.method === 'HEAD') {
@@ -244,10 +233,10 @@ res.send = function send(body) {
res.json = function json(obj) {
// settings
var app = this.app;
- var escape = app.get('json escape');
+ var escape = app.get('json escape')
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
- var body = stringify(obj, replacer, spaces, escape);
+ var body = stringify(obj, replacer, spaces, escape)
// content-type
if (!this.get('Content-Type')) {
@@ -272,10 +261,10 @@ res.json = function json(obj) {
res.jsonp = function jsonp(obj) {
// settings
var app = this.app;
- var escape = app.get('json escape');
+ var escape = app.get('json escape')
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
- var body = stringify(obj, replacer, spaces, escape);
+ var body = stringify(obj, replacer, spaces, escape)
var callback = this.req.query[app.get('jsonp callback name')];
// content-type
@@ -299,22 +288,17 @@ res.jsonp = function jsonp(obj) {
if (body === undefined) {
// empty argument
- body = '';
+ body = ''
} else if (typeof body === 'string') {
// replace chars not allowed in JavaScript that are in JSON
- body = body.replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
+ body = body
+ .replace(/\u2028/g, '\\u2028')
+ .replace(/\u2029/g, '\\u2029')
}
// the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
// the typeof check is just to reduce client error noise
- body =
- '/**/ typeof ' +
- callback +
- " === 'function' && " +
- callback +
- '(' +
- body +
- ');';
+ body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
}
return this.send(body);
@@ -336,7 +320,7 @@ res.jsonp = function jsonp(obj) {
*/
res.sendStatus = function sendStatus(statusCode) {
- var body = statuses.message[statusCode] || String(statusCode);
+ var body = statuses.message[statusCode] || String(statusCode)
this.status(statusCode);
this.type('txt');
@@ -397,7 +381,7 @@ res.sendFile = function sendFile(path, options, callback) {
}
if (typeof path !== 'string') {
- throw new TypeError('path must be a string to res.sendFile');
+ throw new TypeError('path must be a string to res.sendFile')
}
// support function as second arg
@@ -407,9 +391,7 @@ res.sendFile = function sendFile(path, options, callback) {
}
if (!opts.root && !pathIsAbsolute(path)) {
- throw new TypeError(
- 'path must be absolute or specify root to res.sendFile',
- );
+ throw new TypeError('path must be absolute or specify root to res.sendFile');
}
// create file stream
@@ -449,55 +431,55 @@ res.sendFile = function sendFile(path, options, callback) {
* @public
*/
-res.download = function download(path, filename, options, callback) {
+res.download = function download (path, filename, options, callback) {
var done = callback;
var name = filename;
- var opts = options || null;
+ var opts = options || null
// support function as second or third arg
if (typeof filename === 'function') {
done = filename;
name = null;
- opts = null;
+ opts = null
} else if (typeof options === 'function') {
- done = options;
- opts = null;
+ done = options
+ opts = null
}
// support optional filename, where options may be in it's place
- if (
- typeof filename === 'object' &&
- (typeof options === 'function' || options === undefined)
- ) {
- name = null;
- opts = filename;
+ if (typeof filename === 'object' &&
+ (typeof options === 'function' || options === undefined)) {
+ name = null
+ opts = filename
}
// set Content-Disposition when file is sent
var headers = {
- 'Content-Disposition': contentDisposition(name || path),
+ 'Content-Disposition': contentDisposition(name || path)
};
// merge user-provided headers
if (opts && opts.headers) {
- var keys = Object.keys(opts.headers);
+ var keys = Object.keys(opts.headers)
for (var i = 0; i < keys.length; i++) {
- var key = keys[i];
+ var key = keys[i]
if (key.toLowerCase() !== 'content-disposition') {
- headers[key] = opts.headers[key];
+ headers[key] = opts.headers[key]
}
}
}
// merge user-provided options
- opts = Object.create(opts);
- opts.headers = headers;
+ opts = Object.create(opts)
+ opts.headers = headers
// Resolve the full path for sendFile
- var fullPath = !opts.root ? resolve(path) : path;
+ var fullPath = !opts.root
+ ? resolve(path)
+ : path
// send file
- return this.sendFile(fullPath, opts, done);
+ return this.sendFile(fullPath, opts, done)
};
/**
@@ -519,11 +501,11 @@ res.download = function download(path, filename, options, callback) {
* @public
*/
-res.contentType = res.type = function contentType(type) {
- var ct =
- type.indexOf('/') === -1
- ? mime.contentType(type) || 'application/octet-stream'
- : type;
+res.contentType =
+res.type = function contentType(type) {
+ var ct = type.indexOf('/') === -1
+ ? (mime.contentType(type) || 'application/octet-stream')
+ : type;
return this.set('Content-Type', ct);
};
@@ -585,31 +567,28 @@ res.contentType = res.type = function contentType(type) {
* @public
*/
-res.format = function (obj) {
+res.format = function(obj){
var req = this.req;
var next = req.next;
- var keys = Object.keys(obj).filter(function (v) {
- return v !== 'default';
- });
+ var keys = Object.keys(obj)
+ .filter(function (v) { return v !== 'default' })
- var key = keys.length > 0 ? req.accepts(keys) : false;
+ var key = keys.length > 0
+ ? req.accepts(keys)
+ : false;
- this.vary('Accept');
+ this.vary("Accept");
if (key) {
this.set('Content-Type', normalizeType(key).value);
obj[key](req, this, next);
} else if (obj.default) {
- obj.default(req, this, next);
+ obj.default(req, this, next)
} else {
- next(
- createError(406, {
- types: normalizeTypes(keys).map(function (o) {
- return o.value;
- }),
- }),
- );
+ next(createError(406, {
+ types: normalizeTypes(keys).map(function (o) { return o.value })
+ }))
}
return this;
@@ -654,11 +633,9 @@ res.append = function append(field, val) {
if (prev) {
// concat the new and prev vals
- value = Array.isArray(prev)
- ? prev.concat(val)
- : Array.isArray(val)
- ? [prev].concat(val)
- : [prev, val];
+ value = Array.isArray(prev) ? prev.concat(val)
+ : Array.isArray(val) ? [prev].concat(val)
+ : [prev, val]
}
return this.set(field, value);
@@ -685,16 +662,19 @@ res.append = function append(field, val) {
* @public
*/
-res.set = res.header = function header(field, val) {
+res.set =
+res.header = function header(field, val) {
if (arguments.length === 2) {
- var value = Array.isArray(val) ? val.map(String) : String(val);
+ var value = Array.isArray(val)
+ ? val.map(String)
+ : String(val);
// add charset to content-type
if (field.toLowerCase() === 'content-type') {
if (Array.isArray(value)) {
throw new TypeError('Content-Type cannot be set to an Array');
}
- value = mime.contentType(value);
+ value = mime.contentType(value)
}
this.setHeader(field, value);
@@ -714,7 +694,7 @@ res.set = res.header = function header(field, val) {
* @public
*/
-res.get = function (field) {
+res.get = function(field){
return this.getHeader(field);
};
@@ -729,9 +709,9 @@ res.get = function (field) {
res.clearCookie = function clearCookie(name, options) {
// Force cookie expiration by setting expires to the past
- const opts = { path: '/', ...options, expires: new Date(1) };
+ const opts = { path: '/', ...options, expires: new Date(1)};
// ensure maxAge is not passed
- delete opts.maxAge;
+ delete opts.maxAge
return this.cookie(name, '', opts);
};
@@ -911,8 +891,8 @@ res.redirect = function redirect(url) {
// allow status / url
if (arguments.length === 2) {
- status = arguments[0];
- address = arguments[1];
+ status = arguments[0]
+ address = arguments[1]
}
if (!address) {
@@ -932,26 +912,19 @@ res.redirect = function redirect(url) {
// Support text/{plain,html} by default
this.format({
- text: function () {
- body = statuses.message[status] + '. Redirecting to ' + address;
+ text: function(){
+ body = statuses.message[status] + '. Redirecting to ' + address
},
- html: function () {
+ html: function(){
var u = escapeHtml(address);
- body =
- '' +
- statuses.message[status] +
- '' +
- '' +
- statuses.message[status] +
- '. Redirecting to ' +
- u +
- '
';
+ body = '' + statuses.message[status] + ''
+ + '' + statuses.message[status] + '. Redirecting to ' + u + '
'
},
- default: function () {
+ default: function(){
body = '';
- },
+ }
});
// Respond
@@ -974,7 +947,7 @@ res.redirect = function redirect(url) {
* @public
*/
-res.vary = function (field) {
+res.vary = function(field){
vary(this, field);
return this;
@@ -1010,12 +983,10 @@ res.render = function render(view, options, callback) {
opts._locals = self.locals;
// default callback to respond
- done =
- done ||
- function (err, str) {
- if (err) return req.next(err);
- self.send(str);
- };
+ done = done || function (err, str) {
+ if (err) return req.next(err);
+ self.send(str);
+ };
// render
app.render(view, opts, done);
@@ -1124,29 +1095,28 @@ function sendfile(res, file, options, callback) {
* @private
*/
-function stringify(value, replacer, spaces, escape) {
+function stringify (value, replacer, spaces, escape) {
// v8 checks arguments.length for optimizing simple call
// https://bugs.chromium.org/p/v8/issues/detail?id=4730
- var json =
- replacer || spaces
- ? JSON.stringify(value, replacer, spaces)
- : JSON.stringify(value);
+ var json = replacer || spaces
+ ? JSON.stringify(value, replacer, spaces)
+ : JSON.stringify(value);
if (escape && typeof json === 'string') {
json = json.replace(/[<>&]/g, function (c) {
switch (c.charCodeAt(0)) {
case 0x3c:
- return '\\u003c';
+ return '\\u003c'
case 0x3e:
- return '\\u003e';
+ return '\\u003e'
case 0x26:
- return '\\u0026';
+ return '\\u0026'
/* istanbul ignore next: unreachable default */
default:
- return c;
+ return c
}
- });
+ })
}
- return json;
+ return json
}
From 56cb40bf494a0bc93f10efa65e76134604db28a1 Mon Sep 17 00:00:00 2001
From: emilANS
Date: Mon, 16 Mar 2026 14:30:47 -0500
Subject: [PATCH 04/10] Added encryption to cookies
---
examples/cookies/index.js | 12 ++++++-----
lib/response.js | 42 ++++++++++++++++++++-------------------
2 files changed, 29 insertions(+), 25 deletions(-)
diff --git a/examples/cookies/index.js b/examples/cookies/index.js
index 968d288b448..084636f173c 100644
--- a/examples/cookies/index.js
+++ b/examples/cookies/index.js
@@ -8,8 +8,6 @@ var express = require('../../');
var app = module.exports = express();
var logger = require('morgan');
var cookieParser = require('cookie-parser');
-var crypto = require('node:crypto')
-
// custom log format
if (process.env.NODE_ENV !== 'test') app.use(logger(':method :url'))
@@ -51,15 +49,19 @@ app.post('/', function(req, res){
app.post('/encryptCookies', function (req, res) {
const iv = crypto.randomBytes(16);
- const encryptionAlgorithm = 'aes-256-cbc';
+ const encryptionAlgorithm = 'aes-256-gcm';
const hashAlgorithm = 'sha256';
+ console.log('key', key);
+
+ console.log('iv', iv);
+
res.cookie(
'encryptedCookie',
'i like to hide my cookies under the sofa',
{ signed: false },
- { encryptionAlgorithm, hashAlgorithm, iv },
+ { encryptionAlgorithm, hashAlgorithm, iv, key, authTag: true },
);
res.send('cookie encrypted');
@@ -68,7 +70,7 @@ app.post('/encryptCookies', function (req, res) {
app.post('/decryptCookies', function (req, res) {
const encryptedCookie = req.cookies.encryptedCookie;
- const decryptedCookie = res.decryptCookie(encryptedCookie);
+ const decryptedCookie = res.decryptCookie(encryptedCookie, key);
res.send(decryptedCookie);
});
diff --git a/lib/response.js b/lib/response.js
index fe4aefda8ab..803c9942392 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -32,7 +32,6 @@ var send = require('send');
var extname = path.extname;
var resolve = path.resolve;
var vary = require('vary');
-const crypto = require('node:crypto')
const { Buffer } = require('node:buffer');
/**
@@ -737,6 +736,7 @@ res.clearCookie = function clearCookie(name, options) {
* - `encryptionAlgorithm` encryption algorithm you will use
* - `hashAlgorithm` hash algorithm you will use
* - `iv` Initialization Vector used for encryption is recommended you create a entropied random value
+ * - `key` Key for encrypting and decrypting the encrypted cookie
*
* Examples:
* // Create an encrypted cookie
@@ -745,7 +745,7 @@ res.clearCookie = function clearCookie(name, options) {
* @param {String} name
* @param {String|Object} value
* @param {Object} [options]
- * @param {{encryptionAlgorithm: string, hashAlgorithm: string, iv: Buffer}} encrypt
+ * @param {{encryptionAlgorithm: String, authTag: Boolean, iv: Buffer, key: String}} encrypt
* @return {ServerResponse} for chaining
* @public
*/
@@ -767,19 +767,15 @@ res.cookie = function (name, value, options, encrypt) {
}
if (!signed && encrypt) {
- var encryptionAlgorithm = encrypt.encryptionAlgorithm;
+ const encryptionAlgorithm = encrypt.encryptionAlgorithm;
- var hashAlgorithm = encrypt.hashAlgorithm;
+ const iv = encrypt.iv;
- var iv = encrypt.iv;
+ const key = encrypt.key;
const plainText = Buffer.from(JSON.stringify(value), 'utf8');
- const key = crypto
- .createHash(hashAlgorithm)
- .update(String(secret))
- .digest()
- .subarray(0, 32);
+ const authTag = encrypt.authTag;
let cipher = crypto.createCipheriv(encryptionAlgorithm, key, iv);
@@ -789,13 +785,17 @@ res.cookie = function (name, value, options, encrypt) {
]);
const encryptedTextObject = {
- encryptedText: encryptedText,
+ encryptedText: encryptedText.toString('base64'),
encryptionAlgorithm: encryptionAlgorithm,
- hashAlgorithm: hashAlgorithm,
- iv: iv,
- key: key,
+ iv: iv.toString('base64'),
};
+ if (authTag) {
+ encryptedTextObject['authTag'] = cipher.getAuthTag().toString('base64');
+ }
+
+ console.log(encryptedTextObject);
+
val = JSON.stringify(encryptedTextObject);
}
@@ -829,18 +829,20 @@ res.cookie = function (name, value, options, encrypt) {
* @public
**/
-res.decryptCookie = function decryptCookie(encryptedCookie) {
- let { encryptedText, encryptionAlgorithm, iv, key } =
+res.decryptCookie = function decryptCookie(encryptedCookie, key) {
+ let { encryptedText, encryptionAlgorithm, iv, authTag } =
JSON.parse(encryptedCookie);
- iv = Buffer.from(iv);
+ iv = Buffer.from(iv, 'base64');
- key = Buffer.from(key);
-
- encryptedText = Buffer.from(encryptedText);
+ encryptedText = Buffer.from(encryptedText, 'base64');
const decipher = crypto.createDecipheriv(encryptionAlgorithm, key, iv);
+ if (authTag) {
+ decipher.setAuthTag(Buffer.from(authTag, 'base64'));
+ }
+
const plainText = Buffer.concat([
decipher.update(encryptedText),
decipher.final(),
From 6dea16df7384e6d1162640764fd1f003b041398f Mon Sep 17 00:00:00 2001
From: emilANS
Date: Mon, 16 Mar 2026 14:32:11 -0500
Subject: [PATCH 05/10] Added encryption feature to cookies
---
examples/cookies/index.js | 4 ----
lib/response.js | 2 --
2 files changed, 6 deletions(-)
diff --git a/examples/cookies/index.js b/examples/cookies/index.js
index 084636f173c..5f941eb0275 100644
--- a/examples/cookies/index.js
+++ b/examples/cookies/index.js
@@ -53,10 +53,6 @@ app.post('/encryptCookies', function (req, res) {
const hashAlgorithm = 'sha256';
- console.log('key', key);
-
- console.log('iv', iv);
-
res.cookie(
'encryptedCookie',
'i like to hide my cookies under the sofa',
diff --git a/lib/response.js b/lib/response.js
index 803c9942392..16c88bb5595 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -794,8 +794,6 @@ res.cookie = function (name, value, options, encrypt) {
encryptedTextObject['authTag'] = cipher.getAuthTag().toString('base64');
}
- console.log(encryptedTextObject);
-
val = JSON.stringify(encryptedTextObject);
}
From eef0588c2097027a2a14b13aeb93da3591b414d8 Mon Sep 17 00:00:00 2001
From: emilANS
Date: Mon, 16 Mar 2026 14:39:02 -0500
Subject: [PATCH 06/10] Added encryption modules
---
examples/cookies/index.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/examples/cookies/index.js b/examples/cookies/index.js
index 5f941eb0275..0eb8bbf0d61 100644
--- a/examples/cookies/index.js
+++ b/examples/cookies/index.js
@@ -8,6 +8,7 @@ var express = require('../../');
var app = module.exports = express();
var logger = require('morgan');
var cookieParser = require('cookie-parser');
+var crypto = require("node:crypto")
// custom log format
if (process.env.NODE_ENV !== 'test') app.use(logger(':method :url'))
@@ -46,6 +47,8 @@ app.post('/', function(req, res){
res.redirect(req.get('Referrer') || '/');
});
+const key = crypto.randomBytes(32)
+
app.post('/encryptCookies', function (req, res) {
const iv = crypto.randomBytes(16);
From 4b3c42a5da79035eebe7958d577c5c48b11c72fa Mon Sep 17 00:00:00 2001
From: emilANS
Date: Mon, 16 Mar 2026 14:42:02 -0500
Subject: [PATCH 07/10] Added crypto module import
---
lib/response.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lib/response.js b/lib/response.js
index 16c88bb5595..5405ac01035 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -32,6 +32,7 @@ var send = require('send');
var extname = path.extname;
var resolve = path.resolve;
var vary = require('vary');
+var crypto = require("node:crypto")
const { Buffer } = require('node:buffer');
/**
@@ -734,13 +735,13 @@ res.clearCookie = function clearCookie(name, options) {
*
* Encrypt:
* - `encryptionAlgorithm` encryption algorithm you will use
- * - `hashAlgorithm` hash algorithm you will use
+ * - `authTag` do the encryption algorithm supports authTag
* - `iv` Initialization Vector used for encryption is recommended you create a entropied random value
* - `key` Key for encrypting and decrypting the encrypted cookie
*
* Examples:
* // Create an encrypted cookie
- * res.cookie('encryptedCookie', 'secret thing to be encrypted', {signed = false}, {encryptionAlgorithm: 'aes-256-cbc', hashAlgorithm: 'sha256', iv: crypto.randomBytes(16) })
+ * res.cookie('encryptedCookie', 'secret thing to be encrypted', {signed = false}, {encryptionAlgorithm: 'aes-256-cbc', authTag: true iv: crypto.randomBytes(16), key: crypto.randomBytes() })
*
* @param {String} name
* @param {String|Object} value
From e762dac7dde16ea747736a070a0528b198af677e Mon Sep 17 00:00:00 2001
From: emilANS
Date: Wed, 18 Mar 2026 16:29:40 -0500
Subject: [PATCH 08/10] fix issue #5995
---
examples/cookies/index.js | 28 ---
examples/encrypted-cookies/index.js | 72 +++++++
lib/response.js | 298 +++++++++++++++-------------
test/res.cookie.js | 67 +++++++
4 files changed, 298 insertions(+), 167 deletions(-)
create mode 100644 examples/encrypted-cookies/index.js
diff --git a/examples/cookies/index.js b/examples/cookies/index.js
index 0eb8bbf0d61..0620cb40e45 100644
--- a/examples/cookies/index.js
+++ b/examples/cookies/index.js
@@ -8,7 +8,6 @@ var express = require('../../');
var app = module.exports = express();
var logger = require('morgan');
var cookieParser = require('cookie-parser');
-var crypto = require("node:crypto")
// custom log format
if (process.env.NODE_ENV !== 'test') app.use(logger(':method :url'))
@@ -47,33 +46,6 @@ app.post('/', function(req, res){
res.redirect(req.get('Referrer') || '/');
});
-const key = crypto.randomBytes(32)
-
-app.post('/encryptCookies', function (req, res) {
- const iv = crypto.randomBytes(16);
-
- const encryptionAlgorithm = 'aes-256-gcm';
-
- const hashAlgorithm = 'sha256';
-
- res.cookie(
- 'encryptedCookie',
- 'i like to hide my cookies under the sofa',
- { signed: false },
- { encryptionAlgorithm, hashAlgorithm, iv, key, authTag: true },
- );
-
- res.send('cookie encrypted');
-});
-
-app.post('/decryptCookies', function (req, res) {
- const encryptedCookie = req.cookies.encryptedCookie;
-
- const decryptedCookie = res.decryptCookie(encryptedCookie, key);
-
- res.send(decryptedCookie);
-});
-
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
diff --git a/examples/encrypted-cookies/index.js b/examples/encrypted-cookies/index.js
new file mode 100644
index 00000000000..14a0cc3ee2c
--- /dev/null
+++ b/examples/encrypted-cookies/index.js
@@ -0,0 +1,72 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+
+var express = require('../../');
+var app = (module.exports = express());
+var logger = require('morgan');
+var cookieParser = require('cookie-parser');
+var crypto = require('node:crypto');
+
+// custom log format
+if (process.env.NODE_ENV !== 'test') app.use(logger(':method :url'));
+
+// parses request cookies, populating
+// req.cookies and req.signedCookies
+// when the secret is passed, used
+// for signing the cookies.
+app.use(cookieParser('my secret here'));
+
+// parses x-www-form-urlencoded
+app.use(express.urlencoded());
+
+app.get('/', function (req, res) {
+ if (req.signedCookies.encryptedCookie) {
+ res.send('Remembered and encrypted :). Click to forget! Click here to decrypt.' +
+ ''
+ );
+ } else {
+ res.send(
+ '',
+ );
+ }
+});
+
+app.get('/forget', function (req, res) {
+ res.clearCookie('encryptedCookie');
+ res.redirect(req.get('Referrer') || '/');
+});
+
+const key = crypto.randomBytes(32);
+
+app.post('/', function (req, res) {
+ var minute = 60000;
+
+ if (req.body && req.body.encryptedCookie) {
+ res.cookie(
+ 'encryptedCookie',
+ 'I like to hide by cookies under the sofa now',
+ { signed: true, maxAge: minute },
+ { key },
+ );
+ }
+ res.redirect(req.get('Referrer') || '/');
+});
+
+app.post('/decryptCookies', function (req, res) {
+ const encryptedCookie = req.signedCookies.encryptedCookie;
+
+ const decryptedCookie = res.decryptCookie(encryptedCookie, key);
+
+ res.send(decryptedCookie + '
Go back to main page');
+});
+
+/* istanbul ignore next */
+if (!module.parent) {
+ app.listen(3000);
+ console.log('Express started on port 3000');
+}
diff --git a/lib/response.js b/lib/response.js
index 5405ac01035..4e76468c607 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -13,16 +13,16 @@
*/
var contentDisposition = require('content-disposition');
-var createError = require('http-errors')
+var createError = require('http-errors');
var deprecate = require('depd')('express');
var encodeUrl = require('encodeurl');
var escapeHtml = require('escape-html');
var http = require('node:http');
var onFinished = require('on-finished');
-var mime = require('mime-types')
+var mime = require('mime-types');
var path = require('node:path');
var pathIsAbsolute = require('node:path').isAbsolute;
-var statuses = require('statuses')
+var statuses = require('statuses');
var sign = require('cookie-signature').sign;
var normalizeType = require('./utils').normalizeType;
var normalizeTypes = require('./utils').normalizeTypes;
@@ -32,22 +32,24 @@ var send = require('send');
var extname = path.extname;
var resolve = path.resolve;
var vary = require('vary');
-var crypto = require("node:crypto")
+var crypto = require('node:crypto');
const { Buffer } = require('node:buffer');
+const encryptionAlgorithm = 'aes-256-gcm';
+
/**
* Response prototype.
* @public
*/
-var res = Object.create(http.ServerResponse.prototype)
+var res = Object.create(http.ServerResponse.prototype);
/**
* Module exports.
* @public
*/
-module.exports = res
+module.exports = res;
/**
* Set the HTTP status code for the response.
@@ -65,11 +67,15 @@ module.exports = res
res.status = function status(code) {
// Check if the status code is not an integer
if (!Number.isInteger(code)) {
- throw new TypeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be an integer.`);
+ throw new TypeError(
+ `Invalid status code: ${JSON.stringify(code)}. Status code must be an integer.`,
+ );
}
// Check if the status code is outside of Node's valid range
if (code < 100 || code > 999) {
- throw new RangeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be greater than 99 and less than 1000.`);
+ throw new RangeError(
+ `Invalid status code: ${JSON.stringify(code)}. Status code must be greater than 99 and less than 1000.`,
+ );
}
this.statusCode = code;
@@ -95,19 +101,27 @@ res.status = function status(code) {
* @public
*/
-res.links = function(links) {
+res.links = function (links) {
var link = this.get('Link') || '';
if (link) link += ', ';
- return this.set('Link', link + Object.keys(links).map(function(rel) {
- // Allow multiple links if links[rel] is an array
- if (Array.isArray(links[rel])) {
- return links[rel].map(function (singleLink) {
- return `<${singleLink}>; rel="${rel}"`;
- }).join(', ');
- } else {
- return `<${links[rel]}>; rel="${rel}"`;
- }
- }).join(', '));
+ return this.set(
+ 'Link',
+ link +
+ Object.keys(links)
+ .map(function (rel) {
+ // Allow multiple links if links[rel] is an array
+ if (Array.isArray(links[rel])) {
+ return links[rel]
+ .map(function (singleLink) {
+ return `<${singleLink}>; rel="${rel}"`;
+ })
+ .join(', ');
+ } else {
+ return `<${links[rel]}>; rel="${rel}"`;
+ }
+ })
+ .join(', '),
+ );
};
/**
@@ -159,23 +173,23 @@ res.send = function send(body) {
}
// determine if ETag should be generated
- var etagFn = app.get('etag fn')
- var generateETag = !this.get('ETag') && typeof etagFn === 'function'
+ var etagFn = app.get('etag fn');
+ var generateETag = !this.get('ETag') && typeof etagFn === 'function';
// populate Content-Length
- var len
+ var len;
if (chunk !== undefined) {
if (Buffer.isBuffer(chunk)) {
// get length of Buffer
- len = chunk.length
+ len = chunk.length;
} else if (!generateETag && chunk.length < 1000) {
// just calculate length when no ETag + small chunk
- len = Buffer.byteLength(chunk, encoding)
+ len = Buffer.byteLength(chunk, encoding);
} else {
// convert chunk to Buffer and calculate
- chunk = Buffer.from(chunk, encoding)
+ chunk = Buffer.from(chunk, encoding);
encoding = undefined;
- len = chunk.length
+ len = chunk.length;
}
this.set('Content-Length', len);
@@ -202,9 +216,9 @@ res.send = function send(body) {
// alter headers for 205
if (this.statusCode === 205) {
- this.set('Content-Length', '0')
- this.removeHeader('Transfer-Encoding')
- chunk = ''
+ this.set('Content-Length', '0');
+ this.removeHeader('Transfer-Encoding');
+ chunk = '';
}
if (req.method === 'HEAD') {
@@ -233,10 +247,10 @@ res.send = function send(body) {
res.json = function json(obj) {
// settings
var app = this.app;
- var escape = app.get('json escape')
+ var escape = app.get('json escape');
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
- var body = stringify(obj, replacer, spaces, escape)
+ var body = stringify(obj, replacer, spaces, escape);
// content-type
if (!this.get('Content-Type')) {
@@ -261,10 +275,10 @@ res.json = function json(obj) {
res.jsonp = function jsonp(obj) {
// settings
var app = this.app;
- var escape = app.get('json escape')
+ var escape = app.get('json escape');
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
- var body = stringify(obj, replacer, spaces, escape)
+ var body = stringify(obj, replacer, spaces, escape);
var callback = this.req.query[app.get('jsonp callback name')];
// content-type
@@ -288,17 +302,22 @@ res.jsonp = function jsonp(obj) {
if (body === undefined) {
// empty argument
- body = ''
+ body = '';
} else if (typeof body === 'string') {
// replace chars not allowed in JavaScript that are in JSON
- body = body
- .replace(/\u2028/g, '\\u2028')
- .replace(/\u2029/g, '\\u2029')
+ body = body.replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
}
// the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
// the typeof check is just to reduce client error noise
- body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
+ body =
+ '/**/ typeof ' +
+ callback +
+ " === 'function' && " +
+ callback +
+ '(' +
+ body +
+ ');';
}
return this.send(body);
@@ -320,7 +339,7 @@ res.jsonp = function jsonp(obj) {
*/
res.sendStatus = function sendStatus(statusCode) {
- var body = statuses.message[statusCode] || String(statusCode)
+ var body = statuses.message[statusCode] || String(statusCode);
this.status(statusCode);
this.type('txt');
@@ -381,7 +400,7 @@ res.sendFile = function sendFile(path, options, callback) {
}
if (typeof path !== 'string') {
- throw new TypeError('path must be a string to res.sendFile')
+ throw new TypeError('path must be a string to res.sendFile');
}
// support function as second arg
@@ -391,7 +410,9 @@ res.sendFile = function sendFile(path, options, callback) {
}
if (!opts.root && !pathIsAbsolute(path)) {
- throw new TypeError('path must be absolute or specify root to res.sendFile');
+ throw new TypeError(
+ 'path must be absolute or specify root to res.sendFile',
+ );
}
// create file stream
@@ -431,55 +452,55 @@ res.sendFile = function sendFile(path, options, callback) {
* @public
*/
-res.download = function download (path, filename, options, callback) {
+res.download = function download(path, filename, options, callback) {
var done = callback;
var name = filename;
- var opts = options || null
+ var opts = options || null;
// support function as second or third arg
if (typeof filename === 'function') {
done = filename;
name = null;
- opts = null
+ opts = null;
} else if (typeof options === 'function') {
- done = options
- opts = null
+ done = options;
+ opts = null;
}
// support optional filename, where options may be in it's place
- if (typeof filename === 'object' &&
- (typeof options === 'function' || options === undefined)) {
- name = null
- opts = filename
+ if (
+ typeof filename === 'object' &&
+ (typeof options === 'function' || options === undefined)
+ ) {
+ name = null;
+ opts = filename;
}
// set Content-Disposition when file is sent
var headers = {
- 'Content-Disposition': contentDisposition(name || path)
+ 'Content-Disposition': contentDisposition(name || path),
};
// merge user-provided headers
if (opts && opts.headers) {
- var keys = Object.keys(opts.headers)
+ var keys = Object.keys(opts.headers);
for (var i = 0; i < keys.length; i++) {
- var key = keys[i]
+ var key = keys[i];
if (key.toLowerCase() !== 'content-disposition') {
- headers[key] = opts.headers[key]
+ headers[key] = opts.headers[key];
}
}
}
// merge user-provided options
- opts = Object.create(opts)
- opts.headers = headers
+ opts = Object.create(opts);
+ opts.headers = headers;
// Resolve the full path for sendFile
- var fullPath = !opts.root
- ? resolve(path)
- : path
+ var fullPath = !opts.root ? resolve(path) : path;
// send file
- return this.sendFile(fullPath, opts, done)
+ return this.sendFile(fullPath, opts, done);
};
/**
@@ -501,11 +522,11 @@ res.download = function download (path, filename, options, callback) {
* @public
*/
-res.contentType =
-res.type = function contentType(type) {
- var ct = type.indexOf('/') === -1
- ? (mime.contentType(type) || 'application/octet-stream')
- : type;
+res.contentType = res.type = function contentType(type) {
+ var ct =
+ type.indexOf('/') === -1
+ ? mime.contentType(type) || 'application/octet-stream'
+ : type;
return this.set('Content-Type', ct);
};
@@ -567,28 +588,31 @@ res.type = function contentType(type) {
* @public
*/
-res.format = function(obj){
+res.format = function (obj) {
var req = this.req;
var next = req.next;
- var keys = Object.keys(obj)
- .filter(function (v) { return v !== 'default' })
+ var keys = Object.keys(obj).filter(function (v) {
+ return v !== 'default';
+ });
- var key = keys.length > 0
- ? req.accepts(keys)
- : false;
+ var key = keys.length > 0 ? req.accepts(keys) : false;
- this.vary("Accept");
+ this.vary('Accept');
if (key) {
this.set('Content-Type', normalizeType(key).value);
obj[key](req, this, next);
} else if (obj.default) {
- obj.default(req, this, next)
+ obj.default(req, this, next);
} else {
- next(createError(406, {
- types: normalizeTypes(keys).map(function (o) { return o.value })
- }))
+ next(
+ createError(406, {
+ types: normalizeTypes(keys).map(function (o) {
+ return o.value;
+ }),
+ }),
+ );
}
return this;
@@ -633,9 +657,11 @@ res.append = function append(field, val) {
if (prev) {
// concat the new and prev vals
- value = Array.isArray(prev) ? prev.concat(val)
- : Array.isArray(val) ? [prev].concat(val)
- : [prev, val]
+ value = Array.isArray(prev)
+ ? prev.concat(val)
+ : Array.isArray(val)
+ ? [prev].concat(val)
+ : [prev, val];
}
return this.set(field, value);
@@ -662,19 +688,16 @@ res.append = function append(field, val) {
* @public
*/
-res.set =
-res.header = function header(field, val) {
+res.set = res.header = function header(field, val) {
if (arguments.length === 2) {
- var value = Array.isArray(val)
- ? val.map(String)
- : String(val);
+ var value = Array.isArray(val) ? val.map(String) : String(val);
// add charset to content-type
if (field.toLowerCase() === 'content-type') {
if (Array.isArray(value)) {
throw new TypeError('Content-Type cannot be set to an Array');
}
- value = mime.contentType(value)
+ value = mime.contentType(value);
}
this.setHeader(field, value);
@@ -694,7 +717,7 @@ res.header = function header(field, val) {
* @public
*/
-res.get = function(field){
+res.get = function (field) {
return this.getHeader(field);
};
@@ -709,9 +732,9 @@ res.get = function(field){
res.clearCookie = function clearCookie(name, options) {
// Force cookie expiration by setting expires to the past
- const opts = { path: '/', ...options, expires: new Date(1)};
+ const opts = { path: '/', ...options, expires: new Date(1) };
// ensure maxAge is not passed
- delete opts.maxAge
+ delete opts.maxAge;
return this.cookie(name, '', opts);
};
@@ -746,7 +769,7 @@ res.clearCookie = function clearCookie(name, options) {
* @param {String} name
* @param {String|Object} value
* @param {Object} [options]
- * @param {{encryptionAlgorithm: String, authTag: Boolean, iv: Buffer, key: String}} encrypt
+ * @param {key: String, iv: Buffer,} encrypt
* @return {ServerResponse} for chaining
* @public
*/
@@ -767,41 +790,29 @@ res.cookie = function (name, value, options, encrypt) {
val = 's:' + sign(val, secret);
}
- if (!signed && encrypt) {
- const encryptionAlgorithm = encrypt.encryptionAlgorithm;
-
- const iv = encrypt.iv;
+ if (encrypt) {
+ let { key, iv } = encrypt;
- const key = encrypt.key;
-
- const plainText = Buffer.from(JSON.stringify(value), 'utf8');
-
- const authTag = encrypt.authTag;
+ if (!iv) {
+ iv = crypto.randomBytes(16);
+ }
let cipher = crypto.createCipheriv(encryptionAlgorithm, key, iv);
- const encryptedText = Buffer.concat([
- cipher.update(plainText),
- cipher.final(),
- ]);
+ const encryptedText = Buffer.concat([cipher.update(val), cipher.final()]);
const encryptedTextObject = {
encryptedText: encryptedText.toString('base64'),
- encryptionAlgorithm: encryptionAlgorithm,
iv: iv.toString('base64'),
};
- if (authTag) {
- encryptedTextObject['authTag'] = cipher.getAuthTag().toString('base64');
- }
+ encryptedTextObject['authTag'] = cipher.getAuthTag().toString('base64');
- val = JSON.stringify(encryptedTextObject);
- }
-
- if (signed && encrypt) {
- throw new Error(
- 'You should decide between a signed cookie or a encrypted cookie, you have signed = true and passing to encrypt parameters',
- );
+ if (signed) {
+ val = 's:' + sign(JSON.stringify(encryptedTextObject), secret);
+ } else {
+ val = JSON.stringify(encryptedTextObject);
+ }
}
if (opts.maxAge != null) {
@@ -829,8 +840,7 @@ res.cookie = function (name, value, options, encrypt) {
**/
res.decryptCookie = function decryptCookie(encryptedCookie, key) {
- let { encryptedText, encryptionAlgorithm, iv, authTag } =
- JSON.parse(encryptedCookie);
+ let { encryptedText, iv, authTag } = JSON.parse(encryptedCookie);
iv = Buffer.from(iv, 'base64');
@@ -892,8 +902,8 @@ res.redirect = function redirect(url) {
// allow status / url
if (arguments.length === 2) {
- status = arguments[0]
- address = arguments[1]
+ status = arguments[0];
+ address = arguments[1];
}
if (!address) {
@@ -913,19 +923,26 @@ res.redirect = function redirect(url) {
// Support text/{plain,html} by default
this.format({
- text: function(){
- body = statuses.message[status] + '. Redirecting to ' + address
+ text: function () {
+ body = statuses.message[status] + '. Redirecting to ' + address;
},
- html: function(){
+ html: function () {
var u = escapeHtml(address);
- body = '' + statuses.message[status] + ''
- + '' + statuses.message[status] + '. Redirecting to ' + u + '
'
+ body =
+ '' +
+ statuses.message[status] +
+ '' +
+ '' +
+ statuses.message[status] +
+ '. Redirecting to ' +
+ u +
+ '
';
},
- default: function(){
+ default: function () {
body = '';
- }
+ },
});
// Respond
@@ -948,7 +965,7 @@ res.redirect = function redirect(url) {
* @public
*/
-res.vary = function(field){
+res.vary = function (field) {
vary(this, field);
return this;
@@ -984,10 +1001,12 @@ res.render = function render(view, options, callback) {
opts._locals = self.locals;
// default callback to respond
- done = done || function (err, str) {
- if (err) return req.next(err);
- self.send(str);
- };
+ done =
+ done ||
+ function (err, str) {
+ if (err) return req.next(err);
+ self.send(str);
+ };
// render
app.render(view, opts, done);
@@ -1096,28 +1115,29 @@ function sendfile(res, file, options, callback) {
* @private
*/
-function stringify (value, replacer, spaces, escape) {
+function stringify(value, replacer, spaces, escape) {
// v8 checks arguments.length for optimizing simple call
// https://bugs.chromium.org/p/v8/issues/detail?id=4730
- var json = replacer || spaces
- ? JSON.stringify(value, replacer, spaces)
- : JSON.stringify(value);
+ var json =
+ replacer || spaces
+ ? JSON.stringify(value, replacer, spaces)
+ : JSON.stringify(value);
if (escape && typeof json === 'string') {
json = json.replace(/[<>&]/g, function (c) {
switch (c.charCodeAt(0)) {
case 0x3c:
- return '\\u003c'
+ return '\\u003c';
case 0x3e:
- return '\\u003e'
+ return '\\u003e';
case 0x26:
- return '\\u0026'
+ return '\\u0026';
/* istanbul ignore next: unreachable default */
default:
- return c
+ return c;
}
- })
+ });
}
- return json
+ return json;
}
diff --git a/test/res.cookie.js b/test/res.cookie.js
index 180d1be3452..c9083035ea0 100644
--- a/test/res.cookie.js
+++ b/test/res.cookie.js
@@ -51,6 +51,73 @@ describe('res', function(){
})
})
+ describe('.cookie(name, string, options, encrypt)', function () {
+ it('should return a stringified json with the encrypted cookie', function (done) {
+ var app = express();
+ var { Buffer } = require('node:buffer');
+
+ app.use(cookieParser('my-secret'));
+
+ app.use(function (req, res) {
+ res.cookie('name', 'tobi', undefined, {
+ key: Buffer.from([
+ 0x66, 0xcc, 0xc0, 0xa1, 0x9f, 0x64, 0x26, 0x70, 0x84, 0xfe, 0xc7,
+ 0x0b, 0x2a, 0xf5, 0xf9, 0x45, 0x8e, 0xbc, 0x80, 0x4b, 0x60, 0x64,
+ 0xff, 0xc7, 0x77, 0x4f, 0xde, 0x97, 0xc1, 0xdf, 0x09, 0x5b,
+ ]),
+ iv: Buffer.from([
+ 0xdf, 0x16, 0x7e, 0xd1, 0xc9, 0x2c, 0x24, 0x1b, 0x02, 0x4f, 0x48,
+ 0x24, 0x62, 0xc6, 0x3b, 0x9b,
+ ]),
+ });
+ res.end();
+ });
+
+ request(app)
+ .get('/')
+ .expect(
+ 'Set-Cookie',
+ 'name=%7B%22encryptedText%22%3A%22wdYTOw%3D%3D%22%2C%22iv%22%3A%223xZ%2B0cksJBsCT0gkYsY7mw%3D%3D%22%2C%22authTag%22%3A%22pbC2HFCHVKkeAVA46GoNtg%3D%3D%22%7D; Path=/',
+ )
+ .expect(200, done);
+ });
+
+ it('should return a stringified json with the encrypted signed cookie', function (done) {
+ var app = express();
+ var { Buffer } = require('node:buffer');
+
+ app.use(cookieParser('my-secret'));
+
+ app.use(function (req, res) {
+ res.cookie(
+ 'name',
+ 'tobi',
+ { signed: true },
+ {
+ key: Buffer.from([
+ 0x66, 0xcc, 0xc0, 0xa1, 0x9f, 0x64, 0x26, 0x70, 0x84, 0xfe, 0xc7,
+ 0x0b, 0x2a, 0xf5, 0xf9, 0x45, 0x8e, 0xbc, 0x80, 0x4b, 0x60, 0x64,
+ 0xff, 0xc7, 0x77, 0x4f, 0xde, 0x97, 0xc1, 0xdf, 0x09, 0x5b,
+ ]),
+ iv: Buffer.from([
+ 0xdf, 0x16, 0x7e, 0xd1, 0xc9, 0x2c, 0x24, 0x1b, 0x02, 0x4f, 0x48,
+ 0x24, 0x62, 0xc6, 0x3b, 0x9b,
+ ]),
+ },
+ );
+ res.end();
+ });
+
+ request(app)
+ .get('/')
+ .expect(
+ 'Set-Cookie',
+ 'name=s%3A%7B%22encryptedText%22%3A%22wdYTOw%3D%3D%22%2C%22iv%22%3A%223xZ%2B0cksJBsCT0gkYsY7mw%3D%3D%22%2C%22authTag%22%3A%22pbC2HFCHVKkeAVA46GoNtg%3D%3D%22%7D.%2FbjKv%2BoqY%2BsjNKQp%2FyAgxhemLopKyKnQt1ngpRxhfL0; Path=/',
+ )
+ .expect(200, done);
+ });
+ });
+
describe('.cookie(name, string, options)', function(){
it('should set params', function(done){
var app = express();
From b5c2cb29bc6f9a4fb1a2ef3b95d9a63ece2428b4 Mon Sep 17 00:00:00 2001
From: emilANS
Date: Wed, 18 Mar 2026 16:50:13 -0500
Subject: [PATCH 09/10] fix-issue-#5995
---
examples/encrypted-cookies/index.js | 4 +-
lib/response.js | 317 +++++++++++++---------------
test/res.cookie.js | 31 ++-
3 files changed, 160 insertions(+), 192 deletions(-)
diff --git a/examples/encrypted-cookies/index.js b/examples/encrypted-cookies/index.js
index 14a0cc3ee2c..c4ec38e5e48 100644
--- a/examples/encrypted-cookies/index.js
+++ b/examples/encrypted-cookies/index.js
@@ -49,7 +49,7 @@ app.post('/', function (req, res) {
if (req.body && req.body.encryptedCookie) {
res.cookie(
'encryptedCookie',
- 'I like to hide by cookies under the sofa now',
+ 'I like to hide by cookies under the sofa',
{ signed: true, maxAge: minute },
{ key },
);
@@ -62,7 +62,7 @@ app.post('/decryptCookies', function (req, res) {
const decryptedCookie = res.decryptCookie(encryptedCookie, key);
- res.send(decryptedCookie + '
Go back to main page');
+ res.send(decryptedCookie + '
Go back');
});
/* istanbul ignore next */
diff --git a/lib/response.js b/lib/response.js
index 4e76468c607..729e178c514 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -13,16 +13,16 @@
*/
var contentDisposition = require('content-disposition');
-var createError = require('http-errors');
+var createError = require('http-errors')
var deprecate = require('depd')('express');
var encodeUrl = require('encodeurl');
var escapeHtml = require('escape-html');
var http = require('node:http');
var onFinished = require('on-finished');
-var mime = require('mime-types');
+var mime = require('mime-types')
var path = require('node:path');
var pathIsAbsolute = require('node:path').isAbsolute;
-var statuses = require('statuses');
+var statuses = require('statuses')
var sign = require('cookie-signature').sign;
var normalizeType = require('./utils').normalizeType;
var normalizeTypes = require('./utils').normalizeTypes;
@@ -32,24 +32,25 @@ var send = require('send');
var extname = path.extname;
var resolve = path.resolve;
var vary = require('vary');
-var crypto = require('node:crypto');
const { Buffer } = require('node:buffer');
+var crypto = require('node:crypto')
+
+const encryptionAlgorithm = "aes-256-gcm";
-const encryptionAlgorithm = 'aes-256-gcm';
/**
* Response prototype.
* @public
*/
-var res = Object.create(http.ServerResponse.prototype);
+var res = Object.create(http.ServerResponse.prototype)
/**
* Module exports.
* @public
*/
-module.exports = res;
+module.exports = res
/**
* Set the HTTP status code for the response.
@@ -67,15 +68,11 @@ module.exports = res;
res.status = function status(code) {
// Check if the status code is not an integer
if (!Number.isInteger(code)) {
- throw new TypeError(
- `Invalid status code: ${JSON.stringify(code)}. Status code must be an integer.`,
- );
+ throw new TypeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be an integer.`);
}
// Check if the status code is outside of Node's valid range
if (code < 100 || code > 999) {
- throw new RangeError(
- `Invalid status code: ${JSON.stringify(code)}. Status code must be greater than 99 and less than 1000.`,
- );
+ throw new RangeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be greater than 99 and less than 1000.`);
}
this.statusCode = code;
@@ -101,27 +98,19 @@ res.status = function status(code) {
* @public
*/
-res.links = function (links) {
+res.links = function(links) {
var link = this.get('Link') || '';
if (link) link += ', ';
- return this.set(
- 'Link',
- link +
- Object.keys(links)
- .map(function (rel) {
- // Allow multiple links if links[rel] is an array
- if (Array.isArray(links[rel])) {
- return links[rel]
- .map(function (singleLink) {
- return `<${singleLink}>; rel="${rel}"`;
- })
- .join(', ');
- } else {
- return `<${links[rel]}>; rel="${rel}"`;
- }
- })
- .join(', '),
- );
+ return this.set('Link', link + Object.keys(links).map(function(rel) {
+ // Allow multiple links if links[rel] is an array
+ if (Array.isArray(links[rel])) {
+ return links[rel].map(function (singleLink) {
+ return `<${singleLink}>; rel="${rel}"`;
+ }).join(', ');
+ } else {
+ return `<${links[rel]}>; rel="${rel}"`;
+ }
+ }).join(', '));
};
/**
@@ -173,23 +162,23 @@ res.send = function send(body) {
}
// determine if ETag should be generated
- var etagFn = app.get('etag fn');
- var generateETag = !this.get('ETag') && typeof etagFn === 'function';
+ var etagFn = app.get('etag fn')
+ var generateETag = !this.get('ETag') && typeof etagFn === 'function'
// populate Content-Length
- var len;
+ var len
if (chunk !== undefined) {
if (Buffer.isBuffer(chunk)) {
// get length of Buffer
- len = chunk.length;
+ len = chunk.length
} else if (!generateETag && chunk.length < 1000) {
// just calculate length when no ETag + small chunk
- len = Buffer.byteLength(chunk, encoding);
+ len = Buffer.byteLength(chunk, encoding)
} else {
// convert chunk to Buffer and calculate
- chunk = Buffer.from(chunk, encoding);
+ chunk = Buffer.from(chunk, encoding)
encoding = undefined;
- len = chunk.length;
+ len = chunk.length
}
this.set('Content-Length', len);
@@ -216,9 +205,9 @@ res.send = function send(body) {
// alter headers for 205
if (this.statusCode === 205) {
- this.set('Content-Length', '0');
- this.removeHeader('Transfer-Encoding');
- chunk = '';
+ this.set('Content-Length', '0')
+ this.removeHeader('Transfer-Encoding')
+ chunk = ''
}
if (req.method === 'HEAD') {
@@ -247,10 +236,10 @@ res.send = function send(body) {
res.json = function json(obj) {
// settings
var app = this.app;
- var escape = app.get('json escape');
+ var escape = app.get('json escape')
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
- var body = stringify(obj, replacer, spaces, escape);
+ var body = stringify(obj, replacer, spaces, escape)
// content-type
if (!this.get('Content-Type')) {
@@ -275,10 +264,10 @@ res.json = function json(obj) {
res.jsonp = function jsonp(obj) {
// settings
var app = this.app;
- var escape = app.get('json escape');
+ var escape = app.get('json escape')
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
- var body = stringify(obj, replacer, spaces, escape);
+ var body = stringify(obj, replacer, spaces, escape)
var callback = this.req.query[app.get('jsonp callback name')];
// content-type
@@ -302,22 +291,17 @@ res.jsonp = function jsonp(obj) {
if (body === undefined) {
// empty argument
- body = '';
+ body = ''
} else if (typeof body === 'string') {
// replace chars not allowed in JavaScript that are in JSON
- body = body.replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
+ body = body
+ .replace(/\u2028/g, '\\u2028')
+ .replace(/\u2029/g, '\\u2029')
}
// the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
// the typeof check is just to reduce client error noise
- body =
- '/**/ typeof ' +
- callback +
- " === 'function' && " +
- callback +
- '(' +
- body +
- ');';
+ body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
}
return this.send(body);
@@ -339,7 +323,7 @@ res.jsonp = function jsonp(obj) {
*/
res.sendStatus = function sendStatus(statusCode) {
- var body = statuses.message[statusCode] || String(statusCode);
+ var body = statuses.message[statusCode] || String(statusCode)
this.status(statusCode);
this.type('txt');
@@ -400,7 +384,7 @@ res.sendFile = function sendFile(path, options, callback) {
}
if (typeof path !== 'string') {
- throw new TypeError('path must be a string to res.sendFile');
+ throw new TypeError('path must be a string to res.sendFile')
}
// support function as second arg
@@ -410,9 +394,7 @@ res.sendFile = function sendFile(path, options, callback) {
}
if (!opts.root && !pathIsAbsolute(path)) {
- throw new TypeError(
- 'path must be absolute or specify root to res.sendFile',
- );
+ throw new TypeError('path must be absolute or specify root to res.sendFile');
}
// create file stream
@@ -452,55 +434,55 @@ res.sendFile = function sendFile(path, options, callback) {
* @public
*/
-res.download = function download(path, filename, options, callback) {
+res.download = function download (path, filename, options, callback) {
var done = callback;
var name = filename;
- var opts = options || null;
+ var opts = options || null
// support function as second or third arg
if (typeof filename === 'function') {
done = filename;
name = null;
- opts = null;
+ opts = null
} else if (typeof options === 'function') {
- done = options;
- opts = null;
+ done = options
+ opts = null
}
// support optional filename, where options may be in it's place
- if (
- typeof filename === 'object' &&
- (typeof options === 'function' || options === undefined)
- ) {
- name = null;
- opts = filename;
+ if (typeof filename === 'object' &&
+ (typeof options === 'function' || options === undefined)) {
+ name = null
+ opts = filename
}
// set Content-Disposition when file is sent
var headers = {
- 'Content-Disposition': contentDisposition(name || path),
+ 'Content-Disposition': contentDisposition(name || path)
};
// merge user-provided headers
if (opts && opts.headers) {
- var keys = Object.keys(opts.headers);
+ var keys = Object.keys(opts.headers)
for (var i = 0; i < keys.length; i++) {
- var key = keys[i];
+ var key = keys[i]
if (key.toLowerCase() !== 'content-disposition') {
- headers[key] = opts.headers[key];
+ headers[key] = opts.headers[key]
}
}
}
// merge user-provided options
- opts = Object.create(opts);
- opts.headers = headers;
+ opts = Object.create(opts)
+ opts.headers = headers
// Resolve the full path for sendFile
- var fullPath = !opts.root ? resolve(path) : path;
+ var fullPath = !opts.root
+ ? resolve(path)
+ : path
// send file
- return this.sendFile(fullPath, opts, done);
+ return this.sendFile(fullPath, opts, done)
};
/**
@@ -522,11 +504,11 @@ res.download = function download(path, filename, options, callback) {
* @public
*/
-res.contentType = res.type = function contentType(type) {
- var ct =
- type.indexOf('/') === -1
- ? mime.contentType(type) || 'application/octet-stream'
- : type;
+res.contentType =
+res.type = function contentType(type) {
+ var ct = type.indexOf('/') === -1
+ ? (mime.contentType(type) || 'application/octet-stream')
+ : type;
return this.set('Content-Type', ct);
};
@@ -588,31 +570,28 @@ res.contentType = res.type = function contentType(type) {
* @public
*/
-res.format = function (obj) {
+res.format = function(obj){
var req = this.req;
var next = req.next;
- var keys = Object.keys(obj).filter(function (v) {
- return v !== 'default';
- });
+ var keys = Object.keys(obj)
+ .filter(function (v) { return v !== 'default' })
- var key = keys.length > 0 ? req.accepts(keys) : false;
+ var key = keys.length > 0
+ ? req.accepts(keys)
+ : false;
- this.vary('Accept');
+ this.vary("Accept");
if (key) {
this.set('Content-Type', normalizeType(key).value);
obj[key](req, this, next);
} else if (obj.default) {
- obj.default(req, this, next);
+ obj.default(req, this, next)
} else {
- next(
- createError(406, {
- types: normalizeTypes(keys).map(function (o) {
- return o.value;
- }),
- }),
- );
+ next(createError(406, {
+ types: normalizeTypes(keys).map(function (o) { return o.value })
+ }))
}
return this;
@@ -657,11 +636,9 @@ res.append = function append(field, val) {
if (prev) {
// concat the new and prev vals
- value = Array.isArray(prev)
- ? prev.concat(val)
- : Array.isArray(val)
- ? [prev].concat(val)
- : [prev, val];
+ value = Array.isArray(prev) ? prev.concat(val)
+ : Array.isArray(val) ? [prev].concat(val)
+ : [prev, val]
}
return this.set(field, value);
@@ -688,16 +665,19 @@ res.append = function append(field, val) {
* @public
*/
-res.set = res.header = function header(field, val) {
+res.set =
+res.header = function header(field, val) {
if (arguments.length === 2) {
- var value = Array.isArray(val) ? val.map(String) : String(val);
+ var value = Array.isArray(val)
+ ? val.map(String)
+ : String(val);
// add charset to content-type
if (field.toLowerCase() === 'content-type') {
if (Array.isArray(value)) {
throw new TypeError('Content-Type cannot be set to an Array');
}
- value = mime.contentType(value);
+ value = mime.contentType(value)
}
this.setHeader(field, value);
@@ -717,7 +697,7 @@ res.set = res.header = function header(field, val) {
* @public
*/
-res.get = function (field) {
+res.get = function(field){
return this.getHeader(field);
};
@@ -732,13 +712,12 @@ res.get = function (field) {
res.clearCookie = function clearCookie(name, options) {
// Force cookie expiration by setting expires to the past
- const opts = { path: '/', ...options, expires: new Date(1) };
+ const opts = { path: '/', ...options, expires: new Date(1)};
// ensure maxAge is not passed
- delete opts.maxAge;
+ delete opts.maxAge
return this.cookie(name, '', opts);
};
-
/**
* Set cookie `name` to `value`, with the given `options`.
*
@@ -757,14 +736,13 @@ res.clearCookie = function clearCookie(name, options) {
* res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
*
* Encrypt:
- * - `encryptionAlgorithm` encryption algorithm you will use
- * - `authTag` do the encryption algorithm supports authTag
* - `iv` Initialization Vector used for encryption is recommended you create a entropied random value
* - `key` Key for encrypting and decrypting the encrypted cookie
*
* Examples:
* // Create an encrypted cookie
- * res.cookie('encryptedCookie', 'secret thing to be encrypted', {signed = false}, {encryptionAlgorithm: 'aes-256-cbc', authTag: true iv: crypto.randomBytes(16), key: crypto.randomBytes() })
+ * res.cookie('encryptedCookie', 'secret thing to be encrypted', {signed: false}, { key: crypto.randomBytes(), iv: crypto.randomBytes(16) })
+ * res.cookie('encryptedSignedCookie', 'secret thing to be encrypted', {signed: true}, { key: crypto.randomBytes(), iv: crypto.randomBytes(16) })
*
* @param {String} name
* @param {String|Object} value
@@ -775,62 +753,63 @@ res.clearCookie = function clearCookie(name, options) {
*/
res.cookie = function (name, value, options, encrypt) {
- var opts = { ...options };
- var secret = this.req.secret;
- var signed = opts.signed;
+ var opts = { ...options }
+ var secret = this.req.secret
+ var signed = opts.signed
if (signed && !secret) {
- throw new Error('cookieParser("secret") required for signed cookies');
+ throw new Error('cookieParser("secret") required for signed cookies')
}
var val =
- typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value);
+ typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value)
if (signed && !encrypt) {
- val = 's:' + sign(val, secret);
+ val = 's:' + sign(val, secret)
}
if (encrypt) {
- let { key, iv } = encrypt;
+ let { key, iv } = encrypt
if (!iv) {
iv = crypto.randomBytes(16);
}
- let cipher = crypto.createCipheriv(encryptionAlgorithm, key, iv);
+ let cipher = crypto.createCipheriv(encryptionAlgorithm, key, iv)
- const encryptedText = Buffer.concat([cipher.update(val), cipher.final()]);
+ const encryptedText = Buffer.concat([cipher.update(val), cipher.final()])
const encryptedTextObject = {
encryptedText: encryptedText.toString('base64'),
iv: iv.toString('base64'),
- };
+ }
- encryptedTextObject['authTag'] = cipher.getAuthTag().toString('base64');
+ // If you will use a encryption algorithm that don't support auth tags please remove this part of the code
+ encryptedTextObject['authTag'] = cipher.getAuthTag().toString('base64')
if (signed) {
- val = 's:' + sign(JSON.stringify(encryptedTextObject), secret);
+ val = 's:' + sign(JSON.stringify(encryptedTextObject), secret)
} else {
- val = JSON.stringify(encryptedTextObject);
+ val = JSON.stringify(encryptedTextObject)
}
}
if (opts.maxAge != null) {
- var maxAge = opts.maxAge - 0;
+ var maxAge = opts.maxAge - 0
if (!isNaN(maxAge)) {
- opts.expires = new Date(Date.now() + maxAge);
- opts.maxAge = Math.floor(maxAge / 1000);
+ opts.expires = new Date(Date.now() + maxAge)
+ opts.maxAge = Math.floor(maxAge / 1000)
}
}
if (opts.path == null) {
- opts.path = '/';
+ opts.path = '/'
}
- this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
+ this.append('Set-Cookie', cookie.serialize(name, String(val), opts))
- return this;
+ return this
};
/**
@@ -840,24 +819,24 @@ res.cookie = function (name, value, options, encrypt) {
**/
res.decryptCookie = function decryptCookie(encryptedCookie, key) {
- let { encryptedText, iv, authTag } = JSON.parse(encryptedCookie);
+ let { encryptedText, iv, authTag } = JSON.parse(encryptedCookie)
- iv = Buffer.from(iv, 'base64');
+ iv = Buffer.from(iv, 'base64')
- encryptedText = Buffer.from(encryptedText, 'base64');
+ encryptedText = Buffer.from(encryptedText, 'base64')
- const decipher = crypto.createDecipheriv(encryptionAlgorithm, key, iv);
+ const decipher = crypto.createDecipheriv(encryptionAlgorithm, key, iv)
if (authTag) {
- decipher.setAuthTag(Buffer.from(authTag, 'base64'));
+ decipher.setAuthTag(Buffer.from(authTag, 'base64'))
}
const plainText = Buffer.concat([
decipher.update(encryptedText),
decipher.final(),
- ]);
+ ])
- return plainText.toString('utf8');
+ return plainText.toString('utf8')
};
/**
@@ -902,8 +881,8 @@ res.redirect = function redirect(url) {
// allow status / url
if (arguments.length === 2) {
- status = arguments[0];
- address = arguments[1];
+ status = arguments[0]
+ address = arguments[1]
}
if (!address) {
@@ -923,26 +902,19 @@ res.redirect = function redirect(url) {
// Support text/{plain,html} by default
this.format({
- text: function () {
- body = statuses.message[status] + '. Redirecting to ' + address;
+ text: function(){
+ body = statuses.message[status] + '. Redirecting to ' + address
},
- html: function () {
+ html: function(){
var u = escapeHtml(address);
- body =
- '' +
- statuses.message[status] +
- '' +
- '' +
- statuses.message[status] +
- '. Redirecting to ' +
- u +
- '
';
+ body = '' + statuses.message[status] + ''
+ + '' + statuses.message[status] + '. Redirecting to ' + u + '
'
},
- default: function () {
+ default: function(){
body = '';
- },
+ }
});
// Respond
@@ -965,7 +937,7 @@ res.redirect = function redirect(url) {
* @public
*/
-res.vary = function (field) {
+res.vary = function(field){
vary(this, field);
return this;
@@ -1001,12 +973,10 @@ res.render = function render(view, options, callback) {
opts._locals = self.locals;
// default callback to respond
- done =
- done ||
- function (err, str) {
- if (err) return req.next(err);
- self.send(str);
- };
+ done = done || function (err, str) {
+ if (err) return req.next(err);
+ self.send(str);
+ };
// render
app.render(view, opts, done);
@@ -1115,29 +1085,28 @@ function sendfile(res, file, options, callback) {
* @private
*/
-function stringify(value, replacer, spaces, escape) {
+function stringify (value, replacer, spaces, escape) {
// v8 checks arguments.length for optimizing simple call
// https://bugs.chromium.org/p/v8/issues/detail?id=4730
- var json =
- replacer || spaces
- ? JSON.stringify(value, replacer, spaces)
- : JSON.stringify(value);
+ var json = replacer || spaces
+ ? JSON.stringify(value, replacer, spaces)
+ : JSON.stringify(value);
if (escape && typeof json === 'string') {
json = json.replace(/[<>&]/g, function (c) {
switch (c.charCodeAt(0)) {
case 0x3c:
- return '\\u003c';
+ return '\\u003c'
case 0x3e:
- return '\\u003e';
+ return '\\u003e'
case 0x26:
- return '\\u0026';
+ return '\\u0026'
/* istanbul ignore next: unreachable default */
default:
- return c;
+ return c
}
- });
+ })
}
- return json;
+ return json
}
diff --git a/test/res.cookie.js b/test/res.cookie.js
index c9083035ea0..0ccd4aac826 100644
--- a/test/res.cookie.js
+++ b/test/res.cookie.js
@@ -53,10 +53,10 @@ describe('res', function(){
describe('.cookie(name, string, options, encrypt)', function () {
it('should return a stringified json with the encrypted cookie', function (done) {
- var app = express();
- var { Buffer } = require('node:buffer');
+ var app = express()
+ var { Buffer } = require('node:buffer')
- app.use(cookieParser('my-secret'));
+ app.use(cookieParser('my-secret'))
app.use(function (req, res) {
res.cookie('name', 'tobi', undefined, {
@@ -69,9 +69,9 @@ describe('res', function(){
0xdf, 0x16, 0x7e, 0xd1, 0xc9, 0x2c, 0x24, 0x1b, 0x02, 0x4f, 0x48,
0x24, 0x62, 0xc6, 0x3b, 0x9b,
]),
- });
+ })
res.end();
- });
+ })
request(app)
.get('/')
@@ -79,14 +79,14 @@ describe('res', function(){
'Set-Cookie',
'name=%7B%22encryptedText%22%3A%22wdYTOw%3D%3D%22%2C%22iv%22%3A%223xZ%2B0cksJBsCT0gkYsY7mw%3D%3D%22%2C%22authTag%22%3A%22pbC2HFCHVKkeAVA46GoNtg%3D%3D%22%7D; Path=/',
)
- .expect(200, done);
- });
+ .expect(200, done)
+ })
it('should return a stringified json with the encrypted signed cookie', function (done) {
- var app = express();
- var { Buffer } = require('node:buffer');
+ var app = express()
+ var { Buffer } = require('node:buffer')
- app.use(cookieParser('my-secret'));
+ app.use(cookieParser('my-secret'))
app.use(function (req, res) {
res.cookie(
@@ -104,8 +104,8 @@ describe('res', function(){
0x24, 0x62, 0xc6, 0x3b, 0x9b,
]),
},
- );
- res.end();
+ )
+ res.end()
});
request(app)
@@ -114,10 +114,9 @@ describe('res', function(){
'Set-Cookie',
'name=s%3A%7B%22encryptedText%22%3A%22wdYTOw%3D%3D%22%2C%22iv%22%3A%223xZ%2B0cksJBsCT0gkYsY7mw%3D%3D%22%2C%22authTag%22%3A%22pbC2HFCHVKkeAVA46GoNtg%3D%3D%22%7D.%2FbjKv%2BoqY%2BsjNKQp%2FyAgxhemLopKyKnQt1ngpRxhfL0; Path=/',
)
- .expect(200, done);
- });
- });
-
+ .expect(200, done)
+ })
+ })
describe('.cookie(name, string, options)', function(){
it('should set params', function(done){
var app = express();
From ac1a307719b61dbdc88c7d9ece7a875692b0a4c8 Mon Sep 17 00:00:00 2001
From: Emil <162226144+emilANS@users.noreply.github.com>
Date: Wed, 18 Mar 2026 16:58:54 -0500
Subject: [PATCH 10/10] Remove nodemon dependency from package.json
---
package.json | 1 -
1 file changed, 1 deletion(-)
diff --git a/package.json b/package.json
index 4d283e0792e..aa2afbcb543 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,6 @@
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
- "nodemon": "^3.1.14",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",