{"record":{"id":"6818fd65086496c1","repo":"stalwartlabs/stalwart","slug":"invalid-line-raw-response","errorCode":null,"errorMessage":"Invalid line: {raw_response}","messagePattern":"Invalid line: (.+?)","errorType":"exception","errorClass":"io::Error(InvalidData)","httpStatus":null,"severity":"warning","filePath":"crates/spam-filter/src/modules/pyzor.rs","lineNumber":108,"sourceCode":"    let socket = UdpSocket::bind(\"0.0.0.0:0\").await?;\n    tokio::time::timeout(timeout, socket.send_to(message.as_bytes(), addr)).await??;\n\n    let mut buffer = vec![0u8; 1024];\n    let (size, _) = tokio::time::timeout(timeout, socket.recv_from(&mut buffer)).await??;\n\n    let raw_response = std::str::from_utf8(&buffer[..size])\n        .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))?;\n    let mut response = PyzorResponse {\n        code: u32::MAX,\n        count: u64::MAX,\n        wl_count: u64::MAX,\n    };\n\n    for line in raw_response.lines() {\n        if let Some((k, v)) = line.split_once(':') {\n            if k.eq_ignore_ascii_case(\"code\") {\n                response.code = v.trim().parse().map_err(|_| {\n                    std::io::Error::new(\n                        std::io::ErrorKind::InvalidData,\n                        format!(\"Invalid line: {raw_response}\"),\n                    )\n                })?;\n            } else if k.eq_ignore_ascii_case(\"count\") {\n                response.count = v.trim().parse().map_err(|_| {\n                    std::io::Error::new(\n                        std::io::ErrorKind::InvalidData,\n                        format!(\"Invalid line: {raw_response}\"),\n                    )\n                })?;\n            } else if k.eq_ignore_ascii_case(\"wl-count\") {\n                response.wl_count = v.trim().parse().map_err(|_| {\n                    std::io::Error::new(\n                        std::io::ErrorKind::InvalidData,\n                        format!(\"Invalid line: {raw_response}\"),\n                    )\n                })?;","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/stalwartlabs/stalwart/blob/e96200385781a6a9995a8b839ac27d6c75a983ee/crates/spam-filter/src/modules/pyzor.rs#L90-L126","documentation":"Raised while parsing a Pyzor UDP response when the value of the 'code' field cannot be parsed as a u32. The parser in pyzor_send_message splits each line on ':' and wraps parse failures in an InvalidData io::Error that embeds the entire raw response. It indicates the peer did not answer with a well-formed Pyzor key:value reply.","triggerScenarios":"pyzor_send_message receives a UDP datagram containing a 'code:' line whose value is not a valid u32 (empty value, non-numeric text, negative number, or an HTML/human-readable error message). Called via pyzor_check.","commonSituations":"config.address pointing at a wrong host/port where a different service answers; Pyzor server returning an error page instead of protocol fields; captive portal or proxy intercepting UDP traffic; corrupted or truncated datagrams.","solutions":["Verify the configured Pyzor server address (config.address) is a genuine, reachable Pyzor server","Probe the server manually (pyzor check) and inspect the raw reply it returns","Log the raw_response embedded in this error's message to see the malformed payload","Check for proxy/captive-portal interference with UDP traffic","Switch to a healthy public Pyzor mirror or upgrade the server"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// validate a pyzor reply before trusting it\nfn is_valid_pyzor_response(raw: &str) -> bool {\n    raw.lines().any(|l| {\n        l.split_once(':')\n            .map(|(k, v)| k.eq_ignore_ascii_case(\"code\") && v.trim().parse::<u32>().is_ok())\n            .unwrap_or(false)\n    })\n}","typeGuard":null,"tryCatchPattern":"match pyzor_check(&config, &message).await {\n    Ok(score) => score,\n    Err(e) if e.is::<trc::SpamEvent>() && e.reason_is_parse_failure() => {\n        tracing::warn!(\"pyzor reply unparseable; skipping pyzor score\");\n        Default::default()\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Confirm the Pyzor server address/port resolves to a genuine Pyzor server before enabling the module","Health-check the Pyzor server periodically and disable scoring when replies are malformed","Log raw responses (already embedded in the error) to spot proxies or error pages","Keep the server address out of hostile/intercepted networks (no captive portals)"],"tags":["parsing","udp","spam-filter","pyzor"],"backgroundTag":"invalid-argument-format","analyzedSha":"e96200385781a6a9995a8b839ac27d6c75a983ee","analyzedAt":"2026-09-06T22:07:17.982Z","contentChangedAt":"2026-09-06T22:07:17.982Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}