From 2250b5b5e9a5a654cfa1af2191515875d0924ea1 Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Wed, 15 May 2024 19:56:55 +0800 Subject: [PATCH] Add monkey-patching to QUERY method Use X-Original-Method for forcing a POST method to become a QUERY method. --- .../core/src/backend/servers/http/core.ts | 50 +++++++++++++++++++ .../servers/http/decorators/method/index.ts | 15 ++++++ .../core/src/backend/servers/http/utils.ts | 1 + .../core/test/handlers/http/default.test.ts | 14 +++++- packages/core/test/utils.ts | 1 + 5 files changed, 79 insertions(+), 2 deletions(-) diff --git a/packages/core/src/backend/servers/http/core.ts b/packages/core/src/backend/servers/http/core.ts index 629344e..25df2e7 100644 --- a/packages/core/src/backend/servers/http/core.ts +++ b/packages/core/src/backend/servers/http/core.ts @@ -1,4 +1,5 @@ import http, { createServer as httpCreateServer } from 'http'; +import { HTTPParser } from 'http-parser-js'; import { createServer as httpCreateSecureServer } from 'https'; import {constants,} from 'http2'; import * as v from 'valibot'; @@ -195,6 +196,7 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr const isHttps = 'key' in serverParams && 'cert' in serverParams; const theRes = new CqrsEventEmitter(); + http.METHODS.push('QUERY'); const server = isHttps ? httpCreateSecureServer({ key: serverParams.key, @@ -780,6 +782,54 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr )(reqRaw, res); }; + // server.on('connection', (socket) => { + // if (!HTTPParser.methods.includes('QUERY')) { + // HTTPParser.methods.push('QUERY'); + // } + // const httpParserMut = (HTTPParser as unknown as Record); + // console.log(httpParserMut.methods); + // httpParserMut.socket = socket; + // httpParserMut.remove = () => { + // // noop + // }; + // httpParserMut.free = () => { + // // noop + // }; + // socket.parser = httpParserMut; + // }); + + // server.on('connection', (socket) => { + // let newLineOffset; + // let receiveBuffer = Buffer.from(''); + // const listeners = socket.listeners('data'); + // const oldListener = listeners[0]; + // + // function newListener(this: any, d, start, end) { + // console.log(d.slice(start, end).toString('utf-8')); + // + // receiveBuffer = Buffer.concat([receiveBuffer, d.slice(start, end)]); + // if ((newLineOffset = receiveBuffer.toString('ascii').indexOf('\n')) > -1) { + // var firstLineParts = receiveBuffer.slice(0, newLineOffset).toString().split(' '); + // firstLineParts[0] = firstLineParts[0].replace(/^QUERY$/ig, 'POST'); + // //firstLineParts[2] = firstLineParts[2].replace(/^ICE\//ig, 'HTTP/'); + // receiveBuffer = Buffer.concat([ + // Buffer.from( + // firstLineParts.join(' ') + '\r\n' + + // 'Content-Length: 9007199254740992\r\n' + // ), + // receiveBuffer.slice(newLineOffset + 1) + // ]); + // } + // + // console.log(receiveBuffer.toString('utf-8')); + // oldListener.apply(this, d); + // } + // + // socket.on('data', newListener); + // socket.off('data', oldListener); + // }); + + // TODO create server directly from net.createConnection() server.on('request', handleRequest); return { diff --git a/packages/core/src/backend/servers/http/decorators/method/index.ts b/packages/core/src/backend/servers/http/decorators/method/index.ts index a19084d..f89fd17 100644 --- a/packages/core/src/backend/servers/http/decorators/method/index.ts +++ b/packages/core/src/backend/servers/http/decorators/method/index.ts @@ -1,7 +1,22 @@ import {RequestDecorator} from '../../../../common'; +const WHITELISTED_METHODS = [ + 'QUERY' +] as const; + export const decorateRequestWithMethod: RequestDecorator = (req) => { req.method = req.method?.trim().toUpperCase() ?? ''; + if (req.method === 'POST') { + const spoofedMethod = req.headers['x-original-method']; + if (Array.isArray(spoofedMethod)) { + return req; + } + const whitelistedMethod = WHITELISTED_METHODS.find((s) => s === spoofedMethod); + + if (typeof whitelistedMethod === 'string') { + req.method = whitelistedMethod; + } + } return req; }; diff --git a/packages/core/src/backend/servers/http/utils.ts b/packages/core/src/backend/servers/http/utils.ts index cad8657..c9c522c 100644 --- a/packages/core/src/backend/servers/http/utils.ts +++ b/packages/core/src/backend/servers/http/utils.ts @@ -6,6 +6,7 @@ export const isTextMediaType = (mediaType: string) => ( || [ 'application/json', 'application/xml', + 'application/x-www-form-urlencoded', ...PATCH_CONTENT_TYPES, ].includes(mediaType) ); diff --git a/packages/core/test/handlers/http/default.test.ts b/packages/core/test/handlers/http/default.test.ts index 1222f98..6015a30 100644 --- a/packages/core/test/handlers/http/default.test.ts +++ b/packages/core/test/handlers/http/default.test.ts @@ -105,7 +105,7 @@ describe('happy path', () => { }); })); - describe.skip('querying collections', () => { + describe('querying collections', () => { beforeEach(() => { vi .spyOn(DummyDataSource.prototype, 'getMultiple') @@ -121,11 +121,21 @@ describe('happy path', () => { }); it('returns data', async () => { + // const [res, resData] = await client({ + // method: 'QUERY', + // path: `${BASE_PATH}/pianos`, + // headers: { + // 'content-type': 'application/x-www-form-urlencoded', + // }, + // body: 'foo=bar', + // }); + const [res, resData] = await client({ - method: 'QUERY', + method: 'POST', path: `${BASE_PATH}/pianos`, headers: { 'content-type': 'application/x-www-form-urlencoded', + 'x-original-method': 'QUERY', }, body: 'foo=bar', }); diff --git a/packages/core/test/utils.ts b/packages/core/test/utils.ts index 3211781..3468477 100644 --- a/packages/core/test/utils.ts +++ b/packages/core/test/utils.ts @@ -31,6 +31,7 @@ export const createTestClient = (options: Omit