All checks were successful
Deploy / build-deploy (push) Successful in 4m19s
137 lines
6.3 KiB
C++
137 lines
6.3 KiB
C++
/*
|
|
catcrafts.net
|
|
Copyright (C) 2026 Catcrafts
|
|
|
|
The source code of this website is made available for viewing purposes only.
|
|
No permission is granted to copy, modify, distribute, or create derivative works.
|
|
*/
|
|
|
|
// The format ladder. One <picture>/<video> builder serves both the cards and
|
|
// the post bodies, so these assertions cover every image and video the site
|
|
// emits — and the ordering ones matter: a browser takes the FIRST source it
|
|
// understands, so a mis-ordered ladder silently serves the wrong tier to
|
|
// everyone rather than failing visibly.
|
|
|
|
import std;
|
|
import Catcrafts.Shared;
|
|
|
|
using namespace Catcrafts;
|
|
|
|
namespace {
|
|
|
|
int failures = 0;
|
|
|
|
void Check(bool ok, std::string_view what, std::string_view got = {}) {
|
|
if (ok) return;
|
|
++failures;
|
|
std::println(std::cerr, "FAIL: {}{}{}", what,
|
|
got.empty() ? "" : " got: ", got);
|
|
}
|
|
|
|
void CheckEq(const Html::SafeHtml& actual, std::string_view expected, std::string_view what) {
|
|
Check(actual.View() == expected, what, actual.View());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main() {
|
|
auto img = [](std::string src, std::string avif, std::string png,
|
|
std::int64_t w = 0, std::int64_t h = 0) {
|
|
PostMedia m;
|
|
m.src = std::move(src);
|
|
m.kind = "image";
|
|
m.avif = std::move(avif);
|
|
m.fallback = std::move(png);
|
|
m.width = w;
|
|
m.height = h;
|
|
return m;
|
|
};
|
|
|
|
// The full ladder: AVIF, then the mirrored original, then the PNG the <img>
|
|
// itself points at. Exactly one of the three is ever fetched.
|
|
CheckEq(Media::Tag(img("/media/x.webp", "/media/x.avif", "/media/x.png", 800, 600)),
|
|
R"(<picture><source srcset="/media/x.avif" type="image/avif">)"
|
|
R"(<source srcset="/media/x.webp" type="image/webp">)"
|
|
R"(<img class="post-media__item" loading="lazy" decoding="async" alt="")"
|
|
R"( src="/media/x.png" width="800" height="600"></picture>)",
|
|
"media: image ladder is avif, original, png");
|
|
|
|
// Alt text reaches the <img>, not the <picture> — a screen reader reads the
|
|
// img, and an alt on the wrapper is invisible to it.
|
|
Check(Media::Tag(img("/media/x.webp", "/media/x.avif", "/media/x.png"), "a cat")
|
|
.View().find(R"(alt="a cat" src="/media/x.png")") != std::string_view::npos,
|
|
"media: alt lands on the img");
|
|
|
|
// Degradation, one tier at a time. Each of these is a real state: no
|
|
// encoder on the build host, a source that was already PNG, a body image
|
|
// whose download failed so there is nothing but the original URL.
|
|
CheckEq(Media::Tag(img("/media/x.webp", "", "/media/x.png")),
|
|
R"(<picture><source srcset="/media/x.webp" type="image/webp">)"
|
|
R"(<img class="post-media__item" loading="lazy" decoding="async" alt="")"
|
|
R"( src="/media/x.png"></picture>)",
|
|
"media: no avif still offers the original above the png");
|
|
CheckEq(Media::Tag(img("/media/x.webp", "/media/x.avif", "")),
|
|
R"(<picture><source srcset="/media/x.avif" type="image/avif">)"
|
|
R"(<img class="post-media__item" loading="lazy" decoding="async" alt="")"
|
|
R"( src="/media/x.webp"></picture>)",
|
|
"media: no png leaves the original as the base");
|
|
CheckEq(Media::Tag(img("/media/x.webp", "", "")),
|
|
R"(<img class="post-media__item" loading="lazy" decoding="async" alt="")"
|
|
R"( src="/media/x.webp">)",
|
|
"media: no renditions is a bare img, as before any of this existed");
|
|
// A source that is already PNG is its own fallback, and must not be
|
|
// offered twice — once as a <source> and once as the <img>.
|
|
CheckEq(Media::Tag(img("/media/x.png", "/media/x.avif", "/media/x.png")),
|
|
R"(<picture><source srcset="/media/x.avif" type="image/avif">)"
|
|
R"(<img class="post-media__item" loading="lazy" decoding="async" alt="")"
|
|
R"( src="/media/x.png"></picture>)",
|
|
"media: a png source is not also listed as a source");
|
|
// Likewise a source that is already AVIF.
|
|
Check(Media::Tag(img("/media/x.avif", "/media/x.avif", "/media/x.png"))
|
|
.View().find("image/avif\"><source") == std::string_view::npos,
|
|
"media: an avif source is not listed twice");
|
|
|
|
// A URL is still a URL: the scheme allowlist applies to srcset exactly as
|
|
// it does to src, or the ladder becomes a way around it.
|
|
Check(Media::Tag(img("/media/x.webp", "javascript:alert(1)", "/media/x.png"))
|
|
.View().find(R"(srcset="#")") != std::string_view::npos,
|
|
"media: a hostile srcset is neutralised");
|
|
|
|
// Video is unchanged by any of this and must stay so.
|
|
{
|
|
PostMedia v;
|
|
v.src = "/media/v.mp4";
|
|
v.kind = "video";
|
|
v.poster = "/media/v.webp";
|
|
v.fallback = "/media/v.h264.mp4";
|
|
const auto out = Media::Tag(v);
|
|
Check(out.View().starts_with("<video class=\"post-media__item\" controls preload=\"metadata\""),
|
|
"media: video is still a video", out.View());
|
|
Check(out.View().find("codecs=av01") != std::string_view::npos
|
|
&& out.View().find(R"(<source src="/media/v.h264.mp4" type="video/mp4">)")
|
|
!= std::string_view::npos,
|
|
"media: AV1 then H.264, in that order");
|
|
Check(out.View().find("<picture>") == std::string_view::npos,
|
|
"media: a video is not wrapped in a picture");
|
|
}
|
|
|
|
// A path with no record renders from the path alone — the mirror failed,
|
|
// and showing the picture beats dropping the paragraph's subject.
|
|
{
|
|
const std::array<PostMedia, 1> known{
|
|
img("/media/x.webp", "/media/x.avif", "/media/x.png") };
|
|
Check(Media::Find(known, "/media/x.webp") != nullptr, "media: found by src");
|
|
Check(Media::Find(known, "/media/nope.webp") == nullptr, "media: unknown src");
|
|
const PostMedia guessed = Media::Describe(known, "https://i.example/a.webp");
|
|
Check(guessed.kind == "image" && guessed.avif.empty(),
|
|
"media: an unmirrored image is described from its path");
|
|
Check(Media::Describe(known, "https://i.example/a.mp4").kind == "video",
|
|
"media: an unmirrored video is recognised as one");
|
|
}
|
|
|
|
if (failures != 0) {
|
|
std::println(std::cerr, "{} check(s) failed", failures);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|