From b638e7e5c004c4484354c2d45048f9a0410f0f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Wed, 30 Sep 2026 16:05:45 +0200 Subject: [PATCH] feat: add quirk_body_fld_param_value_nil Refs: pimalaya/himalaya#649 --- imap-codec/Cargo.toml | 3 ++ imap-codec/src/body.rs | 64 ++++++++++++++++++++++++++++++++++++++++-- justfile | 3 +- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/imap-codec/Cargo.toml b/imap-codec/Cargo.toml index c887551e..188302b4 100644 --- a/imap-codec/Cargo.toml +++ b/imap-codec/Cargo.toml @@ -35,6 +35,7 @@ quirk = [ "quirk_spaces_between_addresses", "quirk_empty_continue_req", "quirk_body_fld_enc_nil_to_empty", + "quirk_body_fld_param_value_nil", "quirk_always_normalize_sequence_sets", ] # Make `\r` in `\r\n` optional. @@ -61,6 +62,8 @@ quirk_trailing_space_search = [] quirk_empty_continue_req = [] # Encode NIL `body-fld-enc` as empty string. quirk_body_fld_enc_nil_to_empty = [] +# Encode NIL `body-fld-param` value as empty string. +quirk_body_fld_param_value_nil = [] # Always normalize sequence sets during encoding quirk_always_normalize_sequence_sets = [] diff --git a/imap-codec/src/body.rs b/imap-codec/src/body.rs index b78f51cc..7a217a05 100644 --- a/imap-codec/src/body.rs +++ b/imap-codec/src/body.rs @@ -217,13 +217,25 @@ pub(crate) fn body_fields(input: &[u8]) -> IMAPResult<&[u8], BasicFields> { /// ")" / nil /// ``` pub(crate) fn body_fld_param(input: &[u8]) -> IMAPResult<&[u8], Vec<(IString, IString)>> { + #[cfg(not(feature = "quirk_body_fld_param_value_nil"))] + let value = string; + + #[cfg(feature = "quirk_body_fld_param_value_nil")] + let value = alt(( + string, + map(nil, |_| { + log::warn!("Rectified NIL body-fld-param value to empty string"); + IString::try_from("").unwrap() + }), + )); + let mut parser = alt(( delimited( tag(b"("), // Quirk: See https://github.com/emersion/go-imap/issues/557 separated_list0( sp, - map(tuple((string, sp, string)), |(key, _, value)| (key, value)), + map(tuple((string, sp, value)), |(key, _, value)| (key, value)), ), tag(b")"), ), @@ -594,7 +606,11 @@ mod tests { }; use super::*; - use crate::testing::{kat_inverse_response, known_answer_test_encode}; + use crate::{ + ResponseCodec, + decode::Decoder, + testing::{kat_inverse_response, known_answer_test_encode}, + }; #[test] fn test_parse_media_basic() { @@ -790,4 +806,48 @@ mod tests { assert_eq!(body_fld_octets(b"-999999)").unwrap().1, 0); } } + + #[test] + fn test_body_fld_param_nil_quirk() { + let input = b"(\"boundary\" NIL \"charset\" \"utf-8\"))"; + + #[cfg(not(feature = "quirk_body_fld_param_value_nil"))] + assert!(body_fld_param(input).is_err()); + + #[cfg(feature = "quirk_body_fld_param_value_nil")] + assert_eq!( + body_fld_param(input).unwrap(), + ( + &b")"[..], + vec![ + ( + IString::try_from("boundary").unwrap(), + IString::try_from("").unwrap(), + ), + ( + IString::try_from("charset").unwrap(), + IString::try_from("utf-8").unwrap(), + ), + ] + ) + ); + } + + /// Reproducer from a Mail.ru `FETCH` response. + #[test] + fn test_body_fld_param_nil_quirk_mailru_fetch() { + let input = b"* 2010 FETCH (UID 154395 FLAGS (\\Seen) ENVELOPE (\"Fri, 17 Apr 2026 14:15:28 +0000\" \"subject\" ((\"name\" NIL \"local\" \"example.org\")) NIL NIL ((\"\" NIL \"local\" \"example.org\")) NIL NIL NIL \"\") BODYSTRUCTURE ((\"text\" \"plain\" (\"charset\" \"US-ASCII\") NIL NIL \"8bit\" 7078 0 NIL NIL NIL NIL)(\"text\" \"html\" (\"charset\" \"utf-8\") NIL NIL \"quoted-printable\" 37865 0 NIL NIL NIL NIL) \"alternative\" (\"boundary\" NIL)))\r\n"; + + let got = ResponseCodec::default().decode(input); + + #[cfg(not(feature = "quirk_body_fld_param_value_nil"))] + assert!(got.is_err()); + + #[cfg(feature = "quirk_body_fld_param_value_nil")] + { + let (remaining, response) = got.unwrap(); + assert!(remaining.is_empty()); + assert!(matches!(response, Response::Data(Data::Fetch { .. }))); + } + } } diff --git a/justfile b/justfile index 9e7ae902..6d2a5620 100644 --- a/justfile +++ b/justfile @@ -80,7 +80,8 @@ cargo_hack mode: install_cargo_hack quirk_trailing_space_status,\ quirk_spaces_between_addresses,\ quirk_empty_continue_req,\ - quirk_body_fld_enc_nil_to_empty\ + quirk_body_fld_enc_nil_to_empty,\ + quirk_body_fld_param_value_nil\ {{ mode }} cargo hack check -p imap-types \ --no-dev-deps \