Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(node): Fix malformed URLs crashing the server in certain cases #6746

Merged
merged 4 commits into from
Apr 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cyan-sheep-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/node': patch
---

Fix malformed URLs crashing the server in certain cases
2 changes: 1 addition & 1 deletion packages/astro/test/fixtures/ssr-api-route/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/node": "^1.1.0",
"@astrojs/node": "workspace:*",
"astro": "workspace:*"
}
}
22 changes: 19 additions & 3 deletions packages/integrations/node/src/http-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
});
Expand Down
46 changes: 46 additions & 0 deletions packages/integrations/node/test/bad-urls.test.js
Original file line number Diff line number Diff line change
@@ -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(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certain URLs here return 500 instead of 400 because they pass the encodeUri, decodeUri, new URI stuff, but they fail later on in the server.

fetchResult.status,
`${weirdUrl} returned something else than 400 or 500`
);
}
const stillWork = await fixture.fetch('/');
const text = await stillWork.text();
expect(text).to.equal('<!DOCTYPE html>\nHello!');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@test/nodejs-badurls",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*",
"@astrojs/node": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello!
Loading