*/ public static function traversalProvider(): iterable { yield 'dot-dot slash' => ['../master.m3u8']; yield 'dot-dot backslash' => ['..\\master.m3u8']; yield 'absolute Unix' => ['/etc/passwd']; yield 'absolute Windows' => ['C:\\Windows\\win.ini']; yield 'URL encoded' => ['%2e%2e%2fmaster.m3u8']; yield 'double encoded' => ['%252e%252e%252fmaster.m3u8']; yield 'null byte' => ["master.m3u8\0.ts"]; yield 'nested segment' => ['folder/segment00000.ts']; yield 'bad extension' => ['segment00000.php']; yield 'unbounded digits' => ['segment1.ts']; yield 'query text' => ['media.m3u8?x=1']; } public function testLongMediaSegmentNumbersRemainValid(): void { self::assertTrue(ResourcePath::isAllowed('segment100000.ts')); self::assertTrue(ResourcePath::isAllowed('segment999999999.ts')); self::assertFalse(ResourcePath::isAllowed('segment1000000000.ts')); } public function testResolveRejectsSymlinksAndMissingFiles(): void { file_put_contents($this->temporaryDirectory . '/master.m3u8', '#EXTM3U'); self::assertSame( realpath($this->temporaryDirectory . '/master.m3u8'), ResourcePath::resolve($this->temporaryDirectory, 'master.m3u8') ); self::assertNull(ResourcePath::resolve($this->temporaryDirectory, 'media.m3u8')); file_put_contents($this->temporaryDirectory . '/outside', 'secret'); symlink($this->temporaryDirectory . '/outside', $this->temporaryDirectory . '/media.m3u8'); self::assertNull(ResourcePath::resolve($this->temporaryDirectory, 'media.m3u8')); } }