Skip to content

Commit df9f55f

Browse files
Merge pull request #196 from wahajahmed010/fix/193-accept-encoding-wildcard
Fix: handle Accept-Encoding wildcard (*) per RFC 7231
2 parents 32f3210 + c266888 commit df9f55f

2 files changed

Lines changed: 100 additions & 1 deletion

File tree

src/http/handlers/compression/compression-handler.spec.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,80 @@ describe('CompressionHandler', () => {
337337
expect(setHeaderSpy).toHaveBeenCalledWith('content-encoding', 'deflate');
338338
});
339339

340+
it('should compress response when Accept-Encoding is wildcard (*)', () => {
341+
const handler = new CompressionHandler({ brotli: true });
342+
setupMock('*', 'text/plain');
343+
344+
const stream = handler.createCompressionStream(
345+
mockReq as UwsRequest,
346+
mockRes as UwsResponse,
347+
LARGE_DATA
348+
);
349+
350+
expect(stream).toBeInstanceOf(Transform);
351+
// Should pick brotli as highest priority supported encoding
352+
expect(setHeaderSpy).toHaveBeenCalledWith('content-encoding', 'br');
353+
});
354+
355+
it('should expand wildcard to supported encodings not explicitly listed', () => {
356+
const handler = new CompressionHandler();
357+
setupMock('gzip, *', 'text/plain');
358+
359+
const stream = handler.createCompressionStream(
360+
mockReq as UwsRequest,
361+
mockRes as UwsResponse,
362+
LARGE_DATA
363+
);
364+
365+
expect(stream).toBeInstanceOf(Transform);
366+
// gzip explicitly listed with higher priority than wildcard-expanded encodings
367+
expect(setHeaderSpy).toHaveBeenCalledWith('content-encoding', 'gzip');
368+
});
369+
370+
it('should respect explicitly rejected encodings with wildcard', () => {
371+
const handler = new CompressionHandler();
372+
setupMock('gzip;q=0, *', 'text/plain');
373+
374+
const stream = handler.createCompressionStream(
375+
mockReq as UwsRequest,
376+
mockRes as UwsResponse,
377+
LARGE_DATA
378+
);
379+
380+
expect(stream).toBeInstanceOf(Transform);
381+
// gzip rejected (q=0), wildcard should expand to deflate only (brotli not enabled)
382+
expect(setHeaderSpy).toHaveBeenCalledWith('content-encoding', 'deflate');
383+
});
384+
385+
it('should respect explicitly rejected encodings with wildcard when brotli enabled', () => {
386+
const handler = new CompressionHandler({ brotli: true });
387+
setupMock('gzip;q=0, *', 'text/plain');
388+
389+
const stream = handler.createCompressionStream(
390+
mockReq as UwsRequest,
391+
mockRes as UwsResponse,
392+
LARGE_DATA
393+
);
394+
395+
expect(stream).toBeInstanceOf(Transform);
396+
// gzip rejected (q=0), wildcard expands to br and deflate
397+
// br has highest priority
398+
expect(setHeaderSpy).toHaveBeenCalledWith('content-encoding', 'br');
399+
});
400+
401+
it('should return null for wildcard with all supported encodings rejected', () => {
402+
const handler = new CompressionHandler();
403+
setupMock('gzip;q=0, deflate;q=0, *;q=0', 'text/plain');
404+
405+
const stream = handler.createCompressionStream(
406+
mockReq as UwsRequest,
407+
mockRes as UwsResponse,
408+
LARGE_DATA
409+
);
410+
411+
expect(stream).toBeNull();
412+
});
413+
340414
it('should create stream without body check when body not provided', () => {
341415
const handler = new CompressionHandler();
342416
setupMock('gzip', 'text/plain');

src/http/handlers/compression/compression-handler.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,9 @@ export class CompressionHandler {
429429
*/
430430
private parseAcceptEncoding(acceptEncoding: string): string[] {
431431
const encodings: Array<{ encoding: string; quality: number; position: number }> = [];
432+
const explicitlyRejected: Set<string> = new Set();
433+
let hasWildcard = false;
434+
let wildcardQuality = 0;
432435

433436
// Encoding preference order (higher is better)
434437
const encodingPriority: Record<string, number> = {
@@ -456,7 +459,12 @@ export class CompressionHandler {
456459
quality = Number.isNaN(parsed) ? 1.0 : Math.min(1, Math.max(0, parsed));
457460
}
458461

459-
if (quality > 0) {
462+
if (encoding === '*') {
463+
hasWildcard = true;
464+
wildcardQuality = quality;
465+
} else if (quality === 0) {
466+
explicitlyRejected.add(encoding.trim().toLowerCase());
467+
} else {
460468
encodings.push({
461469
encoding: encoding.trim().toLowerCase(),
462470
quality,
@@ -465,6 +473,23 @@ export class CompressionHandler {
465473
}
466474
}
467475

476+
// If we have a wildcard with quality > 0, expand it to supported encodings
477+
// per RFC 7231: Accept-Encoding: * means the client accepts any encoding
478+
if (hasWildcard && wildcardQuality > 0) {
479+
const supportedEncodings = Object.keys(encodingPriority);
480+
481+
for (const encoding of supportedEncodings) {
482+
const isExplicitlyListed = encodings.some((e) => e.encoding === encoding);
483+
if (!isExplicitlyListed && !explicitlyRejected.has(encoding)) {
484+
encodings.push({
485+
encoding,
486+
quality: wildcardQuality,
487+
position: parts.length, // Place wildcard entries after explicit ones
488+
});
489+
}
490+
}
491+
}
492+
468493
// Sort by:
469494
// 1. Quality (highest first)
470495
// 2. Encoding priority (br > gzip > deflate)

0 commit comments

Comments
 (0)