Detect YouTube URLs and emit privacy-enhanced iframe
Recognizes youtu.be, watch?v=, shorts, embed, and mobile variants. Emits an iframe pointing to youtube-nocookie.com with lazy loading, strict-origin referrer policy, and allowfullscreen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,8 @@ public function resolve(string $url): ?string
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return $this->detectVideo($url)
|
return $this->detectVideo($url)
|
||||||
?? $this->detectAudio($url);
|
?? $this->detectAudio($url)
|
||||||
|
?? $this->detectYouTube($url);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function detectVideo(string $url): ?string
|
private function detectVideo(string $url): ?string
|
||||||
@@ -35,6 +36,40 @@ private function detectAudio(string $url): ?string
|
|||||||
return "<audio src=\"{$safe}\" controls class=\"kb-audio\"></audio>";
|
return "<audio src=\"{$safe}\" controls class=\"kb-audio\"></audio>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function detectYouTube(string $url): ?string
|
||||||
|
{
|
||||||
|
$patterns = [
|
||||||
|
'~^https?://youtu\.be/([A-Za-z0-9_-]{11})(?:[/?#]|$)~',
|
||||||
|
'~^https?://(?:www\.|m\.)?youtube\.com/watch\?(?:[^#]*&)?v=([A-Za-z0-9_-]{11})(?:[&#]|$)~',
|
||||||
|
'~^https?://(?:www\.|m\.)?youtube\.com/shorts/([A-Za-z0-9_-]{11})(?:[/?#]|$)~',
|
||||||
|
'~^https?://(?:www\.|m\.)?youtube\.com/embed/([A-Za-z0-9_-]{11})(?:[/?#]|$)~',
|
||||||
|
];
|
||||||
|
$videoId = null;
|
||||||
|
foreach ($patterns as $p) {
|
||||||
|
if (preg_match($p, $url, $m)) {
|
||||||
|
$videoId = $m[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($videoId === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$src = "https://www.youtube-nocookie.com/embed/{$videoId}";
|
||||||
|
return $this->iframeHtml($src, 'youtube');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function iframeHtml(string $src, string $provider): string
|
||||||
|
{
|
||||||
|
$safe = htmlspecialchars($src, ENT_QUOTES, 'UTF-8');
|
||||||
|
return '<iframe src="' . $safe . '" '
|
||||||
|
. 'width="560" height="315" '
|
||||||
|
. 'loading="lazy" '
|
||||||
|
. 'referrerpolicy="strict-origin-when-cross-origin" '
|
||||||
|
. 'allow="autoplay; encrypted-media; picture-in-picture" '
|
||||||
|
. 'allowfullscreen frameborder="0" '
|
||||||
|
. 'class="kb-embed kb-embed-' . $provider . '"></iframe>';
|
||||||
|
}
|
||||||
|
|
||||||
private function getPathExtension(string $url): string
|
private function getPathExtension(string $url): string
|
||||||
{
|
{
|
||||||
$path = parse_url($url, PHP_URL_PATH);
|
$path = parse_url($url, PHP_URL_PATH);
|
||||||
|
|||||||
@@ -87,4 +87,43 @@ public static function audioUrls(): array
|
|||||||
'm4a' => ['/clip.m4a'],
|
'm4a' => ['/clip.m4a'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[DataProvider('youtubeUrls')]
|
||||||
|
public function test_youtube_urls_produce_iframe(string $url): void
|
||||||
|
{
|
||||||
|
$html = $this->resolver->resolve($url);
|
||||||
|
$this->assertNotNull($html);
|
||||||
|
$this->assertStringStartsWith('<iframe', $html);
|
||||||
|
$this->assertStringContainsString('youtube-nocookie.com/embed/dQw4w9WgXcQ', $html);
|
||||||
|
$this->assertStringContainsString('class="kb-embed kb-embed-youtube"', $html);
|
||||||
|
$this->assertStringContainsString('loading="lazy"', $html);
|
||||||
|
$this->assertStringContainsString('allowfullscreen', $html);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function youtubeUrls(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'short youtu.be' => ['https://youtu.be/dQw4w9WgXcQ'],
|
||||||
|
'watch v=' => ['https://www.youtube.com/watch?v=dQw4w9WgXcQ'],
|
||||||
|
'shorts' => ['https://www.youtube.com/shorts/dQw4w9WgXcQ'],
|
||||||
|
'embed' => ['https://www.youtube.com/embed/dQw4w9WgXcQ'],
|
||||||
|
'mobile' => ['https://m.youtube.com/watch?v=dQw4w9WgXcQ'],
|
||||||
|
'no www watch' => ['https://youtube.com/watch?v=dQw4w9WgXcQ'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('invalidYoutubeUrls')]
|
||||||
|
public function test_invalid_youtube_urls_return_null(string $url): void
|
||||||
|
{
|
||||||
|
$this->assertNull($this->resolver->resolve($url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function invalidYoutubeUrls(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'too short id' => ['https://youtu.be/short'],
|
||||||
|
'host mismatch' => ['https://example.com/watch?v=dQw4w9WgXcQ'],
|
||||||
|
'XSS attempt in id' => ['https://youtu.be/abc"><script>'],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user