{"record":{"id":"4fc5f0db37ea1fa6","repo":"ramensoftware/windhawk","slug":"value-contains-a-line-break","errorCode":null,"errorMessage":"value contains a line break","messagePattern":"value contains a line break","errorType":"validation","errorClass":"SettingsError","httpStatus":null,"severity":"error","filePath":"src/windhawk-core/windows/src/ini.rs","lineNumber":196,"sourceCode":"        if let Some(why) = unrepresentable_name(name) {\n            return Err(self.err(\"set\", 0, &format!(\"value name {name:?} {why}\")));\n        }\n        // `WritePrivateProfileStringW` takes the value as a NUL-terminated\n        // string, so an embedded NUL ends it: everything after it is dropped and\n        // the call still reports success. `escape_ini_value` cannot rescue that\n        // (the INI line format has no encoding for a NUL), so refuse the write\n        // rather than store a silently truncated value.\n        if value.contains('\\0') {\n            return Err(self.err(\"set\", 0, \"value contains a NUL character\"));\n        }\n        // An INI entry ends at the line break, so a value carrying one cannot\n        // be stored either: it would read back cut at the break, with the rest\n        // of it parsed as further lines of a file that also holds the mod's\n        // `[Mod]` config. Refused for the same reason as a NUL - the registry\n        // backend stores both halves of such a value faithfully, and a write\n        // that cannot keep the value is better refused than reported as done.\n        if value.contains(['\\r', '\\n']) {\n            return Err(self.err(\"set\", 0, \"value contains a line break\"));\n        }\n        let escaped = escape_ini_value(value);\n        write_profile(&self.file, &self.section, Some(name), Some(&escaped))\n    }\n\n    fn get_int(&self, name: &str) -> Result<Option<i32>, SettingsError> {\n        Ok(self.get_string(name)?.map(|s| parse_c_int(&s)))\n    }\n\n    fn set_int(&mut self, name: &str, value: i32) -> Result<(), SettingsError> {\n        // SetInt -> SetString(to_wstring(value)); a decimal triggers no\n        // escaping, but route through set_string for exactness.\n        self.set_string(name, &value.to_string())\n    }\n\n    fn get_binary(&self, name: &str) -> Result<Option<Vec<u8>>, SettingsError> {\n        match self.get_string(name)? {\n            None => Ok(None),","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/ramensoftware/windhawk/blob/61d99ed8e182e1af1b60109612b6763ad1b4b74e/src/windhawk-core/windows/src/ini.rs#L178-L214","documentation":"The INI settings backend refuses to store a string value containing CR or LF characters. An INI file is line-oriented, so a value with a line break would be silently cut at the break on read-back, with the remainder parsed as extra lines of the mod's [Mod] config file. The write is refused rather than reported as done, mirroring the NUL refusal and matching the registry backend's behavior for the same request.","triggerScenarios":"Calling set_string (directly or via set_int/set_binary wrappers) with a value containing '\\r' or '\\n', e.g. storing a multi-line note, a pasted block of text, or a string built from user input that includes newlines.","commonSituations":"Storing multi-line user comments or regex patterns with embedded newlines; serializing multi-line log excerpts into a mod setting; migrating values from a registry-backed store (where line breaks were stored) into the INI backend.","solutions":["Strip or normalize line breaks from the value before calling set_string (e.g. replace '\\r'/'\\n' with a space).","Encode the value (base64/hex) before storing and decode on read via get_binary/get_string.","Store multi-line data in a separate file or the registry backend, which preserves such bytes faithfully.","Use a delimiter-based encoding (e.g. join with a literal '\\n' escape) and split on read."],"exampleFix":"// before\nsettings.set_string(\"note\", user_text)?; // fails if user_text has newlines\n// after\nlet sanitized: String = user_text.chars().map(|c| if c == '\\n' || c == '\\r' { ' ' } else { c }).collect();\nsettings.set_string(\"note\", &sanitized)?;","handlingStrategy":"validation","validationCode":"fn assert_ini_safe(v: &str) -> Result<(), String> {\n    if v.contains(['\\r', '\\n']) { Err(\"value contains a line break\".into()) } else { Ok(()) }\n}","typeGuard":"fn is_ini_value_safe(v: &str) -> bool { !v.contains('\\r') && !v.contains('\\n') }","tryCatchPattern":"match settings.set_string(\"k\", &v) {\n    Err(e) if e.message().contains(\"line break\") => { /* sanitize and retry */ }\n    r => r?,\n}","preventionTips":["Sanitize all user-provided strings (strip/normalize CRLF) before storing.","Prefer set_binary (hex) for values that may contain control characters.","Add a unit test asserting settings values survive a set/get round-trip.","Document that INI values are single-line by design."],"tags":["ini","validation","line-break","settings"],"backgroundTag":"invalid-argument-value","analyzedSha":"61d99ed8e182e1af1b60109612b6763ad1b4b74e","analyzedAt":"2026-09-12T14:02:41.115Z","contentChangedAt":"2026-09-12T14:02:41.115Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}