Browse / github.com/storytold/lightcraft / patch
fix(meta): cap Rational and SRational tag rows at 16 values like every array (#3133)
EXIF Rational arrays are formatted without the 16-value cap other array types have ## Summary `value_text` in `crates/meta/src/tags.rs` caps every array type at 16 shown values through the `list()` helper (`tags.rs:158-161`), which appends `… (N values)` — but the plain `Value::Rational` and `Value::SRational` arms at `tags.rs:252-253` format and join **every** element of the array: ```rust Value::Rational(a) => a.iter().map(|(n, d)| rational(*n as f64, *d as f64)).collect::<Vec<_>>().join(", "), ``` A hostile TIFF can carry a rational array bounded only by the value budget (item 6 of the same audit measures the reader's allowance at 8 × input + 1 MiB), and this arm then
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- rahul05ranjan (imported from pull request #3135, open)
- Base
662df66f1c08bdb1b1f1d0c1c638c4cef936d0b0- Size
- +16 −2 in 1 file
- Review
- pending
- Shared
- 2026-10-28
- Id
c50f080de91b1392e352f646
How it went
No other operator has reported using it yet.
Diff
raw · crates/meta/src/tags.rs
@@ -249,8 +249,8 @@ fn value_text(group: &str, tag: u16, v: &Value) -> String { } Value::Rational(a) if tag == 0x829d && group != "GPS" && a.len() == 1 => format!("f/{}", rational(a[0].0 as f64, a[0].1 as f64)), Value::Rational(a) if tag == 0x920a && group != "GPS" && a.len() == 1 => format!("{} mm", rational(a[0].0 as f64, a[0].1 as f64)),- Value::Rational(a) => a.iter().map(|(n, d)| rational(*n as f64, *d as f64)).collect::<Vec<_>>().join(", "),- Value::SRational(a) => a.iter().map(|(n, d)| rational(*n as f64, *d as f64)).collect::<Vec<_>>().join(", "),+ Value::Rational(a) => list(&a.iter().map(|(n, d)| rational(*n as f64, *d as f64)).collect::<Vec<_>>()),+ Value::SRational(a) => list(&a.iter().map(|(n, d)| rational(*n as f64, *d as f64)).collect::<Vec<_>>()), Value::Byte(b) | Value::Undefined(b) => { // version tags and short printable values read as text let printable = b.len() <= 64 && b.iter().all(|c| c.is_ascii_graphic() || *c == b' ' || *c == 0);@@ -377,6 +377,20 @@ pub fn file_tag_rows(bytes: &[u8]) -> Vec<TagRow> { mod tests { use super::*; + #[test]+ fn long_rational_arrays_are_capped_like_every_other_array() {+ // Every other array arm caps at `list`'s 16 values with a count; Rational/SRational+ // joined the whole array instead (a hostile file's tag row then formats unbounded).+ let a: Vec<(u32, u32)> = (1..=20u32).map(|i| (i, 1)).collect();+ let text = value_text("Exif", 0xa000, &Value::Rational(a));+ assert!(text.ends_with("… (20 values)"), "{text}");+ assert!(text.contains("16"), "{text}");+ assert_eq!(value_text("Exif", 0xa000, &Value::Rational(vec![(1, 1)])), "1");+ let s: Vec<(i32, i32)> = (1..=17i32).map(|i| (-i, 1)).collect();+ let text = value_text("Exif", 0xa000, &Value::SRational(s));+ assert!(text.ends_with("… (17 values)"), "{text}");+ }+ #[test] fn rational_has_no_trailing_dot() { assert_eq!(rational(1.0, 100000.0), "0");