Browse Source

Add monkey-patching to QUERY method

Use X-Original-Method for forcing a POST method to become a QUERY
method.
master
TheoryOfNekomata 6 months ago
parent
commit
2250b5b5e9
5 changed files with 79 additions and 2 deletions
  1. +50
    -0
      packages/core/src/backend/servers/http/core.ts
  2. +15
    -0
      packages/core/src/backend/servers/http/decorators/method/index.ts
  3. +1
    -0
      packages/core/src/backend/servers/http/utils.ts
  4. +12
    -2
      packages/core/test/handlers/http/default.test.ts
  5. +1
    -0
      packages/core/test/utils.ts

+ 50
- 0
packages/core/src/backend/servers/http/core.ts View File

@@ -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<string, unknown>);
// 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 {


+ 15
- 0
packages/core/src/backend/servers/http/decorators/method/index.ts View File

@@ -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;
};

+ 1
- 0
packages/core/src/backend/servers/http/utils.ts View File

@@ -6,6 +6,7 @@ export const isTextMediaType = (mediaType: string) => (
|| [
'application/json',
'application/xml',
'application/x-www-form-urlencoded',
...PATCH_CONTENT_TYPES,
].includes(mediaType)
);


+ 12
- 2
packages/core/test/handlers/http/default.test.ts View File

@@ -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',
});


+ 1
- 0
packages/core/test/utils.ts View File

@@ -31,6 +31,7 @@ export const createTestClient = (options: Omit<RequestOptions, 'method' | 'path'
const headers: OutgoingHttpHeaders = {
...(options.headers ?? {}),
...etcAdditionalHeaders,
...(params.headers ?? {}),
};

let contentTypeHeader: string | undefined;


Loading…
Cancel
Save