diff --git a/src/assets/favicon.svg b/src/assets/favicon.svg new file mode 100644 index 0000000..4e4b6de --- /dev/null +++ b/src/assets/favicon.svg @@ -0,0 +1,21 @@ + + mapix favicon + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/logo-dark.svg b/src/assets/logo-dark.svg new file mode 100644 index 0000000..70d6ecf --- /dev/null +++ b/src/assets/logo-dark.svg @@ -0,0 +1,42 @@ + + mapix logo for dark backgrounds + A mint map pin shaped camera icon above the mapix wordmark. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mapix + Visualize Photos on Maps + diff --git a/src/assets/logo-light.svg b/src/assets/logo-light.svg new file mode 100644 index 0000000..f0e7d75 --- /dev/null +++ b/src/assets/logo-light.svg @@ -0,0 +1,42 @@ + + mapix logo for light backgrounds + A map pin shaped camera icon above the mapix wordmark. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mapix + Visualize Photos on Maps + diff --git a/src/server/client-assets.ts b/src/server/client-assets.ts index 949e162..eb1caa8 100644 --- a/src/server/client-assets.ts +++ b/src/server/client-assets.ts @@ -3,24 +3,31 @@ import type { ServerResponse } from "node:http"; const CLIENT_ASSET_PREFIX = "/client/"; const VENDOR_ASSET_PREFIX = "/vendor/"; +const APP_ASSET_PREFIX = "/assets/"; export function isClientAssetPath(pathname: string): boolean { - return pathname.startsWith(CLIENT_ASSET_PREFIX) || pathname.startsWith(VENDOR_ASSET_PREFIX); + return ( + pathname.startsWith(CLIENT_ASSET_PREFIX) || + pathname.startsWith(VENDOR_ASSET_PREFIX) || + pathname.startsWith(APP_ASSET_PREFIX) + ); } export async function handleClientAssetRequest(url: URL, res: ServerResponse): Promise { const isVendorAsset = url.pathname.startsWith(VENDOR_ASSET_PREFIX); - const assetPrefix = isVendorAsset ? VENDOR_ASSET_PREFIX : CLIENT_ASSET_PREFIX; + const isAppAsset = url.pathname.startsWith(APP_ASSET_PREFIX); + const assetPrefix = isVendorAsset ? VENDOR_ASSET_PREFIX : isAppAsset ? APP_ASSET_PREFIX : CLIENT_ASSET_PREFIX; const assetName = url.pathname.slice(assetPrefix.length); - if (!isAllowedAssetPath(assetName, isVendorAsset)) { + if (!isAllowedAssetPath(assetName, isVendorAsset, isAppAsset)) { res.statusCode = 404; res.end("Not found"); return; } try { - const assetUrl = new URL(`../${isVendorAsset ? "vendor" : "client"}/${assetName}`, import.meta.url); + const assetDirectory = isVendorAsset ? "vendor" : isAppAsset ? "assets" : "client"; + const assetUrl = new URL(`../${assetDirectory}/${assetName}`, import.meta.url); const source = await readFile(assetUrl); res.statusCode = 200; res.setHeader("content-type", getContentType(assetName)); @@ -32,11 +39,15 @@ export async function handleClientAssetRequest(url: URL, res: ServerResponse): P } } -function isAllowedAssetPath(assetName: string, isVendorAsset: boolean): boolean { +function isAllowedAssetPath(assetName: string, isVendorAsset: boolean, isAppAsset: boolean): boolean { if (assetName.split("/").includes("..")) { return false; } + if (isAppAsset) { + return /^[a-z0-9_.-]+\.svg$/i.test(assetName); + } + if (isVendorAsset) { return /^[a-z0-9/_.-]+\.(css|js|mjs|png)$/i.test(assetName); } @@ -53,5 +64,9 @@ function getContentType(assetName: string): string { return "image/png"; } + if (assetName.endsWith(".svg")) { + return "image/svg+xml; charset=utf-8"; + } + return "text/javascript; charset=utf-8"; } diff --git a/src/server/page-template.ts b/src/server/page-template.ts index d8734a5..88e4938 100644 --- a/src/server/page-template.ts +++ b/src/server/page-template.ts @@ -7,6 +7,7 @@ export function htmlPage(): string { mapix +