Repository: lettre/lettre (language: rust)

Issue:
To: addresses of form "lastname, firstname <name@addr.com>" cause MissingTo error
When creating a message using Message::builder() and adding multiple To: headers of the format `lastname, firstname <name@addr.com>`, an `Error::MissingTo` error result is returned.

NOTE: This is different from #672 in that the issue is in how the address is parsed; in #672 the address is provided as
```
(Some(r#""Doe, Jóhn""#.parse().unwrap()),
        "john.doe@example.com".parse().unwrap())
```
but here I am using str::parse(), which is failing to detect the comma and MIME-encode the name portion.  I suspect that a change in Mailbox::from_str() would be required.

This code snippit will demonstrate the issue:

```
use lettre::Message;

fn main() {
    let builder = Message::builder()
        .from("test@foo.com".parse().unwrap())
        .to("ln1, fn1 <addr1@foo.com>".parse().unwrap())
        .to("ln2, fn2 <addr1@foo.com>".parse().unwrap())
        .to("ln3, fn3 <addr1@foo.com>".parse().unwrap());

    println!("builder is: {:#?}", builder);
    println!(
        "result after calling body(): {:#?}",
        builder.body(String::from("Be happy!"))
    );
}
```

Running this produces:
```
builder is: MessageBuilder {
    headers: Headers {
        headers: [
            (
                HeaderName(
                    "From",
                ),
                "test@foo.com",
            ),
            (
                HeaderName(
                    "To",
                ),
                "ln3, fn3 <addr1@foo.com>",
            ),
        ],
    },
    envelope: None,
}
result after calling body(): Err(
    MissingTo,
)

```
If I remove the commas between the last/first names, I get the (much more reasonable):

```
builder is: MessageBuilder {
    headers: Headers {
        headers: [
            (
                HeaderName(
                    "From",
                ),
                "test@foo.com",
            ),
            (
                HeaderName(
                    "To",
                ),
                "ln1 fn1 <addr1@foo.com>, ln2 fn2 <addr1@foo.com>, ln3 fn3 <addr1@foo.com>",
            ),
        ],
    },
    envelope: None,
}
```

I'm not 100% certain what the "correct" behavior would be, but I would expect it to be something like:
```
            (
                HeaderName(
                    "To",
                ),
                ""ln1, fn1" <addr1@foo.com>, "ln2, fn2" <addr1@foo.com>, "ln3, fn3" <addr1@foo.com>",
            ),

```

This happens with Lettre version 0.10.0-rc.3


Discussion / hints:
I looked into this too and it appears to be that in the builder lettre mushes headers into a comma separated list. Then when it parses them back it does so simply by splitting on comma. It seems that there are a lot of assumptions baked in here and a quick look at the code gave no obvious fix.
https://github.com/lettre/lettre/issues/698 was not enough to fix this. As a hack we could update the code to encode the , using base64 as if it was a disallowed character. IDK if this is a good idea but it would be a quick hack.
I'm thinking of merging #670 (did I miss something for not having merged it earlier?) and then write an hacky solution dependent on https://github.com/lettre/email-encoding while we figure out a clean solution for #693 
I don't think #670 is related but I would be happy to merge it. I'll leave a comment clarifying the state there.

I think a hacky fix would be nice. I am working around this my my code by running `name.replace(',', "")` but that is only really great for my use case. We shouldn't be "corrupting" user's requests silently.
> I don't think #670 is related but I would be happy to merge it. I'll leave a comment clarifying the state there.

It's mainly to avoid the headache around merge conflicts and internal API changes

Fix patch:
diff --git a/src/message/header/mailbox.rs b/src/message/header/mailbox.rs
index 9c17e1392..05fa41ac0 100644
--- a/src/message/header/mailbox.rs
+++ b/src/message/header/mailbox.rs
@@ -296,4 +296,31 @@ mod test {
 
         assert_eq!(headers.get::<From>(), Some(From(from.into())));
     }
+
+    #[test]
+    fn parse_multi_with_name_containing_comma() {
+        let from: Vec<Mailbox> = vec![
+            "Test, test <1@example.com>".parse().unwrap(),
+            "Test2, test2 <2@example.com>".parse().unwrap(),
+        ];
+
+        let mut headers = Headers::new();
+        headers.insert_raw(HeaderValue::new(
+            HeaderName::new_from_ascii_str("From"),
+            "Test, test <1@example.com>, Test2, test2 <2@example.com>".to_string(),
+        ));
+
+        assert_eq!(headers.get::<From>(), Some(From(from.into())));
+    }
+
+    #[test]
+    fn parse_multi_with_name_containing_comma_last_broken() {
+        let mut headers = Headers::new();
+        headers.insert_raw(HeaderValue::new(
+            HeaderName::new_from_ascii_str("From"),
+            "Test, test <1@example.com>, Test2, test2".to_string(),
+        ));
+
+        assert_eq!(headers.get::<From>(), None);
+    }
 }
diff --git a/src/message/mailbox/types.rs b/src/message/mailbox/types.rs
index abbf1db58..64b1952b3 100644
--- a/src/message/mailbox/types.rs
+++ b/src/message/mailbox/types.rs
@@ -352,11 +352,37 @@ impl Display for Mailboxes {
 impl FromStr for Mailboxes {
     type Err = AddressError;
 
-    fn from_str(src: &str) -> Result<Self, Self::Err> {
-        src.split(',')
-            .map(|m| m.trim().parse())
-            .collect::<Result<Vec<_>, _>>()
-            .map(Mailboxes)
+    fn from_str(mut src: &str) -> Result<Self, Self::Err> {
+        let mut mailboxes = Vec::new();
+
+        if !src.is_empty() {
+            // n-1 elements
+            let mut skip = 0;
+            while let Some(i) = src[skip..].find(',') {
+                let left = &src[..skip + i];
+
+                match left.trim().parse() {
+                    Ok(mailbox) => {
+                        mailboxes.push(mailbox);
+
+                        src = &src[left.len() + ",".len()..];
+                        skip = 0;
+                    }
+                    Err(AddressError::MissingParts) => {
+                        skip = left.len() + ",".len();
+                    }
+                    Err(err) => {
+                        return Err(err);
+                    }
+                }
+            }
+
+            // last element
+            let mailbox = src.trim().parse()?;
+            mailboxes.push(mailbox);
+        }
+
+        Ok(Mailboxes(mailboxes))
     }
 }
 


Test patch: