66 lines
2.6 KiB
PHP
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'));
|
|
}
|
|
}
|