//! GET /e/[.ext] — resolve a bufo name to its image and serve the bytes. //! ported from the retired rust backend. //! //! this is the single place that knows where a bufo lives, so consumers (the //! status app, personal sites) use one dumb url and never re-implement the //! candidate walk. the extension is cosmetic: plenty of bufos are gif-only. //! //! resolution order: local static dir (custom bufos) → bufo.zone png → gif. const std = @import("std"); const http = std.http; const mem = std.mem; const Allocator = mem.Allocator; const Io = std.Io; const BUFO_ZONE = "https://all-the.bufo.zone"; /// matches the 14d freshness bufo.zone puts on these exact bytes; anything /// shorter would make /e/ cache worse than redirecting did pub const IMAGE_CACHE_CONTROL = "public, max-age=1209600, stale-while-revalidate=86400"; const MAX_IMAGE_BYTES = 20 * 1024 * 1024; /// strip the cosmetic extension and reject anything that isn't a real bufo /// name. names are `[a-z0-9_-']`; anything else could be a path-traversal /// attempt against the local static check. pub fn bufoName(raw: []const u8) ?[]const u8 { const name = if (mem.lastIndexOfScalar(u8, raw, '.')) |i| raw[0..i] else raw; if (name.len == 0) return null; for (name) |c| { if (!(std.ascii.isAlphanumeric(c) or c == '-' or c == '_' or c == '\'')) return null; } return name; } pub const Image = struct { content_type: []const u8, bytes: []const u8, }; pub const Resolver = struct { io: Io, http: *http.Client, static_dir: []const u8, pub const Error = error{ NotFound, Upstream, OutOfMemory }; pub fn resolve(self: Resolver, alloc: Allocator, name: []const u8) Error!Image { if (self.readLocal(alloc, name)) |img| return img; for ([_][]const u8{ "png", "gif" }) |ext| { const url = try std.fmt.allocPrint(alloc, "{s}/{s}.{s}", .{ BUFO_ZONE, name, ext }); var body: Io.Writer.Allocating = .init(alloc); const res = self.http.fetch(.{ .location = .{ .url = url }, .method = .GET, .response_writer = &body.writer, }) catch |err| { std.log.warn("fetch failed for {s}: {t}", .{ url, err }); continue; }; if (res.status == .ok) { return .{ .content_type = if (mem.eql(u8, ext, "png")) "image/png" else "image/gif", .bytes = body.written(), }; } } std.log.info("bufo not found: {s}", .{name}); return error.NotFound; } fn readLocal(self: Resolver, alloc: Allocator, name: []const u8) ?Image { const path = std.fmt.allocPrint(alloc, "{s}/{s}.png", .{ self.static_dir, name }) catch return null; const bytes = readFile(self.io, alloc, path, MAX_IMAGE_BYTES) catch return null; return .{ .content_type = "image/png", .bytes = bytes }; } }; pub fn readFile(io: Io, alloc: Allocator, path: []const u8, limit: usize) ![]u8 { const dir = Io.Dir.cwd(); const file = try dir.openFile(io, path, .{}); defer file.close(io); var buf: [8192]u8 = undefined; var reader = file.reader(io, &buf); return try reader.interface.allocRemaining(alloc, Io.Limit.limited(limit)); } // --- tests: the rust suite, ported --- const t = std.testing; test "strips the cosmetic extension" { try t.expectEqualStrings("bufo-happy", bufoName("bufo-happy.png").?); try t.expectEqualStrings("bufo-happy", bufoName("bufo-happy.gif").?); try t.expectEqualStrings("bufo-happy", bufoName("bufo-happy").?); } test "accepts real bufo names" { for ([_][]const u8{ "add-bufo", "bufo_underscore", "bigbufo_2_2.png", "according-to-all-known-laws-of-aviation-there-is-no-way-a-bufo-should-be-able-to-fly", "bufo's-a-gamer-girl-but-specifically-nyt-games", "bufo-can't-help-but-wonder-who-watches-the-watchmen.png", "my-name-is-buford-and-i-am-bufo's-father", }) |name| { try t.expect(bufoName(name) != null); } } test "rejects traversal and junk" { for ([_][]const u8{ "../../etc/passwd", "..%2f..%2fetc", "foo/bar.png", "a b.png", ".png", "", "bufo$(whoami).png", "../bufo's.png", "bufo's/../../etc/passwd", }) |raw| { try t.expect(bufoName(raw) == null); } }