collective

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

diff --git a/crates/meta/src/tags.rs b/crates/meta/src/tags.rs--- a/crates/meta/src/tags.rs+++ b/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");