Skip to content

Commit

Permalink
tunnel server: fix encoding
Browse files Browse the repository at this point in the history
when injecting, responses were not compressed.
also fixed wrong chunked response.
  • Loading branch information
Roy Razon committed Sep 19, 2023
1 parent a0905a3 commit 3f0890e
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 14 deletions.
40 changes: 27 additions & 13 deletions tunnel-server/src/proxy/html-manipulation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,52 @@ import zlib from 'zlib'
import stream from 'stream'
import { parse as parseContentType } from 'content-type'
import iconv from 'iconv-lite'
import { inspect } from 'node:util'
import { INJECT_SCRIPTS_HEADER } from '../common'
import { InjectHtmlScriptTransform } from './inject-transform'
import { ScriptInjection } from '../../tunnel-store'

const compressionsForContentEncoding = (contentEncoding: string) => {
const compressionsForContentEncoding = (
contentEncoding: string | undefined,
): [stream.Transform, stream.Transform] | undefined => {
if (!contentEncoding || contentEncoding === 'identity') {
return undefined
}
if (contentEncoding === 'gzip') {
return [zlib.createGunzip(), zlib.createGzip()] as const
return [zlib.createGunzip(), zlib.createGzip()]
}
if (contentEncoding === 'deflate') {
return [zlib.createInflate(), zlib.createDeflate()] as const
return [zlib.createInflate(), zlib.createDeflate()]
}
if (contentEncoding === 'br') {
return [zlib.createBrotliDecompress(), zlib.createBrotliCompress()] as const
return [zlib.createBrotliDecompress(), zlib.createBrotliCompress()]
}
if (contentEncoding === 'identity') {
return undefined
throw new Error(`unsupported content encoding: ${inspect(contentEncoding)}`)
}

const streamsForContentEncoding = (
contentEncoding: string | undefined,
input: stream.Readable,
output: stream.Writable,
): [stream.Readable, stream.Writable] => {
const compress = compressionsForContentEncoding(contentEncoding)
if (!compress) {
return [input, output]
}
throw new Error(`unsupported content encoding: "${contentEncoding}"`)
compress[1].pipe(output)
return [input.pipe(compress[0]), compress[1]]
}

export const injectScripts = (
proxyRes: stream.Readable & Pick<IncomingMessage, 'headers' | 'statusCode'>,
req: Pick<IncomingMessage, 'headers'>,
res: stream.Writable & Pick<ServerResponse<IncomingMessage>, 'writeHead'>,
) => {
res.writeHead(proxyRes.statusCode as number, proxyRes.headers)

const injectsStr = req.headers[INJECT_SCRIPTS_HEADER] as string | undefined
const contentTypeHeader = proxyRes.headers['content-type']

if (!injectsStr || !contentTypeHeader) {
res.writeHead(proxyRes.statusCode as number, proxyRes.headers)
proxyRes.pipe(res)
return undefined
}
Expand All @@ -44,15 +59,14 @@ export const injectScripts = (
} = parseContentType(contentTypeHeader)

if (contentType !== 'text/html') {
res.writeHead(proxyRes.statusCode as number, proxyRes.headers)
proxyRes.pipe(res)
return undefined
}

const compress = compressionsForContentEncoding(proxyRes.headers['content-encoding'] || 'identity')
res.writeHead(proxyRes.statusCode as number, { ...proxyRes.headers, 'transfer-encoding': '' })

const [input, output] = compress
? [proxyRes.pipe(compress[0]), res.pipe(compress[1])]
: [proxyRes, res]
const [input, output] = streamsForContentEncoding(proxyRes.headers['content-encoding'], proxyRes, res)

const injects = JSON.parse(injectsStr) as Omit<ScriptInjection, 'pathRegex'>[]
const transform = new InjectHtmlScriptTransform(injects)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export class InjectHtmlScriptTransform extends stream.Transform {
override _transform(chunk: string, _encoding: BufferEncoding | 'buffer', callback: stream.TransformCallback): void {
if (typeof chunk !== 'string') {
// chunk must be string rather than Buffer so htmlDetector offsets would be in character units, not bytes
throw new Error(`Invalid chunk, expected string, received ${typeof chunk}: ${chunk}`)
throw new Error(`Invalid chunk, expected string, received ${Buffer.isBuffer(chunk) ? 'Buffer' : typeof chunk}: ${chunk}`)
}

if (this.injected) {
Expand Down

0 comments on commit 3f0890e

Please sign in to comment.