From 20048616da297be555a33e3b7c747868627cbb2e Mon Sep 17 00:00:00 2001 From: Matt Godbolt Date: Sat, 19 Apr 2025 14:34:41 -0500 Subject: [PATCH] Migrate to express 5 (#7572) Main changes: - type checker cares about the return value (`void`) of handlers, so no more `return res.send("...")` as that returns `express` type. - regexes on slugs no longer supported, but we weren't really using them in any meaningful way. The two places that had to be updated: - `/clientstate/:clientstate64` - now uses a regex directly and tests added (thanks @partouf for spotting #4844) - `/bits/:bits.html` - was some `\w+` but I believe that's unnecessary for the same reasons - actually call the Sentry handler. I don't know if this actually worked before but the API checks suggest not. --- app.ts | 12 +- lib/handlers/api.ts | 3 +- .../api/assembly-documentation-controller.ts | 5 +- lib/handlers/api/formatting-controller.ts | 6 +- lib/handlers/api/healthcheck-controller.ts | 11 +- lib/handlers/compile.ts | 23 +- lib/handlers/middleware.ts | 5 +- lib/handlers/route-api.ts | 20 +- package-lock.json | 496 ++++++++---------- package.json | 4 +- test/handlers/middleware-tests.ts | 8 +- test/handlers/route-api-test.ts | 56 +- 12 files changed, 318 insertions(+), 331 deletions(-) diff --git a/app.ts b/app.ts index d9fa99731..e58676dd6 100755 --- a/app.ts +++ b/app.ts @@ -47,6 +47,7 @@ import systemdSocket from 'systemd-socket'; import _ from 'underscore'; import urljoin from 'url-join'; +import {unwrap} from './lib/assert.js'; import * as aws from './lib/aws.js'; import * as normalizer from './lib/clientstate-normalizer.js'; import {GoldenLayoutRootStruct} from './lib/clientstate-normalizer.js'; @@ -635,9 +636,10 @@ async function main() { logger.debug('Compilers:', compilers); prevCompilers = compilers; await clientOptionsHandler.setCompilers(compilers); - routeApi.apiHandler.setCompilers(compilers); - routeApi.apiHandler.setLanguages(languages); - routeApi.apiHandler.setOptions(clientOptionsHandler); + const apiHandler = unwrap(routeApi.apiHandler); + apiHandler.setCompilers(compilers); + apiHandler.setLanguages(languages); + apiHandler.setOptions(clientOptionsHandler); } await onCompilerChange(initialCompilers); @@ -687,7 +689,7 @@ async function main() { next({status: 404, message: `page "${req.path}" could not be found`}); }) // sentry error handler must be the first error handling middleware - .use(Sentry.Handlers.errorHandler) + .use(Sentry.Handlers.errorHandler()) // eslint-disable-next-line no-unused-vars .use((err: any, req: express.Request, res: express.Response, _next: express.NextFunction) => { const status = err.status || err.statusCode || err.status_code || err.output?.statusCode || 500; @@ -837,7 +839,7 @@ async function main() { res.set('Content-Type', 'application/javascript'); res.end(`window.compilerExplorerOptions = ${clientOptionsHandler.getJSON()};`); }) - .use('/bits/:bits(\\w+).html', cached, csp, (req, res) => { + .use('/bits/:bits.html', cached, csp, (req, res) => { res.render( `bits/${sanitize(req.params.bits)}`, renderConfig( diff --git a/lib/handlers/api.ts b/lib/handlers/api.ts index 3c98bf12c..6dfbaee8b 100644 --- a/lib/handlers/api.ts +++ b/lib/handlers/api.ts @@ -43,8 +43,7 @@ import {StorageBase} from '../storage/index.js'; import {CompileHandler} from './compile.js'; function methodNotAllowed(req: express.Request, res: express.Response) { - res.send('Method Not Allowed'); - return res.status(405).end(); + res.status(405).send('Method Not Allowed').end(); } export class ApiHandler { diff --git a/lib/handlers/api/assembly-documentation-controller.ts b/lib/handlers/api/assembly-documentation-controller.ts index 85317b6a6..41b23a4a2 100644 --- a/lib/handlers/api/assembly-documentation-controller.ts +++ b/lib/handlers/api/assembly-documentation-controller.ts @@ -48,7 +48,8 @@ export class AssemblyDocumentationController implements HttpController { const instruction = (req.params.opcode || '__UNKNOWN_OPCODE').toUpperCase(); const information = provider.getInstructionInformation(instruction); if (information === null) { - return res.status(404).send({error: `Unknown opcode '${instruction}'`}); + res.status(404).send({error: `Unknown opcode '${instruction}'`}); + return; } const contentType = req.accepts(['text', 'json']); @@ -67,7 +68,7 @@ export class AssemblyDocumentationController implements HttpController { } } } catch { - return res.status(404).send({error: `No documentation for '${req.params.arch}'`}); + res.status(404).send({error: `No documentation for '${req.params.arch}'`}); } } } diff --git a/lib/handlers/api/formatting-controller.ts b/lib/handlers/api/formatting-controller.ts index c293f8861..b343d8c05 100644 --- a/lib/handlers/api/formatting-controller.ts +++ b/lib/handlers/api/formatting-controller.ts @@ -44,7 +44,8 @@ export class FormattingController implements HttpController { const formatter = this.formattingService.getFormatterById(req.params.tool); // Ensure the target formatter exists if (formatter === null) { - return res.status(422).json({exit: 2, answer: `Unknown format tool '${req.params.tool}'`}); + res.status(422).json({exit: 2, answer: `Unknown format tool '${req.params.tool}'`}); + return; } // Ensure there is source code to format if (!req.body || !req.body.source) { @@ -53,7 +54,8 @@ export class FormattingController implements HttpController { } // Ensure that the requested style is supported by the formatter if (!formatter.isValidStyle(req.body.base)) { - return res.status(422).json({exit: 3, answer: `Style '${req.body.base}' is not supported`}); + res.status(422).json({exit: 3, answer: `Style '${req.body.base}' is not supported`}); + return; } // Do the formatting try { diff --git a/lib/handlers/api/healthcheck-controller.ts b/lib/handlers/api/healthcheck-controller.ts index 4a086e1f4..a3e21bac2 100644 --- a/lib/handlers/api/healthcheck-controller.ts +++ b/lib/handlers/api/healthcheck-controller.ts @@ -62,7 +62,8 @@ export class HealthcheckController implements HttpController { // If this is a worker, we don't require that the server has languages configured. if (!this.isExecutionWorker && !this.compileHandler.hasLanguages()) { logger.error('*** HEALTH CHECK FAILURE: no languages/compilers detected'); - return res.status(500).send(); + res.status(500).send(); + return; } // If we have a healthcheck file, we require that it exists and it is non-empty. The /efs/.health file contents @@ -74,14 +75,16 @@ export class HealthcheckController implements HttpController { throw new Error('File is empty'); } res.set('Content-Type', 'text/html'); - return res.send(content); + res.send(content); + return; } catch (e) { logger.error(`*** HEALTH CHECK FAILURE: while reading file '${this.healthCheckFilePath}' got ${e}`); SentryCapture(e, 'Health check'); - return res.status(500).send(); + res.status(500).send(); + return; } } - return res.send('Everything is awesome'); + res.send('Everything is awesome'); } } diff --git a/lib/handlers/compile.ts b/lib/handlers/compile.ts index a67866db3..f8f0826a2 100644 --- a/lib/handlers/compile.ts +++ b/lib/handlers/compile.ts @@ -486,7 +486,8 @@ export class CompileHandler implements ICompileHandler { handlePopularArguments(req: express.Request, res: express.Response) { const compiler = this.compilerFor(req); if (!compiler) { - return res.sendStatus(404); + res.sendStatus(404); + return; } res.send(compiler.possibleArguments.getPopularArguments(this.getUsedOptions(req))); } @@ -494,7 +495,8 @@ export class CompileHandler implements ICompileHandler { handleOptimizationArguments(req: express.Request, res: express.Response) { const compiler = this.compilerFor(req); if (!compiler) { - return res.sendStatus(404); + res.sendStatus(404); + return; } res.send(compiler.possibleArguments.getOptimizationArguments(this.getUsedOptions(req))); } @@ -513,18 +515,20 @@ export class CompileHandler implements ICompileHandler { handleApiError(error: any, res: express.Response, next: express.NextFunction) { if (error.message) { - return res.status(400).send({ + res.status(400).send({ error: true, message: error.message, }); + return; } - return next(error); + next(error); } handleCmake(req: express.Request, res: express.Response, next: express.NextFunction) { const compiler = this.compilerFor(req); if (!compiler) { - return res.sendStatus(404); + res.sendStatus(404); + return; } const remote = compiler.getRemote(); @@ -561,14 +565,16 @@ export class CompileHandler implements ICompileHandler { return this.handleApiError(e, res, next); }); } catch (e) { - return this.handleApiError(e, res, next); + this.handleApiError(e, res, next); + return; } } handle(req: express.Request, res: express.Response, next: express.NextFunction) { const compiler = this.compilerFor(req); if (!compiler) { - return res.sendStatus(404); + res.sendStatus(404); + return; } const remote = compiler.getRemote(); @@ -585,7 +591,8 @@ export class CompileHandler implements ICompileHandler { try { parsedRequest = this.parseRequest(req, compiler); } catch (error) { - return this.handleApiError(error, res, next); + this.handleApiError(error, res, next); + return; } const {source, options, backendOptions, filters, bypassCache, tools, executeParameters, libraries} = diff --git a/lib/handlers/middleware.ts b/lib/handlers/middleware.ts index 3e940c983..f02be6fd0 100644 --- a/lib/handlers/middleware.ts +++ b/lib/handlers/middleware.ts @@ -35,9 +35,10 @@ const ceProps = props.propsFor('compiler-explorer'); */ export const jsonOnly: express.Handler = (req, res, next) => { if (req.headers['content-type'] !== 'application/json') { - return res.status(400).json({message: 'bad request, expected json content'}); + res.status(400).json({message: 'bad request, expected json content'}); + return; } - return next(); + next(); }; /** Add static headers to the response */ diff --git a/lib/handlers/route-api.ts b/lib/handlers/route-api.ts index a1ddb6b7e..5c90773ff 100644 --- a/lib/handlers/route-api.ts +++ b/lib/handlers/route-api.ts @@ -29,7 +29,7 @@ import express from 'express'; import {AppDefaultArguments, CompilerExplorerOptions} from '../../app.js'; import {isString} from '../../shared/common-utils.js'; import {Language} from '../../types/languages.interfaces.js'; -import {assert} from '../assert.js'; +import {assert, unwrap} from '../assert.js'; import {ClientStateGoldenifier, ClientStateNormalizer} from '../clientstate-normalizer.js'; import {ClientState} from '../clientstate.js'; import {CompilationEnvironment} from '../compilation-env.js'; @@ -67,7 +67,7 @@ export type ShortLinkMetaData = { export class RouteAPI { renderGoldenLayout: any; storageHandler: StorageBase; - apiHandler: ApiHandler; + apiHandler: ApiHandler | undefined; constructor( private readonly router: express.Router, @@ -89,17 +89,19 @@ export class RouteAPI { this.apiHandler.setReleaseInfo(config.defArgs.gitReleaseName, config.defArgs.releaseBuildNumber); } else { - this.apiHandler = undefined as any; + this.apiHandler = undefined; } } InitializeRoutes() { + if (this.apiHandler) { + this.router.use('/api', this.apiHandler.handle); + } this.router - .use('/api', this.apiHandler.handle) .get('/z/:id', cached, csp, this.storedStateHandler.bind(this)) .get('/z/:id/code/:session', cached, csp, this.storedCodeHandler.bind(this)) .get('/resetlayout/:id', cached, csp, this.storedStateHandlerResetLayout.bind(this)) - .get('/clientstate/:clientstatebase64([^]*)', cached, csp, this.unstoredStateHandler.bind(this)) + .get(/^\/clientstate\/(?.*)/, cached, csp, this.unstoredStateHandler.bind(this)) .get('/fromsimplelayout', cached, csp, this.simpleLayoutHandler.bind(this)); } @@ -111,7 +113,7 @@ export class RouteAPI { .then(result => { const config = JSON.parse(result.config); - let clientstate: ClientState | null = null; + let clientstate: ClientState | null; if (config.content) { const normalizer = new ClientStateNormalizer(); normalizer.fromGoldenLayout(config); @@ -268,7 +270,7 @@ export class RouteAPI { const sources = utils.glGetMainContents(config.content); if (sources.editors.length === 1) { const editor = sources.editors[0]; - lang = this.apiHandler.languages[editor.language]; + lang = unwrap(this.apiHandler).languages[editor.language]; source = editor.source; } else { const normalizer = new ClientStateNormalizer(); @@ -277,7 +279,7 @@ export class RouteAPI { if (clientstate.trees && clientstate.trees.length === 1) { const tree = clientstate.trees[0]; - lang = this.apiHandler.languages[tree.compilerLanguageId]; + lang = unwrap(this.apiHandler).languages[tree.compilerLanguageId]; if (tree.isCMakeProject) { const firstSource = tree.files.find(file => { @@ -304,7 +306,7 @@ export class RouteAPI { metadata.ogTitle += ` - ${lang.name}`; if (sources && sources.compilers.length === 1) { const compilerId = sources.compilers[0].compiler; - const compiler = this.apiHandler.compilers.find(c => c.id === compilerId); + const compiler = unwrap(this.apiHandler).compilers.find(c => c.id === compilerId); if (compiler) { metadata.ogTitle += ` (${compiler.name})`; } diff --git a/package-lock.json b/package-lock.json index e1978aec1..d424c4838 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "cross-env": "^7.0.3", "enhanced-ms": "^4.1.0", "events": "^3.3.0", - "express": "^4.21.2", + "express": "^5.1.0", "file-saver": "^2.0.5", "golden-layout": "^1.5.9", "http-proxy": "^1.18.1", @@ -82,7 +82,7 @@ "@types/bootstrap": "^5.2.10", "@types/chai": "^5.2.1", "@types/compression": "^1.7.5", - "@types/express": "^4.17.21", + "@types/express": "^5.0.1", "@types/file-saver": "^2.0.7", "@types/http-proxy": "^1.17.16", "@types/jquery": "^3.5.32", @@ -4593,22 +4593,21 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz", + "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", + "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "*" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", "dev": true, "license": "MIT", "dependencies": { @@ -5303,22 +5302,34 @@ } }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/accepts/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5498,12 +5509,6 @@ ], "license": "MIT" }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -5741,69 +5746,23 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, "node_modules/boolbase": { @@ -6508,9 +6467,9 @@ } }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -6538,10 +6497,13 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/cookiejar": { "version": "2.1.4", @@ -7040,7 +7002,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -7095,16 +7056,6 @@ "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -7624,79 +7575,66 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/express/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">= 0.8" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/express/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", + "node_modules/express/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", "dependencies": { - "side-channel": "^1.0.6" + "mime-db": "^1.54.0" }, "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.6" } }, "node_modules/extend": { @@ -7900,38 +7838,22 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -8546,7 +8468,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -9997,12 +9918,12 @@ "license": "CC0-1.0" }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/memfs": { @@ -10026,10 +9947,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -10044,6 +9968,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -10063,18 +9988,6 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -10351,16 +10264,6 @@ "@sinonjs/commons": "^3.0.1" } }, - "node_modules/nise/node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, "node_modules/nock": { "version": "14.0.3", "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.3.tgz", @@ -10771,10 +10674,13 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } }, "node_modules/pathe": { "version": "2.0.3", @@ -11754,7 +11660,6 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -11785,32 +11690,20 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", + "iconv-lite": "0.6.3", "unpipe": "1.0.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -12118,6 +12011,28 @@ "fsevents": "~2.3.2" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/rrweb-cssom": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", @@ -12335,53 +12250,48 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { + "node_modules/send/node_modules/fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -12420,18 +12330,18 @@ "license": "MIT" }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, "node_modules/setimmediate": { @@ -13615,13 +13525,26 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" @@ -13758,15 +13681,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", diff --git a/package.json b/package.json index 30aa9d078..30e187dba 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "cross-env": "^7.0.3", "enhanced-ms": "^4.1.0", "events": "^3.3.0", - "express": "^4.21.2", + "express": "^5.1.0", "file-saver": "^2.0.5", "golden-layout": "^1.5.9", "http-proxy": "^1.18.1", @@ -91,7 +91,7 @@ "@types/bootstrap": "^5.2.10", "@types/chai": "^5.2.1", "@types/compression": "^1.7.5", - "@types/express": "^4.17.21", + "@types/express": "^5.0.1", "@types/file-saver": "^2.0.7", "@types/http-proxy": "^1.17.16", "@types/jquery": "^3.5.32", diff --git a/test/handlers/middleware-tests.ts b/test/handlers/middleware-tests.ts index 65b1bc9ab..a0bf6dc4b 100644 --- a/test/handlers/middleware-tests.ts +++ b/test/handlers/middleware-tests.ts @@ -31,7 +31,9 @@ import {cached, cors} from '../../lib/handlers/middleware.js'; describe('middleware functions', () => { it('adds cache controls headers with cached()', async () => { const app = express(); - app.use('/', cached, (_, res) => res.send('Hello World!')); + app.use('/', cached, (_, res) => { + res.send('Hello World!'); + }); await request(app) .get('/') .expect(200, 'Hello World!') @@ -40,7 +42,9 @@ describe('middleware functions', () => { it('adds cors headers with cors()', async () => { const app = express(); - app.use('/', cors, (_, res) => res.send('Hello World!')); + app.use('/', cors, (_, res) => { + res.send('Hello World!'); + }); await request(app) .get('/') .expect(200, 'Hello World!') diff --git a/test/handlers/route-api-test.ts b/test/handlers/route-api-test.ts index b258c4aec..7cd7b0ccb 100644 --- a/test/handlers/route-api-test.ts +++ b/test/handlers/route-api-test.ts @@ -24,9 +24,17 @@ import zlib from 'node:zlib'; -import {describe, expect, it} from 'vitest'; +import {beforeAll, describe, expect, it} from 'vitest'; -import {extractJsonFromBufferAndInflateIfRequired} from '../../lib/handlers/route-api.js'; +import express from 'express'; +import request from 'supertest'; +import {GoldenLayoutRootStruct} from '../../lib/clientstate-normalizer.js'; +import { + HandlerConfig, + RouteAPI, + ShortLinkMetaData, + extractJsonFromBufferAndInflateIfRequired, +} from '../../lib/handlers/route-api.js'; function possibleCompression(buffer: Buffer): boolean { // code used in extractJsonFromBufferAndInflateIfRequired @@ -63,3 +71,47 @@ describe('extractJsonFromBufferAndInflateIfRequired test cases', () => { expect(() => extractJsonFromBufferAndInflateIfRequired(buffer)).toThrow(); }); }); + +describe('clientStateHandler', () => { + let app: express.Express; + + beforeAll(() => { + app = express(); + const apiHandler = new RouteAPI(app, { + renderGoldenLayout: ( + config: GoldenLayoutRootStruct, + metadata: ShortLinkMetaData, + req: express.Request, + res: express.Response, + ) => { + res.send('This is ok'); + }, + } as unknown as HandlerConfig); + apiHandler.InitializeRoutes(); + }); + + it('should return 200 for /clientstate', async () => { + // A valid document is a base64 encoded JSON object with a sessions array. + const document = Buffer.from(JSON.stringify({sessions: []}), 'utf-8').toString('base64'); + const response = await request(app).get(`/clientstate/${document}`); + expect(response.status).toBe(200); + }); + it('should return 200 for /clientstate even if the base64 contains `/`', async () => { + let document = ''; + for (let i = 0; i < 1000; i++) { + document = Buffer.from( + JSON.stringify({ + sessions: [], + magic: String.fromCharCode(i, i + 1234), + }), + 'utf-8', + ).toString('base64'); + if (document.includes('/')) { + break; + } + } + expect(document).toContain('/'); + const response = await request(app).get(`/clientstate/${document}`); + expect(response.status).toBe(200); + }); +});