From b2d1909b138d8479b52298436b44ca5956d4a104 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 3 Jun 2026 15:10:06 +0200 Subject: [PATCH] feat(web): embedURL helper for privacy-friendly nocookie embeds Validates an 11-char YouTube id and returns the youtube-nocookie embed URL, or ("", false) so callers omit a broken iframe. Table-driven test. --- internal/web/view.go | 16 ++++++++++++++++ internal/web/view_internal_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 internal/web/view_internal_test.go diff --git a/internal/web/view.go b/internal/web/view.go index 5f3a450..648f55e 100644 --- a/internal/web/view.go +++ b/internal/web/view.go @@ -1,6 +1,7 @@ package web import ( + "regexp" "strings" "time" @@ -9,6 +10,21 @@ import ( "gitea.d-ma.be/mathias/tapir/internal/adapters/store" ) +// youtubeIDRe matches a canonical 11-char YouTube video id (the provider's +// base64url alphabet). Anything else is rejected so we never emit a broken +// embed src. +var youtubeIDRe = regexp.MustCompile(`^[A-Za-z0-9_-]{11}$`) + +// embedURL builds a privacy-friendly nocookie embed URL for a YouTube video id. +// It returns ("", false) for any id that isn't a valid 11-char YouTube id, so +// the caller can omit the embed instead of rendering a broken iframe. +func embedURL(providerVideoID string) (string, bool) { + if !youtubeIDRe.MatchString(providerVideoID) { + return "", false + } + return "https://www.youtube-nocookie.com/embed/" + providerVideoID, true +} + // actionVerbs is the fixed, ordered set of action toggles rendered in the button // group. It mirrors the store's allowed actions (store/actions.go); order here is // the display order, not the store's. diff --git a/internal/web/view_internal_test.go b/internal/web/view_internal_test.go new file mode 100644 index 0000000..1e2d1b6 --- /dev/null +++ b/internal/web/view_internal_test.go @@ -0,0 +1,29 @@ +package web + +import "testing" + +func TestEmbedURL(t *testing.T) { + tests := []struct { + name string + id string + wantURL string + wantOK bool + }{ + {"valid 11-char id", "dQw4w9WgXcQ", "https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ", true}, + {"valid with dash and underscore", "a_b-cD12345", "https://www.youtube-nocookie.com/embed/a_b-cD12345", true}, + {"empty", "", "", false}, + {"too short", "abc", "", false}, + {"too long", "dQw4w9WgXcQX", "", false}, + {"invalid char", "dQw4w9WgXc!", "", false}, + {"space", "dQw4w9WgX Q", "", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotURL, gotOK := embedURL(tt.id) + if gotURL != tt.wantURL || gotOK != tt.wantOK { + t.Errorf("embedURL(%q) = (%q, %v), want (%q, %v)", + tt.id, gotURL, gotOK, tt.wantURL, tt.wantOK) + } + }) + } +}