Files

66 lines
2.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Plugin\AzuraCastOnDemandHls\Tests\Security;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use Plugin\AzuraCastOnDemandHls\Security\ResourcePath;
use Plugin\AzuraCastOnDemandHls\Tests\Support\TemporaryDirectoryTestCase;
#[CoversClass(ResourcePath::class)]
final class ResourcePathTest extends TemporaryDirectoryTestCase
{
public function testOnlyExpectedHlsNamesAreAllowed(): void
{
self::assertTrue(ResourcePath::isAllowed('master.m3u8'));
self::assertTrue(ResourcePath::isAllowed('media.m3u8'));
self::assertTrue(ResourcePath::isAllowed('segment00000.ts'));
self::assertTrue(ResourcePath::isAllowed('segment99999.ts'));
}
#[DataProvider('traversalProvider')]
public function testTraversalAndUnexpectedPathsAreRejected(string $path): void
{
self::assertFalse(ResourcePath::isAllowed($path));
}
/** @return iterable<string, array{string}> */
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'));
}
}