1#![forbid(unsafe_code)]
2#![deny(unused_imports)]
3#![deny(missing_docs)]
4use dyf::{DynDisplay, FormatString, dformat};
144use flaglet::flags;
145use flate2::{Compression, read::GzDecoder, write::GzEncoder};
146use memchr::memchr;
147use pest::{Span, error::ErrorVariant};
148use regex::bytes::{self};
149use serde::{Deserialize, Serialize};
150use std::{
151 borrow::Cow,
152 cmp::max,
153 collections::{HashMap, HashSet},
154 fmt::{self, Debug, Display},
155 fs::File,
156 io::{self, Read, SeekFrom, Write},
157 mem::swap,
158 ops::{Add, BitAnd, BitOr, BitXor, Deref, Div, Mul, Rem, Sub},
159 path::Path,
160 sync::OnceLock,
161};
162use tar::Archive;
163use thiserror::Error;
164use tracing::{Level, debug, enabled, error, trace};
165
166use crate::{
167 numeric::{Float, FloatDataType, Scalar, ScalarDataType},
168 parser::{FileMagicParser, Rule},
169 readers::DataRead,
170 utils::{
171 debug_string_from_vec_u8, debug_string_from_vec_u16, decode_id3, find_json_boundaries,
172 run_utf8_validation,
173 },
174};
175
176mod numeric;
177mod parser;
178pub mod readers;
179pub use readers::DataReader;
180mod utils;
181
182const HARDCODED_MAGIC_STRENGTH: u64 = 2048;
183const HARDCODED_SOURCE: &str = "hardcoded";
184const MAX_RECURSION: usize = 50;
186const FILE_REGEX_MAX: usize = 8192;
188
189pub const FILE_BYTES_MAX: usize = 7 * 1024 * 1024;
195pub const DEFAULT_BIN_MIMETYPE: &str = "application/octet-stream";
197pub const DEFAULT_TEXT_MIMETYPE: &str = "text/plain";
199
200pub(crate) const TIMESTAMP_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
201
202macro_rules! debug_panic {
203 ($($arg:tt)*) => {
204 if cfg!(debug_assertions) {
205 panic!($($arg)*);
206 }
207 };
208}
209
210macro_rules! read {
211 ($r: expr, $ty: ty) => {{
212 let mut a = [0u8; std::mem::size_of::<$ty>()];
213 $r.read_exact_into(&mut a)?;
214 a
215 }};
216}
217
218macro_rules! read_le {
219 ($r:expr, $ty: ty ) => {{ <$ty>::from_le_bytes(read!($r, $ty)) }};
220}
221
222macro_rules! read_be {
223 ($r:expr, $ty: ty ) => {{ <$ty>::from_be_bytes(read!($r, $ty)) }};
224}
225
226macro_rules! read_me {
227 ($r: expr) => {{ ((read_le!($r, u16) as i32) << 16) | (read_le!($r, u16) as i32) }};
228}
229
230#[inline(always)]
231fn read_octal_u64<D: DataRead>(haystack: &mut D) -> Option<u64> {
232 let s = haystack
233 .read_while_or_limit(|b| matches!(b, b'0'..=b'7'), 22)
234 .map(|buf| str::from_utf8(buf))
235 .ok()?
236 .ok()?;
237
238 if !s.starts_with("0") {
239 return None;
240 }
241
242 u64::from_str_radix(s, 8).ok()
243}
244
245#[derive(Debug, Error)]
247pub enum Error {
248 #[error("{0}")]
250 Msg(String),
251
252 #[error("source={0} line={1} error={2}")]
254 Verify(String, usize, Box<Error>),
255
256 #[error("source={0} line={1} error={2}")]
258 Localized(String, usize, Box<Error>),
259
260 #[error("missing rule: {0}")]
262 MissingRule(String),
263
264 #[error("maximum recursion reached: {0}")]
266 MaximumRecursion(usize),
267
268 #[error("io: {0}")]
270 Io(#[from] io::Error),
271
272 #[error("parser error: {0}")]
274 Parse(#[from] Box<pest::error::Error<Rule>>),
275
276 #[error("formatting: {0}")]
278 Format(#[from] dyf::Error),
279
280 #[error("regex: {0}")]
282 Regex(#[from] regex::Error),
283
284 #[error("{0}")]
286 Serialize(#[from] bincode::error::EncodeError),
287
288 #[error("{0}")]
290 Deserialize(#[from] bincode::error::DecodeError),
291}
292
293impl Error {
294 #[inline]
295 fn parser<S: ToString>(msg: S, span: Span<'_>) -> Self {
296 Self::Parse(Box::new(pest::error::Error::new_from_span(
297 ErrorVariant::CustomError {
298 message: msg.to_string(),
299 },
300 span,
301 )))
302 }
303
304 fn msg<M: AsRef<str>>(msg: M) -> Self {
305 Self::Msg(msg.as_ref().into())
306 }
307
308 fn localized<S: AsRef<str>>(source: S, line: usize, err: Error) -> Self {
309 Self::Localized(source.as_ref().into(), line, err.into())
310 }
311
312 pub fn unwrap_localized(&self) -> &Self {
314 match self {
315 Self::Localized(_, _, e) => e,
316 _ => self,
317 }
318 }
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize)]
322enum Message {
323 String(String),
324 Format {
325 printf_spec: String,
326 fs: FormatString,
327 },
328}
329
330impl Display for Message {
331 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332 match self {
333 Self::String(s) => write!(f, "{s}"),
334 Self::Format { printf_spec: _, fs } => write!(f, "{}", fs.to_string_lossy()),
335 }
336 }
337}
338
339impl Message {
340 fn to_string_lossy(&self) -> Cow<'_, str> {
341 match self {
342 Message::String(s) => Cow::Borrowed(s),
343 Message::Format { printf_spec: _, fs } => fs.to_string_lossy(),
344 }
345 }
346
347 #[inline(always)]
348 fn format_with(&self, mr: Option<&MatchRes>) -> Result<Cow<'_, str>, Error> {
349 match self {
350 Self::String(s) => Ok(Cow::Borrowed(s.as_str())),
351 Self::Format {
352 printf_spec: c_spec,
353 fs,
354 } => {
355 if let Some(mr) = mr {
356 match mr {
357 MatchRes::Float(_, _) | MatchRes::Bytes(_, _, _, _) => {
358 Ok(Cow::Owned(dformat!(fs, mr)?))
359 }
360 MatchRes::Scalar(_, scalar) => {
361 if c_spec.as_str() == "c" {
363 match scalar {
364 Scalar::byte(b) => {
365 let b = (*b as u8) as char;
366 Ok(Cow::Owned(dformat!(fs, b)?))
367 }
368 Scalar::ubyte(b) => {
369 let b = *b as char;
370 Ok(Cow::Owned(dformat!(fs, b)?))
371 }
372 _ => Ok(Cow::Owned(dformat!(fs, mr)?)),
373 }
374 } else {
375 Ok(Cow::Owned(dformat!(fs, mr)?))
376 }
377 }
378 }
379 } else {
380 Ok(fs.to_string_lossy())
381 }
382 }
383 }
384 }
385}
386
387impl ScalarDataType {
388 #[inline(always)]
389 fn read<R: DataRead>(&self, from: &mut R, switch_endianness: bool) -> Result<Scalar, Error> {
390 macro_rules! _read_le {
391 ($ty: ty) => {{
392 if switch_endianness {
393 <$ty>::from_be_bytes(read!(from, $ty))
394 } else {
395 <$ty>::from_le_bytes(read!(from, $ty))
396 }
397 }};
398 }
399
400 macro_rules! _read_be {
401 ($ty: ty) => {{
402 if switch_endianness {
403 <$ty>::from_le_bytes(read!(from, $ty))
404 } else {
405 <$ty>::from_be_bytes(read!(from, $ty))
406 }
407 }};
408 }
409
410 macro_rules! _read_ne {
411 ($ty: ty) => {{
412 if cfg!(target_endian = "big") {
413 _read_be!($ty)
414 } else {
415 _read_le!($ty)
416 }
417 }};
418 }
419
420 macro_rules! _read_me {
421 () => {
422 ((_read_le!(u16) as i32) << 16) | (_read_le!(u16) as i32)
423 };
424 }
425
426 Ok(match self {
427 Self::byte => Scalar::byte(read!(from, u8)[0] as i8),
429 Self::short => Scalar::short(_read_ne!(i16)),
430 Self::long => Scalar::long(_read_ne!(i32)),
431 Self::date => Scalar::date(_read_ne!(i32)),
432 Self::ldate => Scalar::ldate(_read_ne!(i32)),
433 Self::qwdate => Scalar::qwdate(_read_ne!(i64)),
434 Self::leshort => Scalar::leshort(_read_le!(i16)),
435 Self::lelong => Scalar::lelong(_read_le!(i32)),
436 Self::lequad => Scalar::lequad(_read_le!(i64)),
437 Self::bequad => Scalar::bequad(_read_be!(i64)),
438 Self::belong => Scalar::belong(_read_be!(i32)),
439 Self::bedate => Scalar::bedate(_read_be!(i32)),
440 Self::beldate => Scalar::beldate(_read_be!(i32)),
441 Self::beqdate => Scalar::beqdate(_read_be!(i64)),
442 Self::beqldate => Scalar::beqldate(_read_be!(i64)),
443 Self::ubyte => Scalar::ubyte(read!(from, u8)[0]),
445 Self::ushort => Scalar::ushort(_read_ne!(u16)),
446 Self::uleshort => Scalar::uleshort(_read_le!(u16)),
447 Self::ulelong => Scalar::ulelong(_read_le!(u32)),
448 Self::uledate => Scalar::uledate(_read_le!(u32)),
449 Self::ulequad => Scalar::ulequad(_read_le!(u64)),
450 Self::offset => Scalar::offset(from.stream_position()),
451 Self::ubequad => Scalar::ubequad(_read_be!(u64)),
452 Self::medate => Scalar::medate(_read_me!()),
453 Self::meldate => Scalar::meldate(_read_me!()),
454 Self::melong => Scalar::melong(_read_me!()),
455 Self::beshort => Scalar::beshort(_read_be!(i16)),
456 Self::quad => Scalar::quad(_read_ne!(i64)),
457 Self::uquad => Scalar::uquad(_read_ne!(u64)),
458 Self::ledate => Scalar::ledate(_read_le!(i32)),
459 Self::leldate => Scalar::leldate(_read_le!(i32)),
460 Self::leqdate => Scalar::leqdate(_read_le!(i64)),
461 Self::leqldate => Scalar::leqldate(_read_le!(i64)),
462 Self::leqwdate => Scalar::leqwdate(_read_le!(i64)),
463 Self::ubelong => Scalar::ubelong(_read_be!(u32)),
464 Self::ulong => Scalar::ulong(_read_ne!(u32)),
465 Self::ubeshort => Scalar::ubeshort(_read_be!(u16)),
466 Self::ubeqdate => Scalar::ubeqdate(_read_be!(u64)),
467 Self::lemsdosdate => Scalar::lemsdosdate(_read_le!(u16)),
468 Self::lemsdostime => Scalar::lemsdostime(_read_le!(u16)),
469 Self::guid => Scalar::guid(
471 (_read_le!(u32) as u128) << 96
472 | (_read_le!(u16) as u128) << 80
473 | (_read_le!(u16) as u128) << 64
474 | (_read_be!(u64) as u128),
475 ),
476 Self::leguid => Scalar::leguid(
477 (_read_le!(u32) as u128) << 96
478 | (_read_le!(u16) as u128) << 80
479 | (_read_le!(u16) as u128) << 64
480 | (_read_be!(u64) as u128),
481 ),
482 Self::beguid => Scalar::beguid(_read_be!(u128)),
483 })
484 }
485}
486
487impl FloatDataType {
488 #[inline(always)]
489 fn read<R: DataRead>(&self, from: &mut R, switch_endianness: bool) -> Result<Float, Error> {
490 macro_rules! _read_le {
491 ($ty: ty) => {{
492 if switch_endianness {
493 <$ty>::from_be_bytes(read!(from, $ty))
494 } else {
495 <$ty>::from_le_bytes(read!(from, $ty))
496 }
497 }};
498 }
499
500 macro_rules! _read_be {
501 ($ty: ty) => {{
502 if switch_endianness {
503 <$ty>::from_le_bytes(read!(from, $ty))
504 } else {
505 <$ty>::from_be_bytes(read!(from, $ty))
506 }
507 }};
508 }
509
510 macro_rules! _read_ne {
511 ($ty: ty) => {{
512 if cfg!(target_endian = "big") {
513 _read_be!($ty)
514 } else {
515 _read_le!($ty)
516 }
517 }};
518 }
519
520 macro_rules! _read_me {
521 () => {
522 ((_read_le!(u16) as i32) << 16) | (_read_le!(u16) as i32)
523 };
524 }
525
526 Ok(match self {
527 Self::lefloat => Float::lefloat(_read_le!(f32)),
528 Self::befloat => Float::befloat(_read_le!(f32)),
529 Self::ledouble => Float::ledouble(_read_le!(f64)),
530 Self::bedouble => Float::bedouble(_read_be!(f64)),
531 })
532 }
533}
534
535#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
536enum Op {
537 Mul,
538 Add,
539 Sub,
540 Div,
541 Mod,
542 And,
543 Xor,
544 Or,
545}
546
547impl Display for Op {
548 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
549 match self {
550 Op::Mul => write!(f, "*"),
551 Op::Add => write!(f, "+"),
552 Op::Sub => write!(f, "-"),
553 Op::Div => write!(f, "/"),
554 Op::Mod => write!(f, "%"),
555 Op::And => write!(f, "&"),
556 Op::Or => write!(f, "|"),
557 Op::Xor => write!(f, "^"),
558 }
559 }
560}
561
562#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
563enum CmpOp {
564 Eq,
565 Lt,
566 Gt,
567 BitAnd,
568 Neq, Xor,
570 Not, }
572
573impl CmpOp {
574 #[inline(always)]
575 fn is_neq(&self) -> bool {
576 matches!(self, Self::Neq)
577 }
578}
579
580#[derive(Debug, Clone, Serialize, Deserialize)]
581struct ScalarTransform {
582 op: Op,
583 num: Scalar,
584}
585
586impl ScalarTransform {
587 fn apply(&self, s: Scalar) -> Option<Scalar> {
588 match self.op {
589 Op::Add => s.checked_add(self.num),
590 Op::Sub => s.checked_sub(self.num),
591 Op::Mul => s.checked_mul(self.num),
592 Op::Div => s.checked_div(self.num),
593 Op::Mod => s.checked_rem(self.num),
594 Op::And => Some(s.bitand(self.num)),
595 Op::Xor => Some(s.bitxor(self.num)),
596 Op::Or => Some(s.bitor(self.num)),
597 }
598 }
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize)]
602struct FloatTransform {
603 op: Op,
604 num: Float,
605}
606
607impl FloatTransform {
608 fn apply(&self, s: Float) -> Float {
609 match self.op {
610 Op::Add => s.add(self.num),
611 Op::Sub => s.sub(self.num),
612 Op::Mul => s.mul(self.num),
613 Op::Div => s.div(self.num),
615 Op::Mod => s.rem(self.num),
617 Op::And | Op::Xor | Op::Or => {
619 debug_panic!("unsupported operation");
620 s
621 }
622 }
623 }
624}
625
626#[derive(Clone, Serialize, Deserialize)]
627enum TestValue<T> {
628 Value(T),
629 Any,
630}
631
632impl Debug for TestValue<Vec<u8>> {
633 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
634 match self {
635 Self::Value(v) => write!(f, "\"{}\"", debug_string_from_vec_u8(v)),
636 Self::Any => write!(f, "ANY"),
637 }
638 }
639}
640
641impl Debug for TestValue<Vec<u16>> {
642 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
643 match self {
644 Self::Value(v) => write!(f, "\"{}\"", debug_string_from_vec_u16(v)),
645 Self::Any => write!(f, "ANY"),
646 }
647 }
648}
649
650impl Debug for TestValue<Scalar> {
651 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
652 match self {
653 Self::Value(s) => write!(f, "{s:?}"),
654 Self::Any => write!(f, "ANY"),
655 }
656 }
657}
658
659impl Debug for TestValue<Float> {
660 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
661 match self {
662 Self::Value(fl) => write!(f, "{fl:?}"),
663 Self::Any => write!(f, "ANY"),
664 }
665 }
666}
667
668impl<T> TestValue<T> {
669 #[inline(always)]
670 fn as_ref(&self) -> TestValue<&T> {
671 match self {
672 Self::Value(v) => TestValue::Value(v),
673 Self::Any => TestValue::Any,
674 }
675 }
676}
677
678#[flags(u8)]
679#[derive(Debug, Serialize, Deserialize)]
680enum ReMod {
681 CaseInsensitive = 1 << 0,
682 StartOffsetUpdate = 1 << 1,
683 LineLimit = 1 << 2,
684 ForceBin = 1 << 3,
685 ForceText = 1 << 4,
686 TrimMatch = 1 << 5,
687}
688
689#[derive(Debug, Clone)]
690struct Regex {
691 s: String,
692 captures_len: usize,
693 re: OnceLock<bytes::Regex>,
694}
695
696#[derive(Serialize, Deserialize)]
697struct SerializedRegex {
698 s: String,
699 captures_len: usize,
700}
701
702impl Serialize for Regex {
703 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
704 where
705 S: serde::Serializer,
706 {
707 SerializedRegex {
708 s: self.s.clone(),
709 captures_len: self.captures_len,
710 }
711 .serialize(serializer)
712 }
713}
714
715impl<'de> Deserialize<'de> for Regex {
716 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
717 where
718 D: serde::Deserializer<'de>,
719 {
720 let sr = SerializedRegex::deserialize(deserializer)?;
721 Ok(Self {
722 s: sr.s,
723 captures_len: sr.captures_len,
724 re: OnceLock::new(),
725 })
726 }
727}
728
729impl Regex {
730 #[inline]
731 fn new(s: String) -> Result<Self, regex::Error> {
732 let compiled = bytes::Regex::new(&s)?;
733 let captures_len = compiled.captures_len();
734 let re = OnceLock::from(compiled);
735 Ok(Self {
736 s,
737 captures_len,
738 re,
739 })
740 }
741
742 #[inline]
743 fn get_re(&self) -> Result<&bytes::Regex, regex::Error> {
744 match self.re.get() {
745 Some(re) => Ok(re),
746 None => {
747 let _ = self.re.set(bytes::Regex::new(self.s.as_str())?);
748 Ok(self
749 .re
750 .get()
751 .expect("unreachable: cell is guaranteed to contain a value"))
752 }
753 }
754 }
755}
756
757#[derive(Debug, Clone, Serialize, Deserialize)]
758struct RegexTest {
759 re: Regex,
760 length: Option<usize>,
761 mods: ReModFlags,
762 str_mods: StringModFlags,
763 non_magic_len: usize,
764 binary: bool,
765 cmp_op: CmpOp,
766}
767
768impl RegexTest {
769 fn match_buf<'buf>(
770 &self,
771 off_buf: u64, stream_kind: StreamKind,
773 buf: &'buf [u8],
774 ) -> Option<MatchRes<'buf>> {
775 let mr = match stream_kind {
776 StreamKind::Text(_) => {
777 let mut off_txt = off_buf;
778
779 let mut line_limit = self.length.unwrap_or(usize::MAX);
780
781 for line in buf.split(|c| c == &b'\n') {
782 if line_limit == 0 {
786 break;
787 }
788
789 if let Some(re_match) = self
790 .re
791 .get_re()
792 .inspect_err(|e| {
793 error!(
794 "deferred compile of regex pattern {:?} failed: {e}",
795 self.re.s
796 )
797 })
798 .ok()?
799 .find(line)
800 {
801 let start_offset = off_txt + re_match.start() as u64;
803
804 let stop_offset = if re_match.end() == line.len() {
806 Some(start_offset + re_match.as_bytes().len() as u64 + 1)
807 } else {
808 None
809 };
810
811 return Some(MatchRes::Bytes(
812 start_offset,
813 stop_offset,
814 re_match.as_bytes(),
815 Encoding::Utf8,
816 ));
817 }
818
819 off_txt += line.len() as u64;
820 off_txt += 1;
822 line_limit = line_limit.saturating_sub(1)
823 }
824 None
825 }
826
827 StreamKind::Binary => {
828 self.re
829 .get_re()
830 .inspect_err(|e| {
831 error!(
832 "deferred compile of regex pattern {:?} failed: {e}",
833 self.re.s
834 )
835 })
836 .ok()?
837 .find(buf)
838 .map(|re_match| {
839 MatchRes::Bytes(
840 off_buf + re_match.start() as u64,
842 None,
843 re_match.as_bytes(),
844 Encoding::Utf8,
845 )
846 })
847 }
848 };
849
850 if self.cmp_op.is_neq() && mr.is_none() {
852 return Some(MatchRes::Bytes(off_buf, None, buf, Encoding::Utf8));
853 }
854
855 mr
856 }
857}
858
859impl From<RegexTest> for Test {
860 fn from(value: RegexTest) -> Self {
861 Self::Regex(value)
862 }
863}
864
865#[flags(u8)]
866#[derive(Debug, Serialize, Deserialize)]
867enum StringMod {
868 ForceBin = 1 << 0,
869 UpperInsensitive = 1 << 1,
870 LowerInsensitive = 1 << 2,
871 FullWordMatch = 1 << 3,
872 Trim = 1 << 4,
873 ForceText = 1 << 5,
874 CompactWhitespace = 1 << 6,
875 OptBlank = 1 << 7,
876}
877
878impl StringModFlags {
879 #[inline(always)]
882 fn has_unbounded_length(&self) -> bool {
883 !self.is_disjoint(StringMod::CompactWhitespace | StringMod::OptBlank)
884 }
885
886 #[inline(always)]
889 fn word_boundary_lookahead(&self) -> u64 {
890 self.contains(StringMod::FullWordMatch) as u64
891 }
892}
893
894#[derive(Debug, Clone, Serialize, Deserialize)]
895struct StringTest {
896 test_val: TestValue<Vec<u8>>,
897 cmp_op: CmpOp,
898 length: Option<usize>,
899 mods: StringModFlags,
900 binary: bool,
901}
902
903impl From<StringTest> for Test {
904 fn from(value: StringTest) -> Self {
905 Self::String(value)
906 }
907}
908
909#[inline(always)]
910fn string_match(str: &[u8], mods: StringModFlags, buf: &[u8]) -> (bool, usize) {
911 let mut consumed = 0;
912 if mods.is_disjoint(
914 StringMod::UpperInsensitive
915 | StringMod::LowerInsensitive
916 | StringMod::FullWordMatch
917 | StringMod::CompactWhitespace
918 | StringMod::OptBlank,
919 ) {
920 if buf.starts_with(str) {
922 (true, str.len())
923 } else {
924 (false, consumed)
925 }
926 } else {
927 let mut i_src = 0;
928 let mut iter = buf.iter().peekable();
929
930 macro_rules! consume_target {
931 () => {{
932 if iter.next().is_some() {
933 consumed += 1;
934 }
935 }};
936 }
937
938 macro_rules! continue_next_iteration {
939 () => {{
940 consume_target!();
941 i_src += 1;
942 continue;
943 }};
944 }
945
946 while let Some(&&b) = iter.peek() {
947 let Some(&ref_byte) = str.get(i_src) else {
948 break;
949 };
950
951 if mods.contains(StringMod::OptBlank) && (b == b' ' || ref_byte == b' ') {
952 if b == b' ' {
953 consume_target!();
955 }
956
957 if ref_byte == b' ' {
958 i_src += 1;
960 }
961
962 continue;
963 }
964
965 if mods.contains(StringMod::UpperInsensitive) {
966 if ref_byte.is_ascii_uppercase() && ref_byte == b.to_ascii_uppercase()
968 || ref_byte == b
969 {
970 continue_next_iteration!()
971 }
972 }
973
974 if mods.contains(StringMod::LowerInsensitive)
975 && (ref_byte.is_ascii_lowercase() && ref_byte == b.to_ascii_lowercase()
976 || ref_byte == b)
977 {
978 continue_next_iteration!()
979 }
980
981 if mods.contains(StringMod::CompactWhitespace) && ref_byte == b' ' {
982 let mut src_blk = 0;
983 while let Some(b' ') = str.get(i_src) {
984 src_blk += 1;
985 i_src += 1;
986 }
987
988 let mut tgt_blk = 0;
989 while let Some(b' ') = iter.peek() {
990 tgt_blk += 1;
991 consume_target!();
992 }
993
994 if src_blk > tgt_blk {
995 return (false, consumed);
996 }
997
998 continue;
999 }
1000
1001 if ref_byte == b {
1002 continue_next_iteration!()
1003 } else {
1004 return (false, consumed);
1005 }
1006 }
1007
1008 if mods.contains(StringMod::FullWordMatch)
1009 && let Some(b) = iter.peek()
1010 && !b.is_ascii_whitespace()
1011 {
1012 return (false, consumed);
1013 }
1014
1015 (
1016 consumed > 0 && str.get(i_src).is_none() && consumed <= buf.len(),
1017 consumed,
1018 )
1019 }
1020}
1021
1022impl StringTest {
1023 fn has_length_mod(&self) -> bool {
1024 !self.mods.is_disjoint(
1025 StringMod::UpperInsensitive
1026 | StringMod::LowerInsensitive
1027 | StringMod::FullWordMatch
1028 | StringMod::CompactWhitespace
1029 | StringMod::OptBlank,
1030 )
1031 }
1032
1033 #[inline(always)]
1034 fn test_value_len(&self) -> usize {
1035 match self.test_val.as_ref() {
1036 TestValue::Value(s) => s.len(),
1037 TestValue::Any => 0,
1038 }
1039 }
1040}
1041
1042#[derive(Clone, Serialize, Deserialize)]
1043struct ByteVec(Vec<u8>);
1044
1045impl Debug for ByteVec {
1046 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1047 write!(f, "\"{}\"", debug_string_from_vec_u8(self))
1048 }
1049}
1050
1051impl From<Vec<u8>> for ByteVec {
1052 fn from(value: Vec<u8>) -> Self {
1053 Self(value)
1054 }
1055}
1056
1057impl Deref for ByteVec {
1058 type Target = Vec<u8>;
1059
1060 fn deref(&self) -> &Self::Target {
1061 &self.0
1062 }
1063}
1064
1065#[derive(Debug, Clone, Serialize, Deserialize)]
1066struct SearchTest {
1067 str: ByteVec,
1068 n_pos: Option<usize>,
1069 str_mods: StringModFlags,
1070 re_mods: ReModFlags,
1071 binary: bool,
1072 cmp_op: CmpOp,
1073}
1074
1075impl From<SearchTest> for Test {
1076 fn from(value: SearchTest) -> Self {
1077 Self::Search(value)
1078 }
1079}
1080
1081impl SearchTest {
1082 #[inline]
1084 fn match_buf<'buf>(&self, off_buf: u64, buf: &'buf [u8]) -> Option<MatchRes<'buf>> {
1085 let mut i = 0;
1086
1087 let needle = self.str.first()?;
1088
1089 let scan_end = self
1092 .n_pos
1093 .map(|n| n.saturating_add(1).min(buf.len()))
1094 .unwrap_or(buf.len());
1095
1096 while i < scan_end {
1097 let Some(k) = memchr(*needle, &buf[i..scan_end]) else {
1100 break;
1101 };
1102
1103 i += k;
1104
1105 if self.str_mods.contains(StringMod::FullWordMatch) {
1107 let prev_is_whitespace = buf
1108 .get(i.saturating_sub(1))
1109 .map(|c| c.is_ascii_whitespace())
1110 .unwrap_or_default();
1111
1112 if i > 0 && !prev_is_whitespace {
1117 i += 1;
1118 continue;
1119 }
1120 }
1121
1122 let pos = i;
1123 let (ok, consumed) = string_match(&self.str, self.str_mods, &buf[i..]);
1124
1125 if ok {
1126 return Some(MatchRes::Bytes(
1127 off_buf.saturating_add(pos as u64),
1128 None,
1129 &buf[i..i + consumed],
1130 Encoding::Utf8,
1131 ));
1132 } else {
1133 i += max(consumed, 1)
1134 }
1135 }
1136
1137 if self.cmp_op.is_neq() {
1139 return Some(MatchRes::Bytes(off_buf, None, buf, Encoding::Utf8));
1140 }
1141
1142 None
1143 }
1144}
1145
1146#[derive(Debug, Clone, Serialize, Deserialize)]
1147struct ScalarTest {
1148 ty: ScalarDataType,
1149 transform: Option<ScalarTransform>,
1150 cmp_op: CmpOp,
1151 test_val: TestValue<Scalar>,
1152}
1153
1154#[derive(Debug, Clone, Serialize, Deserialize)]
1155struct FloatTest {
1156 ty: FloatDataType,
1157 transform: Option<FloatTransform>,
1158 cmp_op: CmpOp,
1159 test_val: TestValue<Float>,
1160}
1161
1162#[derive(PartialEq)]
1165enum ReadValue<'buf> {
1166 Float(u64, Float),
1167 Scalar(u64, Scalar),
1168 Bytes(u64, &'buf [u8]),
1169}
1170
1171impl<'buf> Debug for ReadValue<'buf> {
1172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1173 match self {
1174 Self::Float(_, fl) => write!(f, "{fl:?}"),
1175 Self::Scalar(_, s) => write!(f, "{s:?}"),
1176 Self::Bytes(_, b) => {
1177 if b.len() <= 128 {
1178 write!(f, "\"{}\"", debug_string_from_vec_u8(b))
1179 } else {
1180 let limit = 128;
1181 write!(
1182 f,
1183 "\"{}\" (first {limit} bytes)",
1184 debug_string_from_vec_u8(&b[..limit])
1185 )
1186 }
1187 }
1188 }
1189 }
1190}
1191
1192impl DynDisplay for ReadValue<'_> {
1193 fn dyn_fmt(&self, f: &mut dyf::Formatter<'_>) -> dyf::Result {
1194 use std::fmt::Write;
1195 match self {
1196 Self::Float(_, s) => DynDisplay::dyn_fmt(s, f),
1197 Self::Scalar(_, s) => DynDisplay::dyn_fmt(s, f),
1198 Self::Bytes(_, b) => Ok(write!(f, "{b:?}")?),
1199 }
1200 }
1201}
1202
1203impl DynDisplay for &ReadValue<'_> {
1204 fn dyn_fmt(&self, f: &mut dyf::Formatter<'_>) -> dyf::Result {
1205 DynDisplay::dyn_fmt(*self, f)
1207 }
1208}
1209
1210impl Display for ReadValue<'_> {
1211 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1212 match self {
1213 Self::Float(_, v) => write!(f, "{v}"),
1214 Self::Scalar(_, s) => write!(f, "{s}"),
1215 Self::Bytes(_, b) => write!(f, "{b:?}"),
1216 }
1217 }
1218}
1219
1220enum Encoding {
1221 Utf16(String16Encoding),
1222 Utf8,
1223}
1224
1225enum MatchRes<'buf> {
1228 Bytes(u64, Option<u64>, &'buf [u8], Encoding),
1233 Scalar(u64, Scalar),
1234 Float(u64, Float),
1235}
1236
1237impl DynDisplay for &MatchRes<'_> {
1238 fn dyn_fmt(&self, f: &mut dyf::Formatter) -> dyf::Result {
1239 (*self).dyn_fmt(f)
1240 }
1241}
1242
1243impl DynDisplay for MatchRes<'_> {
1244 fn dyn_fmt(&self, f: &mut dyf::Formatter) -> dyf::Result {
1245 match self {
1246 Self::Scalar(_, v) => v.dyn_fmt(f),
1247 Self::Float(_, v) => v.dyn_fmt(f),
1248 Self::Bytes(_, _, v, enc) => match enc {
1249 Encoding::Utf8 => String::from_utf8_lossy(v).to_string().dyn_fmt(f),
1250 Encoding::Utf16(enc) => {
1251 let utf16: Vec<u16> = slice_to_utf16_iter(v, *enc).collect();
1252 String::from_utf16_lossy(&utf16).dyn_fmt(f)
1253 }
1254 },
1255 }
1256 }
1257}
1258
1259impl MatchRes<'_> {
1260 #[inline]
1262 fn start_offset(&self) -> u64 {
1263 match self {
1264 MatchRes::Bytes(o, _, _, _) => *o,
1265 MatchRes::Scalar(o, _) => *o,
1266 MatchRes::Float(o, _) => *o,
1267 }
1268 }
1269
1270 #[inline]
1272 fn end_offset(&self) -> u64 {
1273 match self {
1274 MatchRes::Bytes(start, end, buf, _) => match end {
1275 Some(end) => *end,
1276 None => start.saturating_add(buf.len() as u64),
1277 },
1278 MatchRes::Scalar(o, sc) => o.add(sc.size_of() as u64),
1279 MatchRes::Float(o, f) => o.add(f.size_of() as u64),
1280 }
1281 }
1282}
1283
1284fn slice_to_utf16_iter(read: &[u8], encoding: String16Encoding) -> impl Iterator<Item = u16> {
1285 let even = read
1286 .iter()
1287 .enumerate()
1288 .filter(|(i, _)| i % 2 == 0)
1289 .map(|t| t.1);
1290
1291 let odd = read
1292 .iter()
1293 .enumerate()
1294 .filter(|(i, _)| i % 2 != 0)
1295 .map(|t| t.1);
1296
1297 even.zip(odd).map(move |(e, o)| match encoding {
1298 String16Encoding::Le => u16::from_le_bytes([*e, *o]),
1299 String16Encoding::Be => u16::from_be_bytes([*e, *o]),
1300 })
1301}
1302
1303#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
1304enum String16Encoding {
1305 Le,
1306 Be,
1307}
1308
1309#[derive(Debug, Clone, Serialize, Deserialize)]
1310struct String16Test {
1311 orig: String,
1312 test_val: TestValue<Vec<u16>>,
1313 encoding: String16Encoding,
1314}
1315
1316impl String16Test {
1317 #[inline(always)]
1321 fn test_value_len(&self) -> usize {
1322 match self.test_val.as_ref() {
1323 TestValue::Value(str16) => str16.len(),
1324 TestValue::Any => 0,
1325 }
1326 }
1327}
1328
1329#[flags(u8)]
1330#[derive(Debug, Serialize, Deserialize)]
1331enum IndirectMod {
1332 Relative = 1 << 0,
1333}
1334
1335#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
1336enum PStringLen {
1337 Byte, ShortBe, ShortLe, LongBe, LongLe, }
1343
1344impl PStringLen {
1345 #[inline(always)]
1346 const fn size_of_len(&self) -> usize {
1347 match self {
1348 PStringLen::Byte => 1,
1349 PStringLen::ShortBe => 2,
1350 PStringLen::ShortLe => 2,
1351 PStringLen::LongBe => 4,
1352 PStringLen::LongLe => 4,
1353 }
1354 }
1355}
1356
1357#[derive(Debug, Clone, Serialize, Deserialize)]
1358struct PStringTest {
1359 len: PStringLen,
1360 test_val: TestValue<Vec<u8>>,
1361 include_len: bool,
1362}
1363
1364impl PStringTest {
1365 #[inline]
1366 fn read<'cache, R: DataRead>(
1367 &self,
1368 haystack: &'cache mut R,
1369 ) -> Result<Option<&'cache [u8]>, Error> {
1370 let mut len = match self.len {
1371 PStringLen::Byte => read_le!(haystack, u8) as u32,
1372 PStringLen::ShortBe => read_be!(haystack, u16) as u32,
1373 PStringLen::ShortLe => read_le!(haystack, u16) as u32,
1374 PStringLen::LongBe => read_be!(haystack, u32),
1375 PStringLen::LongLe => read_le!(haystack, u32),
1376 } as usize;
1377
1378 if self.include_len {
1379 len = len.saturating_sub(self.len.size_of_len())
1380 }
1381
1382 if let TestValue::Value(s) = self.test_val.as_ref()
1383 && len != s.len()
1384 {
1385 return Ok(None);
1386 }
1387
1388 let read = haystack.read_exact_count(len as u64)?;
1389
1390 Ok(Some(read))
1391 }
1392
1393 #[inline(always)]
1394 fn test_value_len(&self) -> usize {
1395 match self.test_val.as_ref() {
1396 TestValue::Value(s) => s.len(),
1397 TestValue::Any => 0,
1398 }
1399 }
1400}
1401
1402#[derive(Debug, Clone, Serialize, Deserialize)]
1403enum Test {
1404 Name(String),
1405 Use(bool, String),
1406 Scalar(ScalarTest),
1407 Float(FloatTest),
1408 String(StringTest),
1409 Search(SearchTest),
1410 PString(PStringTest),
1411 Regex(RegexTest),
1412 Indirect(IndirectModFlags),
1413 String16(String16Test),
1414 #[allow(dead_code)]
1416 Der,
1417 Clear,
1418 Default,
1419}
1420
1421impl Display for Test {
1422 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1423 match self {
1424 Test::Name(name) => write!(f, "name {name}"),
1425 Test::Use(flip, rule) => {
1426 if *flip {
1427 write!(f, "use {rule}")
1428 } else {
1429 write!(f, "use ^{rule}")
1430 }
1431 }
1432 Test::Scalar(st) => write!(f, "{st:?}"),
1433 Test::Float(ft) => write!(f, "{ft:?}"),
1434 Test::String(st) => write!(f, "{st:?}"),
1435 Test::Search(st) => write!(f, "{st:?}"),
1436 Test::PString(pt) => write!(f, "{pt:?}"),
1437 Test::Regex(rt) => write!(f, "{rt:?}"),
1438 Test::Indirect(fs) => write!(f, "indirect {fs:?}"),
1439 Test::String16(s16t) => write!(f, "{s16t:?}"),
1440 Test::Der => write!(f, "unimplemented der"),
1441 Test::Clear => write!(f, "clear"),
1442 Test::Default => write!(f, "default"),
1443 }
1444 }
1445}
1446
1447impl Test {
1448 #[inline]
1450 fn read_test_value<'haystack, D: DataRead>(
1451 &self,
1452 haystack: &'haystack mut D,
1453 switch_endianness: bool,
1454 ) -> Result<Option<ReadValue<'haystack>>, Error> {
1455 let test_value_offset = haystack.stream_position();
1456
1457 match self {
1458 Self::Scalar(t) => {
1459 t.ty.read(haystack, switch_endianness)
1460 .map(|s| Some(ReadValue::Scalar(test_value_offset, s)))
1461 }
1462
1463 Self::Float(t) => {
1464 t.ty.read(haystack, switch_endianness)
1465 .map(|f| Some(ReadValue::Float(test_value_offset, f)))
1466 }
1467 Self::String(t) => {
1468 match t.test_val.as_ref() {
1469 TestValue::Value(str) => {
1470 let buf = if let Some(length) = t.length {
1471 haystack.read_exact_count(length as u64)?
1473 } else {
1474 match t.cmp_op {
1477 CmpOp::Eq | CmpOp::Neq => {
1478 if t.mods.has_unbounded_length() {
1479 haystack.read_count(FILE_BYTES_MAX as u64)?
1480 } else {
1481 let len =
1482 str.len() as u64 + t.mods.word_boundary_lookahead();
1483 haystack.read_count(len)?
1484 }
1485 }
1486 CmpOp::Lt | CmpOp::Gt => {
1487 let read =
1488 haystack.read_until_any_delim_or_limit(b"\n\0", 8092)?;
1489
1490 if read.ends_with(b"\0") || read.ends_with(b"\n") {
1491 &read[..read.len() - 1]
1492 } else {
1493 read
1494 }
1495 }
1496 _ => {
1497 return Err(Error::Msg(format!(
1498 "string test does not support {:?} operator",
1499 t.cmp_op
1500 )));
1501 }
1502 }
1503 };
1504
1505 Ok(Some(ReadValue::Bytes(test_value_offset, buf)))
1506 }
1507 TestValue::Any => {
1508 let read = haystack.read_until_any_delim_or_limit(b"\0\n", 8192)?;
1509 let bytes = if read.ends_with(b"\0") || read.ends_with(b"\n") {
1511 &read[..read.len() - 1]
1512 } else {
1513 read
1514 };
1515
1516 Ok(Some(ReadValue::Bytes(test_value_offset, bytes)))
1517 }
1518 }
1519 }
1520
1521 Self::String16(t) => {
1522 match t.test_val.as_ref() {
1523 TestValue::Value(str16) => {
1524 let read = haystack.read_exact_count((str16.len() * 2) as u64)?;
1525
1526 Ok(Some(ReadValue::Bytes(test_value_offset, read)))
1527 }
1528 TestValue::Any => {
1529 let read = haystack.read_until_utf16_or_limit(b"\x00\x00", 8192)?;
1530
1531 let end = if read.len() % 2 == 0 {
1533 read.len()
1534 } else {
1535 read.len().saturating_sub(1)
1538 };
1539
1540 Ok(Some(ReadValue::Bytes(test_value_offset, &read[..end])))
1541 }
1542 }
1543 }
1544
1545 Self::PString(t) => {
1546 let Some(read) = t.read(haystack)? else {
1547 return Ok(None);
1548 };
1549 Ok(Some(ReadValue::Bytes(test_value_offset, read)))
1550 }
1551
1552 Self::Search(s) => {
1553 let length = if s.str_mods.has_unbounded_length() {
1554 FILE_BYTES_MAX as u64
1555 } else {
1556 s.n_pos
1557 .map(|n| n as u64)
1558 .unwrap_or(FILE_BYTES_MAX as u64)
1559 .saturating_add(s.str.len() as u64)
1560 .saturating_add(s.str_mods.word_boundary_lookahead())
1561 };
1562 let buf = haystack.read_count(length)?;
1563 Ok(Some(ReadValue::Bytes(test_value_offset, buf)))
1564 }
1565
1566 Self::Regex(r) => {
1567 let length = {
1568 match r.length {
1569 Some(len) => {
1570 if r.mods.contains(ReMod::LineLimit) {
1571 len * 80
1572 } else {
1573 len
1574 }
1575 }
1576
1577 None => FILE_REGEX_MAX,
1578 }
1579 };
1580
1581 let read = haystack.read_count(length as u64)?;
1582 Ok(Some(ReadValue::Bytes(test_value_offset, read)))
1583 }
1584
1585 Self::Name(_)
1586 | Self::Use(_, _)
1587 | Self::Indirect(_)
1588 | Self::Clear
1589 | Self::Default
1590 | Self::Der => Err(Error::msg("no value to read for this test")),
1591 }
1592 }
1593
1594 #[inline(always)]
1595 fn match_value<'s>(
1596 &'s self,
1597 tv: &ReadValue<'s>,
1598 stream_kind: StreamKind,
1599 ) -> Option<MatchRes<'s>> {
1600 match (self, tv) {
1601 (Self::Scalar(t), ReadValue::Scalar(o, ts)) => {
1602 let read_value: Scalar = match t.transform.as_ref() {
1603 Some(t) => t.apply(*ts)?,
1604 None => *ts,
1605 };
1606
1607 match t.test_val {
1608 TestValue::Value(test_value) => {
1609 let ok = match t.cmp_op {
1610 CmpOp::Not => read_value == !test_value,
1613 CmpOp::Eq => read_value == test_value,
1614 CmpOp::Lt => read_value < test_value,
1615 CmpOp::Gt => read_value > test_value,
1616 CmpOp::Neq => read_value != test_value,
1617 CmpOp::BitAnd => read_value & test_value == test_value,
1618 CmpOp::Xor => (read_value & test_value).is_zero(),
1619 };
1620
1621 if ok {
1622 Some(MatchRes::Scalar(*o, read_value))
1623 } else {
1624 None
1625 }
1626 }
1627
1628 TestValue::Any => Some(MatchRes::Scalar(*o, read_value)),
1629 }
1630 }
1631
1632 (Self::Float(t), ReadValue::Float(o, f)) => {
1633 let read_value: Float = t.transform.as_ref().map(|t| t.apply(*f)).unwrap_or(*f);
1634
1635 match t.test_val {
1636 TestValue::Value(tf) => {
1637 let ok = match t.cmp_op {
1638 CmpOp::Eq => read_value == tf,
1639 CmpOp::Lt => read_value < tf,
1640 CmpOp::Gt => read_value > tf,
1641 CmpOp::Neq => read_value != tf,
1642 _ => {
1643 debug_panic!("unsupported float comparison");
1646 debug!("unsupported float comparison");
1647 false
1648 }
1649 };
1650
1651 if ok {
1652 Some(MatchRes::Float(*o, read_value))
1653 } else {
1654 None
1655 }
1656 }
1657 TestValue::Any => Some(MatchRes::Float(*o, read_value)),
1658 }
1659 }
1660
1661 (Self::String(st), ReadValue::Bytes(o, buf)) => {
1662 macro_rules! trim_buf {
1663 ($buf: expr) => {{
1664 if st.mods.contains(StringMod::Trim) {
1665 $buf.trim_ascii()
1666 } else {
1667 $buf
1668 }
1669 }};
1670 }
1671
1672 match st.test_val.as_ref() {
1673 TestValue::Value(str) => {
1674 match st.cmp_op {
1675 CmpOp::Eq => {
1676 if let (true, _) = string_match(str, st.mods, buf) {
1677 Some(MatchRes::Bytes(*o, None, trim_buf!(str), Encoding::Utf8))
1678 } else {
1679 None
1680 }
1681 }
1682 CmpOp::Neq => {
1683 if let (false, _) = string_match(str, st.mods, buf) {
1684 Some(MatchRes::Bytes(*o, None, trim_buf!(str), Encoding::Utf8))
1685 } else {
1686 None
1687 }
1688 }
1689 CmpOp::Gt => {
1690 if buf.len() > str.len() {
1691 Some(MatchRes::Bytes(*o, None, trim_buf!(buf), Encoding::Utf8))
1692 } else {
1693 None
1694 }
1695 }
1696 CmpOp::Lt => {
1697 if buf.len() < str.len() {
1698 Some(MatchRes::Bytes(*o, None, trim_buf!(buf), Encoding::Utf8))
1699 } else {
1700 None
1701 }
1702 }
1703
1704 _ => {
1706 debug_panic!("unsupported string comparison");
1709 debug!("unsupported string comparison");
1710 None
1711 }
1712 }
1713 }
1714 TestValue::Any => {
1715 Some(MatchRes::Bytes(*o, None, trim_buf!(buf), Encoding::Utf8))
1716 }
1717 }
1718 }
1719
1720 (Self::PString(m), ReadValue::Bytes(o, buf)) => match m.test_val.as_ref() {
1721 TestValue::Value(psv) => {
1722 if buf == psv {
1723 Some(MatchRes::Bytes(*o, None, buf, Encoding::Utf8))
1724 } else {
1725 None
1726 }
1727 }
1728 TestValue::Any => Some(MatchRes::Bytes(*o, None, buf, Encoding::Utf8)),
1729 },
1730
1731 (Self::String16(t), ReadValue::Bytes(o, buf)) => {
1732 match t.test_val.as_ref() {
1733 TestValue::Value(str16) => {
1734 if str16.len() * 2 != buf.len() {
1736 return None;
1737 }
1738
1739 for (i, utf16_char) in slice_to_utf16_iter(buf, t.encoding).enumerate() {
1741 if str16[i] != utf16_char {
1742 return None;
1743 }
1744 }
1745
1746 Some(MatchRes::Bytes(
1747 *o,
1748 None,
1749 t.orig.as_bytes(),
1750 Encoding::Utf16(t.encoding),
1751 ))
1752 }
1753
1754 TestValue::Any => {
1755 Some(MatchRes::Bytes(*o, None, buf, Encoding::Utf16(t.encoding)))
1756 }
1757 }
1758 }
1759
1760 (Self::Regex(r), ReadValue::Bytes(o, buf)) => r.match_buf(*o, stream_kind, buf),
1761
1762 (Self::Search(t), ReadValue::Bytes(o, buf)) => t.match_buf(*o, buf),
1763
1764 _ => None,
1765 }
1766 }
1767
1768 #[inline(always)]
1769 fn strength(&self) -> u64 {
1770 const MULT: usize = 10;
1771
1772 let mut out = 2 * MULT;
1773
1774 match self {
1776 Test::Scalar(s) => {
1777 out += s.ty.type_size() * MULT;
1778 }
1779
1780 Test::Float(t) => {
1781 out += t.ty.type_size() * MULT;
1782 }
1783
1784 Test::String(t) => out += t.test_value_len().saturating_mul(MULT),
1785
1786 Test::PString(t) => out += t.test_value_len().saturating_mul(MULT),
1787
1788 Test::Search(s) => {
1789 let n_pos = s.n_pos.unwrap_or(FILE_BYTES_MAX);
1794
1795 match n_pos {
1796 0..=80 => out += s.str.len().saturating_mul(MULT),
1798 81..=240 => out += s.str.len() * s.str.len().clamp(0, MULT - 2),
1800 _ => out += s.str.len(),
1802 }
1803 }
1804
1805 Test::Regex(r) => {
1806 let v = r.non_magic_len / r.re.captures_len.max(1);
1815
1816 let len = r
1817 .length
1818 .map(|l| {
1819 if r.mods.contains(ReMod::LineLimit) {
1820 l * 80
1821 } else {
1822 l
1823 }
1824 })
1825 .unwrap_or(FILE_BYTES_MAX);
1826
1827 match len {
1828 0..=80 => out += v.saturating_mul(MULT),
1830 81..=240 => out += v * v.clamp(0, MULT - 2),
1832 _ => out += v,
1834 }
1835 }
1836
1837 Test::String16(t) => {
1838 out += t.test_value_len().saturating_mul(MULT);
1843 }
1844
1845 Test::Der => out += MULT,
1846
1847 Test::Default | Test::Name(_) | Test::Use(_, _) | Test::Indirect(_) | Test::Clear => {
1848 return 0;
1849 }
1850 }
1851
1852 if self.is_match_any() {
1854 return 0;
1855 }
1856
1857 if let Some(op) = self.cmp_op() {
1858 match op {
1859 CmpOp::Neq => out = 0,
1861 CmpOp::Eq | CmpOp::Not => out += MULT,
1862 CmpOp::Lt | CmpOp::Gt => out -= 2 * MULT,
1863 CmpOp::Xor | CmpOp::BitAnd => out -= MULT,
1864 }
1865 }
1866
1867 out as u64
1868 }
1869
1870 #[inline(always)]
1871 fn cmp_op(&self) -> Option<CmpOp> {
1872 match self {
1873 Self::String(t) => Some(t.cmp_op),
1874 Self::Scalar(s) => Some(s.cmp_op),
1875 Self::Float(t) => Some(t.cmp_op),
1876 Self::Search(t) => Some(t.cmp_op),
1877 Self::Regex(t) => Some(t.cmp_op),
1878 Self::Name(_)
1879 | Self::Use(_, _)
1880 | Self::PString(_)
1881 | Self::Clear
1882 | Self::Default
1883 | Self::Indirect(_)
1884 | Self::String16(_)
1885 | Self::Der => None,
1886 }
1887 }
1888
1889 #[inline(always)]
1890 fn is_recursive(&self) -> bool {
1891 matches!(self, Test::Use(_, _) | Test::Indirect(_))
1892 }
1893
1894 #[inline(always)]
1895 fn is_match_any(&self) -> bool {
1896 match self {
1897 Test::Name(_) => false,
1898 Test::Use(_, _) => false,
1899 Test::Scalar(scalar_test) => matches!(scalar_test.test_val, TestValue::Any),
1900 Test::Float(float_test) => matches!(float_test.test_val, TestValue::Any),
1901 Test::String(string_test) => matches!(string_test.test_val, TestValue::Any),
1902 Test::Search(_) => false,
1903 Test::PString(pstring_test) => matches!(pstring_test.test_val, TestValue::Any),
1904 Test::Regex(_) => false,
1905 Test::Indirect(_) => false,
1906 Test::String16(string16_test) => matches!(string16_test.test_val, TestValue::Any),
1907 Test::Der => false,
1908 Test::Clear => false,
1909 Test::Default => false,
1910 }
1911 }
1912
1913 #[inline(always)]
1916 fn has_explicit_bin_mod(&self) -> bool {
1917 match self {
1918 Self::String(t) => t.mods.contains(StringMod::ForceBin),
1919 Self::Search(t) => t.str_mods.contains(StringMod::ForceBin),
1920 Self::Regex(t) => {
1921 t.mods.contains(ReMod::ForceBin) || t.str_mods.contains(StringMod::ForceBin)
1922 }
1923 _ => false,
1924 }
1925 }
1926
1927 #[inline(always)]
1930 fn has_explicit_text_mod(&self) -> bool {
1931 match self {
1932 Self::String(t) => t.mods.contains(StringMod::ForceText),
1933 Self::Search(t) => t.str_mods.contains(StringMod::ForceText),
1934 Self::Regex(t) => {
1935 t.mods.contains(ReMod::ForceText) || t.str_mods.contains(StringMod::ForceText)
1936 }
1937 _ => false,
1938 }
1939 }
1940
1941 #[inline(always)]
1946 fn type_default_is_binary(&self) -> Option<bool> {
1947 match self {
1948 Self::Scalar(_) | Self::Float(_) | Self::Der => Some(true),
1949 Self::String(_) | Self::PString(_) | Self::String16(_) => Some(true),
1950 Self::Search(t) => Some(t.binary),
1951 Self::Regex(t) => Some(t.binary),
1952 Self::Name(_) | Self::Use(_, _) | Self::Indirect(_) | Self::Clear | Self::Default => {
1953 None
1954 }
1955 }
1956 }
1957
1958 #[inline(always)]
1963 fn stream_gate(&self) -> StreamGate {
1964 let explicit_bin = self.has_explicit_bin_mod();
1965 let explicit_text = self.has_explicit_text_mod();
1966 if explicit_bin && explicit_text {
1967 return StreamGate::Never;
1968 }
1969 if explicit_bin {
1970 return StreamGate::SkipOnText;
1971 }
1972 if explicit_text || self.type_default_is_binary() == Some(false) {
1973 return StreamGate::SkipOnBinary;
1974 }
1975 StreamGate::Never
1976 }
1977
1978 #[inline(always)]
1980 fn consumes_bytes(&self) -> bool {
1981 !matches!(
1982 self,
1983 Test::Use(_, _) | Test::Indirect(_) | Test::Default | Test::Clear | Test::Name(_)
1984 )
1985 }
1986}
1987
1988#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1991enum StreamGate {
1992 SkipOnText,
1993 SkipOnBinary,
1994 Never,
1995}
1996
1997impl StreamGate {
1998 #[inline(always)]
1999 fn should_skip(self, stream_kind: StreamKind) -> bool {
2000 match self {
2001 Self::SkipOnText => stream_kind.is_text(),
2002 Self::SkipOnBinary => stream_kind.is_binary(),
2003 Self::Never => false,
2004 }
2005 }
2006}
2007
2008#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2009enum OffsetType {
2010 Byte,
2011 DoubleLe,
2012 DoubleBe,
2013 ShortLe,
2014 ShortBe,
2015 Id3Le,
2016 Id3Be,
2017 LongLe,
2018 LongBe,
2019 Middle,
2020 Octal,
2021 QuadBe,
2022 QuadLe,
2023}
2024
2025#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2026enum Shift {
2027 Direct(u64),
2028 Indirect(i64),
2029}
2030
2031#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2032struct IndOffset {
2033 off_addr: DirOffset,
2035 signed: bool,
2037 ty: OffsetType,
2039 op: Option<Op>,
2040 shift: Option<Shift>,
2041}
2042
2043impl IndOffset {
2044 fn read_offset<D: DataRead>(
2046 &self,
2047 haystack: &mut D,
2048 offset_base: Option<u64>,
2049 last_upper_match_offset: Option<u64>,
2050 ) -> Result<Option<u64>, io::Error> {
2051 let offset_address = match self.off_addr {
2052 DirOffset::Start(s) => {
2053 let Some(o) = s.checked_add(offset_base.unwrap_or_default()) else {
2054 return Ok(None);
2055 };
2056
2057 haystack.seek(SeekFrom::Start(o))?
2058 }
2059 DirOffset::LastUpper(c) => haystack.seek(SeekFrom::Start(
2060 (last_upper_match_offset.unwrap_or_default() as i64 + c) as u64,
2061 ))?,
2062 DirOffset::End(e) => haystack.seek(SeekFrom::End(e))?,
2063 };
2064
2065 macro_rules! read_value {
2066 () => {
2067 match self.ty {
2068 OffsetType::Byte => {
2069 if self.signed {
2070 read_le!(haystack, u8) as u64
2071 } else {
2072 read_le!(haystack, i8) as u64
2073 }
2074 }
2075 OffsetType::DoubleLe => read_le!(haystack, f64) as u64,
2076 OffsetType::DoubleBe => read_be!(haystack, f64) as u64,
2077 OffsetType::ShortLe => {
2078 if self.signed {
2079 read_le!(haystack, i16) as u64
2080 } else {
2081 read_le!(haystack, u16) as u64
2082 }
2083 }
2084 OffsetType::ShortBe => {
2085 if self.signed {
2086 read_be!(haystack, i16) as u64
2087 } else {
2088 read_be!(haystack, u16) as u64
2089 }
2090 }
2091 OffsetType::Id3Le => decode_id3(read_le!(haystack, u32)) as u64,
2092 OffsetType::Id3Be => decode_id3(read_be!(haystack, u32)) as u64,
2093 OffsetType::LongLe => {
2094 if self.signed {
2095 read_le!(haystack, i32) as u64
2096 } else {
2097 read_le!(haystack, u32) as u64
2098 }
2099 }
2100 OffsetType::LongBe => {
2101 if self.signed {
2102 read_be!(haystack, i32) as u64
2103 } else {
2104 read_be!(haystack, u32) as u64
2105 }
2106 }
2107 OffsetType::Middle => read_me!(haystack) as u64,
2108 OffsetType::Octal => {
2109 if let Some(o) = read_octal_u64(haystack) {
2110 o
2111 } else {
2112 debug!("failed to read octal offset @ {offset_address}");
2113 return Ok(None);
2114 }
2115 }
2116 OffsetType::QuadLe => {
2117 if self.signed {
2118 read_le!(haystack, i64) as u64
2119 } else {
2120 read_le!(haystack, u64)
2121 }
2122 }
2123 OffsetType::QuadBe => {
2124 if self.signed {
2125 read_be!(haystack, i64) as u64
2126 } else {
2127 read_be!(haystack, u64)
2128 }
2129 }
2130 }
2131 };
2132 }
2133
2134 let o = read_value!();
2136
2137 trace!(
2138 "offset read @ {offset_address} value={o} op={:?} shift={:?}",
2139 self.op, self.shift
2140 );
2141
2142 if let (Some(op), Some(shift)) = (self.op, self.shift) {
2144 let shift = match shift {
2145 Shift::Direct(i) => i,
2146 Shift::Indirect(i) => {
2147 let tmp = offset_address as i128 + i as i128;
2148 if tmp.is_negative() {
2149 return Ok(None);
2150 } else {
2151 haystack.seek(SeekFrom::Start(tmp as u64))?;
2152 };
2153 read_value!()
2156 }
2157 };
2158
2159 match op {
2160 Op::Add => return Ok(o.checked_add(shift)),
2161 Op::Mul => return Ok(o.checked_mul(shift)),
2162 Op::Sub => return Ok(o.checked_sub(shift)),
2163 Op::Div => return Ok(o.checked_div(shift)),
2164 Op::Mod => return Ok(o.checked_rem(shift)),
2165 Op::And => return Ok(Some(o & shift)),
2166 Op::Or => return Ok(Some(o | shift)),
2167 Op::Xor => return Ok(Some(o ^ shift)),
2168 }
2169 }
2170
2171 Ok(Some(o))
2172 }
2173}
2174
2175#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2176enum DirOffset {
2177 Start(u64),
2178 LastUpper(i64),
2180 End(i64),
2181}
2182
2183#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2184enum Offset {
2185 Direct(DirOffset),
2186 Indirect(IndOffset),
2187}
2188
2189impl Offset {
2190 #[inline(always)]
2191 fn is_indirect(&self) -> bool {
2192 matches!(self, Self::Indirect(_))
2193 }
2194}
2195
2196impl From<DirOffset> for Offset {
2197 fn from(value: DirOffset) -> Self {
2198 Self::Direct(value)
2199 }
2200}
2201
2202impl From<IndOffset> for Offset {
2203 fn from(value: IndOffset) -> Self {
2204 Self::Indirect(value)
2205 }
2206}
2207
2208impl Display for DirOffset {
2209 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2210 match self {
2211 DirOffset::Start(i) => write!(f, "{i}"),
2212 DirOffset::LastUpper(c) => write!(f, "&{c}"),
2213 DirOffset::End(e) => write!(f, "-{e}"),
2214 }
2215 }
2216}
2217
2218impl Default for DirOffset {
2219 fn default() -> Self {
2220 Self::LastUpper(0)
2221 }
2222}
2223
2224#[derive(Debug, Clone, Serialize, Deserialize)]
2225struct Match {
2226 line: usize,
2227 depth: u8,
2228 offset: Offset,
2229 test: Test,
2230 test_strength: u64,
2231 message: Option<Message>,
2232 stream_gate: StreamGate,
2233}
2234
2235impl From<Use> for Match {
2236 fn from(value: Use) -> Self {
2237 let test = Test::Use(value.switch_endianness, value.rule_name);
2238 let test_strength = test.strength();
2239 let stream_gate = test.stream_gate();
2240 Self {
2241 line: value.line,
2242 depth: value.depth,
2243 offset: value.start_offset,
2244 test,
2245 test_strength,
2246 message: value.message,
2247 stream_gate,
2248 }
2249 }
2250}
2251
2252impl From<Name> for Match {
2253 fn from(value: Name) -> Self {
2254 let test = Test::Name(value.name);
2255 let test_strength = test.strength();
2256 let stream_gate = test.stream_gate();
2257 Self {
2258 line: value.line,
2259 depth: 0,
2260 offset: Offset::Direct(DirOffset::Start(0)),
2261 test,
2262 test_strength,
2263 message: value.message,
2264 stream_gate,
2265 }
2266 }
2267}
2268
2269impl Match {
2270 #[inline(always)]
2272 fn should_skip_for_stream(&self, stream_kind: StreamKind) -> bool {
2273 self.stream_gate.should_skip(stream_kind)
2274 }
2275
2276 #[inline(always)]
2278 fn offset_from_start<D: DataRead>(
2279 &self,
2280 haystack: &mut D,
2281 offset_base: Option<u64>,
2282 last_level_offset: Option<u64>,
2283 ) -> Result<Option<u64>, io::Error> {
2284 match self.offset {
2285 Offset::Direct(dir_offset) => match dir_offset {
2286 DirOffset::Start(s) => Ok(Some(s)),
2287 DirOffset::LastUpper(shift) => {
2288 let o = last_level_offset.unwrap_or_default() as i64 + shift;
2289
2290 if o >= 0 { Ok(Some(o as u64)) } else { Ok(None) }
2291 }
2292 DirOffset::End(e) => Ok(Some(haystack.offset_from_start(SeekFrom::End(e)))),
2293 },
2294 Offset::Indirect(ind_offset) => {
2295 let Some(o) = ind_offset.read_offset(haystack, offset_base, last_level_offset)?
2296 else {
2297 return Ok(None);
2298 };
2299
2300 Ok(Some(o))
2301 }
2302 }
2303 }
2304
2305 #[inline(always)]
2307 fn anchor_offset<D: DataRead>(
2308 &self,
2309 haystack: &mut D,
2310 buf_base_offset: Option<u64>,
2311 offset_base: Option<u64>,
2312 last_level_offset: Option<u64>,
2313 ) -> Result<Option<u64>, io::Error> {
2314 let Some(offset) = self.offset_from_start(haystack, offset_base, last_level_offset)? else {
2315 return Ok(None);
2316 };
2317
2318 Ok(Some(match self.offset {
2319 Offset::Indirect(_) => buf_base_offset.unwrap_or_default().saturating_add(offset),
2323 Offset::Direct(DirOffset::Start(_)) => {
2326 offset_base.unwrap_or_default().saturating_add(offset)
2327 }
2328 _ => offset,
2329 }))
2330 }
2331
2332 #[inline]
2345 #[allow(clippy::too_many_arguments)]
2346 fn matches<'a: 'h, 'h, D: DataRead>(
2347 &'a self,
2348 source: Option<&str>,
2349 magic: &mut Magic<'a>,
2350 stream_kind: StreamKind,
2351 state: &mut MatchState,
2352 offset: u64,
2353 haystack: &'h mut D,
2354 switch_endianness: bool,
2355 db: &'a MagicDb,
2356 rec_depth: usize,
2357 test_depth: usize,
2358 ) -> Result<(bool, Option<MatchRes<'h>>), Error> {
2359 let source = source.unwrap_or("unknown");
2360 let line = self.line;
2361
2362 if rec_depth >= MAX_RECURSION {
2363 return Err(Error::localized(
2364 source,
2365 line,
2366 Error::MaximumRecursion(MAX_RECURSION),
2367 ));
2368 }
2369
2370 match &self.test {
2371 Test::Clear => {
2372 trace!("source={source} line={line} clear");
2373 state.clear_continuation_level(&self.continuation_level());
2374 Ok((true, None))
2375 }
2376
2377 Test::Name(name) => {
2378 trace!(
2379 "source={source} line={line} running rule {name} switch_endianness={switch_endianness}",
2380 );
2381 Ok((true, None))
2382 }
2383
2384 Test::Use(flip_endianness, rule_name) => {
2385 trace!(
2386 "source={source} line={line} use {rule_name} switch_endianness={flip_endianness}",
2387 );
2388
2389 let switch_endianness = switch_endianness ^ flip_endianness;
2391
2392 let dr: &DependencyRule = db.dependencies.get(rule_name).ok_or(
2393 Error::localized(source, line, Error::MissingRule(rule_name.clone())),
2394 )?;
2395
2396 let new_buf_base_off = if self.offset.is_indirect() {
2397 Some(offset)
2398 } else {
2399 None
2400 };
2401
2402 let nmatch = dr.rule.magic(
2403 magic,
2404 stream_kind,
2405 new_buf_base_off,
2406 Some(offset),
2407 haystack,
2408 db,
2409 switch_endianness,
2410 rec_depth.saturating_add(1),
2411 test_depth,
2412 )?;
2413
2414 let matched = nmatch > 0;
2417 if matched {
2418 state.set_continuation_level(self.continuation_level());
2419 }
2420
2421 Ok((matched, None))
2422 }
2423
2424 Test::Indirect(m) => {
2425 trace!(
2426 "source={source} line={line} indirect mods={:?} offset={offset:#x}",
2427 m
2428 );
2429
2430 let new_buf_base_off = if m.contains(IndirectMod::Relative) {
2431 Some(offset)
2432 } else {
2433 None
2434 };
2435
2436 let msg_checkpoint = magic.message.len();
2438
2439 let mut nmatch = 0u64;
2440 for r in db.rules.iter() {
2441 nmatch = nmatch.saturating_add(r.magic(
2442 magic,
2443 stream_kind,
2444 new_buf_base_off,
2445 Some(offset),
2446 haystack,
2447 db,
2448 false,
2449 rec_depth.saturating_add(1),
2450 test_depth,
2451 )?);
2452
2453 if nmatch > 0 {
2454 break;
2455 }
2456 }
2457
2458 if nmatch > 0
2459 && let Some(msg) = self.message.as_ref()
2460 {
2461 let msg = msg.to_string_lossy();
2462 if !msg.is_empty() {
2463 debug!("pushing message: msg={msg} len={}", msg.len());
2464 magic.message.insert(msg_checkpoint, msg);
2465 }
2466 }
2467
2468 Ok((nmatch > 0, None))
2469 }
2470
2471 Test::Default => {
2472 let ok = !state.get_continuation_level(&self.continuation_level());
2474
2475 trace!("source={source} line={line} default match={ok}");
2476 if ok {
2477 state.set_continuation_level(self.continuation_level());
2478 }
2479
2480 Ok((ok, None))
2481 }
2482
2483 _ => {
2484 if let Err(e) = haystack.seek(SeekFrom::Start(offset)) {
2485 debug!("source={source} line={line} failed to seek in haystack: {e}");
2486 return Ok((false, None));
2487 }
2488
2489 let mut trace_msg = None;
2490
2491 if enabled!(Level::DEBUG) {
2492 trace_msg = Some(vec![format!(
2493 "source={source} line={line} depth={} stream_offset={:#x}",
2494 self.depth,
2495 haystack.stream_position()
2496 )])
2497 }
2498
2499 match self.test.read_test_value(haystack, switch_endianness) {
2503 Err(e) => {
2504 debug!(
2505 "source={source} line={line} error while reading test value @{offset}: {e}",
2506 );
2507
2508 if self.test.cmp_op().is_some_and(|op| op.is_neq()) {
2510 trace!(
2511 "source={source} line={line} unreadable data treated as match for negated test",
2512 );
2513 state.set_continuation_level(self.continuation_level());
2514 return Ok((true, None));
2515 }
2516 }
2517 Ok(opt_test_value) => {
2518 if let Some(v) = trace_msg.as_mut() {
2519 v.push(format!("test={}", self.test))
2520 }
2521
2522 if let Some(v) = trace_msg.as_mut() {
2523 let drv = match opt_test_value.as_ref() {
2524 Some(r) => format!("{r:?}"),
2525 None => String::new(),
2526 };
2527 v.push(format!("read_in_stream={drv}"))
2528 }
2529
2530 let match_res =
2531 opt_test_value.and_then(|tv| self.test.match_value(&tv, stream_kind));
2532
2533 if let Some(v) = trace_msg.as_mut() {
2534 v.push(format!(
2535 "message=\"{}\" match={}",
2536 self.message
2537 .as_ref()
2538 .map(|fs| fs.to_string_lossy())
2539 .unwrap_or_default(),
2540 match_res.is_some()
2541 ))
2542 }
2543
2544 if enabled!(Level::DEBUG) && !enabled!(Level::TRACE) && match_res.is_some()
2546 {
2547 if let Some(m) = trace_msg {
2548 debug!("{}", m.join(" "));
2549 }
2550 } else if enabled!(Level::TRACE)
2551 && let Some(m) = trace_msg
2552 {
2553 trace!("{}", m.join(" "));
2554 }
2555
2556 if let Some(mr) = match_res {
2557 state.set_continuation_level(self.continuation_level());
2558 return Ok((true, Some(mr)));
2559 }
2560 }
2561 }
2562
2563 Ok((false, None))
2564 }
2565 }
2566 }
2567
2568 #[inline(always)]
2569 fn continuation_level(&self) -> ContinuationLevel {
2570 ContinuationLevel(self.depth)
2571 }
2572}
2573
2574#[derive(Debug, Clone)]
2575struct Use {
2576 line: usize,
2577 depth: u8,
2578 start_offset: Offset,
2579 rule_name: String,
2580 switch_endianness: bool,
2581 message: Option<Message>,
2582}
2583
2584#[derive(Debug, Clone, Serialize, Deserialize)]
2585struct StrengthMod {
2586 op: Op,
2587 by: u8,
2588}
2589
2590impl StrengthMod {
2591 #[inline(always)]
2592 fn apply(&self, strength: u64) -> u64 {
2593 let by = self.by as u64;
2594 debug!("applying strength modifier: {strength} {} {}", self.op, by);
2595 match self.op {
2596 Op::Mul => strength.saturating_mul(by),
2597 Op::Add => strength.saturating_add(by),
2598 Op::Sub => strength.saturating_sub(by),
2599 Op::Div => {
2600 if by > 0 {
2601 strength.saturating_div(by)
2602 } else {
2603 strength
2604 }
2605 }
2606 Op::Mod => strength % by,
2607 Op::And => strength & by,
2608 Op::Xor | Op::Or => {
2611 debug_panic!("unsupported strength operator");
2612 strength
2613 }
2614 }
2615 }
2616}
2617
2618#[derive(Debug, Clone)]
2619enum Flag {
2620 Mime(String),
2621 Ext(HashSet<String>),
2622 Strength(StrengthMod),
2623 Apple(String),
2624}
2625
2626#[derive(Debug, Clone)]
2627struct Name {
2628 line: usize,
2629 name: String,
2630 message: Option<Message>,
2631}
2632
2633#[derive(Debug, Clone)]
2634enum Entry<'span> {
2635 Match(Span<'span>, Match),
2636 Flag(Span<'span>, Flag),
2637}
2638
2639#[derive(Debug, Clone, Serialize, Deserialize)]
2640struct EntryNode {
2641 root: bool,
2642 entry: Match,
2643 children: Vec<EntryNode>,
2644 mimetype: Option<String>,
2645 apple: Option<String>,
2646 strength_mod: Option<StrengthMod>,
2647 exts: HashSet<String>,
2648}
2649
2650#[derive(Debug, Default)]
2651struct EntryNodeVisitor {
2652 exts: HashSet<String>,
2653 score: u64,
2654 max_score: u64,
2655}
2656
2657impl EntryNodeVisitor {
2658 fn new() -> Self {
2659 Self {
2660 ..Default::default()
2661 }
2662 }
2663
2664 fn merge(&mut self, other: Self) {
2665 self.exts.extend(other.exts);
2666 self.max_score += other.max_score;
2667 }
2668}
2669
2670impl EntryNode {
2671 #[inline]
2672 fn update_visitor(&self, v: &mut EntryNodeVisitor, depth: usize) {
2673 for ext in self.exts.iter() {
2675 if !v.exts.contains(ext) {
2676 v.exts.insert(ext.clone());
2677 }
2678 }
2679
2680 if self.root {
2681 let mut score = self.entry.test_strength;
2682 if let Some(sm) = self.strength_mod.as_ref() {
2683 score = sm.apply(score);
2684 }
2685 if self.entry.message.is_none() {
2688 score += 1;
2689 }
2690 v.score = score;
2691
2692 v.max_score = v
2693 .max_score
2694 .saturating_add(score)
2695 .saturating_add(self.best_path_bonus(depth));
2696 }
2697 }
2698
2699 fn best_path_bonus(&self, depth: usize) -> u64 {
2702 self.children
2703 .iter()
2704 .filter(|c| !matches!(c.entry.test, Test::Use(_, _)))
2706 .map(|c| {
2707 let d = depth.saturating_add(1);
2708 (c.entry.test_strength >> d).saturating_add(c.best_path_bonus(d))
2710 })
2711 .max()
2712 .unwrap_or(0)
2713 }
2714
2715 fn visit(
2716 &self,
2717 v: &mut EntryNodeVisitor,
2718 deps: &HashMap<String, DependencyRule>,
2719 marked: &mut HashSet<String>,
2720 depth: usize,
2721 ) -> Result<(), Error> {
2722 self.update_visitor(v, depth);
2724
2725 if let Test::Use(_, ref name) = self.entry.test
2728 && !marked.contains(name)
2729 {
2730 marked.insert(name.clone());
2731
2732 if let Some(r) = deps.get(name) {
2733 let dv = r.rule.visit_all_entries(deps, marked, depth)?;
2734 v.merge(dv);
2735 } else {
2736 return Err(Error::MissingRule(name.clone()));
2737 }
2738 }
2739
2740 for c in self.children.iter() {
2742 c.visit(v, deps, marked, depth + 1)?;
2743 }
2744
2745 Ok(())
2746 }
2747
2748 #[inline]
2751 #[allow(clippy::too_many_arguments)]
2752 fn matches<'r, D: DataRead>(
2753 &'r self,
2754 opt_source: Option<&str>,
2755 magic: &mut Magic<'r>,
2756 state: &mut MatchState,
2757 stream_kind: StreamKind,
2758 buf_base_offset: Option<u64>,
2759 offset_base: Option<u64>,
2760 last_level_offset: Option<u64>,
2761 haystack: &mut D,
2762 db: &'r MagicDb,
2763 switch_endianness: bool,
2764 rec_depth: usize, test_depth: usize, ) -> Result<u64, Error> {
2767 let mut nmatch = 0u64;
2768 let source = opt_source.unwrap_or("unknown");
2769 let line = self.entry.line;
2770
2771 if self.root && self.entry.should_skip_for_stream(stream_kind) {
2772 trace!("skip test source={source} line={line} stream_kind={stream_kind:?}");
2773 return Ok(0);
2774 }
2775
2776 let Some(own_anchor) = self
2778 .entry
2779 .anchor_offset(haystack, buf_base_offset, offset_base, last_level_offset)
2780 .inspect_err(|e| debug!("source={source} line={line} failed at computing offset: {e}"))
2781 .ok()
2782 .flatten()
2783 else {
2784 return Ok(0);
2786 };
2787
2788 let (ok, opt_match_res) = self.entry.matches(
2789 opt_source,
2790 magic,
2791 stream_kind,
2792 state,
2793 own_anchor,
2794 haystack,
2795 switch_endianness,
2796 db,
2797 rec_depth,
2798 test_depth,
2799 )?;
2800
2801 if ok {
2802 if !self.entry.test.is_recursive()
2806 && let Some(msg) = self.entry.message.as_ref()
2807 && let Ok(msg) = msg.format_with(opt_match_res.as_ref()).inspect_err(|e| {
2808 debug!("source={source} line={line} failed to format message: {e}")
2809 })
2810 {
2811 nmatch = nmatch.saturating_add(1);
2812 magic.push_message(msg);
2813 }
2814
2815 if let Some(mr) = opt_match_res {
2817 match &self.entry.test {
2818 Test::String(t) if t.has_length_mod() => {
2819 let o = mr.end_offset();
2820 haystack.seek(SeekFrom::Start(o))?;
2821 }
2822 Test::Search(t) => {
2823 if t.re_mods.contains(ReMod::StartOffsetUpdate) {
2824 let o = mr.start_offset();
2825 haystack.seek(SeekFrom::Start(o))?;
2826 } else {
2827 let o = mr.end_offset();
2828 haystack.seek(SeekFrom::Start(o))?;
2829 }
2830 }
2831
2832 Test::Regex(t) => {
2833 if t.mods.contains(ReMod::StartOffsetUpdate) {
2834 let o = mr.start_offset();
2835 haystack.seek(SeekFrom::Start(o))?;
2836 } else {
2837 let o = mr.end_offset();
2838 haystack.seek(SeekFrom::Start(o))?;
2839 }
2840 }
2841 _ => {}
2843 }
2844 }
2845
2846 if let Some(mimetype) = self.mimetype.as_ref() {
2847 magic.set_mime_type(Cow::Borrowed(mimetype));
2848 }
2849
2850 if let Some(apple_ty) = self.apple.as_ref() {
2851 magic.set_creator_code(Cow::Borrowed(apple_ty));
2852 }
2853
2854 if !self.exts.is_empty() {
2855 magic.insert_extensions(self.exts.iter().map(|s| s.as_str()));
2856 }
2857
2858 let mut strength = self.entry.test_strength;
2862
2863 strength >>= test_depth;
2864
2865 if let Some(sm) = self.strength_mod.as_ref() {
2867 strength = sm.apply(strength);
2868 }
2869
2870 if self.entry.message.is_none() {
2872 strength += 1
2873 }
2874
2875 magic.update_strength(strength);
2876
2877 let end_upper_level = if self.entry.test.consumes_bytes() {
2882 haystack.stream_position()
2883 } else {
2884 own_anchor
2885 };
2886
2887 let offset_base = match self.entry.offset {
2890 Offset::Direct(DirOffset::End(_)) if self.root => Some(own_anchor),
2893 Offset::Direct(DirOffset::LastUpper(_)) => None,
2896 _ => offset_base,
2899 };
2900
2901 for e in self.children.iter() {
2902 nmatch = nmatch.saturating_add(e.matches(
2903 opt_source,
2904 magic,
2905 state,
2906 stream_kind,
2907 buf_base_offset,
2908 offset_base,
2909 Some(end_upper_level),
2910 haystack,
2911 db,
2912 switch_endianness,
2913 rec_depth,
2914 test_depth.saturating_add(1),
2915 )?);
2916 }
2917 }
2918
2919 Ok(nmatch)
2920 }
2921}
2922
2923#[derive(Debug, Clone, Serialize, Deserialize)]
2925pub struct MagicRule {
2926 id: usize,
2927 source: Option<String>,
2928 entries: EntryNode,
2929 extensions: HashSet<String>,
2930 score: u64,
2932 is_text: bool,
2934 finalized: bool,
2935 max_score: u64,
2936}
2937
2938impl MagicRule {
2939 #[inline(always)]
2940 fn set_id(&mut self, id: usize) {
2941 self.id = id
2942 }
2943
2944 fn visit_all_entries(
2945 &self,
2946 deps: &HashMap<String, DependencyRule>,
2947 marked: &mut HashSet<String>,
2948 depth: usize,
2949 ) -> Result<EntryNodeVisitor, Error> {
2950 let mut v = EntryNodeVisitor::new();
2951 self.entries.visit(&mut v, deps, marked, depth)?;
2952 Ok(v)
2953 }
2954
2955 fn try_finalize(&mut self, deps: &HashMap<String, DependencyRule>) -> Result<(), Error> {
2959 if self.finalized {
2960 return Ok(());
2961 }
2962
2963 let v = self.visit_all_entries(deps, &mut HashSet::new(), 0)?;
2965
2966 self.extensions.extend(v.exts);
2967 self.score = v.score;
2968 self.is_text = self.compute_is_text();
2969 self.finalized = true;
2970 self.max_score = v.max_score;
2971
2972 Ok(())
2973 }
2974
2975 #[inline]
2976 #[allow(clippy::too_many_arguments)]
2977 fn magic_entrypoint<'r, D: DataRead>(
2978 &'r self,
2979 magic: &mut Magic<'r>,
2980 stream_kind: StreamKind,
2981 haystack: &mut D,
2982 db: &'r MagicDb,
2983 switch_endianness: bool,
2984 rec_depth: usize,
2985 test_depth: usize,
2986 ) -> Result<u64, Error> {
2987 self.entries.matches(
2988 self.source.as_deref(),
2989 magic,
2990 &mut MatchState::empty(),
2991 stream_kind,
2992 None,
2993 None,
2994 None,
2995 haystack,
2996 db,
2997 switch_endianness,
2998 rec_depth,
2999 test_depth,
3000 )
3001 }
3002
3003 #[inline]
3006 #[allow(clippy::too_many_arguments)]
3007 fn magic<'r, D: DataRead>(
3008 &'r self,
3009 magic: &mut Magic<'r>,
3010 stream_kind: StreamKind,
3011 buf_base_offset: Option<u64>,
3012 offset_base: Option<u64>,
3013 haystack: &mut D,
3014 db: &'r MagicDb,
3015 switch_endianness: bool,
3016 rec_depth: usize,
3017 test_depth: usize,
3018 ) -> Result<u64, Error> {
3019 self.entries.matches(
3020 self.source.as_deref(),
3021 magic,
3022 &mut MatchState::empty(),
3023 stream_kind,
3024 buf_base_offset,
3025 offset_base,
3026 None,
3027 haystack,
3028 db,
3029 switch_endianness,
3030 rec_depth,
3031 test_depth,
3032 )
3033 }
3034
3035 #[inline(always)]
3041 pub fn is_text(&self) -> bool {
3042 self.is_text
3043 }
3044
3045 fn compute_is_text(&self) -> bool {
3052 let top = &self.entries.entry.test;
3053
3054 if top.has_explicit_bin_mod() {
3055 return false;
3056 }
3057 if top.has_explicit_text_mod() {
3058 return true;
3059 }
3060
3061 let mut any_text = false;
3064 for t in std::iter::once(top).chain(self.entries.children.iter().map(|e| &e.entry.test)) {
3065 match t.type_default_is_binary() {
3066 Some(true) => return false,
3067 Some(false) => any_text = true,
3068 None => {}
3069 }
3070 }
3071
3072 any_text
3073 }
3074
3075 #[inline(always)]
3081 pub fn score(&self) -> u64 {
3082 self.score
3083 }
3084
3085 #[inline(always)]
3091 pub fn source(&self) -> Option<&str> {
3092 self.source.as_deref()
3093 }
3094
3095 #[inline(always)]
3101 pub fn line(&self) -> usize {
3102 self.entries.entry.line
3103 }
3104
3105 #[inline(always)]
3111 pub fn extensions(&self) -> &HashSet<String> {
3112 &self.extensions
3113 }
3114}
3115
3116#[derive(Debug, Clone, Serialize, Deserialize)]
3117struct DependencyRule {
3118 name: String,
3119 rule: MagicRule,
3120}
3121
3122#[derive(Debug, Clone, Serialize, Deserialize)]
3128pub struct MagicSource {
3129 rules: Vec<MagicRule>,
3130 dependencies: HashMap<String, DependencyRule>,
3131}
3132
3133impl MagicSource {
3134 pub fn open<P: AsRef<Path>>(p: P) -> Result<Self, Error> {
3144 FileMagicParser::parse_file(p)
3145 }
3146}
3147
3148#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
3149struct ContinuationLevel(u8);
3150
3151#[derive(Debug, PartialEq, Eq, Clone, Copy)]
3154pub enum TextEncoding {
3155 Ascii,
3157 Utf8,
3159 Unknown,
3161}
3162
3163impl TextEncoding {
3164 const fn as_magic_str(&self) -> &'static str {
3165 match self {
3166 TextEncoding::Ascii => "ASCII",
3167 TextEncoding::Utf8 => "UTF-8",
3168 TextEncoding::Unknown => "Unknown",
3169 }
3170 }
3171}
3172
3173#[derive(Debug, PartialEq, Eq, Clone, Copy)]
3175pub enum StreamKind {
3176 Binary,
3178 Text(TextEncoding),
3180}
3181
3182impl StreamKind {
3183 #[inline(always)]
3189 pub const fn is_text(&self) -> bool {
3190 matches!(self, StreamKind::Text(_))
3191 }
3192
3193 #[inline(always)]
3199 pub const fn is_binary(&self) -> bool {
3200 matches!(self, StreamKind::Binary)
3201 }
3202
3203 #[inline(always)]
3210 pub const fn as_str(&self) -> &'static str {
3211 match self {
3212 StreamKind::Binary => "binary",
3213 StreamKind::Text(TextEncoding::Ascii) => "text/ascii",
3214 StreamKind::Text(TextEncoding::Utf8) => "text/utf-8",
3215 StreamKind::Text(TextEncoding::Unknown) => "text/unknown",
3216 }
3217 }
3218}
3219
3220#[derive(Debug)]
3221struct MatchState {
3222 continuation_levels: [bool; 256],
3223}
3224
3225impl MatchState {
3226 #[inline(always)]
3227 fn empty() -> Self {
3228 MatchState {
3229 continuation_levels: [false; 256],
3230 }
3231 }
3232
3233 #[inline(always)]
3234 fn get_continuation_level(&mut self, level: &ContinuationLevel) -> bool {
3235 self.continuation_levels
3236 .get(level.0 as usize)
3237 .cloned()
3238 .unwrap_or_default()
3239 }
3240
3241 #[inline(always)]
3242 fn set_continuation_level(&mut self, level: ContinuationLevel) {
3243 if let Some(b) = self.continuation_levels.get_mut(level.0 as usize) {
3244 *b = true
3245 }
3246 }
3247
3248 #[inline(always)]
3249 fn clear_continuation_level(&mut self, level: &ContinuationLevel) {
3250 if let Some(b) = self.continuation_levels.get_mut(level.0 as usize) {
3251 *b = false;
3252 }
3253 }
3254}
3255
3256#[derive(Debug, Default)]
3258pub struct Magic<'m> {
3259 stream_kind: Option<StreamKind>,
3260 source: Option<Cow<'m, str>>,
3261 message: Vec<Cow<'m, str>>,
3262 mime_type: Option<Cow<'m, str>>,
3263 creator_code: Option<Cow<'m, str>>,
3264 strength: u64,
3265 exts: HashSet<Cow<'m, str>>,
3266 is_default: bool,
3267}
3268
3269impl<'m> Magic<'m> {
3270 #[inline(always)]
3271 fn set_source(&mut self, source: Option<&'m str>) {
3272 self.source = source.map(Cow::Borrowed);
3273 }
3274
3275 #[inline(always)]
3276 fn set_stream_kind(&mut self, stream_kind: StreamKind) {
3277 self.stream_kind = Some(stream_kind)
3278 }
3279
3280 #[inline(always)]
3287 pub fn stream_kind(&self) -> Option<StreamKind> {
3288 self.stream_kind
3289 }
3290
3291 #[inline(always)]
3292 fn reset(&mut self) {
3293 self.stream_kind = None;
3294 self.source = None;
3295 self.message.clear();
3296 self.mime_type = None;
3297 self.creator_code = None;
3298 self.strength = 0;
3299 self.exts.clear();
3300 self.is_default = false;
3301 }
3302
3303 #[inline]
3311 pub fn into_owned<'owned>(self) -> Magic<'owned> {
3312 Magic {
3313 stream_kind: self.stream_kind,
3314 source: self.source.map(|s| Cow::Owned(s.into_owned())),
3315 message: self
3316 .message
3317 .into_iter()
3318 .map(Cow::into_owned)
3319 .map(Cow::Owned)
3320 .collect(),
3321 mime_type: self.mime_type.map(|m| Cow::Owned(m.into_owned())),
3322 creator_code: self.creator_code.map(|m| Cow::Owned(m.into_owned())),
3323 strength: self.strength,
3324 exts: self
3325 .exts
3326 .into_iter()
3327 .map(|e| Cow::Owned(e.into_owned()))
3328 .collect(),
3329 is_default: self.is_default,
3330 }
3331 }
3332
3333 #[inline(always)]
3347 pub fn message(&self) -> String {
3348 let mut out = String::new();
3349
3350 macro_rules! push_str {
3351 ($input: expr) => {{
3352 for c in $input.as_bytes() {
3353 match c {
3354 b'\r' => out.push_str("\\r"),
3355 b'\t' => out.push_str("\\t"),
3356 b'\n' => out.push_str("\\n"),
3357 b' ' => out.push(' '),
3358 c if c.is_ascii_graphic() => out.push(*c as char),
3359 _ => out.push_str(&format!("\\{:03o}", *c as u8)),
3361 }
3362 }
3363 }};
3364 }
3365
3366 for (i, m) in self.message.iter().enumerate() {
3367 if let Some(s) = m.strip_prefix(r#"\b"#) {
3368 push_str!(s);
3369 } else {
3370 if i > 0 {
3372 out.push(' ');
3373 }
3374 push_str!(m);
3375 }
3376 }
3377 out
3378 }
3379
3380 #[inline]
3399 pub fn message_parts(&self) -> impl Iterator<Item = &str> {
3400 self.message.iter().map(|p| p.as_ref())
3401 }
3402
3403 #[inline(always)]
3404 fn update_strength(&mut self, value: u64) {
3405 debug!("update strength = {} + {value}", self.strength);
3406 self.strength = self.strength.saturating_add(value);
3407 debug!("updated strength = {}", self.strength)
3408 }
3409
3410 #[inline(always)]
3416 pub fn mime_type(&self) -> &str {
3417 self.mime_type.as_deref().unwrap_or(match self.stream_kind {
3418 Some(StreamKind::Text(_)) => DEFAULT_TEXT_MIMETYPE,
3419 Some(StreamKind::Binary) | None => DEFAULT_BIN_MIMETYPE,
3420 })
3421 }
3422
3423 #[inline(always)]
3424 fn push_message<'a: 'm>(&mut self, msg: Cow<'a, str>) {
3425 if !msg.is_empty() {
3426 debug!("pushing message: msg={msg} len={}", msg.len());
3427 self.message.push(msg);
3428 }
3429 }
3430
3431 #[inline(always)]
3432 fn set_mime_type<'a: 'm>(&mut self, mime: Cow<'a, str>) {
3433 if self.mime_type.is_none() {
3434 debug!("insert mime: {:?}", mime);
3435 self.mime_type = Some(mime)
3436 }
3437 }
3438
3439 #[inline(always)]
3440 fn set_creator_code<'a: 'm>(&mut self, apple_ty: Cow<'a, str>) {
3441 if self.creator_code.is_none() {
3442 debug!("insert apple type: {apple_ty:?}");
3443 self.creator_code = Some(apple_ty)
3444 }
3445 }
3446
3447 #[inline(always)]
3448 fn insert_extensions<'a: 'm, I: Iterator<Item = &'a str>>(&mut self, exts: I) {
3449 if self.exts.is_empty() {
3450 self.exts.extend(exts.filter_map(|e| {
3451 if e.is_empty() {
3452 None
3453 } else {
3454 Some(Cow::Borrowed(e))
3455 }
3456 }));
3457 }
3458 }
3459
3460 #[inline(always)]
3468 pub fn strength(&self) -> u64 {
3469 self.strength
3470 }
3471
3472 #[inline(always)]
3478 pub fn source(&self) -> Option<&str> {
3479 self.source.as_deref()
3480 }
3481
3482 #[inline(always)]
3488 pub fn creator_code(&self) -> Option<&str> {
3489 self.creator_code.as_deref()
3490 }
3491
3492 #[inline(always)]
3498 pub fn extensions(&self) -> &HashSet<Cow<'m, str>> {
3499 &self.exts
3500 }
3501
3502 #[inline(always)]
3508 pub fn is_default(&self) -> bool {
3509 self.is_default
3510 }
3511}
3512
3513#[derive(Debug, Default, Clone, Serialize, Deserialize)]
3515pub struct MagicDb {
3516 rule_id: usize,
3517 rules: Vec<MagicRule>,
3518 dependencies: HashMap<String, DependencyRule>,
3519 finalized: usize,
3520}
3521
3522#[inline(always)]
3523fn is_likely_text(bytes: &[u8]) -> bool {
3525 const CHUNK_SIZE: usize = std::mem::size_of::<usize>();
3526
3527 if bytes.is_empty() {
3528 return false;
3529 }
3530
3531 let mut printable = 0f64;
3532 let mut high_bytes = 0f64; let (chunks, remainder) = bytes.as_chunks::<CHUNK_SIZE>();
3535
3536 macro_rules! handle_byte {
3537 ($byte: expr) => {
3538 match $byte {
3539 0x00 => return false,
3540 0x09 | 0x0A | 0x0D => printable += 1.0, 0x20..=0x7E => printable += 1.0, _ => high_bytes += 1.0,
3543 }
3544 };
3545 }
3546
3547 for bytes in chunks {
3548 for b in bytes {
3549 handle_byte!(b)
3550 }
3551 }
3552
3553 for b in remainder {
3554 handle_byte!(b)
3555 }
3556
3557 let total = bytes.len() as f64;
3558 let printable_ratio = printable / total;
3559 let high_bytes_ratio = high_bytes / total;
3560
3561 printable_ratio > 0.85 && high_bytes_ratio < 0.20
3563}
3564
3565#[inline(always)]
3566fn guess_stream_kind<S: AsRef<[u8]>>(stream: S) -> StreamKind {
3567 let buf = stream.as_ref();
3568
3569 match run_utf8_validation(buf) {
3570 Ok(is_ascii) => {
3571 if is_ascii {
3572 StreamKind::Text(TextEncoding::Ascii)
3573 } else {
3574 StreamKind::Text(TextEncoding::Utf8)
3575 }
3576 }
3577 Err(e) => {
3578 if is_likely_text(&buf[e.valid_up_to..]) {
3579 StreamKind::Text(TextEncoding::Unknown)
3580 } else {
3581 StreamKind::Binary
3582 }
3583 }
3584 }
3585}
3586
3587impl MagicDb {
3588 pub fn new() -> Self {
3594 Self::default()
3595 }
3596
3597 #[inline(always)]
3598 fn next_rule_id(&mut self) -> usize {
3599 let t = self.rule_id;
3600 self.rule_id += 1;
3601 t
3602 }
3603
3604 #[inline(always)]
3605 fn try_json<D: DataRead>(
3606 haystack: &mut D,
3607 stream_kind: StreamKind,
3608 magic: &mut Magic,
3609 ) -> Result<bool, Error> {
3610 if matches!(stream_kind, StreamKind::Binary) {
3612 return Ok(false);
3613 }
3614
3615 let buf = haystack.read_range(0..FILE_BYTES_MAX as u64)?.trim_ascii();
3616
3617 let Some((start, end)) = find_json_boundaries(buf) else {
3618 return Ok(false);
3619 };
3620
3621 for c in buf[0..start].iter() {
3624 if !c.is_ascii_whitespace() {
3625 return Ok(false);
3626 }
3627 }
3628
3629 let mut is_ndjson = false;
3630
3631 trace!("maybe a json document");
3632 let ok = serde_json::from_slice::<serde_json::Value>(&buf[start..=end]).is_ok();
3633 if !ok {
3634 return Ok(false);
3635 }
3636
3637 if end + 1 < buf.len() {
3639 let buf = &buf[end + 1..];
3641 if let Some((second_start, second_end)) = find_json_boundaries(buf) {
3642 if memchr(b'\n', &buf[..second_start]).is_some() {
3644 trace!("might be ndjson");
3645 is_ndjson = serde_json::from_slice::<serde_json::Value>(
3646 &buf[second_start..=second_end],
3647 )
3648 .is_ok();
3649 }
3650 }
3651 }
3652
3653 if is_ndjson {
3654 magic.push_message(Cow::Borrowed("New Line Delimited"));
3655 magic.set_mime_type(Cow::Borrowed("application/x-ndjson"));
3656 magic.insert_extensions(["ndjson", "jsonl"].into_iter());
3657 } else {
3658 magic.set_mime_type(Cow::Borrowed("application/json"));
3659 magic.insert_extensions(["json"].into_iter());
3660 }
3661
3662 magic.push_message(Cow::Borrowed("JSON text data"));
3663 magic.set_source(Some(HARDCODED_SOURCE));
3664 magic.update_strength(HARDCODED_MAGIC_STRENGTH);
3665 Ok(true)
3666 }
3667
3668 #[inline(always)]
3669 fn try_csv<D: DataRead>(
3670 haystack: &mut D,
3671 stream_kind: StreamKind,
3672 magic: &mut Magic,
3673 ) -> Result<bool, Error> {
3674 let StreamKind::Text(enc) = stream_kind else {
3676 return Ok(false);
3677 };
3678
3679 let buf = haystack.read_range(0..FILE_BYTES_MAX as u64)?;
3680 let mut reader = csv::ReaderBuilder::new()
3681 .has_headers(false)
3682 .from_reader(io::Cursor::new(buf));
3683 let mut records = reader.records();
3684
3685 let Some(Ok(first)) = records.next() else {
3686 return Ok(false);
3687 };
3688
3689 if first.len() <= 1 {
3693 return Ok(false);
3694 }
3695
3696 let mut n = 1;
3698 for i in records.take(9) {
3699 if let Ok(rec) = i {
3700 if first.len() != rec.len() {
3701 return Ok(false);
3702 }
3703 } else {
3704 return Ok(false);
3705 }
3706 n += 1;
3707 }
3708
3709 if n < 2 {
3711 return Ok(false);
3712 }
3713
3714 magic.set_mime_type(Cow::Borrowed("text/csv"));
3715 magic.push_message(Cow::Borrowed("CSV"));
3716 magic.push_message(Cow::Borrowed(enc.as_magic_str()));
3717 magic.push_message(Cow::Borrowed("text"));
3718 magic.insert_extensions(["csv"].into_iter());
3719 magic.set_source(Some(HARDCODED_SOURCE));
3720 magic.update_strength(HARDCODED_MAGIC_STRENGTH);
3721 Ok(true)
3722 }
3723
3724 #[inline(always)]
3725 fn try_tar<D: DataRead>(
3726 haystack: &mut D,
3727 stream_kind: StreamKind,
3728 magic: &mut Magic,
3729 ) -> Result<bool, Error> {
3730 if !matches!(stream_kind, StreamKind::Binary) {
3732 return Ok(false);
3733 }
3734
3735 let buf = haystack.read_range(0..FILE_BYTES_MAX as u64)?;
3736 let mut ar = Archive::new(io::Cursor::new(buf));
3737
3738 let Ok(mut entries) = ar.entries() else {
3739 return Ok(false);
3740 };
3741
3742 let Some(Ok(first)) = entries.next() else {
3743 return Ok(false);
3744 };
3745
3746 let header = first.header();
3747
3748 if header.as_ustar().is_some() {
3749 magic.push_message(Cow::Borrowed("POSIX tar archive"));
3750 } else if header.as_gnu().is_some() {
3751 magic.push_message(Cow::Borrowed("POSIX tar archive (GNU)"));
3752 } else {
3753 magic.push_message(Cow::Borrowed("tar archive"));
3754 }
3755
3756 magic.set_mime_type(Cow::Borrowed("application/x-tar"));
3757 magic.set_source(Some(HARDCODED_SOURCE));
3758 magic.update_strength(HARDCODED_MAGIC_STRENGTH);
3759 magic.insert_extensions(["tar"].into_iter());
3760 Ok(true)
3761 }
3762
3763 #[inline(always)]
3764 fn try_hard_magic<D: DataRead>(
3765 haystack: &mut D,
3766 stream_kind: StreamKind,
3767 magic: &mut Magic,
3768 ) -> Result<bool, Error> {
3769 Ok(Self::try_json(haystack, stream_kind, magic)?
3770 || Self::try_csv(haystack, stream_kind, magic)?
3771 || Self::try_tar(haystack, stream_kind, magic)?)
3772 }
3773
3774 #[inline(always)]
3775 fn magic_default<'m, D: DataRead>(
3776 cache: &mut D,
3777 stream_kind: StreamKind,
3778 magic: &mut Magic<'m>,
3779 ) {
3780 magic.set_source(Some(HARDCODED_SOURCE));
3781 magic.set_stream_kind(stream_kind);
3782 magic.is_default = true;
3783
3784 if cache.data_size() == 0 {
3785 magic.push_message(Cow::Borrowed("empty"));
3786 magic.set_mime_type(Cow::Borrowed(DEFAULT_BIN_MIMETYPE));
3787 }
3788
3789 match stream_kind {
3790 StreamKind::Binary => {
3791 magic.push_message(Cow::Borrowed("data"));
3792 }
3793 StreamKind::Text(e) => {
3794 magic.push_message(Cow::Borrowed(e.as_magic_str()));
3795 magic.push_message(Cow::Borrowed("text"));
3796 }
3797 }
3798 }
3799
3800 fn load_rules_no_prepare(&mut self, rules: Vec<MagicRule>) {
3801 for rule in rules.into_iter() {
3802 let mut rule = rule;
3803 rule.set_id(self.next_rule_id());
3804
3805 self.rules.push(rule);
3806 }
3807 }
3808
3809 pub fn load(&mut self, ms: MagicSource) -> &mut Self {
3815 self.load_rules_no_prepare(ms.rules);
3816 self.dependencies.extend(ms.dependencies);
3817 self.try_finalize();
3818 self
3819 }
3820
3821 pub fn load_bulk<I: Iterator<Item = MagicSource>>(&mut self, it: I) -> &mut Self {
3826 for ms in it {
3827 self.load_rules_no_prepare(ms.rules);
3828 self.dependencies.extend(ms.dependencies);
3829 }
3830 self.try_finalize();
3831 self
3832 }
3833
3834 pub fn rules(&self) -> &[MagicRule] {
3840 &self.rules
3841 }
3842
3843 #[inline]
3844 fn first_magic_with_stream_kind<D: DataRead>(
3845 &self,
3846 haystack: &mut D,
3847 stream_kind: StreamKind,
3848 extension: Option<&str>,
3849 ) -> Result<Magic<'_>, Error> {
3850 let mut magic = Magic::default();
3852
3853 if Self::try_hard_magic(haystack, stream_kind, &mut magic)? {
3854 return Ok(magic);
3855 }
3856
3857 macro_rules! do_magic {
3858 ($rule: expr) => {{
3859 $rule.magic_entrypoint(&mut magic, stream_kind, haystack, &self, false, 0, 0)?;
3860
3861 if !magic.message.is_empty() {
3862 magic.set_stream_kind(stream_kind);
3863 magic.set_source($rule.source.as_deref());
3864 return Ok(magic);
3865 }
3866
3867 magic.reset();
3868 }};
3869 }
3870
3871 if let Some(ext) = extension.map(|e| e.to_lowercase())
3872 && !ext.is_empty()
3873 {
3874 let mut marked = vec![false; self.rules.len()];
3875 for rule in self.rules.iter().filter(|r| r.extensions.contains(&ext)) {
3876 do_magic!(rule);
3877 if let Some(f) = marked.get_mut(rule.id) {
3878 *f = true
3879 }
3880 }
3881 for rule in self
3882 .rules
3883 .iter()
3884 .filter(|r| !*marked.get(r.id).unwrap_or(&false))
3886 {
3887 do_magic!(rule)
3888 }
3889 } else {
3890 for rule in self.rules.iter() {
3891 do_magic!(rule)
3892 }
3893 }
3894
3895 Self::magic_default(haystack, stream_kind, &mut magic);
3896
3897 Ok(magic)
3898 }
3899
3900 pub fn first_magic<R: DataRead>(
3928 &self,
3929 r: &mut R,
3930 extension: Option<&str>,
3931 ) -> Result<Magic<'_>, Error> {
3932 let stream_kind = guess_stream_kind(r.read_range(0..FILE_BYTES_MAX as u64)?);
3933 self.first_magic_with_stream_kind(r, stream_kind, extension)
3934 }
3935
3936 pub fn first_magic_file<P: AsRef<Path>>(&self, path: P) -> Result<Magic<'_>, Error> {
3946 let ext = path.as_ref().extension().and_then(|e| e.to_str());
3947 self.first_magic(&mut DataReader::from_file(File::open(path.as_ref())?)?, ext)
3948 }
3949
3950 pub fn first_magic_slice<S: AsRef<[u8]>>(
3958 &self,
3959 s: S,
3960 extension: Option<&str>,
3961 ) -> Result<Magic<'_>, Error> {
3962 self.first_magic(&mut DataReader::from_slice(s.as_ref()), extension)
3963 }
3964
3965 #[inline(always)]
3966 fn all_magics_sort_with_stream_kind<R: DataRead>(
3967 &self,
3968 haystack: &mut R,
3969 stream_kind: StreamKind,
3970 ) -> Result<Vec<Magic<'_>>, Error> {
3971 let mut out = Vec::new();
3972
3973 let mut magic = Magic::default();
3974
3975 if Self::try_hard_magic(haystack, stream_kind, &mut magic)? {
3976 out.push(magic);
3977 magic = Magic::default();
3978 }
3979
3980 for rule in self.rules.iter() {
3981 rule.magic_entrypoint(&mut magic, stream_kind, haystack, self, false, 0, 0)?;
3982
3983 if !magic.message.is_empty() {
3985 magic.set_stream_kind(stream_kind);
3986 magic.set_source(rule.source.as_deref());
3987 out.push(magic);
3988 magic = Magic::default();
3989 }
3990
3991 magic.reset();
3992 }
3993
3994 Self::magic_default(haystack, stream_kind, &mut magic);
3995 out.push(magic);
3996
3997 out.sort_by_key(|b| std::cmp::Reverse(b.strength()));
3998
3999 Ok(out)
4000 }
4001
4002 #[inline]
4017 pub fn all_magics<R: DataRead>(&self, r: &mut R) -> Result<Vec<Magic<'_>>, Error> {
4018 let stream_kind = guess_stream_kind(r.read_range(0..FILE_BYTES_MAX as u64)?);
4019 self.all_magics_sort_with_stream_kind(r, stream_kind)
4020 }
4021
4022 pub fn all_magics_file<P: AsRef<Path>>(&self, path: P) -> Result<Vec<Magic<'_>>, Error> {
4031 self.all_magics(&mut DataReader::from_file(File::open(path)?)?)
4032 }
4033
4034 pub fn all_magics_slice<S: AsRef<[u8]>>(&self, slice: S) -> Result<Vec<Magic<'_>>, Error> {
4043 self.all_magics(&mut DataReader::from_slice(slice.as_ref()))
4044 }
4045
4046 #[inline(always)]
4047 fn best_magic_with_stream_kind<R: DataRead>(
4048 &self,
4049 haystack: &mut R,
4050 stream_kind: StreamKind,
4051 extension: Option<&str>,
4052 ) -> Result<Magic<'_>, Error> {
4053 let mut magic = Magic::default();
4055 let mut best = Magic::default();
4056 let mut best_id = None;
4057
4058 if Self::try_hard_magic(haystack, stream_kind, &mut magic)? {
4059 swap(&mut magic, &mut best);
4060 magic.reset();
4061 }
4062
4063 macro_rules! do_best {
4064 ($rule: expr) => {{
4065 $rule.magic_entrypoint(&mut magic, stream_kind, haystack, &self, false, 0, 0)?;
4066
4067 if !magic.message.is_empty()
4068 && (magic.strength > best.strength || best.message.is_empty())
4069 {
4070 magic.set_stream_kind(stream_kind);
4071 magic.set_source($rule.source.as_deref());
4072 swap(&mut magic, &mut best);
4073 let _ = best_id.insert($rule.id);
4074 }
4075
4076 magic.reset();
4077 }};
4078 }
4079
4080 let ext = extension.map(|e| e.to_lowercase());
4081 if let Some(ext) = ext.as_ref()
4082 && !ext.is_empty()
4083 {
4084 for rule in self.rules.iter().filter(|r| r.extensions.contains(ext)) {
4085 if !best.message.is_empty() && rule.max_score <= best.strength {
4087 continue;
4088 }
4089 do_best!(rule);
4090 }
4091 }
4092
4093 for rule in self.rules.iter() {
4094 if (!best.message.is_empty() && rule.max_score <= best.strength)
4096 || best_id == Some(rule.id)
4097 {
4098 continue;
4099 }
4100
4101 do_best!(rule)
4102 }
4103
4104 if best.message.is_empty() {
4105 Self::magic_default(haystack, stream_kind, &mut best);
4106 }
4107
4108 Ok(best)
4109 }
4110
4111 #[inline]
4126 pub fn best_magic<R: DataRead>(
4127 &self,
4128 r: &mut R,
4129 extension: Option<&str>,
4130 ) -> Result<Magic<'_>, Error> {
4131 let stream_kind = guess_stream_kind(r.read_range(0..FILE_BYTES_MAX as u64)?);
4132 self.best_magic_with_stream_kind(r, stream_kind, extension)
4133 }
4134
4135 pub fn best_magic_file<P: AsRef<Path>>(&self, path: P) -> Result<Magic<'_>, Error> {
4144 let ext = path.as_ref().extension().and_then(|e| e.to_str());
4145 self.best_magic(&mut DataReader::from_file(File::open(&path)?)?, ext)
4146 }
4147
4148 pub fn best_magic_slice<S: AsRef<[u8]>>(
4157 &self,
4158 slice: S,
4159 extension: Option<&str>,
4160 ) -> Result<Magic<'_>, Error> {
4161 self.best_magic(&mut DataReader::from_slice(slice.as_ref()), extension)
4162 }
4163
4164 pub fn serialize<W: Write>(self, w: &mut W) -> Result<(), Error> {
4170 let mut encoder = GzEncoder::new(w, Compression::best());
4171
4172 bincode::serde::encode_into_std_write(&self, &mut encoder, bincode::config::standard())?;
4173 encoder.finish()?;
4174 Ok(())
4175 }
4176
4177 pub fn deserialize<R: Read>(r: &mut R) -> Result<Self, Error> {
4187 let mut buf = vec![];
4188 let mut gz = GzDecoder::new(r);
4189 gz.read_to_end(&mut buf).map_err(|e| {
4190 bincode::error::DecodeError::OtherString(format!("failed to read: {e}"))
4191 })?;
4192 let (sdb, _): (MagicDb, usize) =
4193 bincode::serde::decode_from_slice(&buf, bincode::config::standard())?;
4194 Ok(sdb)
4195 }
4196
4197 pub fn verify(&mut self) -> Result<(), Error> {
4204 if self.rules.len() == self.finalized {
4205 return Ok(());
4206 }
4207
4208 for r in self.rules.iter_mut().filter(|r| !r.finalized) {
4209 r.try_finalize(&self.dependencies).map_err(|e| {
4211 Error::Verify(
4212 r.source.clone().unwrap_or(String::from("unknown")),
4213 r.line(),
4214 e.into(),
4215 )
4216 })?;
4217 self.finalized += 1;
4218 }
4219
4220 debug_assert!(self.finalized <= self.rules.len());
4221
4222 Ok(())
4223 }
4224
4225 #[inline(always)]
4226 fn try_finalize(&mut self) {
4227 if self.rules.len() == self.finalized {
4228 return;
4229 }
4230
4231 let mut finalized = 0usize;
4232 self.rules.iter_mut().for_each(|r| {
4233 if r.try_finalize(&self.dependencies).is_ok() {
4234 finalized += 1;
4235 }
4236 });
4237
4238 self.finalized = finalized;
4239
4240 debug_assert!(self.finalized <= self.rules.len());
4241
4242 self.rules.sort_by_key(|r| (r.is_text(), -(r.score as i64)));
4244 }
4245}
4246
4247#[cfg(test)]
4248mod tests {
4249
4250 use regex::bytes::Regex;
4251
4252 use crate::{readers::BufReader, utils::unix_local_time_to_string};
4253
4254 use super::*;
4255
4256 macro_rules! buf_reader {
4257 ($l: literal) => {
4258 BufReader::from_slice($l.as_bytes())
4259 };
4260 }
4261
4262 fn best_magic(
4263 rule: &str,
4264 content: &[u8],
4265 stream_kind: StreamKind,
4266 ) -> Result<Magic<'static>, Error> {
4267 let mut md = MagicDb::new();
4268 md.load(
4269 FileMagicParser::parse_str(rule, None)
4270 .inspect_err(|e| eprintln!("{e}"))
4271 .unwrap(),
4272 );
4273 let mut reader = BufReader::from_slice(content);
4274 let v = md.best_magic_with_stream_kind(&mut reader, stream_kind, None)?;
4275 Ok(v.into_owned())
4276 }
4277
4278 #[allow(unused_macros)]
4280 macro_rules! enable_trace {
4281 () => {
4282 tracing_subscriber::fmt()
4283 .with_max_level(tracing_subscriber::filter::LevelFilter::TRACE)
4284 .try_init();
4285 };
4286 }
4287
4288 macro_rules! parse_assert {
4289 ($rule:expr) => {
4290 FileMagicParser::parse_str($rule, None)
4291 .inspect_err(|e| eprintln!("{e}"))
4292 .unwrap()
4293 };
4294 }
4295
4296 macro_rules! assert_magic_match_bin {
4297 ($rule: literal, $content:literal) => {{ best_magic($rule, $content, StreamKind::Binary).unwrap() }};
4298 ($rule: literal, $content:literal, $message:expr) => {{
4299 assert_eq!(
4300 best_magic($rule, $content, StreamKind::Binary)
4301 .unwrap()
4302 .message(),
4303 $message
4304 );
4305 }};
4306 }
4307
4308 macro_rules! assert_magic_match_text {
4309 ($rule: literal, $content:literal) => {{ best_magic($rule, $content, StreamKind::Text(TextEncoding::Utf8)).unwrap() }};
4310 ($rule: literal, $content:literal, $message:expr) => {{
4311 assert_eq!(
4312 best_magic($rule, $content, StreamKind::Text(TextEncoding::Utf8))
4313 .unwrap()
4314 .message(),
4315 $message
4316 );
4317 }};
4318 }
4319
4320 macro_rules! assert_magic_not_match_text {
4321 ($rule: literal, $content:literal) => {{
4322 assert!(
4323 best_magic($rule, $content, StreamKind::Text(TextEncoding::Utf8))
4324 .unwrap()
4325 .is_default()
4326 );
4327 }};
4328 }
4329
4330 macro_rules! assert_magic_not_match_bin {
4331 ($rule: literal, $content:literal) => {{
4332 assert!(
4333 best_magic($rule, $content, StreamKind::Binary)
4334 .unwrap()
4335 .is_default()
4336 );
4337 }};
4338 }
4339
4340 #[test]
4341 fn test_regex() {
4342 assert_magic_match_text!(
4343 r#"
43440 regex/1024 \^#![[:space:]]*/usr/bin/env[[:space:]]+
4345!:mime text/x-shellscript
4346>&0 regex/64 .*($|\\b) %s shell script text executable
4347 "#,
4348 br#"#!/usr/bin/env bash
4349 echo hello world"#,
4350 "bash shell script text executable"
4352 );
4353
4354 let re = Regex::new(r"(?-u)\x42\x82").unwrap();
4355 assert!(re.is_match(b"\x42\x82"));
4356
4357 assert_magic_match_bin!(
4358 r#"0 regex \x42\x82 binary regex match"#,
4359 b"\x00\x00\x00\x00\x00\x00\x42\x82"
4360 );
4361
4362 assert_magic_match_bin!(
4364 r#"
4365 0 regex \x42\x82
4366 >&0 string \xde\xad\xbe\xef it works
4367 "#,
4368 b"\x00\x00\x00\x00\x00\x00\x42\x82\xde\xad\xbe\xef"
4369 );
4370
4371 assert_magic_match_bin!(
4372 r#"
4373 0 regex/s \x42\x82
4374 >&0 string \x42\x82\xde\xad\xbe\xef it works
4375 "#,
4376 b"\x00\x00\x00\x00\x00\x00\x42\x82\xde\xad\xbe\xef"
4377 );
4378
4379 assert_magic_match_text!(
4381 r#"
43820 regex/1024 \^HelloWorld$ HelloWorld String"#,
4383 br#"
4384// this is a comment after an empty line
4385HelloWorld
4386 "#
4387 );
4388 }
4389
4390 #[test]
4391 fn test_string_with_mods() {
4392 assert_magic_match_text!(
4393 r#"0 string/w #!\ \ \ /usr/bin/env\ bash BASH
4394 "#,
4395 b"#! /usr/bin/env bash i
4396 echo hello world"
4397 );
4398
4399 assert_magic_match_text!(
4401 r#"0 string/C HelloWorld it works
4402 "#,
4403 b"helloworld"
4404 );
4405
4406 assert_magic_not_match_text!(
4407 r#"0 string/C HelloWorld it works
4408 "#,
4409 b"hELLOwORLD"
4410 );
4411
4412 assert_magic_match_text!(
4414 r#"0 string/c HelloWorld it works
4415 "#,
4416 b"HELLOWORLD"
4417 );
4418
4419 assert_magic_not_match_text!(
4420 r#"0 string/c HelloWorld it works
4421 "#,
4422 b"helloworld"
4423 );
4424
4425 assert_magic_match_text!(
4427 r#"0 string/f #!/usr/bin/env\ bash BASH
4428 "#,
4429 b"#!/usr/bin/env bash"
4430 );
4431
4432 assert_magic_not_match_text!(
4433 r#"0 string/f #!/usr/bin/python PYTHON"#,
4434 b"#!/usr/bin/pythonic"
4435 );
4436
4437 assert_magic_match_text!(
4439 r#"0 string/W #!/usr/bin/env\ python PYTHON"#,
4440 b"#!/usr/bin/env python"
4441 );
4442
4443 assert_magic_not_match_text!(
4444 r#"0 string/W #!/usr/bin/env\ \ python PYTHON"#,
4445 b"#!/usr/bin/env python"
4446 );
4447 }
4448
4449 #[test]
4450 fn test_search_with_mods() {
4451 assert_magic_match_text!(
4452 r#"0 search/1/fwt #!\ /usr/bin/luatex LuaTex script text executable"#,
4453 b"#! /usr/bin/luatex "
4454 );
4455
4456 assert_magic_match_text!(
4458 r#"
4459 0 search/s /usr/bin/env
4460 >&0 string /usr/bin/env it works
4461 "#,
4462 b"#!/usr/bin/env python"
4463 );
4464
4465 assert_magic_not_match_text!(
4466 r#"
4467 0 search /usr/bin/env
4468 >&0 string /usr/bin/env it works
4469 "#,
4470 b"#!/usr/bin/env python"
4471 );
4472 }
4473
4474 #[test]
4475 fn test_pstring() {
4476 assert_magic_match_bin!(r#"0 pstring Toast it works"#, b"\x05Toast");
4477
4478 assert_magic_match_bin!(r#"0 pstring Toast %s"#, b"\x05Toast", "Toast");
4479
4480 assert_magic_not_match_bin!(r#"0 pstring Toast Doesn't work"#, b"\x07Toaster");
4481
4482 assert_magic_match_bin!(r#"0 pstring/H Toast it works"#, b"\x00\x05Toast");
4484
4485 assert_magic_match_bin!(r#"0 pstring/HJ Toast it works"#, b"\x00\x07Toast");
4486
4487 assert_magic_match_bin!(r#"0 pstring/HJ Toast %s"#, b"\x00\x07Toast", "Toast");
4488
4489 assert_magic_match_bin!(r#"0 pstring/h Toast it works"#, b"\x05\x00Toast");
4490
4491 assert_magic_match_bin!(r#"0 pstring/hJ Toast it works"#, b"\x07\x00Toast");
4492
4493 assert_magic_match_bin!(r#"0 pstring/L Toast it works"#, b"\x00\x00\x00\x05Toast");
4494
4495 assert_magic_match_bin!(r#"0 pstring/LJ Toast it works"#, b"\x00\x00\x00\x09Toast");
4496
4497 assert_magic_match_bin!(r#"0 pstring/l Toast it works"#, b"\x05\x00\x00\x00Toast");
4498
4499 assert_magic_match_bin!(r#"0 pstring/lJ Toast it works"#, b"\x09\x00\x00\x00Toast");
4500 }
4501
4502 #[test]
4503 fn test_max_recursion() {
4504 let res = best_magic(
4505 r#"0 indirect x"#,
4506 b"#! /usr/bin/luatex ",
4507 StreamKind::Binary,
4508 );
4509 assert!(res.is_err());
4510 let _ = res.inspect_err(|e| {
4511 assert!(matches!(
4512 e.unwrap_localized(),
4513 Error::MaximumRecursion(MAX_RECURSION)
4514 ))
4515 });
4516 }
4517
4518 #[test]
4519 fn test_string_ops() {
4520 assert_magic_match_text!("0 string/b MZ MZ File", b"MZ\0");
4521 assert_magic_match_text!("0 string !MZ Not MZ File", b"AZ\0");
4522 assert_magic_match_text!("0 string >\0 Any String", b"A\0");
4523 assert_magic_match_text!("0 string >Test Any String", b"Test 1\0");
4524 assert_magic_match_text!("0 string <Test Any String", b"\0");
4525 assert_magic_not_match_text!("0 string >Test Any String", b"\0");
4526 }
4527
4528 #[test]
4529 fn test_lestring16() {
4530 assert_magic_match_bin!(
4531 "0 lestring16 abcd Little-endian UTF-16 string",
4532 b"\x61\x00\x62\x00\x63\x00\x64\x00"
4533 );
4534 assert_magic_match_bin!(
4535 "0 lestring16 x %s",
4536 b"\x61\x00\x62\x00\x63\x00\x64\x00\x00",
4537 "abcd"
4538 );
4539 assert_magic_not_match_bin!(
4540 "0 lestring16 abcd Little-endian UTF-16 string",
4541 b"\x00\x61\x00\x62\x00\x63\x00\x64"
4542 );
4543 assert_magic_match_bin!(
4544 "4 lestring16 abcd Little-endian UTF-16 string",
4545 b"\x00\x00\x00\x00\x61\x00\x62\x00\x63\x00\x64\x00"
4546 );
4547 }
4548
4549 #[test]
4550 fn test_bestring16() {
4551 assert_magic_match_bin!(
4552 "0 bestring16 abcd Big-endian UTF-16 string",
4553 b"\x00\x61\x00\x62\x00\x63\x00\x64"
4554 );
4555 assert_magic_match_bin!(
4556 "0 bestring16 x %s",
4557 b"\x00\x61\x00\x62\x00\x63\x00\x64",
4558 "abcd"
4559 );
4560 assert_magic_not_match_bin!(
4561 "0 bestring16 abcd Big-endian UTF-16 string",
4562 b"\x61\x00\x62\x00\x63\x00\x64\x00"
4563 );
4564 assert_magic_match_bin!(
4565 "4 bestring16 abcd Big-endian UTF-16 string",
4566 b"\x00\x00\x00\x00\x00\x61\x00\x62\x00\x63\x00\x64"
4567 );
4568 }
4569
4570 #[test]
4571 fn test_offset_from_end() {
4572 assert_magic_match_bin!("-1 ubyte 0x42 last byte ok", b"\x00\x00\x42");
4573 assert_magic_match_bin!("-2 ubyte 0x41 last byte ok", b"\x00\x41\x00");
4574 }
4575
4576 #[test]
4577 fn test_relative_offset() {
4578 assert_magic_match_bin!(
4579 "
4580 0 ubyte 0x42
4581 >&0 ubyte 0x00
4582 >>&0 ubyte 0x41 third byte ok
4583 ",
4584 b"\x42\x00\x41\x00"
4585 );
4586 }
4587
4588 #[test]
4589 fn test_indirect_offset() {
4590 assert_magic_match_bin!("(0.l) ubyte 0x42 it works", b"\x04\x00\x00\x00\x42");
4591 assert_magic_match_bin!("(0.l+3) ubyte 0x42 it works", b"\x01\x00\x00\x00\x42");
4593 assert_magic_match_bin!(
4595 "(0.l+(4)) ubyte 0x42 it works",
4596 b"\x04\x00\x00\x00\x04\x00\x00\x00\x42"
4597 );
4598 }
4599
4600 #[test]
4605 fn test_use_with_message() {
4606 assert_magic_match_bin!(
4607 r#"
46080 string MZ
4609>0 use mz first match
4610
46110 name mz then second match
4612>0 string MZ
4613"#,
4614 b"MZ\0",
4615 "then second match"
4616 );
4617 }
4618
4619 #[test]
4624 fn test_indirect_message_only_on_nested_match() {
4625 let rule = r#"
46260 string AB
4627>2 indirect x contains:
4628
46290 string CD nested match
4630"#;
4631
4632 let m = best_magic(rule, b"ABCD", StreamKind::Binary).unwrap();
4633 assert_eq!(m.message(), "contains: nested match");
4634
4635 let m = best_magic(rule, b"ABXX", StreamKind::Binary).unwrap();
4636 assert!(m.is_default());
4637 }
4638
4639 #[test]
4640 fn test_scalar_transform() {
4641 assert_magic_match_bin!("0 ubyte+1 0x1 add works", b"\x00");
4642 assert_magic_match_bin!("0 ubyte-1 0xfe sub works", b"\xff");
4643 assert_magic_match_bin!("0 ubyte%2 0 mod works", b"\x0a");
4644 assert_magic_match_bin!("0 ubyte&0x0f 0x0f bitand works", b"\xff");
4645 assert_magic_match_bin!("0 ubyte|0x0f 0xff bitor works", b"\xf0");
4646 assert_magic_match_bin!("0 ubyte^0x0f 0xf0 bitxor works", b"\xff");
4647
4648 FileMagicParser::parse_str("0 ubyte%0 mod by zero", None)
4649 .expect_err("expect div by zero error");
4650 FileMagicParser::parse_str("0 ubyte/0 div by zero", None)
4651 .expect_err("expect div by zero error");
4652 }
4653
4654 #[test]
4655 fn test_strength_hoisted_to_root() {
4656 let base_rule = r"
46590 string MAGIC found it
4660>0 byte x x
4661 ";
4662 let with_strength_rule = r"
46630 string MAGIC found it
4664>0 byte x x
4665!:strength +100
4666 ";
4667
4668 let mut db = MagicDb::new();
4669 db.load(parse_assert!(base_rule));
4670 db.load(parse_assert!(with_strength_rule));
4671
4672 let modded_score = db.rules()[0].score();
4674 let base_score = db.rules()[1].score();
4675
4676 assert_eq!(
4677 modded_score,
4678 base_score + 100,
4679 "!:strength on a nested continuation must still affect the root's static score"
4680 );
4681
4682 let base_strength = best_magic(base_rule, b"MAGIC", StreamKind::Binary)
4683 .unwrap()
4684 .strength();
4685 let modded_strength = best_magic(with_strength_rule, b"MAGIC", StreamKind::Binary)
4686 .unwrap()
4687 .strength();
4688
4689 assert_eq!(
4690 modded_strength,
4691 base_strength + 100,
4692 "!:strength on a nested continuation must still affect the runtime strength"
4693 );
4694 }
4695
4696 #[test]
4697 fn test_duplicate_strength_rejected() {
4698 FileMagicParser::parse_str(
4700 r"
47010 string MAGIC found it
4702!:strength +10
4703>0 byte x x
4704!:strength +20
4705 ",
4706 None,
4707 )
4708 .expect_err("a second !:strength in the same rule must be rejected");
4709 }
4710
4711 #[test]
4712 fn test_strength_on_name_entry_rejected() {
4713 FileMagicParser::parse_str(
4714 r"
47150 name dep
4716!:strength +10
4717>0 byte x x
4718 ",
4719 None,
4720 )
4721 .expect_err("!:strength directly on a name entry must be rejected");
4722
4723 FileMagicParser::parse_str(
4724 r"
47250 name dep
4726>0 byte x x
4727!:strength +10
4728 ",
4729 None,
4730 )
4731 .expect_err("!:strength anywhere in a name-headed group must be rejected");
4732 }
4733
4734 #[test]
4735 fn test_search_binary_default_matches_libmagic_text_chars() {
4736 assert_magic_not_match_bin!(r"0 search \007test found", b"\x07test");
4740 assert_magic_match_text!(r"0 search \007test found", b"\x07test", "found");
4741 }
4742
4743 #[test]
4744 fn test_search_binary_default_accepts_valid_utf8() {
4745 assert_magic_not_match_bin!(r"0 search caf\xc3\xa9 found", b"caf\xc3\xa9");
4748 assert_magic_match_text!(r"0 search caf\xc3\xa9 found", b"caf\xc3\xa9", "found");
4749 }
4750
4751 #[test]
4752 fn test_belong() {
4753 assert_magic_match_bin!("0 belong 0x12345678 Big-endian long", b"\x12\x34\x56\x78");
4755 assert_magic_not_match_bin!("0 belong 0x12345678 Big-endian long", b"\x78\x56\x34\x12");
4757 assert_magic_match_bin!(
4759 "4 belong 0x12345678 Big-endian long",
4760 b"\x00\x00\x00\x00\x12\x34\x56\x78"
4761 );
4762 assert_magic_match_bin!("0 belong <0x12345678 Big-endian long", b"\x12\x34\x56\x77");
4764 assert_magic_not_match_bin!("0 belong <0x12345678 Big-endian long", b"\x12\x34\x56\x78");
4765
4766 assert_magic_match_bin!("0 belong >0x12345678 Big-endian long", b"\x12\x34\x56\x79");
4768 assert_magic_not_match_bin!("0 belong >0x12345678 Big-endian long", b"\x12\x34\x56\x78");
4769
4770 assert_magic_match_bin!("0 belong &0x5678 Big-endian long", b"\x00\x00\x56\x78");
4772 assert_magic_not_match_bin!("0 belong &0x0000FFFF Big-endian long", b"\x12\x34\x56\x78");
4773
4774 assert_magic_match_bin!("0 belong ^0xFFFF0000 Big-endian long", b"\x00\x00\x56\x78");
4776 assert_magic_not_match_bin!("0 belong ^0xFFFF0000 Big-endian long", b"\x00\x01\x56\x78");
4777
4778 assert_magic_match_bin!("0 belong ~0x12345678 Big-endian long", b"\xed\xcb\xa9\x87");
4780 assert_magic_not_match_bin!("0 belong ~0x12345678 Big-endian long", b"\x12\x34\x56\x78");
4781
4782 assert_magic_match_bin!("0 belong x Big-endian long", b"\x12\x34\x56\x78");
4784 assert_magic_match_bin!("0 belong x Big-endian long", b"\x78\x56\x34\x12");
4785 }
4786
4787 #[test]
4788 fn test_parse_search() {
4789 parse_assert!("0 search test");
4790 parse_assert!("0 search/24/s test");
4791 parse_assert!("0 search/s/24 test");
4792 }
4793
4794 #[test]
4795 fn test_bedate() {
4796 assert_magic_match_bin!(
4797 "0 bedate 946684800 Unix date (Jan 1, 2000)",
4798 b"\x38\x6D\x43\x80"
4799 );
4800 assert_magic_not_match_bin!(
4801 "0 bedate 946684800 Unix date (Jan 1, 2000)",
4802 b"\x00\x00\x00\x00"
4803 );
4804 assert_magic_match_bin!(
4805 "4 bedate 946684800 %s",
4806 b"\x00\x00\x00\x00\x38\x6D\x43\x80",
4807 "2000-01-01 00:00:00"
4808 );
4809 }
4810 #[test]
4811 fn test_beldate() {
4812 assert_magic_match_bin!(
4813 "0 beldate 946684800 Local date (Jan 1, 2000)",
4814 b"\x38\x6D\x43\x80"
4815 );
4816 assert_magic_not_match_bin!(
4817 "0 beldate 946684800 Local date (Jan 1, 2000)",
4818 b"\x00\x00\x00\x00"
4819 );
4820
4821 assert_magic_match_bin!(
4822 "4 beldate 946684800 {}",
4823 b"\x00\x00\x00\x00\x38\x6D\x43\x80",
4824 unix_local_time_to_string(946684800)
4825 );
4826 }
4827
4828 #[test]
4829 fn test_beqdate() {
4830 assert_magic_match_bin!(
4831 "0 beqdate 946684800 Unix date (Jan 1, 2000)",
4832 b"\x00\x00\x00\x00\x38\x6D\x43\x80"
4833 );
4834
4835 assert_magic_not_match_bin!(
4836 "0 beqdate 946684800 Unix date (Jan 1, 2000)",
4837 b"\x00\x00\x00\x00\x00\x00\x00\x00"
4838 );
4839
4840 assert_magic_match_bin!(
4841 "0 beqdate 946684800 %s",
4842 b"\x00\x00\x00\x00\x38\x6D\x43\x80",
4843 "2000-01-01 00:00:00"
4844 );
4845 }
4846
4847 #[test]
4848 fn test_beqldate() {
4849 assert_magic_match_bin!(
4850 "0 beqldate 946684800 Local date (Jan 1, 2000)",
4851 b"\x00\x00\x00\x00\x38\x6D\x43\x80"
4852 );
4853
4854 assert_magic_not_match_bin!(
4855 "0 beqldate 946684800 Local date (Jan 1, 2000)",
4856 b"\x00\x00\x00\x00\x00\x00\x00\x00"
4857 );
4858
4859 assert_magic_match_bin!(
4860 "0 beqldate 946684800 %s",
4861 b"\x00\x00\x00\x00\x38\x6D\x43\x80",
4862 unix_local_time_to_string(946684800)
4863 );
4864 }
4865
4866 #[test]
4867 fn test_medate() {
4868 assert_magic_match_bin!(
4869 "0 medate 946684800 Unix date (Jan 1, 2000)",
4870 b"\x6D\x38\x80\x43"
4871 );
4872
4873 assert_magic_not_match_bin!(
4874 "0 medate 946684800 Unix date (Jan 1, 2000)",
4875 b"\x00\x00\x00\x00"
4876 );
4877
4878 assert_magic_match_bin!(
4879 "4 medate 946684800 %s",
4880 b"\x00\x00\x00\x00\x6D\x38\x80\x43",
4881 "2000-01-01 00:00:00"
4882 );
4883 }
4884
4885 #[test]
4886 fn test_meldate() {
4887 assert_magic_match_bin!(
4888 "0 meldate 946684800 Local date (Jan 1, 2000)",
4889 b"\x6D\x38\x80\x43"
4890 );
4891 assert_magic_not_match_bin!(
4892 "0 meldate 946684800 Local date (Jan 1, 2000)",
4893 b"\x00\x00\x00\x00"
4894 );
4895
4896 assert_magic_match_bin!(
4897 "4 meldate 946684800 %s",
4898 b"\x00\x00\x00\x00\x6D\x38\x80\x43",
4899 unix_local_time_to_string(946684800)
4900 );
4901 }
4902
4903 #[test]
4904 fn test_date() {
4905 assert_magic_match_bin!(
4906 "0 date 946684800 Local date (Jan 1, 2000)",
4907 b"\x80\x43\x6D\x38"
4908 );
4909 assert_magic_not_match_bin!(
4910 "0 date 946684800 Local date (Jan 1, 2000)",
4911 b"\x00\x00\x00\x00"
4912 );
4913 assert_magic_match_bin!(
4914 "4 date 946684800 {}",
4915 b"\x00\x00\x00\x00\x80\x43\x6D\x38",
4916 "2000-01-01 00:00:00"
4917 );
4918 }
4919
4920 #[test]
4921 fn test_leldate() {
4922 assert_magic_match_bin!(
4923 "0 leldate 946684800 Local date (Jan 1, 2000)",
4924 b"\x80\x43\x6D\x38"
4925 );
4926 assert_magic_not_match_bin!(
4927 "0 leldate 946684800 Local date (Jan 1, 2000)",
4928 b"\x00\x00\x00\x00"
4929 );
4930 assert_magic_match_bin!(
4931 "4 leldate 946684800 {}",
4932 b"\x00\x00\x00\x00\x80\x43\x6D\x38",
4933 unix_local_time_to_string(946684800)
4934 );
4935 }
4936
4937 #[test]
4938 fn test_leqdate() {
4939 assert_magic_match_bin!(
4940 "0 leqdate 1577836800 Unix date (Jan 1, 2020)",
4941 b"\x00\xe1\x0b\x5E\x00\x00\x00\x00"
4942 );
4943
4944 assert_magic_not_match_bin!(
4945 "0 leqdate 1577836800 Unix date (Jan 1, 2020)",
4946 b"\x00\x00\x00\x00\x00\x00\x00\x00"
4947 );
4948 assert_magic_match_bin!(
4949 "8 leqdate 1577836800 %s",
4950 b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE1\x0B\x5E\x00\x00\x00\x00",
4951 "2020-01-01 00:00:00"
4952 );
4953 }
4954
4955 #[test]
4956 fn test_leqldate() {
4957 assert_magic_match_bin!(
4958 "0 leqldate 1577836800 Unix date (Jan 1, 2020)",
4959 b"\x00\xe1\x0b\x5E\x00\x00\x00\x00"
4960 );
4961
4962 assert_magic_not_match_bin!(
4963 "0 leqldate 1577836800 Unix date (Jan 1, 2020)",
4964 b"\x00\x00\x00\x00\x00\x00\x00\x00"
4965 );
4966 assert_magic_match_bin!(
4967 "8 leqldate 1577836800 %s",
4968 b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE1\x0B\x5E\x00\x00\x00\x00",
4969 unix_local_time_to_string(1577836800)
4970 );
4971 }
4972
4973 #[test]
4974 fn test_melong() {
4975 assert_magic_match_bin!(
4977 "0 melong =0x12345678 Middle-endian long",
4978 b"\x34\x12\x78\x56"
4979 );
4980 assert_magic_not_match_bin!(
4981 "0 melong =0x12345678 Middle-endian long",
4982 b"\x00\x00\x00\x00"
4983 );
4984
4985 assert_magic_match_bin!(
4987 "0 melong <0x12345678 Middle-endian long",
4988 b"\x34\x12\x78\x55"
4989 ); assert_magic_not_match_bin!(
4991 "0 melong <0x12345678 Middle-endian long",
4992 b"\x34\x12\x78\x56"
4993 ); assert_magic_match_bin!(
4997 "0 melong >0x12345678 Middle-endian long",
4998 b"\x34\x12\x78\x57"
4999 ); assert_magic_not_match_bin!(
5001 "0 melong >0x12345678 Middle-endian long",
5002 b"\x34\x12\x78\x56"
5003 ); assert_magic_match_bin!("0 melong &0x5678 Middle-endian long", b"\xab\xcd\x78\x56"); assert_magic_not_match_bin!(
5008 "0 melong &0x0000FFFF Middle-endian long",
5009 b"\x34\x12\x78\x56"
5010 ); assert_magic_match_bin!(
5014 "0 melong ^0xFFFF0000 Middle-endian long",
5015 b"\x00\x00\x78\x56"
5016 ); assert_magic_not_match_bin!(
5018 "0 melong ^0xFFFF0000 Middle-endian long",
5019 b"\x00\x01\x78\x56"
5020 ); assert_magic_match_bin!(
5024 "0 melong ~0x12345678 Middle-endian long",
5025 b"\xCB\xED\x87\xA9"
5026 );
5027 assert_magic_not_match_bin!(
5028 "0 melong ~0x12345678 Middle-endian long",
5029 b"\x34\x12\x78\x56"
5030 ); assert_magic_match_bin!("0 melong x Middle-endian long", b"\x34\x12\x78\x56");
5034 assert_magic_match_bin!("0 melong x Middle-endian long", b"\x00\x00\x00\x00");
5035 }
5036
5037 #[test]
5038 fn test_uquad() {
5039 assert_magic_match_bin!(
5041 "0 uquad =0x123456789ABCDEF0 Unsigned quad",
5042 b"\xF0\xDE\xBC\x9A\x78\x56\x34\x12"
5043 );
5044 assert_magic_not_match_bin!(
5045 "0 uquad =0x123456789ABCDEF0 Unsigned quad",
5046 b"\x00\x00\x00\x00\x00\x00\x00\x00"
5047 );
5048
5049 assert_magic_match_bin!(
5051 "0 uquad <0x123456789ABCDEF0 Unsigned quad",
5052 b"\xF0\xDE\xBC\x9A\x78\x56\x34\x11"
5053 );
5054 assert_magic_not_match_bin!(
5055 "0 uquad <0x123456789ABCDEF0 Unsigned quad",
5056 b"\xF0\xDE\xBC\x9A\x78\x56\x34\x12"
5057 );
5058
5059 assert_magic_match_bin!(
5061 "0 uquad >0x123456789ABCDEF0 Unsigned quad",
5062 b"\xF0\xDE\xBC\x9A\x78\x56\x34\x13"
5063 );
5064 assert_magic_not_match_bin!(
5065 "0 uquad >0x123456789ABCDEF0 Unsigned quad",
5066 b"\xF0\xDE\xBC\x9A\x78\x56\x34\x12"
5067 );
5068
5069 assert_magic_match_bin!(
5071 "0 uquad &0xF0 Unsigned quad",
5072 b"\xF0\xDE\xBC\x9A\x78\x56\x34\x12"
5073 );
5074 assert_magic_not_match_bin!(
5075 "0 uquad &0xFF Unsigned quad",
5076 b"\xF0\xDE\xBC\x9A\x78\x56\x34\x12"
5077 );
5078
5079 assert_magic_match_bin!(
5081 "0 uquad ^0xFFFFFFFFFFFFFFFF Unsigned quad",
5082 b"\x00\x00\x00\x00\x00\x00\x00\x00"
5083 ); assert_magic_not_match_bin!(
5085 "0 uquad ^0xFFFFFFFFFFFFFFFF Unsigned quad",
5086 b"\xF0\xDE\xBC\x9A\x78\x56\x34\x12"
5087 ); assert_magic_match_bin!(
5091 "0 uquad ~0x123456789ABCDEF0 Unsigned quad",
5092 b"\x0F\x21\x43\x65\x87\xA9\xCB\xED"
5093 );
5094 assert_magic_not_match_bin!(
5095 "0 uquad ~0x123456789ABCDEF0 Unsigned quad",
5096 b"\xF0\xDE\xBC\x9A\x78\x56\x34\x12"
5097 ); assert_magic_match_bin!(
5101 "0 uquad x {:#x}",
5102 b"\xF0\xDE\xBC\x9A\x78\x56\x34\x12",
5103 "0x123456789abcdef0"
5104 );
5105 assert_magic_match_bin!(
5106 "0 uquad x Unsigned quad",
5107 b"\x00\x00\x00\x00\x00\x00\x00\x00"
5108 );
5109 }
5110
5111 #[test]
5115 fn test_guid() {
5116 assert_magic_match_bin!(
5117 "0 guid DDCCBBAA-FFEE-1100-2233-445566778899 It works",
5118 b"\xAA\xBB\xCC\xDD\xEE\xFF\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99"
5119 );
5120
5121 assert_magic_not_match_bin!(
5122 "0 guid DDCCBBAA-FFEE-1100-2233-445566778899 It works",
5123 b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
5124 );
5125
5126 assert_magic_match_bin!(
5127 "0 guid x %s",
5128 b"\xAA\xBB\xCC\xDD\xEE\xFF\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99",
5129 "DDCCBBAA-FFEE-1100-2233-445566778899"
5130 );
5131 }
5132
5133 #[test]
5135 fn test_leguid() {
5136 assert_magic_match_bin!(
5137 "0 leguid DDCCBBAA-FFEE-1100-2233-445566778899 It works",
5138 b"\xAA\xBB\xCC\xDD\xEE\xFF\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99"
5139 );
5140
5141 assert_magic_not_match_bin!(
5142 "0 leguid DDCCBBAA-FFEE-1100-2233-445566778899 It works",
5143 b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
5144 );
5145
5146 assert_magic_match_bin!(
5147 "0 leguid x %s",
5148 b"\xAA\xBB\xCC\xDD\xEE\xFF\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99",
5149 "DDCCBBAA-FFEE-1100-2233-445566778899"
5150 );
5151 }
5152
5153 #[test]
5157 fn test_beguid() {
5158 assert_magic_match_bin!(
5159 "0 beguid AABBCCDD-EEFF-0011-2233-445566778899 It works",
5160 b"\xAA\xBB\xCC\xDD\xEE\xFF\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99"
5161 );
5162
5163 assert_magic_not_match_bin!(
5164 "0 beguid AABBCCDD-EEFF-0011-2233-445566778899 It works",
5165 b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
5166 );
5167
5168 assert_magic_match_bin!(
5169 "0 beguid x %s",
5170 b"\xAA\xBB\xCC\xDD\xEE\xFF\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99",
5171 "AABBCCDD-EEFF-0011-2233-445566778899"
5172 );
5173 }
5174
5175 #[test]
5176 fn test_ubeqdate() {
5177 assert_magic_match_bin!(
5178 "0 ubeqdate 1633046400 It works",
5179 b"\x00\x00\x00\x00\x61\x56\x4f\x80"
5180 );
5181
5182 assert_magic_match_bin!(
5183 "0 ubeqdate x %s",
5184 b"\x00\x00\x00\x00\x61\x56\x4f\x80",
5185 "2021-10-01 00:00:00"
5186 );
5187
5188 assert_magic_not_match_bin!(
5189 "0 ubeqdate 1633046400 It should not work",
5190 b"\x00\x00\x00\x00\x00\x00\x00\x00"
5191 );
5192 }
5193
5194 #[test]
5195 fn test_ldate() {
5196 assert_magic_match_bin!("0 ldate 1640551520 It works", b"\x60\xd4\xC8\x61");
5197
5198 assert_magic_not_match_bin!("0 ldate 1633046400 It should not work", b"\x00\x00\x00\x00");
5199
5200 assert_magic_match_bin!(
5201 "0 ldate x %s",
5202 b"\x60\xd4\xC8\x61",
5203 unix_local_time_to_string(1640551520)
5204 );
5205 }
5206
5207 #[test]
5208 fn test_scalar_with_transform() {
5209 assert_magic_match_bin!("0 ubyte/10 2 {}", b"\x14", "2");
5210 assert_magic_match_bin!("0 ubyte/10 x {}", b"\x14", "2");
5211 assert_magic_match_bin!("0 ubyte%10 x {}", b"\x14", "0");
5212 }
5213
5214 #[test]
5215 fn test_float_with_transform() {
5216 assert_magic_match_bin!("0 lefloat/10 2 {}", b"\x00\x00\xa0\x41", "2");
5217 assert_magic_match_bin!("0 lefloat/10 x {}", b"\x00\x00\xa0\x41", "2");
5218 assert_magic_match_bin!("0 lefloat%10 x {}", b"\x00\x00\xa0\x41", "0");
5219 }
5220
5221 #[test]
5222 fn test_read_octal() {
5223 assert_eq!(read_octal_u64(&mut buf_reader!("0")), Some(0));
5225 assert_eq!(read_octal_u64(&mut buf_reader!("00")), Some(0));
5226 assert_eq!(read_octal_u64(&mut buf_reader!("01")), Some(1));
5227 assert_eq!(read_octal_u64(&mut buf_reader!("07")), Some(7));
5228 assert_eq!(read_octal_u64(&mut buf_reader!("010")), Some(8));
5229 assert_eq!(read_octal_u64(&mut buf_reader!("0123")), Some(83));
5230 assert_eq!(read_octal_u64(&mut buf_reader!("0755")), Some(493));
5231
5232 assert_eq!(read_octal_u64(&mut buf_reader!("0ABC")), Some(0));
5234 assert_eq!(read_octal_u64(&mut buf_reader!("01ABC")), Some(1));
5235 assert_eq!(read_octal_u64(&mut buf_reader!("0755ABC")), Some(493));
5236 assert_eq!(read_octal_u64(&mut buf_reader!("0123ABC")), Some(83));
5237
5238 assert_eq!(read_octal_u64(&mut buf_reader!("08")), Some(0)); assert_eq!(read_octal_u64(&mut buf_reader!("01238")), Some(83)); assert_eq!(read_octal_u64(&mut buf_reader!("123")), None);
5244 assert_eq!(read_octal_u64(&mut buf_reader!("755")), None);
5245
5246 assert_eq!(read_octal_u64(&mut buf_reader!("")), None);
5248
5249 assert_eq!(read_octal_u64(&mut buf_reader!("ABC")), None);
5251 assert_eq!(read_octal_u64(&mut buf_reader!("8ABC")), None); assert_eq!(
5255 read_octal_u64(&mut buf_reader!("01777777777")),
5256 Some(268435455)
5257 );
5258 }
5259
5260 #[test]
5261 fn test_offset_bug_1() {
5262 assert_magic_match_bin!(
5265 r"
52661 string TEST Bread is
5267# offset computation is relative to
5268# rule start
5269>(5.b) use toasted
5270
52710 name toasted
5272>0 string twice Toasted
5273>>0 use toasted_twice
5274
52750 name toasted_twice
5276>(6.b) string x %s
5277 ",
5278 b"\x00TEST\x06twice\x00\x06",
5279 "Bread is Toasted twice"
5280 );
5281 }
5282
5283 #[test]
5289 fn test_offset_bug_2() {
5290 assert_magic_match_bin!(
5293 r"
5294-12 string TEST Bread is
5295>(4.b) use toasted
5296
52970 name toasted
5298>0 string twice Toasted
5299>>0 use toasted_twice
5300
53010 name toasted_twice
5302>(6.b) string x %
5303 ",
5304 b"\x00TEST\x06twice\x00\x06",
5305 "Bread is Toasted twice"
5306 )
5307 }
5308
5309 #[test]
5310 fn test_offset_bug_3() {
5311 assert_magic_match_bin!(
5314 r"
53151 string TEST Bread is
5316>(5.b) indirect/r x
5317
53180 string twice Toasted
5319>0 use toasted_twice
5320
53210 name toasted_twice
5322>0 string x %s
5323 ",
5324 b"\x00TEST\x06twice\x00\x08",
5325 "Bread is Toasted twice"
5326 )
5327 }
5328
5329 #[test]
5330 fn test_offset_bug_4() {
5331 assert_magic_match_bin!(
5334 r"
53351 string Bread %s
5336>(6.b) indirect/r x
5337
5338# this one uses a based offset
5339# computed at indirection
53401 string is\ Toasted %s
5341>(11.b) use toasted_twice
5342
5343# this one is using a new base
5344# offset being previous base
5345# offset + offset of use
53460 name toasted_twice
5347>0 string x %s
5348 ",
5349 b"\x00Bread\x06is Toasted\x0ctwice\x00",
5350 "Bread is Toasted twice"
5351 )
5352 }
5353
5354 #[test]
5355 fn test_offset_bug_5() {
5356 assert_magic_match_bin!(
5357 r"
53581 string TEST Bread is
5359>(5.b) indirect/r x
5360
53610 string twice Toasted
5362>0 use toasted_twice
5363
53640 name toasted_twice
5365>0 string twice
5366>>&1 byte 0x08 twice
5367 ",
5368 b"\x00TEST\x06twice\x00\x08",
5369 "Bread is Toasted twice"
5370 )
5371 }
5372
5373 #[test]
5374 fn test_bug_6() {
5375 assert_magic_match_bin!(
5379 r"
53801 string TEST Bread is toasted
5381>&0 use toasted
5382>>&0 default x but not burnt
5383
53840 name toasted
5385>1 string toasted
5386 ",
5387 b"\x00TEST\x06toasted",
5388 "Bread is toasted"
5389 )
5390 }
5391
5392 #[test]
5393 fn test_offset_bug_7() {
5394 assert_magic_match_bin!(
5398 r"
53991 string TEST Bread is
5400# offset computation is relative to
5401# rule start
5402>(5.b) use toasted
5403
54040 name toasted
5405>0 string toast Toasted
5406>>(6.b) use toasted_twice
5407
54080 name toasted_twice
5409>1 string x %s
5410 ",
5411 b"\x00TEST\x06toast\x00\x06twice\x00",
5412 "Bread is Toasted twice"
5413 );
5414 }
5415
5416 #[test]
5417 fn test_offset_bug_8() {
5418 assert_magic_match_bin!(
5426 r"
5427-2 uleshort 0
5428>&-22 string PK\005\006
5429>>0 string SIG found SIG at absolute start
5430 ",
5431 b"SIG\x00\x00\x00\x00\x00PK\x05\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
5432 "found SIG at absolute start"
5433 );
5434 }
5435
5436 #[test]
5437 fn test_offset_bug_9() {
5438 assert_magic_match_bin!(
5448 r"
5449-2 uleshort 0
5450>&-22 string PK\005\006
5451>>(0.b) use whichbyte
5452
54530 name whichbyte
5454>0 byte 0x03 found byte 0x03 (wrong: stale anchor)
5455>0 string YES found YES marker (correct: absolute)
5456 ",
5457 b"\x03\x00\x00YES\x00\x00PK\x05\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
5458 "found YES marker (correct: absolute)"
5459 );
5460 }
5461
5462 #[test]
5463 fn test_offset_bug_10() {
5464 assert_magic_match_bin!(
5474 r"
54750 string MAGIC
5476>0x10 use inner
5477>0x10 default x
5478>>&5 string TARGET found target via correct anchor
5479
54800 name inner
5481>0 search/1000 NEEDLE
5482 ",
5483 b"MAGIC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TARGET\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
5484 "found target via correct anchor"
5485 );
5486 }
5487
5488 #[test]
5489 fn test_offset_bug_11() {
5490 assert_magic_match_bin!(
5495 r"
54960 string MAGIC
5497>0x10 use inner
5498>0x10 clear x
5499>>&5 string TARGET found target via correct anchor
5500
55010 name inner
5502>0 search/1000 NEEDLE
5503 ",
5504 b"MAGIC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TARGET\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
5505 "found target via correct anchor"
5506 );
5507 }
5508
5509 #[test]
5510 fn test_message_parts() {
5511 let m = best_magic(
5512 r#"0 string/W #!/usr/bin/env\ python PYTHON"#,
5513 b"#!/usr/bin/env python",
5514 StreamKind::Text(TextEncoding::Ascii),
5515 )
5516 .unwrap();
5517
5518 assert!(m.message_parts().any(|p| p.eq_ignore_ascii_case("python")))
5519 }
5520
5521 #[test]
5522 fn test_magic_stream_kind_reflects_detection() {
5523 let mut db = MagicDb::new();
5524 db.load(parse_assert!("0\tsearch\t__NEVER_MATCH__\tnope\n"));
5525
5526 let mut ascii = BufReader::from_slice(b"hello world");
5527 let m = db.best_magic(&mut ascii, None).unwrap();
5528 assert_eq!(m.stream_kind(), Some(StreamKind::Text(TextEncoding::Ascii)));
5529
5530 let mut utf8 = BufReader::from_slice("héllo wörld".as_bytes());
5531 let m = db.best_magic(&mut utf8, None).unwrap();
5532 assert_eq!(m.stream_kind(), Some(StreamKind::Text(TextEncoding::Utf8)));
5533
5534 let mut binary = BufReader::from_slice(&[0x00u8, 0x01, 0x02, 0xff, 0xfe, 0x00, 0x00, 0x00]);
5535 let m = db.best_magic(&mut binary, None).unwrap();
5536 assert_eq!(m.stream_kind(), Some(StreamKind::Binary));
5537 }
5538
5539 #[test]
5546 fn test_stream_kind_rejects_ascii_with_forbidden_control_bytes() {
5547 assert_eq!(
5548 guess_stream_kind(b"MZ\x00\x00\x00\x00this is otherwise all printable ASCII"),
5549 StreamKind::Binary
5550 );
5551 }
5552
5553 #[test]
5554 fn test_load_bulk() {
5555 let mut db = MagicDb::new();
5556
5557 let rules = vec![
5558 parse_assert!("0 search test"),
5559 parse_assert!("0 search/24/s test"),
5560 parse_assert!("0 search/s/24 test"),
5561 ];
5562
5563 db.load_bulk(rules.into_iter());
5564 db.verify().unwrap();
5565 }
5566
5567 #[test]
5568 fn test_load_bulk_failure() {
5569 let mut db = MagicDb::new();
5570
5571 let rules = vec![parse_assert!(
5572 r#"
55730 search/s/24 test
5574>0 use test
5575"#
5576 )];
5577
5578 db.load_bulk(rules.into_iter());
5579 assert!(matches!(db.verify(), Err(Error::Verify(_, _, _))));
5580 }
5581
5582 fn csv_magic(content: &[u8]) -> Magic<'static> {
5585 best_magic(
5586 "0\tstring\t__NEVER_MATCH__\tnope\n",
5587 content,
5588 StreamKind::Text(TextEncoding::Utf8),
5589 )
5590 .unwrap()
5591 }
5592
5593 #[test]
5594 fn test_csv_two_rows_two_cols() {
5595 let m = csv_magic(b"a,b\n1,2\n");
5596 assert_eq!(m.mime_type(), "text/csv");
5597 }
5598
5599 #[test]
5600 fn test_csv_short_consistent_rows() {
5601 let m = csv_magic(b"a,b,c\n1,2,3\n4,5,6\n7,8,9\n10,11,12\n");
5602 assert_eq!(m.mime_type(), "text/csv");
5603 }
5604
5605 #[test]
5606 fn test_csv_many_rows_still_detected() {
5607 let body: &[u8] = b"a,b,c\n1,2,3\n4,5,6\n7,8,9\n10,11,12\n13,14,15\n16,17,18\n19,20,21\n22,23,24\n25,26,27\n28,29,30\n31,32,33\n";
5608 let m = csv_magic(body);
5609 assert_eq!(m.mime_type(), "text/csv");
5610 }
5611
5612 #[test]
5613 fn test_csv_single_field_rejected() {
5614 let m = csv_magic(b"hello\nworld\nfoo\n");
5615 assert_ne!(m.mime_type(), "text/csv");
5616 }
5617
5618 #[test]
5619 fn test_csv_ragged_columns_rejected() {
5620 let m = csv_magic(b"a,b,c\n1,2\n3,4,5\n");
5621 assert_ne!(m.mime_type(), "text/csv");
5622 }
5623
5624 #[test]
5631 fn test_string_parent_with_scalar_children_on_text_stream() {
5632 assert_magic_match_text!(
5633 r"
56340 string {\\rtf
5635>5 ubyte !0xAB
5636>>5 ubyte !0x5C Rich Text Format data
5637!:mime text/rtf
5638!:ext rtf
5639 ",
5640 b"{\\rtf1\\ansi\\ansicpg1252\nHello world}",
5641 "Rich Text Format data"
5642 );
5643 }
5644
5645 #[test]
5649 fn test_rtf_droid_skip_still_rejects() {
5650 assert_magic_not_match_text!(
5651 r"
56520 string {\\rtf
5653>5 ubyte !0xAB
5654>>5 ubyte !0x5C Rich Text Format data
5655 ",
5656 b"{\\rtf\xab......\n"
5657 );
5658 }
5659
5660 #[test]
5662 fn test_neq_test_on_unreadable_offset_matches() {
5663 let mut content = vec![0u8; 64];
5664 content[0] = b'M';
5665 content[1] = b'Z';
5666 content[0x3c..0x40].copy_from_slice(&0xFFFF_FFF0u32.to_le_bytes());
5668
5669 let m = best_magic(
5670 "0\tstring\tMZ\n>(0x3c.l)\tstring\t!PE\\0\\0\tMS-DOS executable\n",
5671 &content,
5672 StreamKind::Binary,
5673 )
5674 .unwrap();
5675 assert_eq!(m.message(), "MS-DOS executable");
5676 }
5677
5678 #[test]
5679 fn test_eq_test_on_unreadable_offset_does_not_match() {
5680 let mut content = vec![0u8; 64];
5681 content[0] = b'M';
5682 content[1] = b'Z';
5683 content[0x3c..0x40].copy_from_slice(&0xFFFF_FFF0u32.to_le_bytes());
5684
5685 let m = best_magic(
5686 "0\tstring\tMZ\n>(0x3c.l)\tstring\tPE\\0\\0\tPE executable\n",
5687 &content,
5688 StreamKind::Binary,
5689 )
5690 .unwrap();
5691 assert_eq!(m.message(), "data");
5692 }
5693}