Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
28 kB · 854 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855//! pure read projections: resolve snapshot -> commit -> serializable Out.//!//! mirrors knot2's knot-xrpc/src/reads.rs read handlers as pure functions//! over already-opened repos and snapshot facts.
use std::time::Instant;
use base64::Engine as _;use base64::engine::general_purpose::STANDARD;use knot_git::{BinaryBudget, EntryKind, PatchRange, Repo, SizedEntry};use knot_types::{BranchName, Oid, RepoPath, TagName};
use crate::project::errors::StaticXrpcError;use crate::project::out;use crate::snapshot::RepoSnapshot;
pub const DEFAULT_RESPONSE_LIMIT: usize = 5 * 1024 * 1024;pub const MAX_BLOB_BYTES: u64 = 25 * 1024 * 1024;pub const DEFAULT_PAGE: usize = 50;pub const MAX_PAGE: usize = 100;pub const LIST_REFS_DEFAULT: usize = 100;pub const LIST_REFS_MAX: usize = 1000;pub const LIST_REPOS_DEFAULT: usize = 50;pub const LIST_REPOS_MAX: usize = 1000;
struct LimitWriter { buf: Vec<u8>, limit: usize,}
impl std::io::Write for LimitWriter { fn write(&mut self, data: &[u8]) -> std::io::Result<usize> { if self.buf.len() + data.len() > self.limit { return Err(std::io::Error::new( std::io::ErrorKind::WriteZero, "response exceeds configured maximum size", )); } self.buf.extend_from_slice(data); Ok(data.len()) }
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }}
// Origin: knot-xrpc/src/reads.rs: jsonpub fn to_json_bytes<T: serde::Serialize>( value: &T, limit: usize,) -> Result<Vec<u8>, StaticXrpcError> { let mut writer = LimitWriter { buf: Vec::new(), limit, }; match serde_json::to_writer(&mut writer, value) { Ok(()) => Ok(writer.buf), Err(error) if error.is_io() => Err(StaticXrpcError::request_too_large( "response exceeds configured maximum size", )), Err(error) => Err(StaticXrpcError::internal(format!( "failed to serialize response: {error}" ))), }}
// Origin: knot-xrpc/src/reads.rs: blob_serving_limitpub fn blob_serving_limit(raw: bool, response_limit: usize) -> u64 { if raw { MAX_BLOB_BYTES } else { MAX_BLOB_BYTES.min(response_limit as u64 / 4 * 3) }}
// Origin: knot-xrpc/src/reads.rs: readme_serving_limitpub fn readme_serving_limit(response_limit: usize) -> u64 { response_limit as u64 / 8}
// Origin: knot-xrpc/src/reads.rs: names_reservedpub fn names_reserved(refspec: &str) -> bool { matches!( knot_git::RefClass::of_revspec(refspec), knot_git::RefClass::Cob | knot_git::RefClass::Checkpoint | knot_git::RefClass::Atproto )}
// Origin: knot-xrpc/src/reads.rs: hidden_staging_commitpub fn hidden_staging_commit(repo: &Repo, refspec: &str) -> Option<Oid> { repo.hidden_ref_commit(refspec) .and_then(|oid| repo.peel_to_commit(oid).ok())}
// Origin: knot-xrpc/src/reads.rs: commit_forpub fn commit_for(repo: &Repo, refspec: &str) -> Result<Oid, StaticXrpcError> { if names_reserved(refspec) { return Err(StaticXrpcError::ref_not_found()); } let oid = if refspec.is_empty() { repo.head().map(|head| head.target) } else { repo.resolve_revision(refspec) } .ok_or_else(StaticXrpcError::ref_not_found)?; let commit = repo .peel_to_commit(oid) .map_err(|_| StaticXrpcError::ref_not_found())?; if hidden_staging_commit(repo, refspec) == Some(commit) { return Ok(commit); } match repo.reachable_from_public(commit) { Ok(true) => Ok(commit), Ok(false) => Err(StaticXrpcError::ref_not_found()), Err(error) => Err(StaticXrpcError::internal(error.to_string())), }}
// Origin: knot-xrpc/src/reads.rs: is_readmefn is_readme(entry: &SizedEntry) -> bool { let lower = entry.name.to_ascii_lowercase(); entry.kind.is_file() && (lower == "readme" || lower .strip_prefix("readme.") .is_some_and(|extension| !extension.is_empty() && !extension.contains('.')))}
// Origin: knot-xrpc/src/reads.rs: readme_offn readme_of( repo: &Repo, commit: Oid, dir: Option<&RepoPath>, entries: &[SizedEntry], response_limit: usize,) -> out::ReadmeOut { entries .iter() .filter(|entry| is_readme(entry)) .find_map(|entry| { let path = match dir { None => RepoPath::new(entry.name.as_str()), Some(dir) => RepoPath::new(format!("{dir}/{}", entry.name)), } .ok()?; let target = repo.entry_at(commit, &path).ok().flatten()?; if repo.blob_size(target.oid).ok()? > readme_serving_limit(response_limit) { return None; } let contents = repo.read_blob(target.oid).ok()?; let contents = String::from_utf8(contents).ok()?; Some(out::ReadmeOut { filename: entry.name.clone(), contents, }) }) .unwrap_or(out::ReadmeOut { filename: String::new(), contents: String::new(), })}
// Origin: knot-xrpc/src/reads.rs: repo_describe_repopub fn describe_repo(snapshot: &RepoSnapshot) -> out::DescribeRepoOut { out::DescribeRepoOut { repo_did: snapshot.repo_did.clone(), owner_did: snapshot.owner.clone(), rkey: snapshot.rkey.clone(), }}
// Origin: knot-xrpc/src/reads.rs: repo_get_default_branchpub fn get_default_branch(repo: &Repo) -> Result<out::DefaultBranchOut, StaticXrpcError> { let name = repo .default_branch() .map(|name| name.as_str().trim_start_matches("refs/heads/").to_string()) .ok_or_else(|| { StaticXrpcError::new(500, "InvalidRequest", "failed to get default branch") })?; Ok(out::DefaultBranchOut { name, hash: String::new(), when: out::rfc3339(0, 0), })}
// Origin: knot-xrpc/src/reads.rs: repo_branchpub fn branch(repo: &Repo, name: &BranchName) -> Result<out::BranchOut, StaticXrpcError> { let target = repo .find_ref(&name.head_ref()) .ok() .flatten() .ok_or_else(StaticXrpcError::branch_not_found)?; let commit = repo .find_commit(target) .map_err(|_| StaticXrpcError::branch_not_found())?; let default = repo .default_branch() .map(|name| name.as_str().trim_start_matches("refs/heads/").to_string()); let hash = target.to_hex(); let when = out::rfc3339(commit.author.time.get(), commit.author.offset_seconds); Ok(out::BranchOut { name: name.to_string(), short_hash: hash[..7].to_string(), hash, when: when.clone(), message: (!commit.message.is_empty()).then_some(commit.message), author: out::SignatureOut { name: commit.author.name, email: commit.author.email, when, }, is_default: default.as_deref() == Some(name.as_str()), })}
// Origin: knot-xrpc/src/reads.rs: repo_branchespub fn branches( repo: &Repo, cursor: usize, limit: Option<usize>,) -> Result<out::BranchesOut, StaticXrpcError> { let limit = limit.unwrap_or(DEFAULT_PAGE).min(MAX_PAGE).max(1); let mut branches = repo.branch_list()?; branches.sort_by(|a, b| { b.tip .created_at() .cmp(&a.tip.created_at()) .then_with(|| a.name.cmp(&b.name)) }); let default = repo .default_branch() .map(|name| name.as_str().trim_start_matches("refs/heads/").to_string()); let absent = repo.object_format().null_oid(); let branches = branches .iter() .skip(cursor) .take(limit) .map(|branch| { out::BranchWire::of( branch, default.as_deref() == Some(branch.name.as_str()), absent, ) }) .rev() .collect(); Ok(out::BranchesOut { branches })}
// Origin: knot-xrpc/src/reads.rs: repo_tagpub fn tag(repo: &Repo, tag_name: &TagName) -> Result<out::TagOut, StaticXrpcError> { let info = repo .tag_list()? .into_iter() .find(|t| t.name == *tag_name) .ok_or_else(StaticXrpcError::tag_not_found)?; Ok(out::TagOut { tag: out::TagWire::of(&info), })}
// Origin: knot-xrpc/src/reads.rs: repo_tagspub fn tags( repo: &Repo, cursor: usize, limit: Option<usize>,) -> Result<out::TagsOut, StaticXrpcError> { let limit = limit.unwrap_or(DEFAULT_PAGE).min(MAX_PAGE).max(1); let mut tags = repo.tag_list()?; tags.sort_by(|a, b| { b.created_at .cmp(&a.created_at) .then_with(|| a.name.cmp(&b.name)) }); let tags = tags .iter() .skip(cursor) .take(limit) .map(out::TagWire::of) .collect(); Ok(out::TagsOut { tags })}
// Origin: knot-xrpc/src/reads.rs: git_list_refspub fn list_refs( repo: &Repo, cursor: usize, limit: Option<usize>,) -> Result<out::ListRefsOut, StaticXrpcError> { let limit = limit.unwrap_or(LIST_REFS_DEFAULT).min(LIST_REFS_MAX).max(1); let mut refs: Vec<_> = repo .references()? .into_iter() .filter(|record| knot_git::RefClass::of(&record.name) == knot_git::RefClass::Public) .collect(); refs.sort_by(|a, b| a.name.as_str().cmp(b.name.as_str())); let total = refs.len(); let window = refs .iter() .skip(cursor) .take(limit) .map(|record| out::RefWire { name: record.name.as_str().to_string(), sha: record.target, }) .collect(); let next_cursor = cursor .checked_add(limit) .filter(|&end| end < total) .map(|end| end.to_string()); let default_branch = repo.head().map(|head| out::DefaultBranchWire { name: head.name.as_str().to_string(), head: Some(head.target.to_hex()), }); Ok(out::ListRefsOut { refs: window, cursor: next_cursor, default_branch, })}
// Origin: knot-xrpc/src/reads.rs: sync_list_repospub fn list_repos( snapshots: &[RepoSnapshot], cursor: usize, limit: Option<usize>, descending: bool,) -> Result<out::ListReposOut, StaticXrpcError> { let limit = limit .unwrap_or(LIST_REPOS_DEFAULT) .min(LIST_REPOS_MAX) .max(1); let mut repos: Vec<out::RepoWire> = snapshots .iter() .map(|s| { let default_branch = s.head.as_ref().map(|head_oid| { let name = s .public_refs .iter() .find(|(_, target)| target == head_oid) .map(|(n, _)| n.clone()) .unwrap_or_else(|| "refs/heads/main".to_string()); out::DefaultBranchWire { name, head: Some(head_oid.clone()), } }); out::RepoWire { repo: s.repo_did.clone(), status: "active", default_branch, } }) .collect();
if descending { repos.reverse(); } let total = repos.len(); let window = repos.into_iter().skip(cursor).take(limit).collect(); let next_cursor = cursor .checked_add(limit) .filter(|&end| end < total) .map(|end| end.to_string()); Ok(out::ListReposOut { repos: window, cursor: next_cursor, })}
// Origin: knot-xrpc/src/reads.rs: repo_treepub fn tree( repo: &Repo, refspec: &str, path: Option<&RepoPath>, deadline: Option<Instant>, response_limit: usize,) -> Result<out::TreeOut, StaticXrpcError> { let commit = commit_for(repo, refspec)?; let entries = repo .tree_entries_at(commit, path)? .ok_or_else(StaticXrpcError::path_not_found)?; let names: Vec<String> = entries.iter().map(|entry| entry.name.clone()).collect(); let attributed = repo .last_commits(commit, path, &names, deadline) .unwrap_or_default(); let files: Vec<out::TreeEntryOut> = entries .iter() .map(|entry| out::TreeEntryOut { name: entry.name.clone(), mode: entry.kind.mode_octal().to_string(), size: entry.size as i64, last_commit: attributed.get(&entry.name).map(|last| out::LastCommitOut { hash: last.id, message: last.subject.clone(), when: out::rfc3339(last.time.get(), 0), author: None, }), }) .collect(); let newest = attributed.values().max_by_key(|last| (last.time, last.id)); let last_commit = newest.map(|last| out::LastCommitOut { hash: last.id, message: last.subject.clone(), when: out::rfc3339(last.time.get(), 0), author: repo .find_commit(last.id) .ok() .map(|commit| out::SignatureOut { name: commit.author.name, email: commit.author.email, when: String::new(), }), }); let readme = readme_of(repo, commit, path, &entries, response_limit); let (parent, dotdot) = match path.map(|p| p.as_str()).filter(|s| !s.is_empty()) { Some(s) => ( Some(s.to_string()), s.rsplit_once('/').map(|(parent, _)| parent.to_string()), ), None => (None, None), }; Ok(out::TreeOut { refspec: refspec.to_string(), parent, dotdot, files, last_commit, readme, })}
#[derive(Debug, Clone, PartialEq, Eq)]pub struct RawBlob { pub mime: &'static str, pub contents: Vec<u8>, pub is_media: bool,}
#[derive(Debug, Clone, PartialEq, Eq)]pub enum BlobOutput { Json(out::BlobOut), Raw(RawBlob),}
// Origin: knot-xrpc/src/reads.rs: repo_blobpub fn blob( repo: &Repo, refspec: &str, path: &str, raw: bool, deadline: Option<Instant>, response_limit: usize,) -> Result<BlobOutput, StaticXrpcError> { if path.is_empty() { return Err(StaticXrpcError::invalid_request("missing path parameter")); } let commit = commit_for(repo, refspec)?; if let Some(submodule) = repo .submodules(commit) .unwrap_or_default() .into_iter() .find(|submodule| submodule.path.as_str() == path) { return Ok(BlobOutput::Json(out::BlobOut { refspec: refspec.to_string(), path: path.to_string(), content: None, encoding: None, size: None, is_binary: None, mime_type: None, last_commit: None, submodule: Some(out::SubmoduleOut { name: submodule.name, url: submodule.url, branch: submodule .branch .map(|branch| branch.to_string()) .unwrap_or_default(), }), })); }
let file_path = RepoPath::new(path).map_err(|_| StaticXrpcError::file_not_found())?; let entry = repo .entry_at(commit, &file_path)? .filter(|entry| { matches!( entry.kind, EntryKind::Blob | EntryKind::BlobExecutable | EntryKind::Link ) }) .ok_or_else(StaticXrpcError::file_not_found)?;
if repo .blob_size(entry.oid) .map_err(|_| StaticXrpcError::file_not_found())? > blob_serving_limit(raw, response_limit) { return Err(StaticXrpcError::blob_too_large()); }
let contents = repo .read_blob(entry.oid) .map_err(|_| StaticXrpcError::file_not_found())?; let mime = sniff::override_by_extension(path, sniff::detect_content_type(&contents));
if raw { let (mime, is_media) = if mime.starts_with("image/") || mime.starts_with("video/") { (mime, true) } else if sniff::is_textual_mime(mime) { ("text/plain; charset=utf-8", false) } else { return Err(StaticXrpcError::disallowed_raw_mime()); }; return Ok(BlobOutput::Raw(RawBlob { mime, contents, is_media, })); }
let is_binary = !sniff::is_textual_mime(mime); let size = contents.len() as i64; let (content, encoding) = if is_binary { (STANDARD.encode(&contents), "base64") } else { (String::from_utf8_lossy(&contents).into_owned(), "utf-8") }; let dir = file_path.parent(); let name = file_path.file_name().to_string(); let last_commit = repo .last_commits(commit, dir.as_ref(), std::slice::from_ref(&name), deadline) .ok() .and_then(|attributed| attributed.get(&name).cloned()) .map(|last| out::LastCommitOut { hash: last.id, message: last.subject, when: out::rfc3339(last.time.get(), 0), author: repo .find_commit(last.id) .ok() .map(|commit| out::SignatureOut { name: commit.author.name, email: commit.author.email, when: String::new(), }), });
Ok(BlobOutput::Json(out::BlobOut { refspec: refspec.to_string(), path: path.to_string(), content: Some(content), encoding: Some(encoding), size: Some(size), is_binary: Some(is_binary), mime_type: Some(mime), last_commit, submodule: None, }))}
// Origin: knot-xrpc/src/reads.rs: repo_languagespub fn languages( repo: &Repo, refspec: &str, deadline: Option<Instant>,) -> Result<out::LanguagesOut, StaticXrpcError> { let commit = commit_for(repo, refspec)?; let sizes = knot_langs::analyze(repo, commit, deadline)?; let total: u64 = sizes.values().map(|size| size.get()).sum(); let mut languages: Vec<out::LanguageOut> = sizes .iter() .filter(|(_, size)| size.get() > 0) .map(|(name, size)| out::LanguageOut { name: *name, size: *size, percentage: ((size.get() as f64) / (total as f64) * 100.0).round() as i64, }) .collect(); languages.sort_by(|a, b| b.size.cmp(&a.size).then_with(|| a.name.cmp(&b.name))); let count = languages.len() as i64; Ok(out::LanguagesOut { refspec: refspec.to_string(), languages: (!languages.is_empty()).then_some(languages), total_size: (total > 0).then_some(total), total_files: (total > 0).then_some(count), })}
// Origin: knot-xrpc/src/reads.rs: repo_diffpub fn diff(repo: &Repo, refspec: &str) -> Result<out::DiffOut, StaticXrpcError> { let target = commit_for(repo, refspec)?; let commit = repo.find_commit(target)?; let patches = repo.commit_patches( PatchRange { base: commit.parents.first().copied(), head: target, }, &mut BinaryBudget::Omit, )?; Ok(out::DiffOut { refspec: refspec.to_string(), diff: out::nice_diff(&commit, &patches), })}
// Origin: knot-xrpc/src/sniff.rsmod sniff { const SNIFF_LIMIT: usize = 512;
fn is_ws(byte: u8) -> bool { matches!(byte, b'\t' | b'\n' | 0x0c | b'\r' | b' ') }
fn is_tt(byte: u8) -> bool { matches!(byte, b' ' | b'>') }
enum Sig { Exact(&'static [u8], &'static str), Masked { mask: &'static [u8], pat: &'static [u8], skip_ws: bool, ct: &'static str, }, Html(&'static [u8]), Mp4, Text, }
impl Sig { fn detect(&self, data: &[u8], first_non_ws: usize) -> Option<&'static str> { match self { Sig::Exact(sig, ct) => data.starts_with(sig).then_some(*ct), Sig::Masked { mask, pat, skip_ws, ct, } => { let data = if *skip_ws { &data[first_non_ws..] } else { data }; (mask.len() == pat.len() && data.len() >= pat.len() && pat .iter() .zip(mask.iter()) .zip(data.iter()) .all(|((byte, mask), d)| d & mask == *byte)) .then_some(*ct) } Sig::Html(tag) => { let data = &data[first_non_ws..]; (data.len() > tag.len() && tag.iter().zip(data.iter()).all(|(byte, cand)| { let c = if byte.is_ascii_uppercase() { cand & 0xDF } else { *cand }; *byte == c }) && is_tt(data[tag.len()])) .then_some("text/html; charset=utf-8") } Sig::Mp4 => mp4(data), Sig::Text => text(data, first_non_ws), } } }
fn mp4(data: &[u8]) -> Option<&'static str> { if data.len() < 12 { return None; } let box_size = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize; if data.len() < box_size || !box_size.is_multiple_of(4) || &data[4..8] != b"ftyp" { return None; } (8..box_size) .step_by(4) .filter(|start| *start != 12) .any(|start| &data[start..start + 3] == b"mp4") .then_some("video/mp4") }
fn text(data: &[u8], first_non_ws: usize) -> Option<&'static str> { data[first_non_ws..] .iter() .all(|byte| !matches!(byte, 0x00..=0x08 | 0x0b | 0x0e..=0x1a | 0x1c..=0x1f)) .then_some("text/plain; charset=utf-8") }
const SIGNATURES: &[Sig] = &[ Sig::Html(b"<!DOCTYPE HTML"), Sig::Html(b"<HTML"), Sig::Html(b"<HEAD"), Sig::Html(b"<SCRIPT"), Sig::Html(b"<IFRAME"), Sig::Html(b"<H1"), Sig::Html(b"<DIV"), Sig::Html(b"<FONT"), Sig::Html(b"<TABLE"), Sig::Html(b"<A"), Sig::Html(b"<STYLE"), Sig::Html(b"<TITLE"), Sig::Html(b"<B"), Sig::Html(b"<BODY"), Sig::Html(b"<BR"), Sig::Html(b"<P"), Sig::Html(b"<!--"), Sig::Masked { mask: b"\xff\xff\xff\xff\xff", pat: b"<?xml", skip_ws: true, ct: "text/xml; charset=utf-8", }, Sig::Exact(b"%PDF-", "application/pdf"), Sig::Exact(b"%!PS-Adobe-", "application/postscript"), Sig::Masked { mask: b"\xff\xff\x00\x00", pat: b"\xfe\xff\x00\x00", skip_ws: false, ct: "text/plain; charset=utf-16be", }, Sig::Masked { mask: b"\xff\xff\x00\x00", pat: b"\xff\xfe\x00\x00", skip_ws: false, ct: "text/plain; charset=utf-16le", }, Sig::Masked { mask: b"\xff\xff\xff\x00", pat: b"\xef\xbb\xbf\x00", skip_ws: false, ct: "text/plain; charset=utf-8", }, Sig::Exact(b"\x00\x00\x01\x00", "image/x-icon"), Sig::Exact(b"\x00\x00\x02\x00", "image/x-icon"), Sig::Exact(b"BM", "image/bmp"), Sig::Exact(b"GIF87a", "image/gif"), Sig::Exact(b"GIF89a", "image/gif"), Sig::Masked { mask: b"\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff", pat: b"RIFF\x00\x00\x00\x00WEBPVP", skip_ws: false, ct: "image/webp", }, Sig::Exact(b"\x89PNG\x0d\x0a\x1a\x0a", "image/png"), Sig::Exact(b"\xff\xd8\xff", "image/jpeg"), Sig::Masked { mask: b"\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff", pat: b"FORM\x00\x00\x00\x00AIFF", skip_ws: false, ct: "audio/aiff", }, Sig::Masked { mask: b"\xff\xff\xff", pat: b"ID3", skip_ws: false, ct: "audio/mpeg", }, Sig::Masked { mask: b"\xff\xff\xff\xff\xff", pat: b"OggS\x00", skip_ws: false, ct: "application/ogg", }, Sig::Masked { mask: b"\xff\xff\xff\xff\xff\xff\xff\xff", pat: b"MThd\x00\x00\x00\x06", skip_ws: false, ct: "audio/midi", }, Sig::Masked { mask: b"\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff", pat: b"RIFF\x00\x00\x00\x00AVI ", skip_ws: false, ct: "video/avi", }, Sig::Masked { mask: b"\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff", pat: b"RIFF\x00\x00\x00\x00WAVE", skip_ws: false, ct: "audio/wave", }, Sig::Mp4, Sig::Exact(b"\x1a\x45\xdf\xa3", "video/webm"), Sig::Masked { mask: b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff", pat: b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00LP", skip_ws: false, ct: "application/vnd.ms-fontobject", }, Sig::Exact(b"\x00\x01\x00\x00", "font/ttf"), Sig::Exact(b"OTTO", "font/otf"), Sig::Exact(b"ttcf", "font/collection"), Sig::Exact(b"wOFF", "font/woff"), Sig::Exact(b"wOF2", "font/woff2"), Sig::Exact(b"\x1f\x8b\x08", "application/x-gzip"), Sig::Exact(b"PK\x03\x04", "application/zip"), Sig::Exact(b"Rar!\x1a\x07\x00", "application/x-rar-compressed"), Sig::Exact(b"Rar!\x1a\x07\x01\x00", "application/x-rar-compressed"), Sig::Exact(b"\x00\x61\x73\x6d", "application/wasm"), Sig::Text, ];
pub fn detect_content_type(content: &[u8]) -> &'static str { let data = &content[..content.len().min(SNIFF_LIMIT)]; let first_non_ws = data .iter() .position(|byte| !is_ws(*byte)) .unwrap_or(data.len()); SIGNATURES .iter() .find_map(|sig| sig.detect(data, first_non_ws)) .unwrap_or("application/octet-stream") }
pub fn override_by_extension(path: &str, detected: &'static str) -> &'static str { let Some((_, ext)) = path.rsplit_once('.') else { return detected; }; match ext.to_ascii_lowercase().as_str() { "svg" => "image/svg+xml", "avif" => "image/avif", "jxl" => "image/jxl", "heic" | "heif" => "image/heif", _ => detected, } }
pub fn is_textual_mime(mime: &str) -> bool { mime.starts_with("text/") || matches!( mime, "application/json" | "application/xml" | "application/yaml" | "application/x-yaml" | "application/toml" | "application/javascript" | "application/ecmascript" ) }}