Skip to content

Commit

Permalink
fix: decompress right-to-left
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed Oct 23, 2024
1 parent 43b0035 commit bc537ac
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 44 deletions.
29 changes: 15 additions & 14 deletions src/interceptors/fetch/utils/decompression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,22 @@ function createDecompressionStream(
return null
}

const transformers: Array<TransformStream> = []
const transformers = codings.reduceRight<Array<TransformStream>>(
(transformers, coding) => {
if (coding === 'gzip' || coding === 'x-gzip') {
return transformers.concat(new DecompressionStream('gzip'))
} else if (coding === 'deflate') {
return transformers.concat(new DecompressionStream('deflate'))
} else if (coding === 'br') {
return transformers.concat(new BrotliDecompressionStream())
} else {
transformers.length = 0
}

for (let i = 0; i < codings.length; i++) {
const coding = codings[i]

if (coding === 'gzip' || coding === 'x-gzip') {
transformers.push(new DecompressionStream('gzip'))
} else if (coding === 'deflate') {
transformers.push(new DecompressionStream('deflate'))
} else if (coding === 'br') {
transformers.push(new BrotliDecompressionStream())
} else {
transformers.length = 0
}
}
return transformers
},
[]
)

return new PipelineStream(transformers)
}
Expand Down
39 changes: 20 additions & 19 deletions test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,24 +319,25 @@ export const useCors: RequestHandler = (req, res, next) => {
return next()
}

function compose(...fns: Array<Function>) {
return fns.reduce((f, g) => {
return (...args: Array<unknown>) => f(g(...args))
})
}
/**
* Compress the given data using the specified `Content-Encoding` codings
* left-to-right.
*/
export function compressResponse(
codings: Array<'gzip' | 'x-gzip' | 'deflate' | 'br'>,
input: string
) {
let output = Buffer.from(input)

for (const coding of codings) {
if (coding === 'gzip' || coding === 'x-gzip') {
output = zlib.gzipSync(output)
} else if (coding === 'deflate') {
output = zlib.deflateSync(output)
} else if (coding === 'br') {
output = zlib.brotliCompressSync(output)
}
}

export function compressResponse(codings: Array<string>) {
return compose(
...codings.map((coding) => {
if (coding === 'gzip' || coding === 'x-gzip') {
return zlib.gzipSync
} else if (coding === 'deflate') {
return zlib.deflateSync
} else if (coding === 'br') {
return zlib.brotliCompressSync
}

return (data: string) => data
})
)
return output
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ const server = new HttpServer((app) => {
app.use(useCors)
app.get('/resource', (req, res) => {
const acceptEncoding = req.header('x-accept-encoding')
const codings = parseContentEncoding(acceptEncoding || '')
const codings = parseContentEncoding(acceptEncoding || '') as any[]

res
.set('content-encoding', acceptEncoding)
.end(compressResponse(codings)('hello world'))
.end(compressResponse(codings, 'hello world'))
})
})

Expand Down
41 changes: 32 additions & 9 deletions test/modules/fetch/compliance/response-content-encoding.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @vitest-environment node
import zlib from 'node:zlib'
import { it, expect, beforeAll, afterEach, afterAll } from 'vitest'
import { HttpServer } from '@open-draft/test-server/http'
import { compressResponse } from '../../../helpers'
Expand All @@ -9,11 +8,11 @@ import { parseContentEncoding } from '../../../../src/interceptors/fetch/utils/d
const httpServer = new HttpServer((app) => {
app.get('/compressed', (req, res) => {
const acceptEncoding = req.header('accept-encoding')
const codings = parseContentEncoding(acceptEncoding || '')
const codings = parseContentEncoding(acceptEncoding || '') as any[]

res
.set('content-encoding', acceptEncoding)
.end(compressResponse(codings)('hello world'))
.end(compressResponse(codings, 'hello world'))
})
})

Expand All @@ -36,7 +35,7 @@ afterAll(async () => {
it('decompresses a mocked "content-encoding: gzip" response body', async () => {
interceptor.on('request', ({ controller }) => {
controller.respondWith(
new Response(zlib.gzipSync('hello world'), {
new Response(compressResponse(['gzip'], 'hello world'), {
headers: {
'content-encoding': 'gzip',
},
Expand All @@ -58,7 +57,7 @@ it('decompresses a bypassed "content-encoding: gzip" response body', async () =>
it('decompresses a mocked "content-encoding: x-gzip" response body', async () => {
interceptor.on('request', ({ controller }) => {
controller.respondWith(
new Response(zlib.gzipSync('hello world'), {
new Response(compressResponse(['gzip'], 'hello world'), {
headers: {
'content-encoding': 'x-gzip',
},
Expand All @@ -80,7 +79,7 @@ it('decompresses a bypassed "content-encoding: x-gzip" response body', async ()
it('decompresses a mocked "content-encoding: deflate" response body', async () => {
interceptor.on('request', ({ controller }) => {
controller.respondWith(
new Response(zlib.deflateSync('hello world'), {
new Response(compressResponse(['deflate'], 'hello world'), {
headers: {
'content-encoding': 'deflate',
},
Expand All @@ -102,7 +101,7 @@ it('decompresses a bypassed "content-encoding: deflate" response body', async ()
it('decompresses a mocked "content-encoding: br" response body', async () => {
interceptor.on('request', ({ controller }) => {
controller.respondWith(
new Response(zlib.brotliCompressSync('hello world'), {
new Response(compressResponse(['br'], 'hello world'), {
headers: {
'content-encoding': 'br',
},
Expand All @@ -121,10 +120,32 @@ it('decompresses a bypassed "content-encoding: br" response body', async () => {
expect(await response.text()).toBe('hello world')
})

it('decompresses a mocked "content-encoding: gzip, deflate" response body', async () => {
interceptor.on('request', ({ controller }) => {
controller.respondWith(
new Response(compressResponse(['gzip', 'deflate'], 'hello world'), {
headers: {
'content-encoding': 'gzip, deflate',
},
})
)
})

const response = await fetch('http://localhost/resource')
expect(await response.text()).toBe('hello world')
})

it('decompresses a bypassed "content-encoding: gzip, deflate" response body', async () => {
const response = await fetch(httpServer.http.url('/compressed'), {
headers: { 'accept-encoding': 'gzip, deflate' },
})
expect(await response.text()).toBe('hello world')
})

it('decompresses a mocked "content-encoding: gzip, br" response body', async () => {
interceptor.on('request', ({ controller }) => {
controller.respondWith(
new Response(compressResponse(['gzip', 'br'])('hello world'), {
new Response(compressResponse(['gzip', 'br'], 'hello world'), {
headers: {
'content-encoding': 'gzip, br',
},
Expand All @@ -137,6 +158,8 @@ it('decompresses a mocked "content-encoding: gzip, br" response body', async ()
})

it('decompresses a bypassed "content-encoding: gzip, br" response body', async () => {
const response = await fetch(httpServer.http.url('/compressed'))
const response = await fetch(httpServer.http.url('/compressed'), {
headers: { 'accept-encoding': 'gzip, br' },
})
expect(await response.text()).toBe('hello world')
})

0 comments on commit bc537ac

Please sign in to comment.