diff --git a/.changeset/cyan-sheep-smile.md b/.changeset/cyan-sheep-smile.md new file mode 100644 index 000000000000..3a9c271b2cc0 --- /dev/null +++ b/.changeset/cyan-sheep-smile.md @@ -0,0 +1,5 @@ +--- +'@astrojs/node': patch +--- + +Fix malformed URLs crashing the server in certain cases diff --git a/packages/astro/test/fixtures/ssr-api-route/package.json b/packages/astro/test/fixtures/ssr-api-route/package.json index 942034f45246..ab761dd6b1e6 100644 --- a/packages/astro/test/fixtures/ssr-api-route/package.json +++ b/packages/astro/test/fixtures/ssr-api-route/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "private": true, "dependencies": { - "@astrojs/node": "^1.1.0", + "@astrojs/node": "workspace:*", "astro": "workspace:*" } } diff --git a/packages/integrations/node/src/http-server.ts b/packages/integrations/node/src/http-server.ts index f0dde82d5f34..850d61bbb9e0 100644 --- a/packages/integrations/node/src/http-server.ts +++ b/packages/integrations/node/src/http-server.ts @@ -12,16 +12,32 @@ interface CreateServerOptions { removeBase: (pathname: string) => string; } +function parsePathname(pathname: string, host: string | undefined, port: number) { + try { + const urlPathname = new URL(pathname, `http://${host}:${port}`).pathname; + return decodeURI(encodeURI(urlPathname)); + } catch (err) { + return undefined; + } +} + export function createServer( { client, port, host, removeBase }: CreateServerOptions, handler: http.RequestListener ) { const listener: http.RequestListener = (req, res) => { if (req.url) { - let pathname = removeBase(req.url); + let pathname: string | undefined = removeBase(req.url); pathname = pathname[0] === '/' ? pathname : '/' + pathname; - pathname = new URL(pathname, `http://${host}:${port}`).pathname; - const stream = send(req, encodeURI(decodeURI(pathname)), { + const encodedURI = parsePathname(pathname, host, port); + + if (!encodedURI) { + res.writeHead(400); + res.end('Bad request.'); + return res; + } + + const stream = send(req, encodedURI, { root: fileURLToPath(client), dotfiles: pathname.startsWith('/.well-known/') ? 'allow' : 'deny', }); diff --git a/packages/integrations/node/test/bad-urls.test.js b/packages/integrations/node/test/bad-urls.test.js new file mode 100644 index 000000000000..24a6e7747015 --- /dev/null +++ b/packages/integrations/node/test/bad-urls.test.js @@ -0,0 +1,46 @@ +import { expect } from 'chai'; +import nodejs from '../dist/index.js'; +import { loadFixture } from './test-utils.js'; + +describe('API routes', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + let devPreview; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/bad-urls/', + output: 'server', + adapter: nodejs({ mode: 'standalone' }), + }); + await fixture.build(); + devPreview = await fixture.preview(); + }); + + after(async () => { + await devPreview.stop(); + }); + + it('Does not crash on bad urls', async () => { + const weirdURLs = [ + '/\\xfs.bxss.me%3Fastrojs.com/hello-world', + '/asdasdasd@ax_zX=.zxczasđŸ„%/Ășadasd000%/', + '%', + '%80', + '%c', + '%c0%80', + '%20foobar%', + ]; + + for (const weirdUrl of weirdURLs) { + const fetchResult = await fixture.fetch(weirdUrl); + expect([400, 500]).to.include( + fetchResult.status, + `${weirdUrl} returned something else than 400 or 500` + ); + } + const stillWork = await fixture.fetch('/'); + const text = await stillWork.text(); + expect(text).to.equal('\nHello!'); + }); +}); diff --git a/packages/integrations/node/test/fixtures/bad-urls/package.json b/packages/integrations/node/test/fixtures/bad-urls/package.json new file mode 100644 index 000000000000..73c1196632e1 --- /dev/null +++ b/packages/integrations/node/test/fixtures/bad-urls/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/nodejs-badurls", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*", + "@astrojs/node": "workspace:*" + } +} diff --git a/packages/integrations/node/test/fixtures/bad-urls/src/pages/index.astro b/packages/integrations/node/test/fixtures/bad-urls/src/pages/index.astro new file mode 100644 index 000000000000..10ddd6d257e0 --- /dev/null +++ b/packages/integrations/node/test/fixtures/bad-urls/src/pages/index.astro @@ -0,0 +1 @@ +Hello! diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79cb4699f3b5..163ddc2fd59e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -332,8 +332,8 @@ importers: astro: ^2.1.9 kleur: ^4.1.5 dependencies: - '@astrojs/markdoc': 0.1.0_astro@2.1.9 - astro: 2.1.9 + '@astrojs/markdoc': link:../../packages/integrations/markdoc + astro: link:../../packages/astro kleur: 4.1.5 examples/with-markdown-plugins: @@ -2494,10 +2494,10 @@ importers: packages/astro/test/fixtures/ssr-api-route: specifiers: - '@astrojs/node': ^1.1.0 + '@astrojs/node': workspace:* astro: workspace:* dependencies: - '@astrojs/node': 1.1.0 + '@astrojs/node': link:../../../../integrations/node astro: link:../../.. packages/astro/test/fixtures/ssr-api-route-custom-404: @@ -3454,6 +3454,14 @@ importers: '@astrojs/node': link:../../.. astro: link:../../../../../astro + packages/integrations/node/test/fixtures/bad-urls: + specifiers: + '@astrojs/node': workspace:* + astro: workspace:* + dependencies: + '@astrojs/node': link:../../.. + astro: link:../../../../../astro + packages/integrations/node/test/fixtures/encoded: specifiers: '@astrojs/node': workspace:* @@ -4276,46 +4284,6 @@ packages: - react dev: false - /@astrojs/markdoc/0.1.0_astro@2.1.9: - resolution: {integrity: sha512-t+9pDDi8JpAoUfkHI7V8lGxrtbYx4nx3QZ5OOdbMtj5BTUqyR+rVQyA5dRcIsEFvg2Wfqb/BqsjpXOrt75s4UA==} - engines: {node: '>=16.12.0'} - peerDependencies: - astro: '*' - dependencies: - '@markdoc/markdoc': 0.2.2 - astro: 2.1.9 - esbuild: 0.17.12 - gray-matter: 4.0.3 - kleur: 4.1.5 - zod: 3.20.6 - transitivePeerDependencies: - - '@types/react' - - react - dev: false - - /@astrojs/markdown-remark/2.1.2_astro@2.1.9: - resolution: {integrity: sha512-rYkmFEv2w7oEk6ZPgxHkhWzwcxSUGc1vJU0cbCu5sHF8iFNnc1cmMsjXWa5DrU5sCEf8VVYE1iFlbbnFzvHQJw==} - peerDependencies: - astro: '*' - dependencies: - '@astrojs/prism': 2.1.1 - astro: 2.1.9 - github-slugger: 1.5.0 - import-meta-resolve: 2.2.1 - rehype-raw: 6.1.1 - rehype-stringify: 9.0.3 - remark-gfm: 3.0.1 - remark-parse: 10.0.1 - remark-rehype: 10.1.0 - remark-smartypants: 2.0.0 - shiki: 0.11.1 - unified: 10.1.2 - unist-util-visit: 4.1.2 - vfile: 5.3.7 - transitivePeerDependencies: - - supports-color - dev: false - /@astrojs/markdown-remark/2.1.2_astro@packages+astro: resolution: {integrity: sha512-rYkmFEv2w7oEk6ZPgxHkhWzwcxSUGc1vJU0cbCu5sHF8iFNnc1cmMsjXWa5DrU5sCEf8VVYE1iFlbbnFzvHQJw==} peerDependencies: @@ -4366,12 +4334,6 @@ packages: - supports-color dev: false - /@astrojs/node/1.1.0: - resolution: {integrity: sha512-4KkCEFYtmTUSvU49UZSJD/VQfD/oKzf0ld8COjFW1pxfquBgvevLxRVpYLRanZB20L3c8/xyyQpDq7zMSMqQrg==} - dependencies: - '@astrojs/webapi': 1.1.1 - dev: false - /@astrojs/preact/1.2.0_preact@10.12.0: resolution: {integrity: sha512-Vm8rkBIE3cNlxhFoUO2Rsv5RxSP7x7Oi9J6qz8+91lwAIjdm6oyDOrrBmdGqsONJ1MqnjPG4EPmIZJGuFtz2SQ==} engines: {node: ^14.18.0 || >=16.12.0} @@ -4395,35 +4357,6 @@ packages: prismjs: 1.29.0 dev: false - /@astrojs/telemetry/2.1.0: - resolution: {integrity: sha512-P3gXNNOkRJM8zpnasNoi5kXp3LnFt0smlOSUXhkynfJpTJMIDrcMbKpNORN0OYbqpKt9JPdgRN7nsnGWpbH1ww==} - engines: {node: '>=16.12.0'} - dependencies: - ci-info: 3.7.1 - debug: 4.3.4 - dlv: 1.1.3 - dset: 3.1.2 - is-docker: 3.0.0 - is-wsl: 2.2.0 - undici: 5.20.0 - which-pm-runs: 1.1.0 - transitivePeerDependencies: - - supports-color - dev: false - - /@astrojs/webapi/1.1.1: - resolution: {integrity: sha512-yeUvP27PoiBK/WCxyQzC4HLYZo4Hg6dzRd/dTsL50WGlAQVCwWcqzVJrIZKvzNDNaW/fIXutZTmdj6nec0PIGg==} - dependencies: - global-agent: 3.0.0 - node-fetch: 3.3.0 - dev: false - - /@astrojs/webapi/2.1.0: - resolution: {integrity: sha512-sbF44s/uU33jAdefzKzXZaENPeXR0sR3ptLs+1xp9xf5zIBhedH2AfaFB5qTEv9q5udUVoKxubZGT3G1nWs6rA==} - dependencies: - undici: 5.20.0 - dev: false - /@babel/code-frame/7.18.6: resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} engines: {node: '>=6.9.0'} @@ -8860,79 +8793,6 @@ packages: ultrahtml: 0.1.3 dev: false - /astro/2.1.9: - resolution: {integrity: sha512-UkbG0lgue1b/t4yMI+AkAGEfdOwcPS2RUYQ/QIurtKjP6W5gtKQveRTBuHH7iwiBziH+z8Ecc5/OAALoXSvMlQ==} - engines: {node: '>=16.12.0', npm: '>=6.14.0'} - hasBin: true - peerDependencies: - sharp: ^0.31.3 - peerDependenciesMeta: - sharp: - optional: true - dependencies: - '@astrojs/compiler': 1.3.0 - '@astrojs/language-server': 0.28.3 - '@astrojs/markdown-remark': 2.1.2_astro@2.1.9 - '@astrojs/telemetry': 2.1.0 - '@astrojs/webapi': 2.1.0 - '@babel/core': 7.20.12 - '@babel/generator': 7.20.14 - '@babel/parser': 7.20.15 - '@babel/plugin-transform-react-jsx': 7.20.13_@babel+core@7.20.12 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 - '@types/babel__core': 7.20.0 - '@types/yargs-parser': 21.0.0 - acorn: 8.8.2 - boxen: 6.2.1 - chokidar: 3.5.3 - ci-info: 3.7.1 - common-ancestor-path: 1.0.1 - cookie: 0.5.0 - debug: 4.3.4 - deepmerge-ts: 4.3.0 - devalue: 4.2.3 - diff: 5.1.0 - es-module-lexer: 1.1.1 - estree-walker: 3.0.3 - execa: 6.1.0 - fast-glob: 3.2.12 - github-slugger: 2.0.0 - gray-matter: 4.0.3 - html-escaper: 3.0.3 - kleur: 4.1.5 - magic-string: 0.27.0 - mime: 3.0.0 - ora: 6.1.2 - path-to-regexp: 6.2.1 - preferred-pm: 3.0.3 - prompts: 2.4.2 - rehype: 12.0.1 - semver: 7.3.8 - server-destroy: 1.0.1 - shiki: 0.11.1 - slash: 4.0.0 - string-width: 5.1.2 - strip-ansi: 7.0.1 - supports-esm: 1.0.0 - tsconfig-resolver: 3.0.1 - typescript: 5.0.2 - unist-util-visit: 4.1.2 - vfile: 5.3.7 - vite: 4.1.2 - vitefu: 0.2.4_vite@4.1.2 - yargs-parser: 21.1.1 - zod: 3.20.6 - transitivePeerDependencies: - - '@types/node' - - less - - sass - - stylus - - sugarss - - supports-color - - terser - dev: false - /async-sema/3.1.1: resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} dev: false @@ -9146,10 +9006,6 @@ packages: /boolbase/1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - /boolean/3.2.0: - resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} - dev: false - /boxen/4.2.0: resolution: {integrity: sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==} engines: {node: '>=8'} @@ -9998,10 +9854,6 @@ packages: resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} engines: {node: '>=8'} - /detect-node/2.1.0: - resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - dev: false - /detective/5.2.1: resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} engines: {node: '>=0.8.0'} @@ -10245,10 +10097,6 @@ packages: is-date-object: 1.0.5 is-symbol: 1.0.4 - /es6-error/4.1.1: - resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - dev: false - /esbuild-android-64/0.15.18: resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==} engines: {node: '>=12'} @@ -11279,18 +11127,6 @@ packages: once: 1.4.0 dev: false - /global-agent/3.0.0: - resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} - engines: {node: '>=10.0'} - dependencies: - boolean: 3.2.0 - es6-error: 4.1.1 - matcher: 3.0.0 - roarr: 2.15.4 - semver: 7.3.8 - serialize-error: 7.0.1 - dev: false - /globals/11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -12253,10 +12089,6 @@ packages: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true - /json-stringify-safe/5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - dev: false - /json5/0.5.1: resolution: {integrity: sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==} hasBin: true @@ -12573,13 +12405,6 @@ packages: hasBin: true dev: false - /matcher/3.0.0: - resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} - engines: {node: '>=10'} - dependencies: - escape-string-regexp: 4.0.0 - dev: false - /mathjax-full/3.2.2: resolution: {integrity: sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==} dependencies: @@ -15049,18 +14874,6 @@ packages: dependencies: glob: 7.2.3 - /roarr/2.15.4: - resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} - engines: {node: '>=8.0'} - dependencies: - boolean: 3.2.0 - detect-node: 2.1.0 - globalthis: 1.0.3 - json-stringify-safe: 5.0.1 - semver-compare: 1.0.0 - sprintf-js: 1.1.2 - dev: false - /rollup-plugin-copy/3.4.0: resolution: {integrity: sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ==} engines: {node: '>=8.3'} @@ -15129,6 +14942,7 @@ packages: hasBin: true optionalDependencies: fsevents: 2.3.2 + dev: true /run-parallel/1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -15213,10 +15027,6 @@ packages: engines: {node: '>=6'} dev: true - /semver-compare/1.0.0: - resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} - dev: false - /semver/5.7.1: resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} hasBin: true @@ -15255,13 +15065,6 @@ packages: - supports-color dev: false - /serialize-error/7.0.1: - resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} - engines: {node: '>=10'} - dependencies: - type-fest: 0.13.1 - dev: false - /serialize-javascript/4.0.0: resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==} dependencies: @@ -15541,10 +15344,6 @@ packages: /sprintf-js/1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - /sprintf-js/1.1.2: - resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} - dev: false - /srcset-parse/1.1.0: resolution: {integrity: sha512-JWp4cG2eybkvKA1QUHGoNK6JDEYcOnSuhzNGjZuYUPqXreDl/VkkvP2sZW7Rmh+icuCttrR9ccb2WPIazyM/Cw==} dev: true @@ -16204,6 +16003,7 @@ packages: /type-fest/0.13.1: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} engines: {node: '>=10'} + dev: true /type-fest/0.16.0: resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} @@ -16667,7 +16467,7 @@ packages: esbuild: 0.16.17 postcss: 8.4.21 resolve: 1.22.1 - rollup: 3.20.1 + rollup: 3.14.0 optionalDependencies: fsevents: 2.3.2 @@ -16756,7 +16556,7 @@ packages: vite: optional: true dependencies: - vite: 4.1.2 + vite: 4.1.2_sass@1.58.0 dev: false /vitest/0.20.3: