diff --git a/src/bootstrap.ts b/src/bootstrap.ts index 5798623..cd088ba 100644 --- a/src/bootstrap.ts +++ b/src/bootstrap.ts @@ -1,5 +1,5 @@ import { fastifyHttpProxy } from '@fastify/http-proxy' -import { fastify, type FastifyInstance } from 'fastify' +import { fastify, type FastifyInstance, type FastifyRequest } from 'fastify' import { config } from './config.js' import { rewriteRequestHeaders } from './http/headers.js' @@ -35,11 +35,11 @@ export async function bootstrap(): Promise { http2: false, replyOptions: { rewriteRequestHeaders: (request, headers) => { - return rewriteRequestHeaders( + return rewriteRequestHeaders({ + request: request as FastifyRequest, headers, - request.ip, - targetHost.replace('https://', '') - ) + host: targetHost.replace('https://', ''), + }) }, }, }) diff --git a/src/http/headers.test.ts b/src/http/headers.test.ts index 63e27c9..4c29db2 100644 --- a/src/http/headers.test.ts +++ b/src/http/headers.test.ts @@ -1,72 +1,116 @@ +import type { FastifyRequest, HTTPMethods } from 'fastify' + import { config, initConfig } from '../config.js' import { rewriteRequestHeaders } from './headers.js' describe('HTTP server', () => { + const ip = '192.168.1.1' + const host = 'example.com' + + function getMockRequest(method?: HTTPMethods): FastifyRequest { + return { + ip, + ...(method && { method }), + } as FastifyRequest + } + beforeEach(() => { initConfig() }) it('Should keep existing headers', () => { - const ip = '192.168.1.1' + const originalHeaders = { + referer: 'https://www.test.com', + } - const headers = rewriteRequestHeaders( - { - referer: 'https://www.test.com', - }, - ip, - 'example.com' - ) + const headers = rewriteRequestHeaders({ + headers: originalHeaders, + request: getMockRequest(), + host, + }) expect(headers).toEqual({ - referer: 'https://www.test.com', + ...originalHeaders, 'x-forwarded-for': ip, 'x-forwarded-by': config.appName, 'x-forwarded-host': 'example.com', - host: 'example.com', + host, }) }) - it('Should delete forbidden headers', () => { - const ip = '192.168.1.1' + it('Should delete forbidden HTTP2 headers', () => { + const originalHeaders = { + referer: 'https://www.test.com', + } - const headers = rewriteRequestHeaders( - { - referer: 'https://www.test.com', + const headers = rewriteRequestHeaders({ + headers: { + ...originalHeaders, connection: 'keep-alive', upgrade: 'websocket', }, - ip, - 'example.com' - ) + request: getMockRequest(), + host, + }) expect(headers).toEqual({ - referer: 'https://www.test.com', + ...originalHeaders, 'x-forwarded-for': ip, 'x-forwarded-by': config.appName, 'x-forwarded-host': 'example.com', - host: 'example.com', + host, }) }) it('Should respect x-forwarded-for', () => { - const ip = '192.168.1.1' + const originalHeaders = { + referer: 'https://www.test.com', + } - const headers = rewriteRequestHeaders( - { - referer: 'https://www.test.com', + const headers = rewriteRequestHeaders({ + headers: { + ...originalHeaders, 'x-forwarded-for': '127.0.0.1', }, - ip, - 'example.com' - ) + request: getMockRequest(), + host, + }) expect(headers).toEqual({ - referer: 'https://www.test.com', + ...originalHeaders, 'x-forwarded-for': '127.0.0.1', 'x-forwarded-by': config.appName, 'x-forwarded-host': 'example.com', - host: 'example.com', + host, }) }) + + describe('Should remove content headers if method is not PUT, PATCH or POST', () => { + for (const method of ['GET', 'HEAD', 'OPTIONS', 'DELETE']) { + it(`Should remove content headers if method is ${method}`, () => { + const originalHeaders = { + referer: 'https://www.test.com', + 'x-forwarded-for': '127.0.0.1', + 'content-type': 'application/json', + 'content-length': '123', + 'transfer-encoding': 'chunked', + } + + const headers = rewriteRequestHeaders({ + headers: originalHeaders, + request: { ip, method } as FastifyRequest, + host, + }) + + expect(headers).toEqual({ + referer: originalHeaders.referer, + 'x-forwarded-for': '127.0.0.1', + 'x-forwarded-by': config.appName, + 'x-forwarded-host': 'example.com', + host, + }) + }) + } + }) }) diff --git a/src/http/headers.ts b/src/http/headers.ts index 41d496a..31367ba 100644 --- a/src/http/headers.ts +++ b/src/http/headers.ts @@ -1,20 +1,37 @@ import type { IncomingHttpHeaders } from 'node:http' +import type { FastifyRequest } from 'fastify' + import { config } from '../config.js' -export function rewriteRequestHeaders( - headers: IncomingHttpHeaders, - ip: string, +export function rewriteRequestHeaders({ + headers, + request, + host, +}: { + headers: IncomingHttpHeaders + request: FastifyRequest host: string -): IncomingHttpHeaders { +}): IncomingHttpHeaders { const filteredHeaders: IncomingHttpHeaders = { ...headers, - 'x-forwarded-for': headers['x-forwarded-for'] ?? ip, + 'x-forwarded-for': headers['x-forwarded-for'] ?? request.ip, 'x-forwarded-host': host, 'x-forwarded-by': config.appName, host, } + const expectsPayload = + request.method === 'PUT' || + request.method === 'POST' || + request.method === 'PATCH' + + if (!expectsPayload) { + delete filteredHeaders['content-length'] + delete filteredHeaders['content-type'] + delete filteredHeaders['transfer-encoding'] + } + // {@link https://github.com/smartlook/smartlook-relay-proxy/pull/106} delete filteredHeaders.connection delete filteredHeaders.upgrade