Skip to content

Commit

Permalink
tunnel server: fix headers
Browse files Browse the repository at this point in the history
- remove `transfer-encoding`, `content-length`, `connection` headers to use default node chunked encoding when possible
- remove `accept-ranges` header because range requests are not compatible with injection
- transform `etag` to indicate content changes due to injection. also transform `if-match` and `if-none-match` in outgoing requests
  • Loading branch information
Roy Razon committed Oct 3, 2023
1 parent cd427bf commit 11b7225
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 5 deletions.
1 change: 1 addition & 0 deletions tunnel-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"license": "Apache-2.0",
"dependencies": {
"@fastify/request-context": "^5.0.0",
"@sindresorhus/fnv1a": "^3.0.0",
"content-type": "^1.0.5",
"cookies": "^0.8.0",
"fastify": "^4.22.2",
Expand Down
33 changes: 30 additions & 3 deletions tunnel-server/src/proxy/html-manipulation/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IncomingMessage, ServerResponse } from 'http'
import { IncomingMessage, ServerResponse, OutgoingHttpHeaders, IncomingHttpHeaders } from 'http'
import zlib from 'zlib'
import stream from 'stream'
import { parse as parseContentType } from 'content-type'
Expand All @@ -8,6 +8,22 @@ import { Logger } from 'pino'
import { InjectHtmlScriptTransform } from './inject-transform'
import { ScriptInjectionBase, ScriptInjection } from '../../tunnel-store'

const incomingEtagHeaders = ['if-none-match', 'if-match'] as const
export const removeEtagSuffix = (headers: IncomingHttpHeaders) => {
for (const header of incomingEtagHeaders) {
const value = headers[header]
if (value) {
headers[header] = value.replace(/\+preevy:[^"]+/, '')
}
}
}

const addEtagSuffix = (headers: OutgoingHttpHeaders, added: string) => {
if (headers.etag && typeof headers.etag === 'string') {
headers.etag = headers.etag.replace(/"$/, `+preevy:${added}"`)
}
}

const compressionsForContentEncoding = (
contentEncoding: string | undefined,
): [stream.Transform, stream.Transform] | undefined => {
Expand Down Expand Up @@ -45,11 +61,22 @@ const proxyWithInjection = (
injects: Omit<ScriptInjection, 'pathRegex'>[],
charset = 'utf-8',
) => {
res.writeHead(proxyRes.statusCode as number, { ...proxyRes.headers, 'transfer-encoding': '' })
const resHeaders = { ...proxyRes.headers }

const [input, output] = streamsForContentEncoding(proxyRes.headers['content-encoding'], proxyRes, res)
// enable default node chunked encoding when possible
delete resHeaders['transfer-encoding']
delete resHeaders['content-length']
delete resHeaders.connection

// ranges not compatible with injection
delete resHeaders['accept-ranges']

const transform = new InjectHtmlScriptTransform(injects)
addEtagSuffix(resHeaders, transform.scriptTagsEtag())

res.writeHead(proxyRes.statusCode as number, resHeaders)

const [input, output] = streamsForContentEncoding(proxyRes.headers['content-encoding'], proxyRes, res)

input
.pipe(iconv.decodeStream(charset))
Expand Down
11 changes: 10 additions & 1 deletion tunnel-server/src/proxy/html-manipulation/inject-transform.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable no-underscore-dangle */
import stream from 'stream'
import fnv1a from '@sindresorhus/fnv1a'
import { Parser } from 'htmlparser2'
import { ScriptInjection } from '../../tunnel-store'

Expand Down Expand Up @@ -49,6 +50,7 @@ export class InjectHtmlScriptTransform extends stream.Transform {
stringSoFar = ''
currentChunkOffset = 0
injected = false
private cachedScriptTags: string | undefined

constructor(readonly injects: Omit<ScriptInjection, 'pathRegex'>[]) {
super({ decodeStrings: false, encoding: 'utf-8' })
Expand All @@ -61,8 +63,15 @@ export class InjectHtmlScriptTransform extends stream.Transform {
}
}

public scriptTagsEtag() {
return fnv1a(this.scriptTags(), { size: 32 }).toString(36)
}

private scriptTags() {
return this.injects.map(scriptTag).join('')
if (this.cachedScriptTags === undefined) {
this.cachedScriptTags = this.injects.map(scriptTag).join('')
}
return this.cachedScriptTags
}

private headWithScriptTags() {
Expand Down
3 changes: 2 additions & 1 deletion tunnel-server/src/proxy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Claims, jwtAuthenticator, AuthenticationResult, AuthError, saasIdentity
import { SessionStore } from '../session'
import { BadGatewayError, BadRequestError, BasicAuthUnauthorizedError, RedirectError, UnauthorizedError, errorHandler, errorUpgradeHandler, tryHandler, tryUpgradeHandler } from '../http-server-helpers'
import { TunnelFinder, proxyRouter } from './router'
import { proxyResHandler } from './html-manipulation'
import { proxyResHandler, removeEtagSuffix } from './html-manipulation'

const loginRedirectUrl = (loginUrl: string) => ({ env, returnPath }: { env: string; returnPath?: string }) => {
const url = new URL(loginUrl)
Expand Down Expand Up @@ -142,6 +142,7 @@ export const proxy = ({

if (injects?.length) {
injectsMap.set(mutatedReq, injects)
removeEtagSuffix(mutatedReq.headers)
}

return theProxy.web(
Expand Down
5 changes: 5 additions & 0 deletions tunnel-server/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,11 @@
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==

"@sindresorhus/fnv1a@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/fnv1a/-/fnv1a-3.0.0.tgz#e8ce2e7c7738ec8c354867d38e3bfcde622b87ca"
integrity sha512-M6pmbdZqAryzjZ4ELAzrdCMoMZk5lH/fshKrapfSeXdf2W+GDqZvPmfXaNTZp43//FVbSwkTPwpEMnehSyskkQ==

"@sinonjs/commons@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72"
Expand Down

0 comments on commit 11b7225

Please sign in to comment.