Files
shaka-player/test/net/networking_utils_unit.js
T
Marc-Antoine 14821c30fb fix: Correctly extract file extension from URLs with dots in query params (#9946)
### Description

Fixes #9945 

The `getExtension` function (rewritten for performance in #9816) scans
the URI right-to-left, encountering dots in query parameter values (e.g.
hostnames like `app.example.com`, filenames like `en.vtt`) before
finding the `?` delimiter. This causes incorrect extension detection,
breaking MIME type resolution for URLs with signed parameters or other
query strings containing dots.

**Fix:** Split the single right-to-left scan into two passes:
1. **Left-to-right:** find the first `?` or `#` to establish the path
boundary
2. **Right-to-left:** scan backward within the path portion only,
looking for `.` or `/`

This preserves the lightweight character-scanning approach from #9816
without reintroducing the `goog.Uri` dependency.

**New test cases added:**
- Dots in query parameter values
(`?host=app.example.com&signature=abc.def`)
- Signed URLs with encoded paths and filenames in query strings
- Hostname-like values in query parameters (`?origin=cdn.example.co.uk`)
- No path extension with dotted query values (`/stream?file=video.mp4` →
`""`)
- Dots in fragment identifiers (`#t=1.5`)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:11:03 -07:00

160 lines
5.7 KiB
JavaScript

/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
const testGetMimeType = async (expertedMimeType, contentType) => {
const netEngine = new shaka.test.FakeNetworkingEngine()
.setHeaders('dummy://foo', {'content-type': contentType});
const mimeType = await shaka.net.NetworkingUtils
.getMimeType('dummy://foo', netEngine,
shaka.net.NetworkingEngine.defaultRetryParameters());
expect(mimeType).toBe(expertedMimeType);
};
describe('NetworkingUtils', () => {
describe('getMimeType', () => {
it('test correct mimeType', () => {
testGetMimeType('application/dash+xml', 'application/dash+xml');
});
it('test mimeType with charset', () => {
testGetMimeType('application/dash+xml',
'application/dash+xml;charset=UTF-8');
});
it('test content-type with uppercase letters', () => {
testGetMimeType('application/dash+xml',
'Application/Dash+XML');
});
it('test content-type with uppercase letters and charset', () => {
testGetMimeType('text/html',
'Text/HTML;Charset="utf-8"');
});
});
describe('getExtension', () => {
it('should return the extension in lowercase', () => {
const uri = 'https://example.com/file.TXT';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('txt');
});
it('should return empty string if there is no extension', () => {
const uri = 'https://example.com/file';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('');
});
it('should handle multiple dots correctly', () => {
const uri = 'https://example.com/archive.tar.gz';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('gz');
});
it('should return empty string for trailing slash', () => {
const uri = 'https://example.com/path/';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('');
});
it('should return empty string for empty path', () => {
const uri = 'https://example.com/';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('');
});
it('should return correct extension for local file path', () => {
const uri = 'file:///C:/Users/Alvaro/Documents/report.pdf';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('pdf');
});
it('should ignore query parameters when extracting extension', () => {
const uri = 'https://example.com/image.jpeg?size=large';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('jpeg');
});
it('should ignore hash fragments when extracting extension', () => {
const uri = 'https://example.com/video.mp4#section1';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('mp4');
});
it('should handle both query and hash together', () => {
const uri = 'https://example.com/document.docx?download=true#top';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('docx');
});
it('should return extension from relative URL with filename', () => {
const uri = './assets/image.PNG';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('png');
});
it('should return extension from relative URL with nested path', () => {
const uri = '../downloads/archive.zip';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('zip');
});
it('should return empty string from relative URL without extension', () => {
const uri = './scripts/run';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('');
});
it('should return extension from relative URL with query params', () => {
const uri = './data/file.json?version=2';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('json');
});
it('should return extension from relative URL with hash', () => {
const uri = './docs/manual.pdf#page=3';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('pdf');
});
it('should ignore dots in query parameter values', () => {
const uri =
'https://cdn.example.com/media/en.cmft' +
'?host=app.example.com&signature=abc.def';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('cmft');
});
it('should handle signed URLs with dots in query params', () => {
const uri =
'https://cdn.example.com/media/subtitle.vtt' +
'?algorithm=HMAC-SHA256&credential=key1234' +
'&signed-headers=host&signature=abcdef.1234567890' +
'&filename=subtitle.en.vtt';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('vtt');
});
it('should handle query with hostname-like values', () => {
const uri = 'https://example.com/video.mp4?origin=cdn.example.co.uk';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('mp4');
});
it('should handle query with dotted filename and no path extension', () => {
const uri = 'https://example.com/stream?file=video.mp4';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('');
});
it('should handle fragment with dots', () => {
const uri = 'https://example.com/file.mp3#t=1.5';
const result = shaka.net.NetworkingUtils.getExtension(uri);
expect(result).toBe('mp3');
});
});
});