use crate::http::Response;
const INDEX: &[u8] = include_bytes!("index.html");
const STYLE: &[u8] = include_bytes!("style.css");
const APP: &[u8] = include_bytes!("app.js");
const SANS_400: &[u8] = include_bytes!("fonts/IBMPlexSans-Regular.woff2");
const SANS_500: &[u8] = include_bytes!("fonts/IBMPlexSans-Medium.woff2");
const SANS_600: &[u8] = include_bytes!("fonts/IBMPlexSans-SemiBold.woff2");
const MONO_400: &[u8] = include_bytes!("fonts/IBMPlexMono-Regular.woff2");
const MONO_500: &[u8] = include_bytes!("fonts/IBMPlexMono-Medium.woff2");
const FONT_LICENSE: &[u8] = include_bytes!("fonts/LICENSE.txt");
const LOGO: &[u8] = include_bytes!("logo.svg");
const LOGO_DARK: &[u8] = include_bytes!("logo-dark.svg");
const FAVICON_ICO: &[u8] = include_bytes!("icons/favicon.ico");
const FAVICON_16: &[u8] = include_bytes!("icons/favicon-16x16.png");
const FAVICON_32: &[u8] = include_bytes!("icons/favicon-32x32.png");
const APPLE_TOUCH: &[u8] = include_bytes!("icons/apple-touch-icon.png");
const ANDROID_192: &[u8] = include_bytes!("icons/android-chrome-192x192.png");
const ANDROID_512: &[u8] = include_bytes!("icons/android-chrome-512x512.png");
const WEBMANIFEST: &[u8] = include_bytes!("icons/site.webmanifest");
const ASSETS: &[(&str, &str, &[u8])] = &[
("/style.css", "text/css; charset=utf-8", STYLE),
("/app.js", "text/javascript; charset=utf-8", APP),
("/logo.svg", "image/svg+xml", LOGO),
("/logo-dark.svg", "image/svg+xml", LOGO_DARK),
("/favicon.ico", "image/x-icon", FAVICON_ICO),
("/icons/favicon-16x16.png", "image/png", FAVICON_16),
("/icons/favicon-32x32.png", "image/png", FAVICON_32),
("/icons/apple-touch-icon.png", "image/png", APPLE_TOUCH),
(
"/icons/android-chrome-192x192.png",
"image/png",
ANDROID_192,
),
(
"/icons/android-chrome-512x512.png",
"image/png",
ANDROID_512,
),
(
"/icons/site.webmanifest",
"application/manifest+json",
WEBMANIFEST,
),
("/fonts/IBMPlexSans-Regular.woff2", "font/woff2", SANS_400),
("/fonts/IBMPlexSans-Medium.woff2", "font/woff2", SANS_500),
("/fonts/IBMPlexSans-SemiBold.woff2", "font/woff2", SANS_600),
("/fonts/IBMPlexMono-Regular.woff2", "font/woff2", MONO_400),
("/fonts/IBMPlexMono-Medium.woff2", "font/woff2", MONO_500),
(
"/fonts/LICENSE.txt",
"text/plain; charset=utf-8",
FONT_LICENSE,
),
];
pub fn page() -> Response {
Response::asset("text/html; charset=utf-8", INDEX)
}
pub fn asset(path: &str) -> Option<(&'static str, &'static [u8])> {
ASSETS
.iter()
.find(|(route, _, _)| *route == path)
.map(|(_, kind, bytes)| (*kind, *bytes))
}
pub fn routes() -> Vec<&'static str> {
let mut out = vec!["/"];
out.extend(ASSETS.iter().map(|(route, _, _)| *route));
out
}
pub fn weight() -> usize {
INDEX.len()
+ ASSETS
.iter()
.map(|(_, _, bytes)| bytes.len())
.sum::<usize>()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_embedded_assets_stay_inside_their_budget() {
let total = weight();
assert!(
total < 1024 * 1024,
"the portal now weighs {total} bytes, over the 1 MiB budget"
);
}
#[test]
fn nothing_in_the_portal_points_at_another_origin() {
let parsed: &[(&str, &[u8])] = &[("/", INDEX), ("/style.css", STYLE), ("/app.js", APP)];
for (route, bytes) in parsed {
let text = String::from_utf8_lossy(bytes).replace("http://www.w3.org/2000/svg", "");
for needle in ["http://", "https://", "//cdn", "fonts.googleapis", "unpkg"] {
assert!(
!text.contains(needle),
"{route} contains {needle:?}, which the CSP forbids loading"
);
}
}
}
#[test]
fn every_asset_the_page_references_is_served() {
let html = String::from_utf8_lossy(INDEX);
for reference in ["style.css", "app.js"] {
assert!(
html.contains(reference),
"the page does not load {reference}"
);
assert!(
asset(&format!("/{reference}")).is_some(),
"{reference} is referenced but not served"
);
}
let css = String::from_utf8_lossy(STYLE);
for (route, _, _) in ASSETS.iter().filter(|(r, _, _)| r.ends_with(".woff2")) {
let file = route.trim_start_matches('/');
assert!(css.contains(file), "{file} ships but no @font-face uses it");
}
}
}