use std::{collections::BTreeSet, sync::LazyLock}; use axum::{ body::Body, extract::{multipart::Field, Multipart, Path, Query, State}, http::header, response::{IntoResponse, Response}, Json, }; use regex::Regex; use serde::{Deserialize, Serialize}; use sqlx::{Row, SqlitePool}; use tokio::io::AsyncWriteExt; use tokio_util::io::ReaderStream; use uuid::Uuid; use crate::{ auth::Session, error::{AppError, AppResult}, routes::nodes, state::AppState, vault, }; /// Media referenced from a document body always looks like `/api/media/`, /// which is how a document claims ownership of the files it embeds. pub(crate) static MEDIA_REF: LazyLock = LazyLock::new(|| { Regex::new(r"/api/media/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})") .expect("static regex is valid") }); /// An upload is attacker-controlled content served from our own origin, so the /// browser is only ever told it is a type that cannot execute script. Anything /// else — `text/html`, `image/svg+xml`, an unrecognised type — is stored and /// served as an opaque download instead. pub(crate) fn sanitize_mime(raw: &str) -> String { let base = raw .split(';') .next() .unwrap_or_default() .trim() .to_ascii_lowercase(); let (top, sub) = base.split_once('/').unwrap_or_default(); let inlineable = match top { // SVG is a document: it can carry