Files
ClawdDotNet/tests/ClawdDotNet.Tools.Tests/SocialMedia/YouTubeUrlTests.cs
T
RichardandClaude Opus 5.5 837a634a22 Stufe 4c (#764): Kommentare in C#, WebUI-Skripten, CSS und HTML auf Englisch
Rund 2.600 Kommentarblöcke in 310 C#-Dateien übersetzt, Begründungen vollständig
erhalten: blockweise gleiche Anzahl wie vorher, kein Block unter 60 % seiner
Länge, der Code außerhalb der Kommentare per Tokenizer byte-identisch geprüft.
Zitate deutscher Oberflächentexte und Testdaten bleiben deutsch.

WebUI: Kommentare in JS/CSS/HTML übersetzt, JSDoc-@param an die in 4b
umbenannten Parameter angeglichen.

Dabei behoben:
- 4b hatte "tag" (HTML-Tag) als deutsches "Tag" gelesen und zu "dayValue"
  gemacht (makeElement, svg, Tagesübersicht) - zurück auf tag/day.
- Aufgaben "InProgress" bekamen die Plakettenart "laufend", für die es nie eine
  CSS-Klasse gab - jetzt "success" wie laufende Instanzen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:45:03 +02:00

140 lines
5.9 KiB
C#

using ClawdDotNet.Tools.SocialMediaManager;
using Shouldly;
namespace ClawdDotNet.Tools.Tests.SocialMedia;
/// <summary>
/// S2 from the inventory: the channel/video statement was interpolated unchecked into an
/// argument string for yt-dlp. UseShellExecute=false prevents
/// shell metacharacters, but not option injection — yt-dlp knows --exec, which executes arbitrary
/// commands.
///
/// The attack cases stay permanently documented here as test cases.
/// </summary>
public sealed class YouTubeUrlTests
{
// ═══════════════════════════════════════════════════════════
// Attacks
// ═══════════════════════════════════════════════════════════
[Theory]
[InlineData("--exec cmd.exe https://www.youtube.com/@kanal")]
[InlineData("--exec=calc.exe")]
[InlineData("-o /tmp/evil")]
[InlineData("--config-location /tmp/evil.conf")]
[InlineData("--paths /windows/system32")]
[InlineData("-")]
public void Optionsartige_Eingaben_werden_abgelehnt(string input)
{
YouTubeUrl.TryResolveChannelUrl(input, out _, out var error).ShouldBeFalse();
error.ShouldNotBeNull();
}
[Theory]
[InlineData("https://evil.com/video")]
[InlineData("https://youtube.com.attacker.net/@kanal")]
[InlineData("https://notyoutube.com/@kanal")]
[InlineData("http://192.168.178.10:8418/Richard/ClawdDotNet.git")]
[InlineData("file:///C:/Windows/win.ini")]
[InlineData("ftp://example.com/datei")]
public void Fremde_Hosts_und_Schemata_werden_abgelehnt(string url)
{
YouTubeUrl.TryResolveChannelUrl(url, out _, out var error).ShouldBeFalse();
error.ShouldNotBeNull();
}
[Theory]
[InlineData("kanal name")] // space
[InlineData("kanal\"name")] // quotation marks
[InlineData("kanal\nname")] // line break
[InlineData("kanal;name")]
[InlineData("kanal&name")]
[InlineData("../../etc/passwd")]
[InlineData("@kanal/../../evil")]
public void Unsaubere_Handles_werden_abgelehnt(string input)
{
YouTubeUrl.TryResolveChannelUrl(input, out _, out var error).ShouldBeFalse();
error.ShouldNotBeNull();
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void Leere_Eingaben_werden_abgelehnt(string? input)
{
YouTubeUrl.TryResolveChannelUrl(input, out _, out _).ShouldBeFalse();
}
// ═══════════════════════════════════════════════════════════
// Counter-checks — legitimate inputs must keep working
// ═══════════════════════════════════════════════════════════
[Theory]
[InlineData("@Computerphile", "https://www.youtube.com/@Computerphile/videos")]
[InlineData("Computerphile", "https://www.youtube.com/@Computerphile/videos")]
[InlineData("kanal_mit-punkt.name", "https://www.youtube.com/@kanal_mit-punkt.name/videos")]
public void Handles_werden_zu_Kanal_URLs(string input, string expected)
{
YouTubeUrl.TryResolveChannelUrl(input, out var url, out var error).ShouldBeTrue(error);
url.ShouldBe(expected);
}
[Theory]
[InlineData("https://www.youtube.com/watch?v=dQw4w9WgXcQ")]
[InlineData("https://youtube.com/@kanal/videos")]
[InlineData("https://m.youtube.com/watch?v=abc")]
[InlineData("https://music.youtube.com/watch?v=abc")]
[InlineData("https://youtu.be/dQw4w9WgXcQ")]
public void Echte_YouTube_URLs_werden_durchgelassen(string url)
{
YouTubeUrl.TryResolveChannelUrl(url, out var resolved, out var error).ShouldBeTrue(error);
resolved.ShouldBe(url);
}
// ═══════════════════════════════════════════════════════════
// Building the arguments
// ═══════════════════════════════════════════════════════════
[Fact]
public void Die_URL_steht_hinter_dem_Optionsende_Trenner()
{
// "--" ends the option list. Even if a value looked like an option,
// yt-dlp would treat it as an address after that.
var args = YouTubeUrl.BuildLatestVideoIdArgs("https://www.youtube.com/@kanal/videos");
var separator = args.IndexOf("--");
separator.ShouldBeGreaterThanOrEqualTo(0);
args[^1].ShouldBe("https://www.youtube.com/@kanal/videos");
args.IndexOf(args[^1]).ShouldBeGreaterThan(separator);
}
[Fact]
public void Jedes_Argument_ist_ein_eigener_Eintrag()
{
// Crucial: no entry may contain several arguments — otherwise they could
// be split apart again at startup.
var args = YouTubeUrl.BuildAudioDownloadArgs(
@"C:\Pfad mit Leerzeichen\datei.mp3",
"https://www.youtube.com/watch?v=abc",
@"C:\ffmpeg\bin",
convertToMp3: true);
args.ShouldContain(@"C:\Pfad mit Leerzeichen\datei.mp3");
args.ShouldContain(@"C:\ffmpeg\bin");
args.ShouldContain("--audio-format");
args.ShouldContain("mp3");
args[^1].ShouldBe("https://www.youtube.com/watch?v=abc");
}
[Fact]
public void Ohne_ffmpeg_entfaellt_die_Konvertierung()
{
var args = YouTubeUrl.BuildAudioDownloadArgs(
"/tmp/x.%(ext)s", "https://youtu.be/abc", null, convertToMp3: false);
args.ShouldNotContain("--audio-format");
args.ShouldNotContain("--ffmpeg-location");
}
}