This repository has no description
0

Configure Feed

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

core / knot2 / crates / knot-lfs / src / pointer.rs
3.0 kB 88 lines
1use crate::{ClaimedSize, LfsOid}; 2 3pub const POINTER_MAX_BYTES: u64 = 1024; 4 5const SPEC_URLS: [&str; 2] = [ 6 "https://git-lfs.github.com/spec/v1", 7 "https://hawser.github.com/spec/v1", 8]; 9 10#[derive(Debug, Clone, PartialEq, Eq)] 11pub struct LfsPointer { 12 pub oid: LfsOid, 13 pub size: ClaimedSize, 14} 15 16pub fn parse_pointer(blob: &[u8]) -> Option<LfsPointer> { 17 if blob.len() as u64 > POINTER_MAX_BYTES { 18 return None; 19 } 20 let text = std::str::from_utf8(blob).ok()?; 21 let mut lines = text.lines(); 22 let spec = lines.next()?.strip_prefix("version ")?; 23 SPEC_URLS.contains(&spec).then_some(())?; 24 let fields: Vec<(&str, &str)> = lines.filter_map(|line| line.split_once(' ')).collect(); 25 let value_of = |key: &str| { 26 fields 27 .iter() 28 .find_map(|(name, value)| (*name == key).then_some(*value)) 29 }; 30 let oid = LfsOid::new(value_of("oid")?.strip_prefix("sha256:")?).ok()?; 31 let size = ClaimedSize::new(value_of("size")?.parse().ok()?); 32 Some(LfsPointer { oid, size }) 33} 34 35#[cfg(test)] 36mod tests { 37 use super::*; 38 39 const OID: &str = "6c17f2007cbe934aee6e309b28b2fba3c119d98be6ea4156da3aa3173456ad16"; 40 41 fn pointer_text() -> String { 42 format!("version https://git-lfs.github.com/spec/v1\noid sha256:{OID}\nsize 12345\n") 43 } 44 45 #[test] 46 fn valid_variants_beyond_the_canonical_form_still_parse() { 47 let hawser = pointer_text().replace("git-lfs.github.com", "hawser.github.com"); 48 assert!( 49 parse_pointer(hawser.as_bytes()).is_some(), 50 "the legacy hawser spec still counts" 51 ); 52 let extra = format!( 53 "version https://git-lfs.github.com/spec/v1\nname media/clip.mp4\noid sha256:{OID}\nsize 7\n" 54 ); 55 assert!( 56 parse_pointer(extra.as_bytes()).is_some(), 57 "extra sorted keys are tolerated" 58 ); 59 } 60 61 #[test] 62 fn non_pointers_are_rejected() { 63 let cases: Vec<Vec<u8>> = vec![ 64 b"".to_vec(), 65 b"plain source file\n".to_vec(), 66 format!("oid sha256:{OID}\nsize 7\n").into_bytes(), 67 format!("version https://oyster.cafe/spec/v1\noid sha256:{OID}\nsize 7\n").into_bytes(), 68 pointer_text().replace("sha256:", "sha512:").into_bytes(), 69 pointer_text() 70 .replace("size 12345", "size lots") 71 .into_bytes(), 72 format!("version https://git-lfs.github.com/spec/v1\noid sha256:{OID}\n").into_bytes(), 73 b"version https://git-lfs.github.com/spec/v1\nsize 7\n".to_vec(), 74 [pointer_text().into_bytes(), vec![0xff, 0xfe]].concat(), 75 [ 76 pointer_text().into_bytes(), 77 vec![b' '; POINTER_MAX_BYTES as usize], 78 ] 79 .concat(), 80 ]; 81 cases.iter().enumerate().for_each(|(index, blob)| { 82 assert!( 83 parse_pointer(blob).is_none(), 84 "case {index} parsed as a pointer" 85 ); 86 }); 87 } 88}