diff --git a/imptune/services/inf_parser.py b/imptune/services/inf_parser.py index 8018e3b..545d6c6 100644 --- a/imptune/services/inf_parser.py +++ b/imptune/services/inf_parser.py @@ -44,6 +44,33 @@ def _detect_encoding(raw: bytes) -> str: return "cp1252" # ANSI / Windows-1252 safe fallback +def _neutralize_bare_lines(inf_text: str) -> str: + """Rewrite INF lines that lack ``=`` so configparser can ingest the file. + + Real vendor INFs contain sections like ``[SourceDisksFiles]`` or copy-list + sections whose entries are bare filenames (no key/value). configparser is + strict and aborts on those. We only care about ``key = value`` lines for + DriverDesc extraction, so it's safe to convert each bare payload line into + a synthetic ``__bare_N = `` entry. + """ + out: list[str] = [] + counter = 0 + for line in inf_text.splitlines(): + stripped = line.strip() + if ( + not stripped + or stripped.startswith(";") + or stripped.startswith("#") + or stripped.startswith("[") + or "=" in stripped + ): + out.append(line) + continue + counter += 1 + out.append(f"__bare_{counter} = {stripped}") + return "\n".join(out) + + def _resolve_tokens(value: str, strings: dict[str, str]) -> str: """Expand %TOKEN% placeholders using the [Strings] section lookup dict. @@ -92,7 +119,7 @@ def parse_inf(inf_text: str, inf_filename: str, zip_names: list[str]) -> ParsedI # into "acme superprint 9000". We disable that behaviour here and manually lowercase # only when building the [Strings] lookup dict. parser.optionxform = str # type: ignore[assignment] - parser.read_string(inf_text) + parser.read_string(_neutralize_bare_lines(inf_text)) # Build strings lookup with LOWERCASED keys (case-insensitive token resolution). # INF token references are case-insensitive per WDK spec. @@ -144,6 +171,8 @@ def parse_inf(inf_text: str, inf_filename: str, zip_names: list[str]) -> ParsedI # Each option key in a Models section is a device-description (DriverDesc) for key, _val in parser.items(section): + if key.startswith("__bare_"): + continue resolved = _resolve_tokens(key, strings) # Skip empty, purely numeric, or clearly non-driver-name entries if resolved and not resolved.isdigit(): diff --git a/tests/test_inf_parser.py b/tests/test_inf_parser.py index faefee5..1007227 100644 --- a/tests/test_inf_parser.py +++ b/tests/test_inf_parser.py @@ -279,3 +279,40 @@ def test_empty_models_section(): """INF with empty Models section returns empty driver_names list.""" result = parse_inf(EMPTY_MODELS_INF, "empty.inf", []) assert result.driver_names == [] + + +# --------------------------------------------------------------------------- +# parse_inf – tolerates bare-line sections (real-world Ricoh oemsetup.inf) +# --------------------------------------------------------------------------- + +BARE_LINE_INF = """\ +[Version] +Signature="$Windows NT$" + +[Manufacturer] +%MFG%=Models,NTamd64 + +[Models.NTamd64] +%RICOH_DRIVER%=Install,{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC} + +[SourceDisksFiles] +ricu18ui.dll,ricu18ui.dl_ +ricu18ui.irj +ricu18ui.rdj +ricu18gl.dll,ricu18gl.dl_ +RD01Kd64.dll,RD01Kd64.dl_,,0x00000020 + +[Strings] +MFG="Ricoh" +RICOH_DRIVER="Ricoh PCL6 Universal" +""" + + +def test_bare_line_sections_do_not_raise(): + """Real INFs (e.g. Ricoh oemsetup.inf) include [SourceDisksFiles] entries + with bare filename lines and no '=' — parse_inf must not raise and must + still extract DriverDesc from the Models section.""" + result = parse_inf(BARE_LINE_INF, "oemsetup.inf", ["oemsetup.inf"]) + assert "Ricoh PCL6 Universal" in result.driver_names + # Synthetic __bare_N keys must not leak into driver_names + assert not any(n.startswith("__bare_") for n in result.driver_names)