From b3cd8d699d6c1e9acca8917d2cc148974a048573 Mon Sep 17 00:00:00 2001 From: Dorian Niemiec Date: Tue, 27 Aug 2024 21:09:43 +0200 Subject: [PATCH] Replace shallow cloning with deep cloning when needed. Also create deep cloning JavaScript file and corresponding tests --- src/handlers/clientErrorHandler.js | 6 ++- src/handlers/noproxyHandler.js | 5 +- src/handlers/proxyHandler.js | 3 +- src/handlers/requestHandler.js | 5 +- src/utils/deepClone.js | 37 ++++++++++++++ tests/utils/deepClone.test.js | 77 ++++++++++++++++++++++++++++++ 6 files changed, 125 insertions(+), 8 deletions(-) create mode 100644 src/utils/deepClone.js create mode 100644 tests/utils/deepClone.test.js diff --git a/src/handlers/clientErrorHandler.js b/src/handlers/clientErrorHandler.js index c894b19..ee85d86 100644 --- a/src/handlers/clientErrorHandler.js +++ b/src/handlers/clientErrorHandler.js @@ -4,16 +4,18 @@ const defaultPageCSS = require("../res/defaultPageCSS.js"); const generateErrorStack = require("../utils/generateErrorStack.js"); const serverHTTPErrorDescs = require("../res/httpErrorDescriptions.js"); const generateServerString = require("../utils/generateServerString.js"); +const deepClone = require("../utils/deepClone.js"); + let serverconsole = {}; function clientErrorHandler(err, socket) { - const config = Object.assign({}, process.serverConfig); + const config = deepClone(process.serverConfig); config.generateServerString = () => generateServerString(config.exposeServerVersion); // getCustomHeaders() in SVR.JS 3.x - config.getCustomHeaders = () => Object.assign({}, config.customHeaders); + config.getCustomHeaders = () => deepClone(config.customHeaders); // Prevent multiple error handlers from one request if (socket.__assigned__) { diff --git a/src/handlers/noproxyHandler.js b/src/handlers/noproxyHandler.js index 635d9ec..205b45f 100644 --- a/src/handlers/noproxyHandler.js +++ b/src/handlers/noproxyHandler.js @@ -1,5 +1,4 @@ -// eslint-disable-next-line no-unused-vars -const svrjsInfo = require("../../svrjs.json"); +const deepClone = require("../utils/deepClone.js"); let serverconsole = {}; @@ -28,7 +27,7 @@ function noproxyHandler(req, socket, head) { socket.on("error", () => {}); // SVR.JS configuration object (modified) - const config = Object.assign({}, process.serverConfig); + const config = deepClone(process.serverConfig); var reqip = socket.remoteAddress; var reqport = socket.remotePort; diff --git a/src/handlers/proxyHandler.js b/src/handlers/proxyHandler.js index 6864fc9..4bf8f9d 100644 --- a/src/handlers/proxyHandler.js +++ b/src/handlers/proxyHandler.js @@ -1,4 +1,5 @@ const generateServerString = require("../utils/generateServerString"); +const deepClone = require("../utils/deepClone.js"); const svrjsInfo = require("../../svrjs.json"); const { name } = svrjsInfo; @@ -29,7 +30,7 @@ function proxyHandler(req, socket, head) { socket.on("error", () => {}); // SVR.JS configuration object (modified) - const config = Object.assign({}, process.serverConfig); + const config = deepClone(process.serverConfig); config.generateServerString = () => { return generateServerString(config.exposeServerVersion); diff --git a/src/handlers/requestHandler.js b/src/handlers/requestHandler.js index 3f9b95a..6bc471c 100644 --- a/src/handlers/requestHandler.js +++ b/src/handlers/requestHandler.js @@ -9,6 +9,7 @@ const ipMatch = require("../utils/ipMatch.js"); const matchHostname = require("../utils/matchHostname.js"); const generateServerString = require("../utils/generateServerString.js"); const parseURL = require("../utils/urlParser.js"); +const deepClone = require("../utils/deepClone.js"); let serverconsole = {}; let middleware = []; @@ -31,7 +32,7 @@ function requestHandler(req, res) { }; // SVR.JS configuration object (modified) - const config = Object.assign({}, process.serverConfig); + const config = deepClone(process.serverConfig); config.generateServerString = () => { return generateServerString(config.exposeServerVersion); @@ -39,7 +40,7 @@ function requestHandler(req, res) { // getCustomHeaders() in SVR.JS 3.x config.getCustomHeaders = () => { - let ph = Object.assign({}, config.customHeaders); + let ph = deepClone(config.customHeaders); if (config.customHeadersVHost) { let vhostP = null; config.customHeadersVHost.every((vhost) => { diff --git a/src/utils/deepClone.js b/src/utils/deepClone.js new file mode 100644 index 0000000..dbaa979 --- /dev/null +++ b/src/utils/deepClone.js @@ -0,0 +1,37 @@ +// Function to deep clone an object or array +function deepClone(obj, _objectsArray, _clonesArray) { + if (!_objectsArray) _objectsArray = []; + if (!_clonesArray) _clonesArray = []; + if (typeof obj !== "object" || obj === null) { + return obj; + } + + let objectsArrayIndex = _objectsArray.indexOf(obj); + if (objectsArrayIndex != -1) { + return _clonesArray[objectsArrayIndex]; + } + + let clone; + + if (Array.isArray(obj)) { + clone = []; + _objectsArray.push(obj); + _clonesArray.push(clone); + obj.forEach((item, index) => { + clone[index] = deepClone(item, _objectsArray, _clonesArray); + }); + } else { + clone = {}; + _objectsArray.push(obj); + _clonesArray.push(clone); + Object.keys(obj).forEach((key) => { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + clone[key] = deepClone(obj[key], _objectsArray, _clonesArray); + } + }); + } + + return clone; +} + +module.exports = deepClone; diff --git a/tests/utils/deepClone.test.js b/tests/utils/deepClone.test.js new file mode 100644 index 0000000..8bcbc1c --- /dev/null +++ b/tests/utils/deepClone.test.js @@ -0,0 +1,77 @@ +const deepClone = require("../../src/utils/deepClone"); + +describe("Deep cloning function", () => { + test("should clone a simple object", () => { + const original = { a: 1, b: 2 }; + const cloned = deepClone(original); + expect(cloned).toEqual(original); + expect(cloned).not.toBe(original); + }); + + test("should clone a nested object", () => { + const original = { a: 1, b: { c: 2, d: { e: 3 } } }; + const cloned = deepClone(original); + expect(cloned).toEqual(original); + expect(cloned).not.toBe(original); + expect(cloned.b).not.toBe(original.b); + expect(cloned.b.d).not.toBe(original.b.d); + }); + + test("should clone an array", () => { + const original = [1, 2, 3]; + const cloned = deepClone(original); + expect(cloned).toEqual(original); + expect(cloned).not.toBe(original); + }); + + test("should clone an array of objects", () => { + const original = [{ a: 1 }, { b: 2 }]; + const cloned = deepClone(original); + expect(cloned).toEqual(original); + expect(cloned).not.toBe(original); + expect(cloned[0]).not.toBe(original[0]); + expect(cloned[1]).not.toBe(original[1]); + }); + + test("should clone an object with arrays", () => { + const original = { a: [1, 2], b: { c: [3, 4] } }; + const cloned = deepClone(original); + expect(cloned).toEqual(original); + expect(cloned).not.toBe(original); + expect(cloned.a).not.toBe(original.a); + expect(cloned.b).not.toBe(original.b); + expect(cloned.b.c).not.toBe(original.b.c); + }); + + test("should return the same value for non-objects", () => { + expect(deepClone(null)).toBe(null); + expect(deepClone(undefined)).toBe(undefined); + expect(deepClone(42)).toBe(42); + expect(deepClone("string")).toBe("string"); + expect(deepClone(true)).toBe(true); + }); + + test("should handle circular references", () => { + const original = {}; + original.self = original; + const cloned = deepClone(original); + expect(cloned).not.toBe(original); + expect(cloned.self).toBe(cloned); + }); + + test("should handle complex nested structures", () => { + const original = { + a: 1, + b: [2, 3, { c: 4 }], + d: { e: 5, f: [6, { g: 7 }] }, + }; + const cloned = deepClone(original); + expect(cloned).toEqual(original); + expect(cloned).not.toBe(original); + expect(cloned.b).not.toBe(original.b); + expect(cloned.b[2]).not.toBe(original.b[2]); + expect(cloned.d).not.toBe(original.d); + expect(cloned.d.f).not.toBe(original.d.f); + expect(cloned.d.f[1]).not.toBe(original.d.f[1]); + }); +});