diff --git a/src/app/Markdown/MediaUrlResolver.php b/src/app/Markdown/MediaUrlResolver.php index 1fd476d..89f5ff6 100644 --- a/src/app/Markdown/MediaUrlResolver.php +++ b/src/app/Markdown/MediaUrlResolver.php @@ -4,11 +4,31 @@ class MediaUrlResolver { + private const VIDEO_EXT = ['mp4', 'webm', 'ogv', 'mov', 'm4v']; + public function resolve(string $url): ?string { if ($url === '') { return null; } - return null; + return $this->detectVideo($url); + } + + private function detectVideo(string $url): ?string + { + if (!in_array($this->getPathExtension($url), self::VIDEO_EXT, true)) { + return null; + } + $safe = htmlspecialchars($url, ENT_QUOTES, 'UTF-8'); + return ""; + } + + private function getPathExtension(string $url): string + { + $path = parse_url($url, PHP_URL_PATH); + if ($path === null || $path === false) { + return ''; + } + return strtolower(pathinfo($path, PATHINFO_EXTENSION)); } } diff --git a/src/tests/Unit/Markdown/MediaUrlResolverTest.php b/src/tests/Unit/Markdown/MediaUrlResolverTest.php index b28446c..c24411a 100644 --- a/src/tests/Unit/Markdown/MediaUrlResolverTest.php +++ b/src/tests/Unit/Markdown/MediaUrlResolverTest.php @@ -35,4 +35,36 @@ public static function nonMediaUrls(): array 'youtu.be lookalike host' => ['https://example.com/youtu.be-fake/abc'], ]; } + + #[DataProvider('videoUrls')] + public function test_video_urls_produce_video_tag(string $url): void + { + $html = $this->resolver->resolve($url); + $this->assertNotNull($html); + $this->assertStringStartsWith('assertStringContainsString('controls', $html); + $this->assertStringContainsString('class="kb-video"', $html); + } + + public static function videoUrls(): array + { + return [ + 'mp4' => ['/demo.mp4'], + 'webm' => ['/demo.webm'], + 'ogv' => ['/demo.ogv'], + 'mov' => ['/demo.mov'], + 'm4v' => ['/demo.m4v'], + 'uppercase extension' => ['/demo.MP4'], + 'with query string' => ['https://example.com/path/demo.mp4?token=abc'], + 'absolute http' => ['https://example.com/demo.mp4'], + ]; + } + + public function test_video_url_is_html_escaped(): void + { + $html = $this->resolver->resolve('/path/with"quote.mp4'); + $this->assertNotNull($html); + $this->assertStringNotContainsString('"quote.mp4"', $html); + $this->assertStringContainsString('"', $html); + } }