This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / crates / knot-xrpc / src / sniff.rs
8.5 kB 264 lines
1const SNIFF_LIMIT: usize = 512; 2 3fn is_ws(byte: u8) -> bool { 4 matches!(byte, b'\t' | b'\n' | 0x0c | b'\r' | b' ') 5} 6 7fn is_tt(byte: u8) -> bool { 8 matches!(byte, b' ' | b'>') 9} 10 11enum Sig { 12 Exact(&'static [u8], &'static str), 13 Masked { 14 mask: &'static [u8], 15 pat: &'static [u8], 16 skip_ws: bool, 17 ct: &'static str, 18 }, 19 Html(&'static [u8]), 20 Mp4, 21 Text, 22} 23 24impl Sig { 25 fn detect(&self, data: &[u8], first_non_ws: usize) -> Option<&'static str> { 26 match self { 27 Sig::Exact(sig, ct) => data.starts_with(sig).then_some(*ct), 28 Sig::Masked { 29 mask, 30 pat, 31 skip_ws, 32 ct, 33 } => { 34 let data = if *skip_ws { 35 &data[first_non_ws..] 36 } else { 37 data 38 }; 39 (mask.len() == pat.len() 40 && data.len() >= pat.len() 41 && pat 42 .iter() 43 .zip(mask.iter()) 44 .enumerate() 45 .all(|(index, (byte, mask))| data[index] & mask == *byte)) 46 .then_some(*ct) 47 } 48 Sig::Html(tag) => { 49 let data = &data[first_non_ws..]; 50 (data.len() > tag.len() 51 && tag.iter().enumerate().all(|(index, byte)| { 52 let candidate = data[index]; 53 let candidate = match byte.is_ascii_uppercase() { 54 true => candidate & 0xDF, 55 false => candidate, 56 }; 57 *byte == candidate 58 }) 59 && is_tt(data[tag.len()])) 60 .then_some("text/html; charset=utf-8") 61 } 62 Sig::Mp4 => mp4(data), 63 Sig::Text => text(data, first_non_ws), 64 } 65 } 66} 67 68fn mp4(data: &[u8]) -> Option<&'static str> { 69 if data.len() < 12 { 70 return None; 71 } 72 let box_size = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize; 73 if data.len() < box_size || !box_size.is_multiple_of(4) || &data[4..8] != b"ftyp" { 74 return None; 75 } 76 (8..box_size) 77 .step_by(4) 78 .filter(|start| *start != 12) 79 .any(|start| &data[start..start + 3] == b"mp4") 80 .then_some("video/mp4") 81} 82 83fn text(data: &[u8], first_non_ws: usize) -> Option<&'static str> { 84 data[first_non_ws..] 85 .iter() 86 .all(|byte| !matches!(byte, 0x00..=0x08 | 0x0b | 0x0e..=0x1a | 0x1c..=0x1f)) 87 .then_some("text/plain; charset=utf-8") 88} 89 90const SIGNATURES: &[Sig] = &[ 91 Sig::Html(b"<!DOCTYPE HTML"), 92 Sig::Html(b"<HTML"), 93 Sig::Html(b"<HEAD"), 94 Sig::Html(b"<SCRIPT"), 95 Sig::Html(b"<IFRAME"), 96 Sig::Html(b"<H1"), 97 Sig::Html(b"<DIV"), 98 Sig::Html(b"<FONT"), 99 Sig::Html(b"<TABLE"), 100 Sig::Html(b"<A"), 101 Sig::Html(b"<STYLE"), 102 Sig::Html(b"<TITLE"), 103 Sig::Html(b"<B"), 104 Sig::Html(b"<BODY"), 105 Sig::Html(b"<BR"), 106 Sig::Html(b"<P"), 107 Sig::Html(b"<!--"), 108 Sig::Masked { 109 mask: b"\xff\xff\xff\xff\xff", 110 pat: b"<?xml", 111 skip_ws: true, 112 ct: "text/xml; charset=utf-8", 113 }, 114 Sig::Exact(b"%PDF-", "application/pdf"), 115 Sig::Exact(b"%!PS-Adobe-", "application/postscript"), 116 Sig::Masked { 117 mask: b"\xff\xff\x00\x00", 118 pat: b"\xfe\xff\x00\x00", 119 skip_ws: false, 120 ct: "text/plain; charset=utf-16be", 121 }, 122 Sig::Masked { 123 mask: b"\xff\xff\x00\x00", 124 pat: b"\xff\xfe\x00\x00", 125 skip_ws: false, 126 ct: "text/plain; charset=utf-16le", 127 }, 128 Sig::Masked { 129 mask: b"\xff\xff\xff\x00", 130 pat: b"\xef\xbb\xbf\x00", 131 skip_ws: false, 132 ct: "text/plain; charset=utf-8", 133 }, 134 Sig::Exact(b"\x00\x00\x01\x00", "image/x-icon"), 135 Sig::Exact(b"\x00\x00\x02\x00", "image/x-icon"), 136 Sig::Exact(b"BM", "image/bmp"), 137 Sig::Exact(b"GIF87a", "image/gif"), 138 Sig::Exact(b"GIF89a", "image/gif"), 139 Sig::Masked { 140 mask: b"\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff", 141 pat: b"RIFF\x00\x00\x00\x00WEBPVP", 142 skip_ws: false, 143 ct: "image/webp", 144 }, 145 Sig::Exact(b"\x89PNG\x0d\x0a\x1a\x0a", "image/png"), 146 Sig::Exact(b"\xff\xd8\xff", "image/jpeg"), 147 Sig::Masked { 148 mask: b"\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff", 149 pat: b"FORM\x00\x00\x00\x00AIFF", 150 skip_ws: false, 151 ct: "audio/aiff", 152 }, 153 Sig::Masked { 154 mask: b"\xff\xff\xff", 155 pat: b"ID3", 156 skip_ws: false, 157 ct: "audio/mpeg", 158 }, 159 Sig::Masked { 160 mask: b"\xff\xff\xff\xff\xff", 161 pat: b"OggS\x00", 162 skip_ws: false, 163 ct: "application/ogg", 164 }, 165 Sig::Masked { 166 mask: b"\xff\xff\xff\xff\xff\xff\xff\xff", 167 pat: b"MThd\x00\x00\x00\x06", 168 skip_ws: false, 169 ct: "audio/midi", 170 }, 171 Sig::Masked { 172 mask: b"\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff", 173 pat: b"RIFF\x00\x00\x00\x00AVI ", 174 skip_ws: false, 175 ct: "video/avi", 176 }, 177 Sig::Masked { 178 mask: b"\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff", 179 pat: b"RIFF\x00\x00\x00\x00WAVE", 180 skip_ws: false, 181 ct: "audio/wave", 182 }, 183 Sig::Mp4, 184 Sig::Exact(b"\x1a\x45\xdf\xa3", "video/webm"), 185 Sig::Masked { 186 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", 187 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", 188 skip_ws: false, 189 ct: "application/vnd.ms-fontobject", 190 }, 191 Sig::Exact(b"\x00\x01\x00\x00", "font/ttf"), 192 Sig::Exact(b"OTTO", "font/otf"), 193 Sig::Exact(b"ttcf", "font/collection"), 194 Sig::Exact(b"wOFF", "font/woff"), 195 Sig::Exact(b"wOF2", "font/woff2"), 196 Sig::Exact(b"\x1f\x8b\x08", "application/x-gzip"), 197 Sig::Exact(b"PK\x03\x04", "application/zip"), 198 Sig::Exact(b"Rar!\x1a\x07\x00", "application/x-rar-compressed"), 199 Sig::Exact(b"Rar!\x1a\x07\x01\x00", "application/x-rar-compressed"), 200 Sig::Exact(b"\x00\x61\x73\x6d", "application/wasm"), 201 Sig::Text, 202]; 203 204pub(crate) fn detect_content_type(content: &[u8]) -> &'static str { 205 let data = &content[..content.len().min(SNIFF_LIMIT)]; 206 let first_non_ws = data 207 .iter() 208 .position(|byte| !is_ws(*byte)) 209 .unwrap_or(data.len()); 210 SIGNATURES 211 .iter() 212 .find_map(|sig| sig.detect(data, first_non_ws)) 213 .unwrap_or("application/octet-stream") 214} 215 216pub(crate) fn override_by_extension(path: &str, detected: &'static str) -> &'static str { 217 let extension = path.rsplit_once('.').map(|(_, ext)| ext).unwrap_or(""); 218 match extension.to_ascii_lowercase().as_str() { 219 "svg" => "image/svg+xml", 220 "avif" => "image/avif", 221 "jxl" => "image/jxl", 222 "heic" | "heif" => "image/heif", 223 _ => detected, 224 } 225} 226 227pub(crate) fn is_textual_mime(mime: &str) -> bool { 228 mime.starts_with("text/") 229 || matches!( 230 mime, 231 "application/json" 232 | "application/xml" 233 | "application/yaml" 234 | "application/x-yaml" 235 | "application/toml" 236 | "application/javascript" 237 | "application/ecmascript" 238 ) 239} 240 241#[cfg(test)] 242mod tests { 243 use super::detect_content_type; 244 245 #[test] 246 fn detects_common_content_signatures() { 247 let cases: &[(&[u8], &str)] = &[ 248 (b" <!DOCTYPE html>\n", "text/html; charset=utf-8"), 249 (b"<html><body>", "text/html; charset=utf-8"), 250 (b"<a href>", "text/html; charset=utf-8"), 251 (b"\n\t<?xml version=\"1.0\"?>", "text/xml; charset=utf-8"), 252 (b"\xfe\xff\x00h", "text/plain; charset=utf-16be"), 253 (b"\xef\xbb\xbfhello", "text/plain; charset=utf-8"), 254 (b"\x89PNG\x0d\x0a\x1a\x0a", "image/png"), 255 (b"GIF89a", "image/gif"), 256 (b"fn main() {}\n", "text/plain; charset=utf-8"), 257 (b"\x00\x01\x02\x03", "application/octet-stream"), 258 (b"", "text/plain; charset=utf-8"), 259 ]; 260 cases.iter().for_each(|(input, expected)| { 261 assert_eq!(detect_content_type(input), *expected); 262 }); 263 } 264}