mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
labels: reuse b06 pixelogic cap, real ctrm order test, setNumber fallback
This commit is contained in:
+9
-5
@@ -207,11 +207,8 @@ fn extract_title(xml_text: &str) -> Option<String> {
|
||||
/// the denominator (both forms appear in the wild).
|
||||
fn extract_disc_set(xml_text: &str) -> Option<(u32, u32)> {
|
||||
// Real discs carry <di:setNumber>; <di:discNumber> is kept as a fallback.
|
||||
let n = xml::text(xml_text, "setNumber")
|
||||
.or_else(|| xml::text(xml_text, "discNumber"))?
|
||||
.trim()
|
||||
.parse::<u32>()
|
||||
.ok()?;
|
||||
let num = |tag| xml::text(xml_text, tag).and_then(|t| t.trim().parse::<u32>().ok());
|
||||
let n = num("setNumber").or_else(|| num("discNumber"))?;
|
||||
let total = xml::text(xml_text, "numSets")
|
||||
.or_else(|| xml::text(xml_text, "numberOfSets"))?
|
||||
.trim()
|
||||
@@ -243,6 +240,13 @@ mod tests {
|
||||
assert_eq!(parse_bdmt_xml(xml).expect("parse").2, Some((2, 3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_numeric_set_number_falls_back_to_disc_number() {
|
||||
let xml = "<d><di:name>X</di:name><di:numSets>3</di:numSets>\
|
||||
<di:setNumber>two</di:setNumber><di:discNumber>2</di:discNumber></d>";
|
||||
assert_eq!(parse_bdmt_xml(xml).expect("parse").2, Some((2, 3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_title_and_description_are_capped() {
|
||||
let big = "é".repeat(5000);
|
||||
|
||||
+10
-5
@@ -242,11 +242,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn menu_base_tie_order_is_deterministic_by_prefix() {
|
||||
let labels = parse_props(
|
||||
"b_1.class=AudioButton\nb_1.streamNumber=2\nb_1.name=Second\n\
|
||||
a_1.class=AudioButton\na_1.streamNumber=2\na_1.name=First\n",
|
||||
);
|
||||
assert_eq!(labels[0].name, "First");
|
||||
// Inserted in reverse; many prefixes so hash order can't match by luck.
|
||||
let mut text = String::new();
|
||||
for i in (0..64).rev() {
|
||||
text.push_str(&format!(
|
||||
"p{i:02}.class=AudioButton\np{i:02}.streamNumber=2\np{i:02}.name=N{i:02}\n"
|
||||
));
|
||||
}
|
||||
let names: Vec<String> = parse_props(&text).into_iter().map(|l| l.name).collect();
|
||||
let want: Vec<String> = (0..64).map(|i| format!("N{i:02}")).collect();
|
||||
assert_eq!(names, want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -78,9 +78,6 @@ impl UnknownParts {
|
||||
}
|
||||
}
|
||||
|
||||
// Retained-run cap for the label blob (far above any real disc's token count).
|
||||
const MAX_STRINGS: usize = 65_536;
|
||||
|
||||
pub fn detect(_reader: &mut dyn SectorSource, udf: &UdfFs) -> bool {
|
||||
super::jar_file_exists(udf, "bluray_project.bin")
|
||||
}
|
||||
@@ -88,8 +85,8 @@ pub fn detect(_reader: &mut dyn SectorSource, udf: &UdfFs) -> bool {
|
||||
pub fn parse(reader: &mut dyn SectorSource, udf: &UdfFs) -> Option<ParseResult> {
|
||||
let data = super::read_jar_file(reader, udf, "bluray_project.bin")?;
|
||||
// The token grammar is `{lang3}_{codec?}_{purpose?}_{region?}_` so the
|
||||
// shortest meaningful run is 4 chars (lang + underscore). Capped: untrusted blob.
|
||||
let strings = text::extract_ascii_strings_capped(&data, 4, MAX_STRINGS);
|
||||
// shortest meaningful run is 4 chars (lang + underscore). The extractor caps run count (untrusted blob).
|
||||
let strings = text::extract_ascii_strings(&data, 4);
|
||||
|
||||
// Collects uncatalogued token components; if any, confidence downgrades to Medium
|
||||
// and they're reported once (see `UnknownParts`). Sequential parse, so a plain
|
||||
|
||||
+1
-21
@@ -13,24 +13,10 @@
|
||||
/// Non-printable bytes (including `\t`, `\n`, NUL) terminate the
|
||||
/// current run. Output strings are guaranteed valid UTF-8 (they're
|
||||
/// pure 7-bit ASCII). Strings shorter than `min_len` are dropped.
|
||||
#[cfg(test)]
|
||||
pub fn extract_ascii_strings(data: &[u8], min_len: usize) -> Vec<String> {
|
||||
extract_ascii_strings_capped(data, min_len, usize::MAX)
|
||||
}
|
||||
|
||||
/// [`extract_ascii_strings`], stopping after `max_strings` runs so untrusted
|
||||
/// input can't amplify into millions of owned `String`s.
|
||||
pub fn extract_ascii_strings_capped(
|
||||
data: &[u8],
|
||||
min_len: usize,
|
||||
max_strings: usize,
|
||||
) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
let mut current = String::new();
|
||||
for &b in data {
|
||||
if out.len() >= max_strings {
|
||||
return out;
|
||||
}
|
||||
if (0x20..=0x7E).contains(&b) {
|
||||
current.push(b as char);
|
||||
} else if !current.is_empty() && current.len() >= min_len {
|
||||
@@ -39,7 +25,7 @@ pub fn extract_ascii_strings_capped(
|
||||
current.clear();
|
||||
}
|
||||
}
|
||||
if !current.is_empty() && current.len() >= min_len && out.len() < max_strings {
|
||||
if !current.is_empty() && current.len() >= min_len {
|
||||
out.push(current);
|
||||
}
|
||||
out
|
||||
@@ -49,12 +35,6 @@ pub fn extract_ascii_strings_capped(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn capped_extraction_stops_at_the_cap() {
|
||||
let got = extract_ascii_strings_capped(b"abcd\0abcd\0abcd\0abcd", 4, 2);
|
||||
assert_eq!(got.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_simple_runs() {
|
||||
let got = extract_ascii_strings(b"hello\0world\0", 3);
|
||||
|
||||
Reference in New Issue
Block a user