├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── src ├── lib.rs ├── rfc5322 │ ├── email_address.rs │ ├── error.rs │ ├── headers.rs │ ├── mod.rs │ └── types.rs └── tests.rs └── tests └── unicode.rs.fails /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "email-format" 3 | version = "0.8.1" 4 | description = "Email message format (parser and data structure)" 5 | authors = ["Mike Dilger "] 6 | readme = "README.md" 7 | repository = "https://github.com/mikedilger/email-format" 8 | documentation = "https://mikedilger.github.io/email-format" 9 | license = "MIT OR Apache-2.0" 10 | keywords = [ "email", "rfc5322" ] 11 | 12 | [features] 13 | default = [ ] 14 | 15 | [dependencies] 16 | buf-read-ext = { version = "0.3", default-features = false } 17 | time = { version = "0.1", optional = true } 18 | chrono = { version = "0.4", optional = true } 19 | lettre = { version = ">=0.9.2, <0.10", optional = true } 20 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Michael Dilger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # email-format 2 | 3 | "Internet Message Format" meticulously implemented for email construction 4 | and validation, as defined in RFC 5322 and other RFCs. 5 | 6 | [Documentation (released)](https://docs.rs/email-format) 7 | 8 | [Documentation (master)](https://mikedilger.github.io/email-format) 9 | 10 | ## Features 11 | 12 | * **Parses** bytes into an Email structure (represented internally as a tree) and validates 13 | RFC 5322 "Internet Message Format" compliance. 14 | * Extensive RFC 5322 Parser/validator: If you generate an email using this crate, you are 15 | guaranteed that will be a valid RFC 5322 formatted email, or else you will get a ParseError. 16 | The only exception that I am currently aware of is that lines can be longer than 998 17 | characters (see issue #3). 18 | * **Streams** an Email structure back into bytes. 19 | * **Generates and modifies** Email structures using functions like `set_subject()`, 20 | `get_from()`, `clear_reply_to()`, `add_optional_field()`, etc. 21 | * Integrates with [lettre](https://github.com/lettre/lettre) 22 | (enable optional feature `lettre`) 23 | and [mailstrom](https://github.com/mikedilger/mailstrom) 24 | * Supports [chrono](https://github.com/chronotope/chrono) `DateTime` 25 | and [time](https://github.com/rust-lang/time) `Tm` for setting the `Date` field 26 | (enable optional feature `chrono` and/or `time`) 27 | 28 | ## Limitations 29 | 30 | * Valid emails are 7-bit ASCII, and this crate requires all content to be 7-bit ASCII. 31 | The proper way to send richer content is to use a transfer encoding, and to set a 32 | `content-transfer-encoding` header. We don't yet offer any help in this regard, beyond 33 | the ability to add_optional_field(). You'll have to manage the encoding yourself. 34 | We plan to add convenience functions for this eventually (see issue #19) 35 | * Obsolete email formats are not implemented in the parser. Therefore, it is not sufficient 36 | for parsing inbound emails if you need to recognize formats that were obsoleted in 2008. 37 | 38 | ## Plans (not yet implemented) 39 | 40 | * Support for content-transfer-encodings (unicode via Quoted Printable or Base64 or otherwise) 41 | * Support for email headers defined in other RFCs: 42 | * Support for RFC 6854 (updated From and Sender syntax) 43 | * Support for all headers registered at IANA (http://www.iana.org/assignments/message-headers/message-headers.xhtml) 44 | * Support for MIME (RFC 2045, RFC 4021, RFC 2231, RFC 6352) using 45 | [mime_multipart](https://github.com/mikedilger/mime-multipart) 46 | * Support for streaming of MIME parts from disk. 47 | 48 | ## History 49 | 50 | This project was inspired by the earlier [email](https://github.com/niax/rust-email) crate, 51 | but was reworked from scratch due to a number of significant differences in design, 52 | implementation and interface. 53 | 54 | ## License 55 | 56 | Licensed under either of 57 | 58 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 59 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 60 | 61 | at your option. 62 | 63 | ### Contribution 64 | 65 | Unless you explicitly state otherwise, any contribution intentionally submitted 66 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall 67 | be dual licensed as above, without any additional terms or conditions. 68 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # email-format 2 | //! 3 | //! This crate allows you to construct email messages in a way that assures that 4 | //! they are compliant with relevant email standards (especially RFC 5322). Invalid 5 | //! data submitted will return a ParseError. 6 | //! 7 | //! The main structure to work with is `Email`. It has many functions to set or add 8 | //! headers and to set the body. All of these will accept an `&str` or `&[u8]` argument 9 | //! and attempt to parse it. These setters return a `Result<(), ParseError>` as the 10 | //! parse may fail. 11 | //! 12 | //! ## Composition 13 | //! 14 | //! You can compose an email like this: 15 | //! 16 | //! ``` 17 | //! use email_format::Email; 18 | //! 19 | //! let mut email = Email::new( 20 | //! "myself@mydomain.com", // "From:" 21 | //! "Wed, 5 Jan 2015 15:13:05 +1300" // "Date:" 22 | //! ).unwrap(); 23 | //! email.set_sender("from_myself@mydomain.com").unwrap(); 24 | //! email.set_reply_to("My Mailer ").unwrap(); 25 | //! email.set_to("You ").unwrap(); 26 | //! email.set_cc("Our Friend ").unwrap(); 27 | //! email.set_message_id("").unwrap(); 28 | //! email.set_subject("Hello Friend").unwrap(); 29 | //! email.set_body("Good to hear from you.\r\n\ 30 | //! I wish you the best.\r\n\ 31 | //! \r\n\ 32 | //! Your Friend").unwrap(); 33 | //! 34 | //! println!("{}", email); 35 | //! ``` 36 | //! 37 | //! This outputs: 38 | //! 39 | //! ```text 40 | //! Date:Wed, 5 Jan 2015 15:13:05 +1300 41 | //! From:myself@mydomain.com 42 | //! Sender:from_myself@mydomain.com 43 | //! Reply-To:My Mailer 44 | //! To:You 45 | //! Cc:Our Friend 46 | //! Message-ID: 47 | //! Subject:Hello Friend 48 | //! 49 | //! Good to hear from you. 50 | //! I wish you the best. 51 | //! 52 | //! Your Friend 53 | //! ``` 54 | //! 55 | //! ## Parsing 56 | //! 57 | //! The following code will parse the input bytes (or panic if the parse 58 | //! failed), and leave any trailing bytes in the remainder, which should 59 | //! be checked to verify it is empty. 60 | //! 61 | //! ``` 62 | //! use email_format::Email; 63 | //! use email_format::rfc5322::Parsable; 64 | //! 65 | //! let input = "Date: Wed, 5 Jan 2015 15:13:05 +1300\r\n\ 66 | //! From: myself@mydomain.com\r\n\ 67 | //! Sender: from_myself@mydomain.com\r\n\ 68 | //! My-Crazy-Field: this is my field\r\n\ 69 | //! Subject: Hello Friend\r\n\ 70 | //! \r\n\ 71 | //! Good to hear from you.\r\n\ 72 | //! I wish you the best.\r\n\ 73 | //! \r\n\ 74 | //! Your Friend".as_bytes(); 75 | //! let (mut email, remainder) = Email::parse(&input).unwrap(); 76 | //! assert_eq!(remainder.len(), 0); 77 | //! ``` 78 | //! 79 | //! ## Usage with lettre and/or mailstrom 80 | //! 81 | //! If compiled with the `lettre` feature, you can generate a `SendableEmail` 82 | //! like this, and then use the `lettre` crate (or `mailstrom`) to send it. 83 | //! 84 | //! ```ignore 85 | //! let sendable_email = email.as_sendable_email().unwrap(); 86 | //! ``` 87 | 88 | extern crate buf_read_ext; 89 | 90 | #[cfg(feature="time")] 91 | extern crate time; 92 | #[cfg(feature="chrono")] 93 | extern crate chrono; 94 | #[cfg(feature="lettre")] 95 | extern crate lettre; 96 | 97 | #[cfg(test)] 98 | mod tests; 99 | 100 | /// This module contains nitty-gritty details about parsing, storage, and streaming 101 | /// an `Email`. 102 | pub mod rfc5322; 103 | 104 | use std::io::Write; 105 | use std::io::Error as IoError; 106 | use std::fmt; 107 | 108 | use rfc5322::{Message, Fields, Field}; 109 | use rfc5322::{Parsable, Streamable}; 110 | use rfc5322::error::ParseError; 111 | use rfc5322::Body; 112 | use rfc5322::headers::{From, OrigDate, Sender, ReplyTo, To, Cc, Bcc, MessageId, 113 | InReplyTo, References, Subject, Comments, Keywords, 114 | OptionalField}; 115 | 116 | /// Attempt to construct `Self` via a conversion (borrowed from rust `std`) 117 | /// 118 | /// This TryFrom trait is defined in the rust std library but is behind a 119 | /// feature gate. We place it here so that people using stable compilers 120 | /// can still use our crate. In the future, the std trait should be used. 121 | pub trait TryFrom: Sized { 122 | /// The type returned in the event of a conversion error. 123 | type Error; 124 | 125 | /// Performs the conversion. 126 | fn try_from(_: T) -> Result; 127 | } 128 | 129 | // We implement TryFrom from T to T with our ParseError for crate ergonomics 130 | // (Rust won't let it be implemented with an unconstrained error type) 131 | impl TryFrom for T { 132 | type Error = ::rfc5322::error::ParseError; 133 | fn try_from(input: T) -> Result { 134 | Ok(input) 135 | } 136 | } 137 | 138 | #[derive(Debug, Clone)] 139 | pub struct Email { 140 | message: Message, 141 | } 142 | 143 | impl Email { 144 | /// Create a new email structure. The `From` address and `Date` fields are 145 | /// required in all valid emails, thus you must pass these in. 146 | pub fn new(from: F, date: D) -> Result 147 | where From: TryFrom, OrigDate: TryFrom 148 | { 149 | Ok(Email { 150 | message: Message { 151 | fields: Fields { 152 | trace_blocks: vec![], 153 | fields: vec![ 154 | Field::OrigDate(TryFrom::try_from(date)?), 155 | Field::From(TryFrom::try_from(from)?) ], 156 | }, 157 | body: None, 158 | } 159 | }) 160 | } 161 | 162 | /// Replace the `Date` field in the email 163 | pub fn set_date(&mut self, date: D) -> Result<(), ParseError> 164 | where OrigDate: TryFrom 165 | { 166 | let value: OrigDate = TryFrom::try_from(date)?; 167 | for field in self.message.fields.fields.iter_mut() { 168 | if let Field::OrigDate(_) = *field { 169 | *field = Field::OrigDate(value); 170 | return Ok(()) 171 | } 172 | } 173 | unreachable!() 174 | } 175 | /// Fetch the `Date` field from the email 176 | pub fn get_date(&self) -> OrigDate { 177 | for field in self.message.fields.fields.iter() { 178 | if let Field::OrigDate(ref d) = *field { 179 | return d.clone(); 180 | } 181 | } 182 | unreachable!() 183 | } 184 | 185 | /// Replace the `From` field in the email 186 | pub fn set_from(&mut self, from: F) -> Result<(), ParseError> 187 | where From: TryFrom 188 | { 189 | let value: From = TryFrom::try_from(from)?; 190 | for field in self.message.fields.fields.iter_mut() { 191 | if let Field::From(_) = *field { 192 | *field = Field::From(value); 193 | return Ok(()); 194 | } 195 | } 196 | unreachable!() 197 | } 198 | /// Fetch the `From` field from the email 199 | pub fn get_from(&self) -> From { 200 | for field in self.message.fields.fields.iter() { 201 | if let Field::From(ref f) = *field { 202 | return f.clone() 203 | } 204 | } 205 | unreachable!() 206 | } 207 | 208 | /// Set or replace the `Sender` field in the email 209 | pub fn set_sender(&mut self, sender: S) -> Result<(), ParseError> 210 | where Sender: TryFrom 211 | { 212 | let value: Sender = TryFrom::try_from(sender)?; 213 | for field in self.message.fields.fields.iter_mut() { 214 | if let Field::Sender(_) = *field { 215 | *field = Field::Sender(value); 216 | return Ok(()); 217 | } 218 | } 219 | self.message.fields.fields.push(Field::Sender(value)); 220 | Ok(()) 221 | } 222 | /// Fetch the `Sender` field from the email 223 | pub fn get_sender(&self) -> Option { 224 | for field in self.message.fields.fields.iter() { 225 | if let Field::Sender(ref s) = *field { 226 | return Some(s.clone()); 227 | } 228 | } 229 | None 230 | } 231 | /// Remove the `Sender` field from the email 232 | pub fn clear_sender(&mut self) { 233 | self.message.fields.fields.retain(|field| { 234 | if let Field::Sender(_) = *field { false } else { true } 235 | }); 236 | } 237 | 238 | /// Set or replace the `Reply-To` field in the email 239 | pub fn set_reply_to(&mut self, reply_to: R) -> Result<(), ParseError> 240 | where ReplyTo: TryFrom 241 | { 242 | let value: ReplyTo = TryFrom::try_from(reply_to)?; 243 | for field in self.message.fields.fields.iter_mut() { 244 | if let Field::ReplyTo(_) = *field { 245 | *field = Field::ReplyTo(value); 246 | return Ok(()); 247 | } 248 | } 249 | self.message.fields.fields.push(Field::ReplyTo(value)); 250 | Ok(()) 251 | } 252 | /// Fetch the `Reply-To` field from the email 253 | pub fn get_reply_to(&self) -> Option { 254 | for field in self.message.fields.fields.iter() { 255 | if let Field::ReplyTo(ref rt) = *field { 256 | return Some(rt.clone()) 257 | } 258 | } 259 | None 260 | } 261 | /// Remove the `Reply-To` field from the email 262 | pub fn clear_reply_to(&mut self) { 263 | self.message.fields.fields.retain(|field| { 264 | if let Field::ReplyTo(_) = *field { false } else { true } 265 | }); 266 | } 267 | 268 | /// Set or replace the `To` field in the email 269 | pub fn set_to(&mut self, to: T) -> Result<(), ParseError> 270 | where To: TryFrom 271 | { 272 | let value: To = TryFrom::try_from(to)?; 273 | for field in self.message.fields.fields.iter_mut() { 274 | if let Field::To(_) = *field { 275 | *field = Field::To(value); 276 | return Ok(()); 277 | } 278 | } 279 | self.message.fields.fields.push(Field::To(value)); 280 | Ok(()) 281 | } 282 | /// Fetch the `To` field from the email 283 | pub fn get_to(&self) -> Option { 284 | for field in self.message.fields.fields.iter() { 285 | if let Field::To(ref t) = *field { 286 | return Some(t.clone()) 287 | } 288 | } 289 | None 290 | } 291 | /// Remove the `To` field from the email 292 | pub fn clear_to(&mut self) { 293 | self.message.fields.fields.retain(|field| { 294 | if let Field::To(_) = *field { false } else { true } 295 | }); 296 | } 297 | 298 | /// Set or replace the `Cc` field in the email 299 | pub fn set_cc(&mut self, cc: C) -> Result<(), ParseError> 300 | where Cc: TryFrom 301 | { 302 | let value: Cc = TryFrom::try_from(cc)?; 303 | for field in self.message.fields.fields.iter_mut() { 304 | if let Field::Cc(_) = *field { 305 | *field = Field::Cc(value); 306 | return Ok(()); 307 | } 308 | } 309 | self.message.fields.fields.push(Field::Cc(value)); 310 | Ok(()) 311 | } 312 | /// Fetch the `Cc` field from the email 313 | pub fn get_cc(&self) -> Option { 314 | for field in self.message.fields.fields.iter() { 315 | if let Field::Cc(ref cc) = *field { 316 | return Some(cc.clone()) 317 | } 318 | } 319 | None 320 | } 321 | /// Remove the `Cc` field from the email 322 | pub fn clear_cc(&mut self) { 323 | self.message.fields.fields.retain(|field| { 324 | if let Field::Cc(_) = *field { false } else { true } 325 | }); 326 | } 327 | 328 | /// Set or replace the `Bcc` field in the email 329 | pub fn set_bcc(&mut self, bcc: B) -> Result<(), ParseError> 330 | where Bcc: TryFrom 331 | { 332 | let value: Bcc = TryFrom::try_from(bcc)?; 333 | for field in self.message.fields.fields.iter_mut() { 334 | if let Field::Bcc(_) = *field { 335 | *field = Field::Bcc(value); 336 | return Ok(()); 337 | } 338 | } 339 | self.message.fields.fields.push(Field::Bcc(value)); 340 | Ok(()) 341 | } 342 | /// Fetch the `Bcc` field from the email 343 | pub fn get_bcc(&self) -> Option { 344 | for field in self.message.fields.fields.iter() { 345 | if let Field::Bcc(ref b) = *field { 346 | return Some(b.clone()) 347 | } 348 | } 349 | None 350 | } 351 | /// Remove the `Bcc` field from the email 352 | pub fn clear_bcc(&mut self) { 353 | self.message.fields.fields.retain(|field| { 354 | if let Field::Bcc(_) = *field { false } else { true } 355 | }); 356 | } 357 | 358 | /// Set or replace the `Message-ID` field in the email 359 | pub fn set_message_id(&mut self, message_id: M) -> Result<(), ParseError> 360 | where MessageId: TryFrom 361 | { 362 | let value: MessageId = TryFrom::try_from(message_id)?; 363 | for field in self.message.fields.fields.iter_mut() { 364 | if let Field::MessageId(_) = *field { 365 | *field = Field::MessageId(value); 366 | return Ok(()); 367 | } 368 | } 369 | self.message.fields.fields.push(Field::MessageId(value)); 370 | Ok(()) 371 | } 372 | /// Fetch the `Message-ID` field from the email 373 | pub fn get_message_id(&self) -> Option { 374 | for field in self.message.fields.fields.iter() { 375 | if let Field::MessageId(ref m) = *field { 376 | return Some(m.clone()) 377 | } 378 | } 379 | None 380 | } 381 | /// Remove the `Message-ID` field from the email 382 | pub fn clear_message_id(&mut self) { 383 | self.message.fields.fields.retain(|field| { 384 | if let Field::MessageId(_) = *field { false } else { true } 385 | }); 386 | } 387 | 388 | /// Set or replace the `In-Reply-To` field in the email 389 | pub fn set_in_reply_to(&mut self, in_reply_to: I) -> Result<(), ParseError> 390 | where InReplyTo: TryFrom 391 | { 392 | let value: InReplyTo = TryFrom::try_from(in_reply_to)?; 393 | for field in self.message.fields.fields.iter_mut() { 394 | if let Field::InReplyTo(_) = *field { 395 | *field = Field::InReplyTo(value); 396 | return Ok(()); 397 | } 398 | } 399 | self.message.fields.fields.push(Field::InReplyTo(value)); 400 | Ok(()) 401 | } 402 | /// Fetch the `In-Reply-To` field from the email 403 | pub fn get_in_reply_to(&self) -> Option { 404 | for field in self.message.fields.fields.iter() { 405 | if let Field::InReplyTo(ref x) = *field { 406 | return Some(x.clone()) 407 | } 408 | } 409 | None 410 | } 411 | /// Remove the `In-Reply-To` field from the email 412 | pub fn clear_in_reply_to(&mut self) { 413 | self.message.fields.fields.retain(|field| { 414 | if let Field::InReplyTo(_) = *field { false } else { true } 415 | }); 416 | } 417 | 418 | /// Set or replace the `References` field in the email 419 | pub fn set_references(&mut self, references: R) -> Result<(), ParseError> 420 | where References: TryFrom 421 | { 422 | let value: References = TryFrom::try_from(references)?; 423 | for field in self.message.fields.fields.iter_mut() { 424 | if let Field::References(_) = *field { 425 | *field = Field::References(value); 426 | return Ok(()); 427 | } 428 | } 429 | self.message.fields.fields.push(Field::References(value)); 430 | Ok(()) 431 | } 432 | /// Fetch the `References` field from the email 433 | pub fn get_references(&self) -> Option { 434 | for field in self.message.fields.fields.iter() { 435 | if let Field::References(ref x) = *field { 436 | return Some(x.clone()) 437 | } 438 | } 439 | None 440 | } 441 | /// Remove the `References` field from the email 442 | pub fn clear_references(&mut self) { 443 | self.message.fields.fields.retain(|field| { 444 | if let Field::References(_) = *field { false } else { true } 445 | }); 446 | } 447 | 448 | /// Set or replace the `Subject` field in the email 449 | pub fn set_subject(&mut self, subject: S) -> Result<(), ParseError> 450 | where Subject: TryFrom 451 | { 452 | let value: Subject = TryFrom::try_from(subject)?; 453 | for field in self.message.fields.fields.iter_mut() { 454 | if let Field::Subject(_) = *field { 455 | *field = Field::Subject(value); 456 | return Ok(()); 457 | } 458 | } 459 | self.message.fields.fields.push(Field::Subject(value)); 460 | Ok(()) 461 | } 462 | /// Fetch the `Subject` field from the email 463 | pub fn get_subject(&self) -> Option { 464 | for field in self.message.fields.fields.iter() { 465 | if let Field::Subject(ref x) = *field { 466 | return Some(x.clone()) 467 | } 468 | } 469 | None 470 | } 471 | /// Remove the `Subject` field from the email 472 | pub fn clear_subject(&mut self) { 473 | self.message.fields.fields.retain(|field| { 474 | if let Field::Subject(_) = *field { false } else { true } 475 | }); 476 | } 477 | 478 | /// Add a `Comments` field in the email. This may be in addition to 479 | /// existing `Comments` fields. 480 | pub fn add_comments(&mut self, comments: C) -> Result<(), ParseError> 481 | where Comments: TryFrom 482 | { 483 | let value: Comments = TryFrom::try_from(comments)?; 484 | self.message.fields.fields.push(Field::Comments(value)); 485 | Ok(()) 486 | } 487 | /// Fetch all `Comments` fields from the email 488 | pub fn get_comments(&self) -> Vec { 489 | let mut output: Vec = Vec::new(); 490 | for field in self.message.fields.fields.iter() { 491 | if let Field::Comments(ref x) = *field { 492 | output.push(x.clone()); 493 | } 494 | } 495 | output 496 | } 497 | /// Remove all `Comments` fields from the email 498 | pub fn clear_comments(&mut self) { 499 | self.message.fields.fields.retain(|field| { 500 | if let Field::Comments(_) = *field { false } else { true } 501 | }); 502 | } 503 | 504 | /// Add a `Keywords` field in the email. This may be in addition to existing 505 | /// `Keywords` fields. 506 | pub fn add_keywords(&mut self, keywords: K) -> Result<(), ParseError> 507 | where Keywords: TryFrom 508 | { 509 | let value: Keywords = TryFrom::try_from(keywords)?; 510 | self.message.fields.fields.push(Field::Keywords(value)); 511 | Ok(()) 512 | } 513 | /// Fetch all `Keywords` fields from the email 514 | pub fn get_keywords(&self) -> Vec { 515 | let mut output: Vec = Vec::new(); 516 | for field in self.message.fields.fields.iter() { 517 | if let Field::Keywords(ref x) = *field { 518 | output.push(x.clone()); 519 | } 520 | } 521 | output 522 | } 523 | /// Remove all `Keywords` fields from the email 524 | pub fn clear_keywords(&mut self) { 525 | self.message.fields.fields.retain(|field| { 526 | if let Field::Keywords(_) = *field { false } else { true } 527 | }); 528 | } 529 | 530 | /// Add an optional field to the email. This may be in addition to existing 531 | /// optional fields. 532 | pub fn add_optional_field(&mut self, optional_field: O) -> Result<(), ParseError> 533 | where OptionalField: TryFrom 534 | { 535 | let value: OptionalField = TryFrom::try_from(optional_field)?; 536 | self.message.fields.fields.push(Field::OptionalField(value)); 537 | Ok(()) 538 | } 539 | /// Fetch all optional fields from the email 540 | pub fn get_optional_fields(&self) -> Vec { 541 | let mut output: Vec = Vec::new(); 542 | for field in self.message.fields.fields.iter() { 543 | if let Field::OptionalField(ref x) = *field { 544 | output.push(x.clone()); 545 | } 546 | } 547 | output 548 | } 549 | /// Clear all optional fields from the email 550 | pub fn clear_optional_fields(&mut self) { 551 | self.message.fields.fields.retain(|field| { 552 | if let Field::OptionalField(_) = *field { 553 | false 554 | } else { 555 | true 556 | } 557 | }) 558 | } 559 | 560 | // TBD: trace 561 | // TBD: resent-date 562 | // TBD: resent-from 563 | // TBD: resent-sender 564 | // TBD: resent-to 565 | // TBD: resent-cc 566 | // TBD: resent-bcc 567 | // TBD: resent-msg-id 568 | 569 | /// Set or replace the `Body` in the email 570 | pub fn set_body(&mut self, body: B) -> Result<(), ParseError> 571 | where Body: TryFrom 572 | { 573 | let value: Body = TryFrom::try_from(body)?; 574 | self.message.body = Some(value); 575 | Ok(()) 576 | } 577 | /// Fetch the `Body` from the email 578 | pub fn get_body(&self) -> Option { 579 | self.message.body.clone() 580 | } 581 | /// Remove the `Body` from the email, leaving an empty body 582 | pub fn clear_body(&mut self) { 583 | self.message.body = None; 584 | } 585 | 586 | /// Stream the email into a byte vector and return that 587 | pub fn as_bytes(&self) -> Vec { 588 | let mut output: Vec = Vec::new(); 589 | let _ = self.stream(&mut output); // no IoError ought to occur. 590 | output 591 | } 592 | 593 | /// Stream the email into a byte vector, convert to a String, and 594 | /// return that 595 | pub fn as_string(&self) -> String { 596 | let mut vec: Vec = Vec::new(); 597 | let _ = self.stream(&mut vec); // no IoError ought to occur. 598 | unsafe { 599 | // rfc5322 formatted emails fall within utf8, so this should not be 600 | // able to fail 601 | String::from_utf8_unchecked(vec) 602 | } 603 | } 604 | 605 | /// Create a `lettre::SendableEmail` from this Email. 606 | /// 607 | /// We require `&mut self` because we temporarily strip off the Bcc line 608 | /// when we generate the message, and then we put it back afterwards to 609 | /// leave `self` in a functionally unmodified state. 610 | #[cfg(feature="lettre")] 611 | pub fn as_sendable_email(&mut self) -> 612 | Result<::lettre::SendableEmail, &'static str> 613 | { 614 | use lettre::{SendableEmail, EmailAddress, Envelope}; 615 | use rfc5322::types::Address; 616 | 617 | let mut rfc_recipients: Vec
= Vec::new(); 618 | if let Some(to) = self.get_to() { 619 | rfc_recipients.extend((to.0).0); 620 | } 621 | if let Some(cc) = self.get_cc() { 622 | rfc_recipients.extend((cc.0).0); 623 | } 624 | if let Some(bcc) = self.get_bcc() { 625 | if let Bcc::AddressList(al) = bcc { 626 | rfc_recipients.extend(al.0); 627 | } 628 | } 629 | 630 | // Remove duplicates 631 | rfc_recipients.dedup(); 632 | 633 | let rfc_address_to_lettre = |a: Address| -> Result { 634 | let s = format!("{}", a).trim().to_string(); 635 | EmailAddress::new(s).map_err(|_| "Invalid email to address") 636 | }; 637 | let rfc_from_to_lettre = |f: From| -> Result { 638 | let s = format!("{}", f.0).trim().to_string(); 639 | EmailAddress::new(s).map_err(|_| "Invalid email from address") 640 | }; 641 | 642 | // Map to lettre::EmailAddress 643 | let mut lettre_recipients: Vec = vec![]; 644 | for address in rfc_recipients.drain(..) { 645 | lettre_recipients.push(rfc_address_to_lettre(address)?); 646 | } 647 | 648 | let from_addr = rfc_from_to_lettre(self.get_from())?; 649 | 650 | let message_id = match self.get_message_id() { 651 | Some(mid) => format!("{}@{}", mid.0.id_left, mid.0.id_right), 652 | None => return Err("email has no Message-ID"), 653 | }; 654 | 655 | // Remove Bcc header before creating body (RFC 5321 section 7.2) 656 | let maybe_bcc = self.get_bcc(); 657 | self.clear_bcc(); 658 | 659 | let message = format!("{}", self); 660 | 661 | // Put the Bcc back to restore the caller's argument 662 | if let Some(bcc) = maybe_bcc { 663 | if let Err(_) = self.set_bcc(bcc) { 664 | return Err("Unable to restore the Bcc line"); 665 | } 666 | } 667 | 668 | let envelope = Envelope::new(Some(from_addr), lettre_recipients) 669 | .map_err(|_| "Invalid envelope")?; 670 | Ok(SendableEmail::new(envelope, message_id, message.as_bytes().to_vec())) 671 | } 672 | } 673 | 674 | impl Parsable for Email { 675 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 676 | let mut rem = input; 677 | match Message::parse(rem).map(|(value, r)| { rem = r; value }) { 678 | Ok(message) => Ok((Email { message: message}, rem)), 679 | Err(e) => Err(ParseError::Parse("Email", Box::new(e))) 680 | } 681 | } 682 | } 683 | 684 | impl Streamable for Email { 685 | fn stream(&self, w: &mut W) -> Result { 686 | self.message.stream(w) 687 | } 688 | } 689 | 690 | impl fmt::Display for Email { 691 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 692 | let bytes = self.as_bytes(); 693 | unsafe { 694 | // rfc5322 formatted emails fall within utf8, so this should not be 695 | // able to fail 696 | write!(f, "{}", ::std::str::from_utf8_unchecked(&*bytes)) 697 | } 698 | } 699 | } 700 | -------------------------------------------------------------------------------- /src/rfc5322/email_address.rs: -------------------------------------------------------------------------------- 1 | 2 | use super::types::{AddressList, Address, Mailbox, Group, NameAddr, AddrSpec, 3 | GroupList, MailboxList}; 4 | 5 | /// This type represents an Email Address in a way that is simpler and more 6 | /// directly useful than the ABNF-based rfc5322 types. It is not used by the 7 | /// main parser, but may be useful to consumers of this library. 8 | pub struct EmailAddress { 9 | pub display_name: Option, 10 | pub local_part: String, 11 | pub domain: String, 12 | } 13 | 14 | impl EmailAddress { 15 | pub fn from_addresses(addr: &AddressList) -> Vec 16 | { 17 | let mut output: Vec = Vec::new(); 18 | for address in &addr.0 { 19 | output.extend( EmailAddress::from_address(address).into_iter() ); 20 | } 21 | output 22 | } 23 | 24 | pub fn from_address(addr: &Address) -> Vec 25 | { 26 | match *addr { 27 | Address::Mailbox(ref mbox) => vec![ EmailAddress::from_mailbox(mbox) ], 28 | Address::Group(ref group) => EmailAddress::from_group(group), 29 | } 30 | } 31 | 32 | pub fn from_mailbox(mbox: &Mailbox) -> EmailAddress 33 | { 34 | match *mbox { 35 | Mailbox::NameAddr(ref name_addr) => EmailAddress::from_name_addr(name_addr), 36 | Mailbox::AddrSpec(ref addr_spec) => EmailAddress::from_addr_spec(addr_spec), 37 | } 38 | } 39 | 40 | pub fn from_name_addr(name_addr: &NameAddr) -> EmailAddress 41 | { 42 | let mut email_address = EmailAddress::from_addr_spec( 43 | &name_addr.angle_addr.addr_spec); 44 | if let Some(ref display_name) = name_addr.display_name { 45 | email_address.display_name = Some(format!("{}", display_name)); 46 | } 47 | email_address 48 | } 49 | 50 | pub fn from_addr_spec(addr_spec: &AddrSpec) -> EmailAddress 51 | { 52 | EmailAddress { 53 | display_name: None, 54 | local_part: format!("{}", addr_spec.local_part), 55 | domain: format!("{}", addr_spec.domain), 56 | } 57 | } 58 | 59 | pub fn from_group(group: &Group) -> Vec 60 | { 61 | match group.group_list { 62 | Some(ref gl) => EmailAddress::from_group_list(gl), 63 | None => Vec::new(), 64 | } 65 | } 66 | 67 | pub fn from_group_list(group_list: &GroupList) -> Vec 68 | { 69 | match *group_list { 70 | GroupList::MailboxList(ref mbl) => EmailAddress::from_mailbox_list(mbl), 71 | _ => Vec::new(), 72 | } 73 | } 74 | 75 | pub fn from_mailbox_list(mbl: &MailboxList) -> Vec 76 | { 77 | let mut output: Vec = Vec::new(); 78 | for mailbox in &mbl.0 { 79 | output.push( EmailAddress::from_mailbox(mailbox) ); 80 | } 81 | output 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/rfc5322/error.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::error::Error as StdError; 3 | use std::fmt; 4 | use std::io::Error as IoError; 5 | 6 | pub enum ParseError { 7 | Eof(&'static str), 8 | NotFound(&'static str), 9 | Expected(Vec), 10 | ExpectedType(&'static str), 11 | Io(IoError), 12 | InvalidBodyChar(u8), 13 | LineTooLong(usize), 14 | TrailingInput(&'static str, usize), 15 | InternalError, 16 | Parse(&'static str, Box), 17 | } 18 | 19 | impl fmt::Display for ParseError { 20 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> 21 | { 22 | match *self { 23 | ParseError::Eof(ref field) => write!(f, "End of File while looking for \"{}\"", field), 24 | ParseError::NotFound(ref token) => write!(f, "\"{}\" Not Found", token), 25 | ParseError::Expected(ref bytes) => write!(f, "Expectation Failed. Expected \"{:?}\"", bytes), 26 | ParseError::ExpectedType(ref t) => write!(f, "Expectation Failed. Expected {}", t), 27 | ParseError::Io(ref e) => write!(f, "I/O Error: {}", e), 28 | ParseError::InvalidBodyChar(ref c) => write!(f, "Invalid Body Character: {} is not 7-bit ASCII", c), 29 | ParseError::LineTooLong(ref l) => write!(f, "Line {} is too long", l), 30 | ParseError::TrailingInput(ref field, ref c) => write!(f, "Trailing input at byte {} in {}", c, field), 31 | ParseError::InternalError => write!(f, "Internal error"), 32 | ParseError::Parse(ref field, ref inner) => write!(f, "Unable to parse {}: {}", field, inner), 33 | } 34 | } 35 | } 36 | 37 | impl fmt::Debug for ParseError { 38 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> 39 | { 40 | ::fmt(self, f) 41 | } 42 | } 43 | 44 | impl StdError for ParseError { } 45 | -------------------------------------------------------------------------------- /src/rfc5322/headers.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::io::Write; 3 | use std::io::Error as IoError; 4 | use ::TryFrom; 5 | use super::{Parsable, ParseError, Streamable}; 6 | use super::types::{DateTime, MailboxList, Mailbox, AddressList, CFWS, MsgId, 7 | Unstructured, Phrase, ReceivedToken, Path, FieldName}; 8 | 9 | macro_rules! req_name { 10 | ($rem:ident, $str:expr) => { 11 | let len: usize = $str.as_bytes().len(); 12 | if $rem.len() < len || 13 | &(&$rem[0..len]).to_ascii_lowercase().as_slice() != &$str.as_bytes() 14 | { 15 | return Err(ParseError::NotFound($str)); 16 | } 17 | $rem = &$rem[len..]; 18 | }; 19 | } 20 | 21 | macro_rules! req_crlf { 22 | ($rem:ident) => { 23 | if $rem.len() < 2 { 24 | return Err(ParseError::NotFound("CRLF")); 25 | } 26 | if &$rem[..2] != b"\r\n" { 27 | return Err(ParseError::NotFound("CRLF")); 28 | } 29 | $rem = &$rem[2..]; 30 | } 31 | } 32 | 33 | macro_rules! impl_try_from { 34 | ($from:ident, $to:ident) => { 35 | impl<'a> TryFrom<&'a [u8]> for $to { 36 | type Error = ParseError; 37 | fn try_from(input: &'a [u8]) -> Result<$to, ParseError> { 38 | let (out,rem) = $from::parse(input)?; 39 | if rem.len() > 0 { 40 | return Err(ParseError::TrailingInput("$to", input.len() - rem.len())); 41 | } 42 | Ok($to(out)) 43 | } 44 | } 45 | impl<'a> TryFrom<&'a str> for $to { 46 | type Error = ParseError; 47 | fn try_from(input: &'a str) -> Result<$to, ParseError> { 48 | TryFrom::try_from(input.as_bytes()) 49 | } 50 | } 51 | impl<'a> TryFrom<$from> for $to { 52 | type Error = ParseError; 53 | fn try_from(input: $from) -> Result<$to, ParseError> { 54 | Ok($to(input)) 55 | } 56 | } 57 | } 58 | } 59 | 60 | // 3.6.1 61 | // orig-date = "Date:" date-time CRLF 62 | #[derive(Debug, Clone, PartialEq)] 63 | pub struct OrigDate(pub DateTime); 64 | impl Parsable for OrigDate { 65 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 66 | if input.len() == 0 { return Err(ParseError::Eof("Date")); } 67 | let mut rem = input; 68 | req_name!(rem, "date:"); 69 | match parse!(DateTime, rem) { 70 | Ok(dt) => { 71 | req_crlf!(rem); 72 | Ok((OrigDate(dt), rem)) 73 | }, 74 | Err(e) => Err(ParseError::Parse("Date", Box::new(e))) 75 | } 76 | } 77 | } 78 | impl Streamable for OrigDate { 79 | fn stream(&self, w: &mut W) -> Result { 80 | Ok(w.write(b"Date:")? 81 | + self.0.stream(w)? 82 | + w.write(b"\r\n")?) 83 | } 84 | } 85 | impl_try_from!(DateTime, OrigDate); 86 | #[cfg(feature="time")] 87 | impl<'a> TryFrom<&'a ::time::Tm> for OrigDate { 88 | type Error = ParseError; 89 | fn try_from(input: &'a ::time::Tm) -> Result { 90 | let s = match input.strftime("%a, %d %b %Y %T %z") { 91 | Ok(s) => format!("{}",s), 92 | Err(_) => return Err(ParseError::InternalError), 93 | }; 94 | TryFrom::try_from(s.as_bytes()) 95 | } 96 | } 97 | #[cfg(feature="chrono")] 98 | impl<'a, Tz: ::chrono::TimeZone> TryFrom<&'a ::chrono::DateTime> for OrigDate 99 | where Tz::Offset: ::std::fmt::Display 100 | { 101 | type Error = ParseError; 102 | fn try_from(input: &'a ::chrono::DateTime) -> Result { 103 | let s = input.format("%a, %d %b %Y %T %z").to_string(); 104 | TryFrom::try_from(s.as_bytes()) 105 | } 106 | } 107 | impl_display!(OrigDate); 108 | 109 | // 3.6.2 110 | // from = "From:" mailbox-list CRLF 111 | #[derive(Debug, Clone, PartialEq)] 112 | pub struct From(pub MailboxList); 113 | impl Parsable for From { 114 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 115 | if input.len() == 0 { return Err(ParseError::Eof("From")); } 116 | let mut rem = input; 117 | req_name!(rem, "from:"); 118 | match parse!(MailboxList, rem) { 119 | Ok(mbl) => { 120 | req_crlf!(rem); 121 | return Ok((From(mbl), rem)); 122 | }, 123 | Err(e) => Err(ParseError::Parse("From", Box::new(e))) 124 | } 125 | } 126 | } 127 | impl Streamable for From { 128 | fn stream(&self, w: &mut W) -> Result { 129 | Ok(w.write(b"From:")? 130 | + self.0.stream(w)? 131 | + w.write(b"\r\n")?) 132 | } 133 | } 134 | impl_try_from!(MailboxList, From); 135 | impl_display!(From); 136 | 137 | // 3.6.2 138 | // sender = "Sender:" mailbox CRLF 139 | #[derive(Debug, Clone, PartialEq)] 140 | pub struct Sender(pub Mailbox); 141 | impl Parsable for Sender { 142 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 143 | if input.len() == 0 { return Err(ParseError::Eof("Sender")); } 144 | let mut rem = input; 145 | req_name!(rem, "sender:"); 146 | match parse!(Mailbox, rem) { 147 | Ok(mb) => { 148 | req_crlf!(rem); 149 | return Ok((Sender(mb), rem)); 150 | }, 151 | Err(e) => Err(ParseError::Parse("Sender", Box::new(e))) 152 | } 153 | } 154 | } 155 | impl Streamable for Sender { 156 | fn stream(&self, w: &mut W) -> Result { 157 | Ok(w.write(b"Sender:")? 158 | + self.0.stream(w)? 159 | + w.write(b"\r\n")?) 160 | } 161 | } 162 | impl_try_from!(Mailbox, Sender); 163 | impl_display!(Sender); 164 | 165 | // 3.6.2 166 | // reply-to = "Reply-To:" address-list CRLF 167 | #[derive(Debug, Clone, PartialEq)] 168 | pub struct ReplyTo(pub AddressList); 169 | impl Parsable for ReplyTo { 170 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 171 | if input.len() == 0 { return Err(ParseError::Eof("Reply-To")); } 172 | let mut rem = input; 173 | req_name!(rem, "reply-to:"); 174 | match parse!(AddressList, rem) { 175 | Ok(x) => { 176 | req_crlf!(rem); 177 | return Ok((ReplyTo(x), rem)); 178 | }, 179 | Err(e) => Err(ParseError::Parse("Reply-To", Box::new(e))) 180 | } 181 | } 182 | } 183 | impl Streamable for ReplyTo { 184 | fn stream(&self, w: &mut W) -> Result { 185 | Ok(w.write(b"Reply-To:")? 186 | + self.0.stream(w)? 187 | + w.write(b"\r\n")?) 188 | } 189 | } 190 | impl_try_from!(AddressList, ReplyTo); 191 | impl_display!(ReplyTo); 192 | 193 | // 3.6.3 194 | // to = "To:" address-list CRLF 195 | #[derive(Debug, Clone, PartialEq)] 196 | pub struct To(pub AddressList); 197 | impl Parsable for To { 198 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 199 | if input.len() == 0 { return Err(ParseError::Eof("To")); } 200 | let mut rem = input; 201 | req_name!(rem, "to:"); 202 | match parse!(AddressList, rem) { 203 | Ok(x) => { 204 | req_crlf!(rem); 205 | return Ok((To(x), rem)); 206 | }, 207 | Err(e) => Err(ParseError::Parse("To", Box::new(e))), 208 | } 209 | } 210 | } 211 | impl Streamable for To { 212 | fn stream(&self, w: &mut W) -> Result { 213 | Ok(w.write(b"To:")? 214 | + self.0.stream(w)? 215 | + w.write(b"\r\n")?) 216 | } 217 | } 218 | impl_try_from!(AddressList, To); 219 | impl_display!(To); 220 | 221 | // 3.6.3 222 | // cc = "Cc:" address-list CRLF 223 | #[derive(Debug, Clone, PartialEq)] 224 | pub struct Cc(pub AddressList); 225 | impl Parsable for Cc { 226 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 227 | if input.len() == 0 { return Err(ParseError::Eof("Cc")); } 228 | let mut rem = input; 229 | req_name!(rem, "cc:"); 230 | match parse!(AddressList, rem) { 231 | Ok(x) => { 232 | req_crlf!(rem); 233 | return Ok((Cc(x), rem)); 234 | }, 235 | Err(e) => Err(ParseError::Parse("Cc", Box::new(e))), 236 | } 237 | } 238 | } 239 | impl Streamable for Cc { 240 | fn stream(&self, w: &mut W) -> Result { 241 | Ok(w.write(b"Cc:")? 242 | + self.0.stream(w)? 243 | + w.write(b"\r\n")?) 244 | } 245 | } 246 | impl_try_from!(AddressList, Cc); 247 | impl_display!(Cc); 248 | 249 | // 3.6.3 250 | // bcc = "Bcc:" [address-list / CFWS] CRLF 251 | #[derive(Debug, Clone, PartialEq)] 252 | pub enum Bcc { 253 | AddressList(AddressList), 254 | CFWS(CFWS), 255 | Empty 256 | } 257 | impl Parsable for Bcc { 258 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 259 | if input.len() == 0 { return Err(ParseError::Eof("Bcc")); } 260 | let mut rem = input; 261 | req_name!(rem, "bcc:"); 262 | if let Ok(x) = parse!(AddressList, rem) { 263 | req_crlf!(rem); 264 | return Ok((Bcc::AddressList(x), rem)); 265 | } 266 | if let Ok(x) = parse!(CFWS, rem) { 267 | req_crlf!(rem); 268 | return Ok((Bcc::CFWS(x), rem)); 269 | } 270 | req_crlf!(rem); 271 | return Ok((Bcc::Empty, rem)); 272 | } 273 | } 274 | impl Streamable for Bcc { 275 | fn stream(&self, w: &mut W) -> Result { 276 | let mut count: usize = 0; 277 | count += w.write(b"Bcc:")?; 278 | count += match *self { 279 | Bcc::AddressList(ref al) => al.stream(w)?, 280 | Bcc::CFWS(ref cfws) => cfws.stream(w)?, 281 | Bcc::Empty => 0, 282 | }; 283 | count += w.write(b"\r\n")?; 284 | Ok(count) 285 | } 286 | } 287 | impl<'a> TryFrom<&'a [u8]> for Bcc { 288 | type Error = ParseError; 289 | fn try_from(input: &'a [u8]) -> Result { 290 | let (out,rem) = AddressList::parse(input)?; 291 | if rem.len() > 0 { 292 | return Err(ParseError::TrailingInput("Bcc", input.len() - rem.len())); 293 | } 294 | Ok(Bcc::AddressList(out)) 295 | } 296 | } 297 | impl<'a> TryFrom<&'a str> for Bcc { 298 | type Error = ParseError; 299 | fn try_from(input: &'a str) -> Result { 300 | TryFrom::try_from(input.as_bytes()) 301 | } 302 | } 303 | impl<'a> TryFrom for Bcc { 304 | type Error = ParseError; 305 | fn try_from(input: AddressList) -> Result { 306 | Ok(Bcc::AddressList(input)) 307 | } 308 | } 309 | impl_display!(Bcc); 310 | 311 | // 3.6.4 312 | // message-id = "Message-ID:" msg-id CRLF 313 | #[derive(Debug, Clone, PartialEq)] 314 | pub struct MessageId(pub MsgId); 315 | impl Parsable for MessageId { 316 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 317 | if input.len() == 0 { return Err(ParseError::Eof("MessageId")); } 318 | let mut rem = input; 319 | req_name!(rem, "message-id:"); 320 | match parse!(MsgId, rem) { 321 | Ok(x) => { 322 | req_crlf!(rem); 323 | return Ok((MessageId(x), rem)); 324 | }, 325 | Err(e) => Err(ParseError::Parse("Message-Id", Box::new(e))), 326 | } 327 | } 328 | } 329 | impl Streamable for MessageId { 330 | fn stream(&self, w: &mut W) -> Result { 331 | Ok(w.write(b"Message-ID:")? 332 | + self.0.stream(w)? 333 | + w.write(b"\r\n")?) 334 | } 335 | } 336 | impl_try_from!(MsgId, MessageId); 337 | impl_display!(MessageId); 338 | 339 | // 3.6.4 340 | // in-reply-to = "In-Reply-To:" 1*msg-id CRLF 341 | #[derive(Debug, Clone, PartialEq)] 342 | pub struct InReplyTo(pub Vec); 343 | impl Parsable for InReplyTo { 344 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 345 | if input.len() == 0 { return Err(ParseError::Eof("InReplyTo")); } 346 | let mut rem = input; 347 | let mut contents: Vec = Vec::new(); 348 | req_name!(rem, "in-reply-to:"); 349 | let err; 350 | loop { 351 | match parse!(MsgId, rem) { 352 | Ok(x) => contents.push(x), 353 | Err(e) => { err = e; break; } 354 | } 355 | } 356 | if contents.len() == 0 { 357 | return Err(ParseError::Parse("In-Reply-To", Box::new(err))); 358 | } 359 | req_crlf!(rem); 360 | Ok((InReplyTo(contents), rem)) 361 | } 362 | } 363 | impl Streamable for InReplyTo { 364 | fn stream(&self, w: &mut W) -> Result { 365 | let mut count: usize = 0; 366 | count += w.write(b"In-Reply-To:")?; 367 | for msgid in &self.0 { 368 | count += msgid.stream(w)? 369 | } 370 | count += w.write(b"\r\n")?; 371 | Ok(count) 372 | } 373 | } 374 | impl<'a> TryFrom<&'a [u8]> for InReplyTo { 375 | type Error = ParseError; 376 | fn try_from(input: &'a [u8]) -> Result { 377 | let mut msgids: Vec = Vec::new(); 378 | let mut rem = input; 379 | while let Ok(x) = parse!(MsgId, rem) { 380 | msgids.push(x); 381 | } 382 | if rem.len() > 0 { 383 | Err(ParseError::TrailingInput("In-Reply-To", input.len() - rem.len())) 384 | } else { 385 | Ok(InReplyTo(msgids)) 386 | } 387 | } 388 | } 389 | impl<'a> TryFrom<&'a str> for InReplyTo { 390 | type Error = ParseError; 391 | fn try_from(input: &'a str) -> Result { 392 | TryFrom::try_from(input.as_bytes()) 393 | } 394 | } 395 | impl<'a> TryFrom> for InReplyTo { 396 | type Error = ParseError; 397 | fn try_from(input: Vec) -> Result { 398 | Ok(InReplyTo(input)) 399 | } 400 | } 401 | impl_display!(InReplyTo); 402 | 403 | // 3.6.4 404 | // references = "References:" 1*msg-id CRLF 405 | #[derive(Debug, Clone, PartialEq)] 406 | pub struct References(pub Vec); 407 | impl Parsable for References { 408 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 409 | if input.len() == 0 { return Err(ParseError::Eof("References")); } 410 | let mut rem = input; 411 | let mut contents: Vec = Vec::new(); 412 | req_name!(rem, "references:"); 413 | let err; 414 | loop { 415 | match parse!(MsgId, rem) { 416 | Ok(x) => contents.push(x), 417 | Err(e) => { err = e; break } 418 | } 419 | } 420 | if contents.len() == 0 { 421 | return Err(ParseError::Parse("References", Box::new(err))); 422 | } 423 | req_crlf!(rem); 424 | Ok((References(contents), rem)) 425 | } 426 | } 427 | impl Streamable for References { 428 | fn stream(&self, w: &mut W) -> Result { 429 | let mut count: usize = 0; 430 | count += w.write(b"References:")?; 431 | for msgid in &self.0 { 432 | count += msgid.stream(w)? 433 | } 434 | count += w.write(b"\r\n")?; 435 | Ok(count) 436 | } 437 | } 438 | impl<'a> TryFrom<&'a [u8]> for References { 439 | type Error = ParseError; 440 | fn try_from(input: &'a [u8]) -> Result { 441 | let mut msgids: Vec = Vec::new(); 442 | let mut rem = input; 443 | while let Ok(x) = parse!(MsgId, rem) { 444 | msgids.push(x); 445 | } 446 | if rem.len() > 0 { 447 | Err(ParseError::TrailingInput("References", input.len() - rem.len())) 448 | } else { 449 | Ok(References(msgids)) 450 | } 451 | } 452 | } 453 | impl<'a> TryFrom<&'a str> for References { 454 | type Error = ParseError; 455 | fn try_from(input: &'a str) -> Result { 456 | TryFrom::try_from(input.as_bytes()) 457 | } 458 | } 459 | impl<'a> TryFrom> for References { 460 | type Error = ParseError; 461 | fn try_from(input: Vec) -> Result { 462 | Ok(References(input)) 463 | } 464 | } 465 | impl_display!(References); 466 | 467 | // 3.6.5 468 | // subject = "Subject:" unstructured CRLF 469 | #[derive(Debug, Clone, PartialEq)] 470 | pub struct Subject(pub Unstructured); 471 | impl Parsable for Subject { 472 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 473 | if input.len() == 0 { return Err(ParseError::Eof("Subject")); } 474 | let mut rem = input; 475 | req_name!(rem, "subject:"); 476 | match parse!(Unstructured, rem) { 477 | Ok(x) => { 478 | req_crlf!(rem); 479 | return Ok((Subject(x), rem)); 480 | }, 481 | Err(e) => Err(ParseError::Parse("Subject", Box::new(e))), 482 | } 483 | } 484 | } 485 | impl Streamable for Subject { 486 | fn stream(&self, w: &mut W) -> Result { 487 | Ok(w.write(b"Subject:")? 488 | + self.0.stream(w)? 489 | + w.write(b"\r\n")?) 490 | } 491 | } 492 | impl_try_from!(Unstructured, Subject); 493 | impl_display!(Subject); 494 | 495 | // 3.6.5 496 | // comments = "Comments:" unstructured CRLF 497 | #[derive(Debug, Clone, PartialEq)] 498 | pub struct Comments(pub Unstructured); 499 | impl Parsable for Comments { 500 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 501 | if input.len() == 0 { return Err(ParseError::Eof("Comments")); } 502 | let mut rem = input; 503 | req_name!(rem, "comments:"); 504 | match parse!(Unstructured, rem) { 505 | Ok(x) => { 506 | req_crlf!(rem); 507 | return Ok((Comments(x), rem)); 508 | }, 509 | Err(e) => Err(ParseError::Parse("Comments", Box::new(e))), 510 | } 511 | } 512 | } 513 | impl Streamable for Comments { 514 | fn stream(&self, w: &mut W) -> Result { 515 | Ok(w.write(b"Comments:")? 516 | + self.0.stream(w)? 517 | + w.write(b"\r\n")?) 518 | } 519 | } 520 | impl_try_from!(Unstructured, Comments); 521 | impl_display!(Comments); 522 | 523 | // 3.6.5 524 | // keywords = "Keywords:" phrase *("," phrase) CRLF 525 | #[derive(Debug, Clone, PartialEq)] 526 | pub struct Keywords(pub Vec); 527 | impl Parsable for Keywords { 528 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 529 | if input.len() == 0 { return Err(ParseError::Eof("Keywords")); } 530 | let mut rem = input; 531 | req_name!(rem, "keywords:"); 532 | let mut output: Vec = Vec::new(); 533 | let err; 534 | loop { 535 | match parse!(Phrase, rem) { 536 | Ok(x) => output.push(x), 537 | Err(e) => { err = e; break; } 538 | } 539 | } 540 | if output.len()==0 { 541 | return Err(ParseError::Parse("Keywords", Box::new(err))); 542 | } 543 | req_crlf!(rem); 544 | Ok((Keywords(output), rem)) 545 | } 546 | } 547 | impl Streamable for Keywords { 548 | fn stream(&self, w: &mut W) -> Result { 549 | let mut count: usize = 0; 550 | count += w.write(b"Keywords:")?; 551 | let mut virgin = true; 552 | for phrase in &self.0 { 553 | if ! virgin { 554 | count += w.write(b",")?; 555 | } 556 | count += phrase.stream(w)?; 557 | virgin = false 558 | } 559 | count += w.write(b"\r\n")?; 560 | Ok(count) 561 | } 562 | } 563 | impl<'a> TryFrom<&'a [u8]> for Keywords { 564 | type Error = ParseError; 565 | fn try_from(input: &'a [u8]) -> Result { 566 | let mut msgids: Vec = Vec::new(); 567 | let mut rem = input; 568 | while let Ok(x) = parse!(Phrase, rem) { 569 | msgids.push(x); 570 | } 571 | if rem.len() > 0 { 572 | Err(ParseError::TrailingInput("Keywords", input.len() - rem.len())) 573 | } else { 574 | Ok(Keywords(msgids)) 575 | } 576 | } 577 | } 578 | impl<'a> TryFrom<&'a str> for Keywords { 579 | type Error = ParseError; 580 | fn try_from(input: &'a str) -> Result { 581 | TryFrom::try_from(input.as_bytes()) 582 | } 583 | } 584 | impl<'a> TryFrom> for Keywords { 585 | type Error = ParseError; 586 | fn try_from(input: Vec) -> Result { 587 | Ok(Keywords(input)) 588 | } 589 | } 590 | impl_display!(Keywords); 591 | 592 | // 3.6.6 593 | // resent-date = "Resent-Date:" date-time CRLF 594 | #[derive(Debug, Clone, PartialEq)] 595 | pub struct ResentDate(pub DateTime); 596 | impl Parsable for ResentDate { 597 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 598 | if input.len() == 0 { return Err(ParseError::Eof("Resent-Date")); } 599 | let mut rem = input; 600 | req_name!(rem, "resent-date:"); 601 | match parse!(DateTime, rem) { 602 | Ok(dt) => { 603 | req_crlf!(rem); 604 | Ok((ResentDate(dt), rem)) 605 | }, 606 | Err(e) => Err(ParseError::Parse("Resent-Date", Box::new(e))) 607 | } 608 | } 609 | } 610 | impl Streamable for ResentDate { 611 | fn stream(&self, w: &mut W) -> Result { 612 | Ok(w.write(b"Resent-Date:")? 613 | + self.0.stream(w)? 614 | + w.write(b"\r\n")?) 615 | } 616 | } 617 | impl_try_from!(DateTime, ResentDate); 618 | impl_display!(ResentDate); 619 | 620 | // 3.6.6 621 | // resent-from = "Resent-From:" mailbox-list CRLF 622 | #[derive(Debug, Clone, PartialEq)] 623 | pub struct ResentFrom(pub MailboxList); 624 | impl Parsable for ResentFrom { 625 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 626 | if input.len() == 0 { return Err(ParseError::Eof("Resent-From")); } 627 | let mut rem = input; 628 | req_name!(rem, "resent-from:"); 629 | match parse!(MailboxList, rem) { 630 | Ok(mbl) => { 631 | req_crlf!(rem); 632 | return Ok((ResentFrom(mbl), rem)); 633 | }, 634 | Err(e) => Err(ParseError::Parse("Resent-From", Box::new(e))), 635 | } 636 | } 637 | } 638 | impl Streamable for ResentFrom { 639 | fn stream(&self, w: &mut W) -> Result { 640 | Ok(w.write(b"Resent-From:")? 641 | + self.0.stream(w)? 642 | + w.write(b"\r\n")?) 643 | } 644 | } 645 | impl_try_from!(MailboxList, ResentFrom); 646 | impl_display!(ResentFrom); 647 | 648 | // 3.6.6 649 | // resent-sender = "Resent-Sender:" mailbox CRLF 650 | #[derive(Debug, Clone, PartialEq)] 651 | pub struct ResentSender(pub Mailbox); 652 | impl Parsable for ResentSender { 653 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 654 | if input.len() == 0 { return Err(ParseError::Eof("Resent-Sender")); } 655 | let mut rem = input; 656 | req_name!(rem, "resent-sender:"); 657 | match parse!(Mailbox, rem) { 658 | Ok(mb) => { 659 | req_crlf!(rem); 660 | return Ok((ResentSender(mb), rem)); 661 | }, 662 | Err(e) => Err(ParseError::Parse("Resent-Sender", Box::new(e))), 663 | } 664 | } 665 | } 666 | impl Streamable for ResentSender { 667 | fn stream(&self, w: &mut W) -> Result { 668 | Ok(w.write(b"Resent-Sender:")? 669 | + self.0.stream(w)? 670 | + w.write(b"\r\n")?) 671 | } 672 | } 673 | impl_try_from!(Mailbox, ResentSender); 674 | impl_display!(ResentSender); 675 | 676 | // 3.6.6 677 | // resent-to = "Resent-To:" address-list CRLF 678 | #[derive(Debug, Clone, PartialEq)] 679 | pub struct ResentTo(pub AddressList); 680 | impl Parsable for ResentTo { 681 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 682 | if input.len() == 0 { return Err(ParseError::Eof("Resent-To")); } 683 | let mut rem = input; 684 | req_name!(rem, "resent-to:"); 685 | match parse!(AddressList, rem) { 686 | Ok(x) => { 687 | req_crlf!(rem); 688 | return Ok((ResentTo(x), rem)); 689 | }, 690 | Err(e) => Err(ParseError::Parse("Resent-To", Box::new(e))), 691 | } 692 | } 693 | } 694 | impl Streamable for ResentTo { 695 | fn stream(&self, w: &mut W) -> Result { 696 | Ok(w.write(b"Resent-To:")? 697 | + self.0.stream(w)? 698 | + w.write(b"\r\n")?) 699 | } 700 | } 701 | impl_try_from!(AddressList, ResentTo); 702 | impl_display!(ResentTo); 703 | 704 | // 3.6.6 705 | // resent-cc = "Resent-Cc:" address-list CRLF 706 | #[derive(Debug, Clone, PartialEq)] 707 | pub struct ResentCc(pub AddressList); 708 | impl Parsable for ResentCc { 709 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 710 | if input.len() == 0 { return Err(ParseError::Eof("Resent-Cc")); } 711 | let mut rem = input; 712 | req_name!(rem, "resent-cc:"); 713 | match parse!(AddressList, rem) { 714 | Ok(x) => { 715 | req_crlf!(rem); 716 | return Ok((ResentCc(x), rem)); 717 | }, 718 | Err(e) => Err(ParseError::Parse("Resent-Cc", Box::new(e))) 719 | } 720 | } 721 | } 722 | impl Streamable for ResentCc { 723 | fn stream(&self, w: &mut W) -> Result { 724 | Ok(w.write(b"Resent-Cc:")? 725 | + self.0.stream(w)? 726 | + w.write(b"\r\n")?) 727 | } 728 | } 729 | impl_try_from!(AddressList, ResentCc); 730 | impl_display!(ResentCc); 731 | 732 | // 3.6.6 733 | // resent-bcc = "Resent-Bcc:" [address-list / CFWS] CRLF 734 | #[derive(Debug, Clone, PartialEq)] 735 | pub enum ResentBcc { 736 | AddressList(AddressList), 737 | CFWS(CFWS), 738 | Empty 739 | } 740 | impl Parsable for ResentBcc { 741 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 742 | if input.len() == 0 { return Err(ParseError::Eof("Resent-Bcc")); } 743 | let mut rem = input; 744 | req_name!(rem, "resent-bcc:"); 745 | if let Ok(x) = parse!(AddressList, rem) { 746 | req_crlf!(rem); 747 | return Ok((ResentBcc::AddressList(x), rem)); 748 | } 749 | if let Ok(x) = parse!(CFWS, rem) { 750 | req_crlf!(rem); 751 | return Ok((ResentBcc::CFWS(x), rem)); 752 | } 753 | req_crlf!(rem); 754 | return Ok((ResentBcc::Empty, rem)); 755 | } 756 | } 757 | impl Streamable for ResentBcc { 758 | fn stream(&self, w: &mut W) -> Result { 759 | let mut count: usize = 0; 760 | count += w.write(b"Resent-Bcc:")?; 761 | count += match *self { 762 | ResentBcc::AddressList(ref al) => al.stream(w)?, 763 | ResentBcc::CFWS(ref cfws) => cfws.stream(w)?, 764 | ResentBcc::Empty => 0, 765 | }; 766 | count += w.write(b"\r\n")?; 767 | Ok(count) 768 | } 769 | } 770 | impl<'a> TryFrom<&'a [u8]> for ResentBcc { 771 | type Error = ParseError; 772 | fn try_from(input: &'a [u8]) -> Result { 773 | let (out,rem) = AddressList::parse(input)?; 774 | if rem.len() > 0 { 775 | return Err(ParseError::TrailingInput("Resent-Bcc", input.len() - rem.len())); 776 | } 777 | Ok(ResentBcc::AddressList(out)) 778 | } 779 | } 780 | impl<'a> TryFrom<&'a str> for ResentBcc { 781 | type Error = ParseError; 782 | fn try_from(input: &'a str) -> Result { 783 | TryFrom::try_from(input.as_bytes()) 784 | } 785 | } 786 | impl<'a> TryFrom for ResentBcc { 787 | type Error = ParseError; 788 | fn try_from(input: AddressList) -> Result { 789 | Ok(ResentBcc::AddressList(input)) 790 | } 791 | } 792 | impl_display!(ResentBcc); 793 | 794 | // 3.6.6 795 | // resent-msg-id = "Resent-Message-ID:" msg-id CRLF 796 | #[derive(Debug, Clone, PartialEq)] 797 | pub struct ResentMessageId(pub MsgId); 798 | impl Parsable for ResentMessageId { 799 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 800 | if input.len() == 0 { return Err(ParseError::Eof("Resent-Message-ID")); } 801 | let mut rem = input; 802 | req_name!(rem, "resent-message-id:"); 803 | match parse!(MsgId, rem) { 804 | Ok(x) => { 805 | req_crlf!(rem); 806 | return Ok((ResentMessageId(x), rem)); 807 | }, 808 | Err(e) => Err(ParseError::Parse("Resent-Message-Id", Box::new(e))), 809 | } 810 | } 811 | } 812 | impl Streamable for ResentMessageId { 813 | fn stream(&self, w: &mut W) -> Result { 814 | Ok(w.write(b"Resent-Message-ID:")? 815 | + self.0.stream(w)? 816 | + w.write(b"\r\n")?) 817 | } 818 | } 819 | impl_try_from!(MsgId, ResentMessageId); 820 | impl_display!(ResentMessageId); 821 | 822 | // 3.6.7 823 | // received = "Received:" *received-token ";" date-time CRLF 824 | // Errata ID 3979: 825 | // received = "Received:" [1*received-token / CFWS] 826 | // ";" date-time CRLF 827 | #[derive(Debug, Clone, PartialEq)] 828 | pub enum ReceivedTokens { 829 | Tokens(Vec), 830 | Comment(CFWS), 831 | } 832 | #[derive(Debug, Clone, PartialEq)] 833 | pub struct Received { 834 | pub received_tokens: ReceivedTokens, 835 | pub date_time: DateTime, 836 | } 837 | impl Parsable for Received { 838 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 839 | if input.len() == 0 { return Err(ParseError::Eof("Received")); } 840 | let mut rem = input; 841 | req_name!(rem, "received:"); 842 | let mut tokens: Vec = Vec::new(); 843 | let err; 844 | loop { 845 | match parse!(ReceivedToken, rem) { 846 | Ok(r) => tokens.push(r), 847 | Err(e) => { err = e; break; } 848 | } 849 | } 850 | let received_tokens = if tokens.len()==0 { 851 | if let Ok(cfws) = parse!(CFWS, rem) { 852 | ReceivedTokens::Comment(cfws) 853 | } else { 854 | return Err(ParseError::Parse("Received", Box::new(err))); 855 | } 856 | } else { 857 | ReceivedTokens::Tokens(tokens) 858 | }; 859 | req!(rem, b";", input); 860 | match parse!(DateTime, rem) { 861 | Ok(dt) => { 862 | req_crlf!(rem); 863 | return Ok((Received { 864 | received_tokens: received_tokens, 865 | date_time: dt 866 | }, rem)); 867 | }, 868 | Err(e) => Err(ParseError::Parse("Received", Box::new(e))), 869 | } 870 | } 871 | } 872 | impl Streamable for Received { 873 | fn stream(&self, w: &mut W) -> Result { 874 | let mut count: usize = 0; 875 | count += w.write(b"Received:")?; 876 | match self.received_tokens { 877 | ReceivedTokens::Tokens(ref vec) => { 878 | for token in vec { 879 | count += token.stream(w)?; 880 | } 881 | }, 882 | ReceivedTokens::Comment(ref c) => { 883 | count += c.stream(w)?; 884 | }, 885 | } 886 | count += w.write(b";")?; 887 | count += self.date_time.stream(w)?; 888 | count += w.write(b"\r\n")?; 889 | Ok(count) 890 | } 891 | } 892 | impl<'a> TryFrom<&'a [u8]> for Received { 893 | type Error = ParseError; 894 | fn try_from(input: &'a [u8]) -> Result { 895 | let mut fudged_input: Vec = "Received:".as_bytes().to_owned(); 896 | fudged_input.extend(&*input); 897 | fudged_input.extend("\r\n".as_bytes()); 898 | let (out,rem) = Received::parse(input)?; 899 | if rem.len() > 0 { 900 | return Err(ParseError::TrailingInput("Received", input.len() - rem.len())); 901 | } else { 902 | Ok(out) 903 | } 904 | } 905 | } 906 | impl<'a> TryFrom<&'a str> for Received { 907 | type Error = ParseError; 908 | fn try_from(input: &'a str) -> Result { 909 | TryFrom::try_from(input.as_bytes()) 910 | } 911 | } 912 | impl<'a> TryFrom<(ReceivedTokens, DateTime)> for Received { 913 | type Error = ParseError; 914 | fn try_from(input: (ReceivedTokens, DateTime)) -> Result { 915 | Ok(Received { 916 | received_tokens: input.0, 917 | date_time: input.1 }) 918 | } 919 | } 920 | impl_display!(Received); 921 | 922 | // 3.6.7 923 | // return = "Return-Path:" path CRLF 924 | #[derive(Debug, Clone, PartialEq)] 925 | pub struct Return(pub Path); 926 | impl Parsable for Return { 927 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 928 | let mut rem = input; 929 | req_name!(rem, "return-path:"); 930 | match parse!(Path, rem) { 931 | Ok(path) => { 932 | req_crlf!(rem); 933 | return Ok((Return(path), rem)); 934 | }, 935 | Err(e) => Err(ParseError::Parse("Return-Path", Box::new(e))), 936 | } 937 | } 938 | } 939 | impl Streamable for Return { 940 | fn stream(&self, w: &mut W) -> Result { 941 | Ok(w.write(b"Return-Path:")? 942 | + self.0.stream(w)? 943 | + w.write(b"\r\n")?) 944 | } 945 | } 946 | impl_try_from!(Path, Return); 947 | impl_display!(Return); 948 | 949 | // 3.6.8 950 | // optional-field = field-name ":" unstructured CRLF 951 | #[derive(Debug, Clone, PartialEq)] 952 | pub struct OptionalField { 953 | pub name: FieldName, 954 | pub value: Unstructured, 955 | } 956 | impl Parsable for OptionalField { 957 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 958 | let mut rem = input; 959 | 960 | match parse!(FieldName, rem) { 961 | Ok(name) => { 962 | req!(rem, b":", input); 963 | match parse!(Unstructured, rem) { 964 | Ok(value) => { 965 | req_crlf!(rem); 966 | return Ok((OptionalField { 967 | name: name, 968 | value: value, 969 | }, rem)); 970 | }, 971 | Err(e) => Err(ParseError::Parse("Optional Field", Box::new(e))), 972 | } 973 | }, 974 | Err(e) => Err(ParseError::Parse("Optional Field", Box::new(e))), 975 | } 976 | } 977 | } 978 | impl Streamable for OptionalField { 979 | fn stream(&self, w: &mut W) -> Result { 980 | Ok(self.name.stream(w)? 981 | + w.write(b":")? 982 | + self.value.stream(w)? 983 | + w.write(b"\r\n")?) 984 | } 985 | } 986 | impl<'a> TryFrom<(FieldName, Unstructured)> for OptionalField { 987 | type Error = ParseError; 988 | fn try_from(input: (FieldName, Unstructured)) -> Result { 989 | Ok(OptionalField { 990 | name: input.0, 991 | value: input.1 }) 992 | } 993 | } 994 | impl<'a,'b> TryFrom<(&'a [u8], &'b [u8])> for OptionalField { 995 | type Error = ParseError; 996 | fn try_from(input: (&'a [u8], &'b [u8])) -> Result { 997 | let (name,rem) = FieldName::parse(input.0)?; 998 | if rem.len() > 0 { 999 | return Err(ParseError::TrailingInput("Optional Field", input.0.len() - rem.len())); 1000 | } 1001 | let (value,rem) = Unstructured::parse(input.1)?; 1002 | if rem.len() > 0 { 1003 | return Err(ParseError::TrailingInput("Optional Field", input.1.len() - rem.len())); 1004 | } 1005 | Ok(OptionalField { 1006 | name: name, 1007 | value: value, 1008 | }) 1009 | } 1010 | } 1011 | impl<'a,'b> TryFrom<(&'a str, &'b str)> for OptionalField { 1012 | type Error = ParseError; 1013 | fn try_from(input: (&'a str, &'b str)) -> Result { 1014 | TryFrom::try_from((input.0.as_bytes(), input.1.as_bytes())) 1015 | } 1016 | } 1017 | impl_display!(OptionalField); 1018 | -------------------------------------------------------------------------------- /src/rfc5322/mod.rs: -------------------------------------------------------------------------------- 1 | // Format validated types representing lexical tokens defined in 2 | // RFC 5322 (as well as some referred from RFC 5234) 3 | // in order to support SMTP (RFC 5321) 4 | 5 | // Macro for defining sequences of characters within a character class 6 | macro_rules! def_cclass { 7 | ( $typ:ident, $test:ident) => { 8 | #[derive(Debug, Clone, PartialEq)] 9 | pub struct $typ(pub Vec); 10 | impl Parsable for $typ { 11 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 12 | let mut pos: usize = 0; 13 | let mut output: Vec = Vec::new(); 14 | while pos < input.len() && $test(input[pos]) { 15 | output.push(input[pos]); 16 | pos += 1; 17 | } 18 | if output.len() > 0 { 19 | Ok( ($typ(output), &input[pos..]) ) 20 | } 21 | else { 22 | if pos >= input.len() { Err( ParseError::Eof("$typ") ) } 23 | else { Err( ParseError::NotFound("$typ") ) } 24 | } 25 | } 26 | } 27 | impl Streamable for $typ { 28 | fn stream(&self, w: &mut W) -> Result { 29 | Ok(w.write(&self.0[..])?) 30 | } 31 | } 32 | }; 33 | } 34 | 35 | // Macro for assigning the returned remaining input of a parse function to an existing 36 | // variable 37 | macro_rules! parse { 38 | ($pth:ident, $rem:ident) => { 39 | { 40 | $pth::parse($rem).map(|(value, r)| { $rem = r; value }) 41 | } 42 | }; 43 | } 44 | 45 | macro_rules! req { 46 | ($rem:ident, $bytes:expr, $input:ident) => { 47 | let len: usize = $bytes.len(); 48 | if $rem.len() < len { 49 | return Err(ParseError::Eof("$bytes")); 50 | } 51 | if &$rem[0..len] != $bytes { 52 | return Err(ParseError::Expected($bytes.to_vec())); 53 | } 54 | $rem = &$rem[len..]; 55 | }; 56 | } 57 | 58 | macro_rules! impl_display { 59 | ($t:ty) => { 60 | impl ::std::fmt::Display for $t { 61 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { 62 | let mut output: Vec = Vec::new(); 63 | if let Err(_) = self.stream(&mut output) { 64 | return Err(::std::fmt::Error); 65 | } 66 | unsafe { 67 | // rfc5322 formatted emails fall within utf8 68 | write!(f, "{}", ::std::str::from_utf8_unchecked(&*output)) 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | pub mod error; 76 | pub use self::error::ParseError; 77 | pub mod types; 78 | pub mod headers; 79 | pub mod email_address; 80 | 81 | use std::io::Write; 82 | use std::io::Error as IoError; 83 | use buf_read_ext::BufReadExt; 84 | use ::TryFrom; 85 | use self::headers::{Return, Received}; 86 | use self::headers::{ResentDate, ResentFrom, ResentSender, ResentTo, ResentCc, ResentBcc, 87 | ResentMessageId}; 88 | use self::headers::{OrigDate, From, Sender, ReplyTo, To, Cc, Bcc, MessageId, InReplyTo, 89 | References, Subject, Comments, Keywords, OptionalField}; 90 | 91 | pub trait Parsable: Sized { 92 | /// Parse the object off of the beginning of the `input`. If found, returns Some object, 93 | /// and a slice containing the remainer of the input. 94 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError>; 95 | } 96 | 97 | pub trait Streamable { 98 | /// Serializes and sends the content out to `w`, returning the number of bytes written. 99 | fn stream(&self, w: &mut W) -> Result; 100 | } 101 | 102 | // 3.6.7 103 | // trace = [return] 104 | // 1*received 105 | #[derive(Debug, Clone, PartialEq)] 106 | pub struct Trace { 107 | return_path: Option, 108 | received: Vec 109 | } 110 | impl Parsable for Trace { 111 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 112 | let mut rem = input; 113 | let maybe_return = parse!(Return, rem).ok(); 114 | let mut received: Vec = Vec::new(); 115 | while let Ok(r) = parse!(Received, rem) { 116 | received.push(r); 117 | } 118 | if received.len() < 1 { return Err(ParseError::NotFound("Trace")); } 119 | Ok((Trace { 120 | return_path: maybe_return, 121 | received: received, 122 | }, rem)) 123 | } 124 | } 125 | impl Streamable for Trace { 126 | fn stream(&self, w: &mut W) -> Result { 127 | let mut count: usize = 0; 128 | if let Some(ref rp) = self.return_path { 129 | count += rp.stream(w)?; 130 | } 131 | for r in &self.received { 132 | count += r.stream(w)?; 133 | } 134 | Ok(count) 135 | } 136 | } 137 | impl_display!(Trace); 138 | 139 | #[derive(Debug, Clone, PartialEq)] 140 | pub enum ResentField { 141 | Date(ResentDate), 142 | From(ResentFrom), 143 | Sender(ResentSender), 144 | To(ResentTo), 145 | Cc(ResentCc), 146 | Bcc(ResentBcc), 147 | MessageId(ResentMessageId), 148 | } 149 | impl Parsable for ResentField { 150 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 151 | let mut rem = input; 152 | if let Ok(x) = parse!(ResentDate, rem) { 153 | return Ok((ResentField::Date(x), rem)); 154 | } 155 | if let Ok(x) = parse!(ResentFrom, rem) { 156 | return Ok((ResentField::From(x), rem)); 157 | } 158 | if let Ok(x) = parse!(ResentSender, rem) { 159 | return Ok((ResentField::Sender(x), rem)); 160 | } 161 | if let Ok(x) = parse!(ResentTo, rem) { 162 | return Ok((ResentField::To(x), rem)); 163 | } 164 | if let Ok(x) = parse!(ResentCc, rem) { 165 | return Ok((ResentField::Cc(x), rem)); 166 | } 167 | if let Ok(x) = parse!(ResentBcc, rem) { 168 | return Ok((ResentField::Bcc(x), rem)); 169 | } 170 | if let Ok(x) = parse!(ResentMessageId, rem) { 171 | return Ok((ResentField::MessageId(x), rem)); 172 | } 173 | Err(ParseError::NotFound("Resent-Field")) 174 | } 175 | } 176 | impl Streamable for ResentField { 177 | fn stream(&self, w: &mut W) -> Result { 178 | match *self { 179 | ResentField::Date(ref x) => x.stream(w), 180 | ResentField::From(ref x) => x.stream(w), 181 | ResentField::Sender(ref x) => x.stream(w), 182 | ResentField::To(ref x) => x.stream(w), 183 | ResentField::Cc(ref x) => x.stream(w), 184 | ResentField::Bcc(ref x) => x.stream(w), 185 | ResentField::MessageId(ref x) => x.stream(w), 186 | } 187 | } 188 | } 189 | impl_display!(ResentField); 190 | 191 | // 3.6 192 | // a sub part of the Fields definition 193 | #[derive(Debug, Clone, PartialEq)] 194 | pub enum Field { 195 | OrigDate(OrigDate), 196 | From(From), 197 | Sender(Sender), 198 | ReplyTo(ReplyTo), 199 | To(To), 200 | Cc(Cc), 201 | Bcc(Bcc), 202 | MessageId(MessageId), 203 | InReplyTo(InReplyTo), 204 | References(References), 205 | Subject(Subject), 206 | Comments(Comments), 207 | Keywords(Keywords), 208 | OptionalField(OptionalField), 209 | } 210 | impl Parsable for Field { 211 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 212 | let mut rem = input; 213 | if let Ok(x) = parse!(OrigDate, rem) { 214 | return Ok((Field::OrigDate(x), rem)); 215 | } 216 | if let Ok(x) = parse!(From, rem) { 217 | return Ok((Field::From(x), rem)); 218 | } 219 | if let Ok(x) = parse!(Sender, rem) { 220 | return Ok((Field::Sender(x), rem)); 221 | } 222 | if let Ok(x) = parse!(ReplyTo, rem) { 223 | return Ok((Field::ReplyTo(x), rem)); 224 | } 225 | if let Ok(x) = parse!(To, rem) { 226 | return Ok((Field::To(x), rem)); 227 | } 228 | if let Ok(x) = parse!(Cc, rem) { 229 | return Ok((Field::Cc(x), rem)); 230 | } 231 | if let Ok(x) = parse!(Bcc, rem) { 232 | return Ok((Field::Bcc(x), rem)); 233 | } 234 | if let Ok(x) = parse!(MessageId, rem) { 235 | return Ok((Field::MessageId(x), rem)); 236 | } 237 | if let Ok(x) = parse!(InReplyTo, rem) { 238 | return Ok((Field::InReplyTo(x), rem)); 239 | } 240 | if let Ok(x) = parse!(References, rem) { 241 | return Ok((Field::References(x), rem)); 242 | } 243 | if let Ok(x) = parse!(Subject, rem) { 244 | return Ok((Field::Subject(x), rem)); 245 | } 246 | if let Ok(x) = parse!(Comments, rem) { 247 | return Ok((Field::Comments(x), rem)); 248 | } 249 | if let Ok(x) = parse!(Keywords, rem) { 250 | return Ok((Field::Keywords(x), rem)); 251 | } 252 | if let Ok(x) = parse!(OptionalField, rem) { 253 | return Ok((Field::OptionalField(x), rem)); 254 | } 255 | Err(ParseError::NotFound("Field")) 256 | } 257 | } 258 | impl Streamable for Field { 259 | fn stream(&self, w: &mut W) -> Result { 260 | match *self { 261 | Field::OrigDate(ref x) => x.stream(w), 262 | Field::From(ref x) => x.stream(w), 263 | Field::Sender(ref x) => x.stream(w), 264 | Field::ReplyTo(ref x) => x.stream(w), 265 | Field::To(ref x) => x.stream(w), 266 | Field::Cc(ref x) => x.stream(w), 267 | Field::Bcc(ref x) => x.stream(w), 268 | Field::MessageId(ref x) => x.stream(w), 269 | Field::InReplyTo(ref x) => x.stream(w), 270 | Field::References(ref x) => x.stream(w), 271 | Field::Subject(ref x) => x.stream(w), 272 | Field::Comments(ref x) => x.stream(w), 273 | Field::Keywords(ref x) => x.stream(w), 274 | Field::OptionalField(ref x) => x.stream(w), 275 | } 276 | } 277 | } 278 | impl_display!(Field); 279 | 280 | // 3.6 281 | // a sub part of the Fields definition 282 | #[derive(Debug, Clone, PartialEq)] 283 | pub struct ResentTraceBlock { 284 | pub trace: Trace, 285 | pub resent_fields: Vec, 286 | } 287 | impl Parsable for ResentTraceBlock { 288 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 289 | let mut rem = input; 290 | if let Ok(t) = parse!(Trace, rem) { 291 | let mut fields: Vec = Vec::new(); 292 | while let Ok(f) = parse!(ResentField, rem) { 293 | fields.push(f); 294 | } 295 | if fields.len() == 0 { 296 | Err(ParseError::ExpectedType("Resent Field")) 297 | } else { 298 | Ok((ResentTraceBlock { 299 | trace: t, 300 | resent_fields: fields 301 | }, rem)) 302 | } 303 | } else { 304 | Err(ParseError::NotFound("Resent Trace Block")) 305 | } 306 | } 307 | } 308 | impl Streamable for ResentTraceBlock { 309 | fn stream(&self, w: &mut W) -> Result { 310 | let mut count: usize = 0; 311 | count += self.trace.stream(w)?; 312 | for field in &self.resent_fields { 313 | count += field.stream(w)?; 314 | } 315 | Ok(count) 316 | } 317 | } 318 | impl_display!(ResentTraceBlock); 319 | 320 | // 3.6 321 | // a sub part of the Fields definition 322 | #[derive(Debug, Clone, PartialEq)] 323 | pub struct OptTraceBlock { 324 | pub trace: Trace, 325 | pub opt_fields: Vec, 326 | } 327 | impl Parsable for OptTraceBlock { 328 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 329 | let mut rem = input; 330 | if let Ok(t) = parse!(Trace, rem) { 331 | let mut fields: Vec = Vec::new(); 332 | while let Ok(f) = parse!(OptionalField, rem) { 333 | fields.push(f); 334 | } 335 | if fields.len() == 0 { 336 | Err(ParseError::ExpectedType("Optional Field")) 337 | } else { 338 | Ok((OptTraceBlock { 339 | trace: t, 340 | opt_fields: fields 341 | }, rem)) 342 | } 343 | } else { 344 | Err(ParseError::NotFound("Opt Trace Block")) 345 | } 346 | } 347 | } 348 | impl Streamable for OptTraceBlock { 349 | fn stream(&self, w: &mut W) -> Result { 350 | let mut count: usize = 0; 351 | count += self.trace.stream(w)?; 352 | for field in &self.opt_fields { 353 | count += field.stream(w)?; 354 | } 355 | Ok(count) 356 | } 357 | } 358 | impl_display!(OptTraceBlock); 359 | 360 | // 3.6 361 | // a sub part of the Fields definition 362 | #[derive(Debug, Clone, PartialEq)] 363 | pub enum TraceBlock { 364 | Resent(ResentTraceBlock), 365 | Opt(OptTraceBlock), 366 | } 367 | impl Parsable for TraceBlock { 368 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 369 | let mut rem = input; 370 | if let Ok(block) = parse!(ResentTraceBlock, rem) { 371 | Ok((TraceBlock::Resent(block), rem)) 372 | } 373 | else if let Ok(block) = parse!(OptTraceBlock, rem) { 374 | Ok((TraceBlock::Opt(block), rem)) 375 | } 376 | else { 377 | Err(ParseError::NotFound("Trace Block")) 378 | } 379 | } 380 | } 381 | impl Streamable for TraceBlock { 382 | fn stream(&self, w: &mut W) -> Result { 383 | match *self { 384 | TraceBlock::Resent(ref block) => block.stream(w), 385 | TraceBlock::Opt(ref block) => block.stream(w), 386 | } 387 | } 388 | } 389 | impl_display!(TraceBlock); 390 | 391 | // 3.6 392 | // fields = *(trace 393 | // *optional-field / 394 | // *(resent-date / 395 | // resent-from / 396 | // resent-sender / 397 | // resent-to / 398 | // resent-cc / 399 | // resent-bcc / 400 | // resent-msg-id)) 401 | // *(orig-date / 402 | // from / 403 | // sender / 404 | // reply-to / 405 | // to / 406 | // cc / 407 | // bcc / 408 | // message-id / 409 | // in-reply-to / 410 | // references / 411 | // subject / 412 | // comments / 413 | // keywords / 414 | // optional-field) 415 | #[derive(Debug, Clone, PartialEq)] 416 | pub struct Fields { 417 | pub trace_blocks: Vec, 418 | pub fields: Vec, 419 | } 420 | impl Parsable for Fields { 421 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 422 | let mut rem = input; 423 | let mut trace_blocks: Vec = Vec::new(); 424 | while let Ok(tb) = parse!(TraceBlock, rem) { 425 | trace_blocks.push(tb); 426 | } 427 | let mut fields: Vec = Vec::new(); 428 | while let Ok(f) = parse!(Field, rem) { 429 | fields.push(f); 430 | } 431 | Ok((Fields { 432 | trace_blocks: trace_blocks, 433 | fields: fields, 434 | }, rem)) 435 | } 436 | } 437 | impl Streamable for Fields { 438 | fn stream(&self, w: &mut W) -> Result { 439 | let mut count: usize = 0; 440 | for tb in &self.trace_blocks { 441 | count += tb.stream(w)?; 442 | } 443 | for f in &self.fields { 444 | count += f.stream(w)?; 445 | } 446 | Ok(count) 447 | } 448 | } 449 | impl_display!(Fields); 450 | 451 | // 3.5 452 | // text = %d1-9 / ; Characters excluding CR 453 | // %d11 / ; and LF 454 | // %d12 / 455 | // %d14-127 456 | #[inline] 457 | pub fn is_text(c: u8) -> bool { 458 | (c>=1 && c<=9) || c==11 || c==12 || (c>=14 && c<=127) 459 | } 460 | def_cclass!(Text, is_text); 461 | 462 | // 3.5 463 | // body = (*(*998text CRLF) *998text) / obs-body 464 | #[derive(Debug, Clone, PartialEq)] 465 | // for performance/memory reasons, we store as a Vec 466 | // rather than Vec where Line is Vec. 467 | pub struct Body(pub Vec); 468 | impl Parsable for Body { 469 | fn parse(mut input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 470 | let mut body: Vec = Vec::new(); 471 | let mut line_number: usize = 0; 472 | loop { 473 | line_number += 1; 474 | let mut line: Vec = Vec::new(); 475 | match input.stream_until_token(b"\r\n", &mut line) { 476 | Err(e) => return Err(ParseError::Io(e)), 477 | Ok((_, found)) => { 478 | let mut rem = &*line; 479 | if let Ok(text) = parse!(Text, rem) { 480 | if rem.len() > 0 { 481 | return Err(ParseError::InvalidBodyChar(rem[0])); 482 | } 483 | if text.0.len() > 998 { 484 | return Err(ParseError::LineTooLong(line_number)); 485 | } 486 | body.extend(text.0.clone()); 487 | } 488 | if !found { break; } // end of input 489 | else { body.extend_from_slice(b"\r\n"); } 490 | } 491 | } 492 | } 493 | Ok((Body(body), input)) 494 | } 495 | } 496 | impl Streamable for Body { 497 | fn stream(&self, w: &mut W) -> Result { 498 | w.write(&self.0) 499 | } 500 | } 501 | impl<'a> TryFrom<&'a [u8]> for Body { 502 | type Error = ParseError; 503 | fn try_from(input: &'a [u8]) -> Result { 504 | let (out,rem) = Body::parse(input)?; 505 | if rem.len() > 0 { 506 | Err(ParseError::TrailingInput("Body", input.len() - rem.len())) 507 | } else { 508 | Ok(out) 509 | } 510 | } 511 | } 512 | impl<'a> TryFrom<&'a str> for Body { 513 | type Error = ParseError; 514 | fn try_from(input: &'a str) -> Result { 515 | TryFrom::try_from(input.as_bytes()) 516 | } 517 | } 518 | impl_display!(Body); 519 | 520 | // 3.5 521 | // message = (fields / obs-fields) 522 | // [CRLF body] 523 | #[derive(Debug, Clone, PartialEq)] 524 | pub struct Message { 525 | pub fields: Fields, 526 | pub body: Option 527 | } 528 | impl Parsable for Message { 529 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 530 | let mut rem = input; 531 | if let Ok(fields) = parse!(Fields, rem) { 532 | if &rem[..2] != b"\r\n" { 533 | return Ok((Message { 534 | fields: fields, 535 | body: None, 536 | }, rem)); 537 | } 538 | rem = &rem[2..]; 539 | parse!(Body, rem).map(|b| (Message { 540 | fields: fields, 541 | body: Some(b), 542 | }, rem)) 543 | } else { 544 | Err(ParseError::NotFound("Message")) 545 | } 546 | } 547 | } 548 | impl Streamable for Message { 549 | fn stream(&self, w: &mut W) -> Result { 550 | let mut count: usize = 0; 551 | count += self.fields.stream(w)?; 552 | if let Some(ref body) = self.body { 553 | count += w.write(b"\r\n")?; 554 | count += body.stream(w)?; 555 | } 556 | Ok(count) 557 | } 558 | } 559 | impl_display!(Message); 560 | -------------------------------------------------------------------------------- /src/rfc5322/types.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::io::Write; 3 | use std::io::Error as IoError; 4 | use super::{Parsable, Streamable, ParseError}; 5 | 6 | // RFC 5234, B.1 Core Rules 7 | //const CR: u8 = 0x0D; // CR = %x0D ; carriage return 8 | //const LF: u8 = 0x0A; // LF = %x0A ; linefeed 9 | const SP: u8 = 0x20; // SP = %x20 10 | const HTAB: u8 = 0x09; // HTAB = %x09 ; horizontal tab 11 | //const DQUOTE: u8 = 0x22; // DQUOTE = %x22 ; " (Double Quote) 12 | 13 | // RFC 5234, B.1 Core Rules 14 | // VCHAR = %x21-7E ; visible (printing) characters) 15 | #[inline] 16 | pub fn is_vchar(c: u8) -> bool { c>=0x21 && c<=0x7E } 17 | def_cclass!(VChar, is_vchar); 18 | impl_display!(VChar); 19 | 20 | // RFC 5234, B.1 Core Rules WSP = SP / HTAB ; white space 21 | #[inline] 22 | pub fn is_wsp(c: u8) -> bool { c==SP || c==HTAB } 23 | def_cclass!(WSP, is_wsp); 24 | impl_display!(WSP); 25 | 26 | // RFC 5234, B.1 Core Rules CHAR = %x01-7F ; any 7-bit US-ASCII character, 27 | // ; excluding NUL 28 | #[inline] 29 | pub fn is_ascii(c: u8) -> bool { c>=1 && c<=127 } 30 | def_cclass!(ASCII, is_ascii); 31 | impl_display!(ASCII); 32 | 33 | // RFC 5234, B.1 Core Rules DIGIT = %x30-39 ; 0-9 34 | #[inline] 35 | pub fn is_digit(c: u8) -> bool { c>=0x30 && c<=0x39 } 36 | def_cclass!(Digit, is_digit); 37 | impl_display!(Digit); 38 | 39 | // RFC 5234, B.1 Core Rules ALPHA = %x41-5A / %x61-7A ; A-Z / a-z 40 | #[inline] 41 | pub fn is_alpha(c: u8) -> bool { (c>=0x41 && c<=0x5A) || (c>=0x61 && c<=0x7A) } 42 | def_cclass!(Alpha, is_alpha); 43 | impl_display!(Alpha); 44 | 45 | // 3.2.1 46 | // quoted-pair = ("\" (VCHAR / WSP)) / obs-qp 47 | #[derive(Debug, Clone, Copy, PartialEq)] 48 | pub struct QuotedPair(pub u8); 49 | impl Parsable for QuotedPair { 50 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 51 | let mut pos: usize = 0; 52 | if pos >= input.len() { return Err(ParseError::Eof("Quoted Pair")); } 53 | if pos + 1 >= input.len() { return Err(ParseError::NotFound("Quoted Pair")); } 54 | if input[pos]!=b'\\' { return Err(ParseError::NotFound("Quoted Pair")); } 55 | if is_vchar(input[pos + 1]) || is_wsp(input[pos + 1]) { 56 | pos += 2; 57 | let qp = QuotedPair(input[pos - 1]); 58 | return Ok((qp, &input[pos..])); 59 | } 60 | Err(ParseError::NotFound("Quoted Pair")) 61 | } 62 | } 63 | impl Streamable for QuotedPair { 64 | fn stream(&self, w: &mut W) -> Result { 65 | Ok(w.write(b"\\")? + w.write(&[self.0])?) 66 | } 67 | } 68 | impl_display!(QuotedPair); 69 | 70 | // 3.2.2 71 | // FWS = ([*WSP CRLF] 1*WSP) / obs-FWS 72 | // ; Folding white space 73 | #[derive(Debug, Clone, Copy, PartialEq)] 74 | pub struct FWS; 75 | impl Parsable for FWS { 76 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 77 | let mut rem = input; 78 | if rem.len() == 0 { return Err(ParseError::Eof("Folding White Space")); } 79 | while rem.len() > 0 { 80 | if is_wsp(rem[0]) { 81 | rem = &rem[1..]; 82 | } 83 | else if rem.len() > 2 && &rem[0..2]==b"\r\n" && is_wsp(rem[2]) { 84 | rem = &rem[3..]; 85 | } 86 | else { 87 | break; 88 | } 89 | } 90 | if rem.len() == input.len() { Err(ParseError::NotFound("Folding White Space")) } 91 | else { Ok((FWS, rem)) } 92 | } 93 | } 94 | impl Streamable for FWS { 95 | fn stream(&self, w: &mut W) -> Result { 96 | Ok(w.write(b" ")?) // FIXME - fold? 97 | } 98 | } 99 | impl_display!(FWS); 100 | 101 | // 3.2.2 102 | // ctext = %d33-39 / ; Printable US-ASCII 103 | // %d42-91 / ; characters not including 104 | // %d93-126 / ; "(", ")", or "\" 105 | // obs-ctext 106 | #[inline] 107 | pub fn is_ctext(c: u8) -> bool { (c>=33 && c<=39) || (c>=42 && c<=91) || (c>=93 && c<=126) } 108 | def_cclass!(CText, is_ctext); 109 | impl_display!(CText); 110 | 111 | // 3.2.2 112 | // ccontent = ctext / quoted-pair / comment 113 | #[derive(Debug, Clone, PartialEq)] 114 | pub enum CContent { 115 | CText(CText), 116 | QuotedPair(QuotedPair), 117 | Comment(Comment), 118 | } 119 | impl Parsable for CContent { 120 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 121 | if let Ok((na, rem)) = CText::parse(input) { 122 | Ok((CContent::CText(na), rem)) 123 | } 124 | else if let Ok((asp, rem)) = QuotedPair::parse(input) { 125 | Ok((CContent::QuotedPair(asp), rem)) 126 | } 127 | else if let Ok((c, rem)) = Comment::parse(input) { 128 | Ok((CContent::Comment(c), rem)) 129 | } 130 | else { 131 | Err(ParseError::NotFound("CContent")) 132 | } 133 | } 134 | } 135 | impl Streamable for CContent { 136 | fn stream(&self, w: &mut W) -> Result { 137 | match *self { 138 | CContent::CText(ref x) => x.stream(w), 139 | CContent::QuotedPair(ref x) => x.stream(w), 140 | CContent::Comment(ref x) => x.stream(w), 141 | } 142 | } 143 | } 144 | impl_display!(CContent); 145 | 146 | // 3.2.2 147 | // comment = "(" *([FWS] ccontent) [FWS] ")" 148 | #[derive(Debug, Clone, PartialEq)] 149 | pub struct Comment { 150 | pub ccontent: Vec<(bool, CContent)>, // bool representing if whitespace preceeds it 151 | pub trailing_ws: bool, 152 | } 153 | impl Parsable for Comment { 154 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 155 | let mut rem: &[u8] = input; 156 | if rem.len() == 0 { return Err(ParseError::Eof("Comment")); } 157 | req!(rem, b"(", input); 158 | let mut ccontent: Vec<(bool, CContent)> = Vec::new(); 159 | let mut ws: bool = false; 160 | while rem.len() > 0 { 161 | let t = parse!(FWS, rem); 162 | ws = t.is_ok(); 163 | if let Ok(cc) = parse!(CContent, rem) { 164 | ccontent.push((ws, cc)); 165 | continue; 166 | } 167 | break; 168 | } 169 | req!(rem, b")", input); 170 | return Ok((Comment { 171 | ccontent: ccontent, 172 | trailing_ws: ws, 173 | }, rem)); 174 | } 175 | } 176 | impl Streamable for Comment { 177 | fn stream(&self, w: &mut W) -> Result { 178 | let mut count: usize = 0; 179 | count += w.write(b"(")?; 180 | for &(ws, ref cc) in &self.ccontent { 181 | if ws { count += w.write(b" ")? } 182 | count += cc.stream(w)?; 183 | } 184 | if self.trailing_ws { count += w.write(b" ")? } 185 | count += w.write(b")")?; 186 | Ok(count) 187 | } 188 | } 189 | impl_display!(Comment); 190 | 191 | // 3.2.2 192 | // CFWS = (1*([FWS] comment) [FWS]) / FWS 193 | #[derive(Debug, Clone, PartialEq)] 194 | pub struct CFWS { 195 | pub comments: Vec<(bool, Comment)>, // bool representing if whitespace preceeds it 196 | pub trailing_ws: bool, 197 | } 198 | impl Parsable for CFWS { 199 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 200 | if input.len() == 0 { return Err(ParseError::Eof("Comment Folding White Space")); } 201 | let mut comments: Vec<(bool, Comment)> = Vec::new(); 202 | let mut rem = input; 203 | let mut ws: bool = false; 204 | while rem.len() > 0 { 205 | let w = parse!(FWS, rem); 206 | ws = w.is_ok(); 207 | if let Ok(comment) = parse!(Comment, rem) { 208 | comments.push((ws, comment)); 209 | continue; 210 | } 211 | break; 212 | } 213 | if comments.len() > 0 || ws { 214 | Ok((CFWS { 215 | comments: comments, 216 | trailing_ws: ws, 217 | }, rem)) 218 | } else { 219 | Err(ParseError::NotFound("Comment Folding White Space")) 220 | } 221 | } 222 | } 223 | impl Streamable for CFWS { 224 | fn stream(&self, w: &mut W) -> Result { 225 | let mut count: usize = 0; 226 | for &(ws, ref comment) in &self.comments { 227 | if ws { count += w.write(b" ")? } 228 | count += comment.stream(w)?; 229 | } 230 | if self.trailing_ws { count += w.write(b" ")? } 231 | Ok(count) 232 | } 233 | } 234 | impl_display!(CFWS); 235 | 236 | // 3.2.3 237 | // atext = ALPHA / DIGIT / ; Printable US-ASCII 238 | // "!" / "#" / ; characters not including 239 | // "$" / "%" / ; specials. Used for atoms. 240 | // "&" / "'" / 241 | // "*" / "+" / 242 | // "-" / "/" / 243 | // "=" / "?" / 244 | // "^" / "_" / 245 | // "`" / "{" / 246 | // "|" / "}" / 247 | // "~" 248 | #[inline] 249 | pub fn is_atext(c: u8) -> bool { 250 | is_alpha(c) || is_digit(c) 251 | || c==b'!' || c==b'#' || c==b'$' || c==b'%' 252 | || c==b'&' || c==b'\'' || c==b'*' || c==b'+' 253 | || c==b'-' || c==b'/' || c==b'=' || c==b'?' 254 | || c==b'^' || c==b'_' || c==b'`' || c==b'{' 255 | || c==b'|' || c==b'}' || c==b'~' 256 | } 257 | def_cclass!(AText, is_atext); 258 | impl_display!(AText); 259 | 260 | // 3.2.3 261 | // atom = [CFWS] 1*atext [CFWS] 262 | #[derive(Debug, Clone, PartialEq)] 263 | pub struct Atom { 264 | pub pre_cfws: Option, 265 | pub atext: AText, 266 | pub post_cfws: Option, 267 | } 268 | impl Parsable for Atom { 269 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 270 | if input.len()==0 { return Err(ParseError::Eof("Atom")); } 271 | let mut rem = input; 272 | let pre_cfws = parse!(CFWS, rem); 273 | if let Ok(atext) = parse!(AText, rem) { 274 | let post_cfws = parse!(CFWS, rem); 275 | return Ok((Atom { 276 | pre_cfws: pre_cfws.ok(), 277 | atext: atext, 278 | post_cfws: post_cfws.ok(), 279 | }, rem)); 280 | } 281 | Err(ParseError::NotFound("Atom")) 282 | } 283 | } 284 | impl Streamable for Atom { 285 | fn stream(&self, w: &mut W) -> Result { 286 | let mut count: usize = 0; 287 | if let Some(ref cfws) = self.pre_cfws { 288 | count += cfws.stream(w)?; 289 | } 290 | count += self.atext.stream(w)?; 291 | if let Some(ref cfws) = self.post_cfws { 292 | count += cfws.stream(w)?; 293 | } 294 | Ok(count) 295 | } 296 | } 297 | impl_display!(Atom); 298 | 299 | // 3.2.3 300 | // dot-atom-text = 1*atext *("." 1*atext) 301 | #[derive(Debug, Clone, PartialEq)] 302 | pub struct DotAtomText(pub Vec); 303 | impl Parsable for DotAtomText { 304 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 305 | let mut rem = input; 306 | let mut parts: Vec = Vec::new(); 307 | match parse!(AText, rem) { 308 | Ok(part) => parts.push(part), 309 | Err(e) => return Err(e), 310 | } 311 | while rem.len() > 0 { 312 | if rem[0]!=b'.' { break; }; 313 | let rem2 = &rem[1..]; 314 | if let Ok((part, r)) = AText::parse(rem2) { 315 | rem = r; 316 | parts.push(part); 317 | continue; 318 | } else { 319 | break; 320 | } 321 | } 322 | Ok((DotAtomText(parts), rem)) 323 | } 324 | } 325 | impl Streamable for DotAtomText { 326 | fn stream(&self, w: &mut W) -> Result { 327 | let mut count: usize = 0; 328 | let mut virgin: bool = true; 329 | for part in &self.0 { 330 | if !virgin { count += w.write(b".")? } 331 | count += part.stream(w)?; 332 | virgin = false; 333 | } 334 | Ok(count) 335 | } 336 | } 337 | impl_display!(DotAtomText); 338 | 339 | // 3.2.3 340 | // dot-atom = [CFWS] dot-atom-text [CFWS] 341 | #[derive(Debug, Clone, PartialEq)] 342 | pub struct DotAtom { 343 | pub pre_cfws: Option, 344 | pub dot_atom_text: DotAtomText, 345 | pub post_cfws: Option, 346 | } 347 | impl Parsable for DotAtom { 348 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 349 | let mut rem = input; 350 | if rem.len() == 0 { return Err(ParseError::Eof("DotAtom")); } 351 | let pre_cfws = parse!(CFWS, rem); 352 | if let Ok(dat) = parse!(DotAtomText, rem) { 353 | let post_cfws = parse!(CFWS, rem); 354 | Ok((DotAtom { 355 | pre_cfws: pre_cfws.ok(), 356 | dot_atom_text: dat, 357 | post_cfws: post_cfws.ok(), 358 | }, rem)) 359 | } else { 360 | Err(ParseError::NotFound("DotAtom")) 361 | } 362 | } 363 | } 364 | impl Streamable for DotAtom { 365 | fn stream(&self, w: &mut W) -> Result { 366 | let mut count: usize = 0; 367 | if let Some(ref cfws) = self.pre_cfws { 368 | count += cfws.stream(w)?; 369 | } 370 | count += self.dot_atom_text.stream(w)?; 371 | if let Some(ref cfws) = self.post_cfws { 372 | count += cfws.stream(w)?; 373 | } 374 | Ok(count) 375 | } 376 | } 377 | impl_display!(DotAtom); 378 | 379 | // 3.2.3 (we don't need to parse this one, it is not used. could be used as a tokenization 380 | // point in lexical analysis) 381 | // specials = "(" / ")" / ; Special characters that do 382 | // "<" / ">" / ; not appear in atext 383 | // "[" / "]" / 384 | // ":" / ";" / 385 | // "@" / "\" / 386 | // "," / "." / 387 | // DQUOTE 388 | 389 | // 3.2.4 390 | // qtext = %d33 / ; Printable US-ASCII 391 | // %d35-91 / ; characters not including 392 | // %d93-126 / ; "\" or the quote character 393 | // obs-qtext 394 | #[inline] 395 | pub fn is_qtext(c: u8) -> bool { c==33 || (c>=35 && c<=91) || (c>=93 && c<=126) } 396 | def_cclass!(QText, is_qtext); 397 | impl_display!(QText); 398 | 399 | // 3.2.4 400 | // qcontent = qtext / quoted-pair 401 | #[derive(Debug, Clone, PartialEq)] 402 | pub enum QContent { 403 | QText(QText), 404 | QuotedPair(QuotedPair), 405 | } 406 | impl Parsable for QContent { 407 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 408 | if input.len() == 0 { return Err(ParseError::Eof("QContent")); } 409 | if let Ok((x, rem)) = QText::parse(input) { 410 | Ok((QContent::QText(x), rem)) 411 | } 412 | else if let Ok((x, rem)) = QuotedPair::parse(input) { 413 | Ok((QContent::QuotedPair(x), rem)) 414 | } 415 | else { 416 | Err(ParseError::NotFound("QContent")) 417 | } 418 | } 419 | } 420 | impl Streamable for QContent { 421 | fn stream(&self, w: &mut W) -> Result { 422 | match *self { 423 | QContent::QText(ref x) => x.stream(w), 424 | QContent::QuotedPair(ref x) => x.stream(w), 425 | } 426 | } 427 | } 428 | impl_display!(QContent); 429 | 430 | // 3.2.4 431 | // quoted-string = [CFWS] 432 | // DQUOTE *([FWS] qcontent) [FWS] DQUOTE 433 | // [CFWS] 434 | #[derive(Debug, Clone, PartialEq)] 435 | pub struct QuotedString { 436 | pub pre_cfws: Option, 437 | pub qcontent: Vec<(bool, QContent)>, // bool representing if whitespace preceeds it 438 | pub trailing_ws: bool, 439 | pub post_cfws: Option, 440 | } 441 | impl Parsable for QuotedString { 442 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 443 | if input.len() == 0 { return Err(ParseError::Eof("QuotedString")); } 444 | let mut rem = input; 445 | let pre_cfws = parse!(CFWS, rem); 446 | req!(rem, b"\"", input); 447 | let mut qcontent: Vec<(bool, QContent)> = Vec::new(); 448 | let mut ws: bool = false; 449 | while rem.len() > 0 { 450 | let t = parse!(FWS, rem); 451 | ws = t.is_ok(); 452 | if let Ok(qc) = parse!(QContent, rem) { 453 | qcontent.push((ws, qc)); 454 | continue; 455 | } 456 | break; 457 | } 458 | req!(rem, b"\"", input); 459 | let post_cfws = parse!(CFWS, rem); 460 | Ok((QuotedString { 461 | pre_cfws: pre_cfws.ok(), 462 | qcontent: qcontent, 463 | trailing_ws: ws, 464 | post_cfws: post_cfws.ok() }, rem)) 465 | } 466 | } 467 | impl Streamable for QuotedString { 468 | fn stream(&self, w: &mut W) -> Result { 469 | let mut count: usize = 0; 470 | if let Some(ref cfws) = self.pre_cfws { 471 | count += cfws.stream(w)?; 472 | } 473 | count += w.write(b"\"")?; 474 | for &(ws, ref qc) in &self.qcontent { 475 | if ws { 476 | count += w.write(b" ")?; 477 | } 478 | count += qc.stream(w)?; 479 | } 480 | if self.trailing_ws { 481 | count += w.write(b" ")?; 482 | } 483 | count += w.write(b"\"")?; 484 | if let Some(ref cfws) = self.post_cfws { 485 | count += cfws.stream(w)?; 486 | } 487 | Ok(count) 488 | } 489 | } 490 | impl_display!(QuotedString); 491 | 492 | // 3.2.5 493 | // word = atom / quoted-string 494 | #[derive(Debug, Clone, PartialEq)] 495 | pub enum Word { 496 | Atom(Atom), 497 | QuotedString(QuotedString), 498 | } 499 | impl Parsable for Word { 500 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 501 | if input.len() == 0 { return Err(ParseError::Eof("Word")); } 502 | if let Ok((x, rem)) = Atom::parse(input) { 503 | Ok((Word::Atom(x), rem)) 504 | } 505 | else if let Ok((x, rem)) = QuotedString::parse(input) { 506 | Ok((Word::QuotedString(x), rem)) 507 | } 508 | else { 509 | Err(ParseError::NotFound("Word")) 510 | } 511 | } 512 | } 513 | impl Streamable for Word { 514 | fn stream(&self, w: &mut W) -> Result { 515 | match *self { 516 | Word::Atom(ref x) => x.stream(w), 517 | Word::QuotedString(ref x) => x.stream(w), 518 | } 519 | } 520 | } 521 | impl_display!(Word); 522 | 523 | // 3.2.5 524 | // phrase = 1*word / obs-phrase 525 | #[derive(Debug, Clone, PartialEq)] 526 | pub struct Phrase(pub Vec); 527 | impl Parsable for Phrase { 528 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 529 | if input.len() == 0 { return Err(ParseError::Eof("Phrase")); } 530 | let mut rem = input; 531 | let mut output: Vec = Vec::new(); 532 | while let Ok(word) = parse!(Word, rem) { 533 | output.push(word); 534 | } 535 | if output.len() == 0 { 536 | Err(ParseError::NotFound("Phrase")) 537 | } else { 538 | Ok((Phrase(output), rem)) 539 | } 540 | } 541 | } 542 | impl Streamable for Phrase { 543 | fn stream(&self, w: &mut W) -> Result { 544 | let mut count: usize = 0; 545 | for word in &self.0 { 546 | count += word.stream(w)?; 547 | } 548 | Ok(count) 549 | } 550 | } 551 | impl_display!(Phrase); 552 | 553 | // 3.2.5 554 | // unstructured = (*([FWS] VCHAR) *WSP) / obs-unstruct 555 | #[derive(Debug, Clone, PartialEq)] 556 | pub struct Unstructured { 557 | pub leading_ws: bool, 558 | pub parts: Vec, // always separated by whitespace 559 | pub trailing_ws: bool, 560 | } 561 | impl Parsable for Unstructured { 562 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 563 | if input.len() == 0 { return Err(ParseError::Eof("Unstructured")); } 564 | let mut rem = input; 565 | let mut output: Vec = Vec::new(); 566 | let t = parse!(FWS, rem); 567 | let leading_ws: bool = t.is_ok(); 568 | while rem.len() > 0 { 569 | let mut rem2 = match FWS::parse(rem) { 570 | Ok((_, rem2)) => rem2, 571 | Err(_) => rem, 572 | }; 573 | if let Ok(vchar) = parse!(VChar, rem2) { 574 | rem = rem2; 575 | output.push(vchar); 576 | continue; 577 | } 578 | break; 579 | } 580 | if output.len() == 0 { return Err(ParseError::NotFound("Unstructured")); } 581 | let t = parse!(WSP, rem); 582 | Ok((Unstructured { 583 | leading_ws: leading_ws, 584 | parts: output, 585 | trailing_ws: t.is_ok() 586 | }, rem)) 587 | } 588 | } 589 | impl Streamable for Unstructured { 590 | fn stream(&self, w: &mut W) -> Result { 591 | let mut count: usize = 0; 592 | if self.leading_ws { count += w.write(b" ")?; } 593 | let mut first: bool = true; 594 | for vc in &self.parts { 595 | if !first { 596 | count += w.write(b" ")?; 597 | } 598 | count += vc.stream(w)?; 599 | first = false; 600 | } 601 | if self.trailing_ws { count += w.write(b" ")?; } 602 | Ok(count) 603 | } 604 | } 605 | impl_display!(Unstructured); 606 | 607 | // 3.4.1 608 | // local-part = dot-atom / quoted-string / obs-local-part 609 | #[derive(Debug, Clone, PartialEq)] 610 | pub enum LocalPart { 611 | DotAtom(DotAtom), 612 | QuotedString(QuotedString), 613 | } 614 | impl Parsable for LocalPart { 615 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 616 | if input.len() == 0 { return Err(ParseError::Eof("LocalPart")); } 617 | if let Ok((x, rem)) = DotAtom::parse(input) { 618 | Ok((LocalPart::DotAtom(x), rem)) 619 | } 620 | else if let Ok((x, rem)) = QuotedString::parse(input) { 621 | Ok((LocalPart::QuotedString(x), rem)) 622 | } 623 | else { 624 | Err(ParseError::NotFound("LocalPart")) 625 | } 626 | } 627 | } 628 | impl Streamable for LocalPart { 629 | fn stream(&self, w: &mut W) -> Result { 630 | match *self { 631 | LocalPart::DotAtom(ref x) => x.stream(w), 632 | LocalPart::QuotedString(ref x) => x.stream(w), 633 | } 634 | } 635 | } 636 | impl_display!(LocalPart); 637 | 638 | // 3.4.1 639 | // dtext = %d33-90 / ; Printable US-ASCII 640 | // %d94-126 / ; characters not including 641 | // obs-dtext ; "[", "]", or "\" 642 | #[inline] 643 | pub fn is_dtext(c: u8) -> bool { (c>=33 && c<=90) || (c>=94 && c<=126) } 644 | def_cclass!(DText, is_dtext); 645 | impl_display!(DText); 646 | 647 | // 3.4.1 648 | // domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS] 649 | #[derive(Debug, Clone, PartialEq)] 650 | pub struct DomainLiteral { 651 | pub pre_cfws: Option, 652 | pub dtext: Vec<(bool, DText)>, // bool representing if whitespace preceeds it 653 | pub trailing_ws: bool, 654 | pub post_cfws: Option, 655 | } 656 | impl Parsable for DomainLiteral { 657 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 658 | if input.len() == 0 { return Err(ParseError::Eof("DomainLiteral")); } 659 | let mut rem = input; 660 | let mut dtext: Vec<(bool, DText)> = Vec::new(); 661 | let pre_cfws = parse!(CFWS, rem); 662 | req!(rem, b"[", input); 663 | let mut ws: bool = false; 664 | while rem.len() > 0 { 665 | let t = parse!(FWS, rem); 666 | ws = t.is_ok(); 667 | if let Ok(d) = parse!(DText, rem) { 668 | dtext.push((ws,d)); 669 | continue; 670 | } 671 | break; 672 | } 673 | req!(rem, b"]", input); 674 | let post_cfws = parse!(CFWS, rem); 675 | Ok((DomainLiteral { 676 | pre_cfws: pre_cfws.ok(), 677 | dtext: dtext, 678 | trailing_ws: ws, 679 | post_cfws: post_cfws.ok(), 680 | }, rem)) 681 | } 682 | } 683 | impl Streamable for DomainLiteral { 684 | fn stream(&self, w: &mut W) -> Result { 685 | let mut count: usize = 0; 686 | if let Some(ref cfws) = self.pre_cfws { 687 | count += cfws.stream(w)?; 688 | } 689 | count += w.write(b"[")?; 690 | for &(ws, ref dt) in &self.dtext { 691 | if ws { count += w.write(b" ")?; } 692 | count += dt.stream(w)?; 693 | } 694 | count += w.write(b"]")?; 695 | if let Some(ref cfws) = self.post_cfws { 696 | count += cfws.stream(w)?; 697 | } 698 | Ok(count) 699 | } 700 | } 701 | impl_display!(DomainLiteral); 702 | 703 | // 3.4.1 704 | // domain = dot-atom / domain-literal / obs-domain 705 | #[derive(Debug, Clone, PartialEq)] 706 | pub enum Domain { 707 | DotAtom(DotAtom), 708 | DomainLiteral(DomainLiteral), 709 | } 710 | impl Parsable for Domain { 711 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 712 | if input.len() == 0 { return Err(ParseError::Eof("Domain")); } 713 | if let Ok((x, rem)) = DotAtom::parse(input) { 714 | Ok((Domain::DotAtom(x), rem)) 715 | } 716 | else if let Ok((x, rem)) = DomainLiteral::parse(input) { 717 | Ok((Domain::DomainLiteral(x), rem)) 718 | } 719 | else { 720 | Err(ParseError::NotFound("Domain")) 721 | } 722 | } 723 | } 724 | impl Streamable for Domain { 725 | fn stream(&self, w: &mut W) -> Result { 726 | match *self { 727 | Domain::DotAtom(ref x) => x.stream(w), 728 | Domain::DomainLiteral(ref x) => x.stream(w), 729 | } 730 | } 731 | } 732 | impl_display!(Domain); 733 | 734 | // 3.4.1 735 | // addr-spec = local-part "@" domain 736 | #[derive(Debug, Clone, PartialEq)] 737 | pub struct AddrSpec { 738 | pub local_part: LocalPart, 739 | pub domain: Domain, 740 | } 741 | impl Parsable for AddrSpec { 742 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 743 | if input.len() == 0 { return Err(ParseError::Eof("AddrSpec")); } 744 | if let Ok((lp, rem)) = LocalPart::parse(input) { 745 | if rem.len() > 0 && rem[0]==b'@' { 746 | if let Ok((d, rem)) = Domain::parse(&rem[1..]) { 747 | return Ok((AddrSpec { 748 | local_part: lp, 749 | domain: d 750 | }, rem)); 751 | } 752 | } 753 | } 754 | Err(ParseError::NotFound("AddrSpec")) 755 | } 756 | } 757 | impl Streamable for AddrSpec { 758 | fn stream(&self, w: &mut W) -> Result { 759 | Ok(self.local_part.stream(w)? 760 | + w.write(b"@")? 761 | + self.domain.stream(w)?) 762 | } 763 | } 764 | impl_display!(AddrSpec); 765 | 766 | // 3.4 767 | // angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / 768 | // obs-angle-addr 769 | #[derive(Debug, Clone, PartialEq)] 770 | pub struct AngleAddr{ 771 | pub pre_cfws: Option, 772 | pub addr_spec: AddrSpec, 773 | pub post_cfws: Option, 774 | } 775 | impl Parsable for AngleAddr { 776 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 777 | if input.len() == 0 { return Err(ParseError::Eof("AngleAddr")); } 778 | let mut rem = input; 779 | let pre_cfws = parse!(CFWS, rem); 780 | req!(rem, b"<", input); 781 | if let Ok(aspec) = parse!(AddrSpec, rem) { 782 | req!(rem, b">", input); 783 | let post_cfws = parse!(CFWS, rem); 784 | return Ok((AngleAddr { 785 | pre_cfws: pre_cfws.ok(), 786 | addr_spec: aspec, 787 | post_cfws: post_cfws.ok(), 788 | }, rem)); 789 | } 790 | Err(ParseError::NotFound("AngleAddr")) 791 | } 792 | } 793 | impl Streamable for AngleAddr { 794 | fn stream(&self, w: &mut W) -> Result { 795 | let mut count: usize = 0; 796 | if let Some(ref cfws) = self.pre_cfws { 797 | count += cfws.stream(w)? 798 | } 799 | count += w.write(b"<")?; 800 | count += self.addr_spec.stream(w)?; 801 | count += w.write(b">")?; 802 | if let Some(ref cfws) = self.post_cfws { 803 | count += cfws.stream(w)? 804 | } 805 | Ok(count) 806 | } 807 | } 808 | impl_display!(AngleAddr); 809 | 810 | // 3.4 811 | // display-name = phrase 812 | #[derive(Debug, Clone, PartialEq)] 813 | pub struct DisplayName(pub Phrase); 814 | impl Parsable for DisplayName { 815 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 816 | Phrase::parse(input).map(|(p,rem)| (DisplayName(p),rem)) 817 | } 818 | } 819 | impl Streamable for DisplayName { 820 | fn stream(&self, w: &mut W) -> Result { 821 | self.0.stream(w) 822 | } 823 | } 824 | impl_display!(DisplayName); 825 | 826 | // 3.4 827 | // name-addr = [display-name] angle-addr 828 | #[derive(Debug, Clone, PartialEq)] 829 | pub struct NameAddr { 830 | pub display_name: Option, 831 | pub angle_addr: AngleAddr 832 | } 833 | impl Parsable for NameAddr { 834 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 835 | if input.len() == 0 { return Err(ParseError::Eof("NameAddr")); } 836 | let mut rem = input; 837 | let maybe_dn = parse!(DisplayName, rem); 838 | if let Ok(aa) = parse!(AngleAddr, rem) { 839 | return Ok((NameAddr { 840 | display_name: maybe_dn.ok(), 841 | angle_addr: aa, 842 | }, rem)); 843 | } 844 | Err(ParseError::NotFound("NameAddr")) 845 | } 846 | } 847 | impl Streamable for NameAddr { 848 | fn stream(&self, w: &mut W) -> Result { 849 | let mut count: usize = 0; 850 | if self.display_name.is_some() { 851 | count += self.display_name.as_ref().unwrap().stream(w)?; 852 | } 853 | count += self.angle_addr.stream(w)?; 854 | Ok(count) 855 | } 856 | } 857 | impl_display!(NameAddr); 858 | 859 | // 3.4 860 | // mailbox = name-addr / addr-spec 861 | #[derive(Debug, Clone, PartialEq)] 862 | pub enum Mailbox { 863 | NameAddr(NameAddr), 864 | AddrSpec(AddrSpec), 865 | } 866 | impl Parsable for Mailbox { 867 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 868 | if input.len() == 0 { return Err(ParseError::Eof("Mailbox")); } 869 | if let Ok((x, rem)) = NameAddr::parse(input) { 870 | Ok((Mailbox::NameAddr(x), rem)) 871 | } 872 | else if let Ok((x, rem)) = AddrSpec::parse(input) { 873 | Ok((Mailbox::AddrSpec(x), rem)) 874 | } 875 | else { 876 | Err(ParseError::NotFound("Mailbox")) 877 | } 878 | } 879 | } 880 | impl Streamable for Mailbox { 881 | fn stream(&self, w: &mut W) -> Result { 882 | match *self { 883 | Mailbox::NameAddr(ref na) => na.stream(w), 884 | Mailbox::AddrSpec(ref asp) => asp.stream(w), 885 | } 886 | } 887 | } 888 | impl_display!(Mailbox); 889 | 890 | // 3.4 891 | // mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list 892 | #[derive(Debug, Clone, PartialEq)] 893 | pub struct MailboxList(pub Vec); 894 | impl Parsable for MailboxList { 895 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 896 | if input.len() == 0 { return Err(ParseError::Eof("Mailbox List")); } 897 | let mut rem = input; 898 | let mut output: Vec = Vec::new(); 899 | let mut savedrem = rem; 900 | while let Ok(mailbox) = parse!(Mailbox, rem) { 901 | savedrem = rem; 902 | output.push(mailbox); 903 | if rem.len()==0 || rem[0]!=b',' { 904 | break; 905 | } 906 | rem = &rem[1..]; 907 | } 908 | rem = savedrem; 909 | if output.len() == 0 { 910 | Err(ParseError::NotFound("MailboxList")) 911 | } else { 912 | Ok((MailboxList(output), rem)) 913 | } 914 | } 915 | } 916 | impl Streamable for MailboxList { 917 | fn stream(&self, w: &mut W) -> Result { 918 | let mut count: usize = 0; 919 | let mut virgin: bool = true; 920 | for mb in &self.0 { 921 | if ! virgin { 922 | count += w.write(b",")?; 923 | } 924 | count += mb.stream(w)?; 925 | virgin = false; 926 | } 927 | Ok(count) 928 | } 929 | } 930 | impl_display!(MailboxList); 931 | 932 | // 3.4 933 | // group-list = mailbox-list / CFWS / obs-group-list 934 | #[derive(Debug, Clone, PartialEq)] 935 | pub enum GroupList { 936 | MailboxList(MailboxList), 937 | CFWS(CFWS), 938 | } 939 | impl Parsable for GroupList { 940 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 941 | if input.len() == 0 { return Err(ParseError::Eof("Group List")); } 942 | if let Ok((x, rem)) = MailboxList::parse(input) { 943 | Ok((GroupList::MailboxList(x), rem)) 944 | } 945 | else if let Ok((x, rem)) = CFWS::parse(input) { 946 | Ok((GroupList::CFWS(x), rem)) 947 | } 948 | else { 949 | Err(ParseError::NotFound("GroupList")) 950 | } 951 | } 952 | } 953 | impl Streamable for GroupList { 954 | fn stream(&self, w: &mut W) -> Result { 955 | match *self { 956 | GroupList::MailboxList(ref na) => na.stream(w), 957 | GroupList::CFWS(ref asp) => asp.stream(w), 958 | } 959 | } 960 | } 961 | impl_display!(GroupList); 962 | 963 | // 3.4 964 | // group = display-name ":" [group-list] ";" [CFWS] 965 | #[derive(Debug, Clone, PartialEq)] 966 | pub struct Group { 967 | pub display_name: DisplayName, 968 | pub group_list: Option, 969 | pub cfws: Option, 970 | } 971 | impl Parsable for Group { 972 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 973 | if input.len() == 0 { return Err(ParseError::Eof("Group")); } 974 | let mut rem = input; 975 | if let Ok(dn) = parse!(DisplayName, rem) { 976 | req!(rem, b":", input); 977 | let group_list = parse!(GroupList, rem); 978 | req!(rem, b";", input); 979 | let cfws = parse!(CFWS, rem); 980 | return Ok((Group { 981 | display_name: dn, 982 | group_list: group_list.ok(), 983 | cfws: cfws.ok(), 984 | }, rem)); 985 | } 986 | Err(ParseError::NotFound("Group")) 987 | } 988 | } 989 | impl Streamable for Group { 990 | fn stream(&self, w: &mut W) -> Result { 991 | let mut count: usize = 0; 992 | count += self.display_name.stream(w)?; 993 | count += w.write(b":")?; 994 | if let Some(ref gl) = self.group_list { 995 | count += gl.stream(w)?; 996 | } 997 | count += w.write(b";")?; 998 | if let Some(ref cfws) = self.cfws { 999 | count += cfws.stream(w)?; 1000 | } 1001 | Ok(count) 1002 | } 1003 | } 1004 | impl_display!(Group); 1005 | 1006 | // 3.4 1007 | // address = mailbox / group 1008 | #[derive(Debug, Clone, PartialEq)] 1009 | pub enum Address { 1010 | Mailbox(Mailbox), 1011 | Group(Group), 1012 | } 1013 | impl Parsable for Address { 1014 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1015 | if input.len() == 0 { return Err(ParseError::Eof("Address")); } 1016 | if let Ok((x, rem)) = Mailbox::parse(input) { 1017 | Ok((Address::Mailbox(x), rem)) 1018 | } 1019 | else if let Ok((x, rem)) = Group::parse(input) { 1020 | Ok((Address::Group(x), rem)) 1021 | } 1022 | else { 1023 | Err(ParseError::NotFound("Address")) 1024 | } 1025 | } 1026 | } 1027 | impl Streamable for Address { 1028 | fn stream(&self, w: &mut W) -> Result { 1029 | match *self { 1030 | Address::Mailbox(ref x) => x.stream(w), 1031 | Address::Group(ref x) => x.stream(w), 1032 | } 1033 | } 1034 | } 1035 | impl_display!(Address); 1036 | 1037 | // 3.4 1038 | // address-list = (address *("," address)) / obs-addr-list 1039 | #[derive(Debug, Clone, PartialEq)] 1040 | pub struct AddressList(pub Vec
); 1041 | impl Parsable for AddressList { 1042 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1043 | if input.len() == 0 { return Err(ParseError::Eof("Address List")); } 1044 | let mut rem = input; 1045 | let mut output: Vec
= Vec::new(); 1046 | let mut savedrem = rem; 1047 | while let Ok(mailbox) = parse!(Address, rem) { 1048 | savedrem = rem; 1049 | output.push(mailbox); 1050 | if rem.len()==0 || rem[0]!=b',' { 1051 | break; 1052 | } 1053 | rem = &rem[1..]; 1054 | } 1055 | rem = savedrem; 1056 | if output.len() == 0 { 1057 | Err(ParseError::NotFound("AddressList")) 1058 | } else { 1059 | Ok((AddressList(output), rem)) 1060 | } 1061 | } 1062 | } 1063 | impl Streamable for AddressList { 1064 | fn stream(&self, w: &mut W) -> Result { 1065 | let mut count: usize = 0; 1066 | let mut virgin: bool = true; 1067 | for a in &self.0 { 1068 | if ! virgin { 1069 | count += w.write(b",")?; 1070 | } 1071 | count += a.stream(w)?; 1072 | virgin = false; 1073 | } 1074 | Ok(count) 1075 | } 1076 | } 1077 | impl_display!(AddressList); 1078 | 1079 | // 3.3 1080 | // zone = (FWS ( "+" / "-" ) 4DIGIT) / obs-zone 1081 | #[derive(Debug, Clone, PartialEq)] 1082 | pub struct Zone(pub i32); 1083 | impl Parsable for Zone { 1084 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1085 | if input.len() == 0 { return Err(ParseError::Eof("Zone")); } 1086 | let mut rem = input; 1087 | let fws = parse!(FWS, rem); 1088 | if fws.is_err() { return Err(ParseError::NotFound("Zone")); } 1089 | if rem.len() < 5 { return Err(ParseError::NotFound("Zone")); } 1090 | let sign: i32 = match rem[0] { 1091 | b'+' => 1, 1092 | b'-' => -1, 1093 | _ => return Err(ParseError::NotFound("Zone")), 1094 | }; 1095 | if !is_digit(rem[1]) || !is_digit(rem[2]) || !is_digit(rem[3]) || !is_digit(rem[4]) { 1096 | return Err(ParseError::NotFound("Zone")); 1097 | } 1098 | let v: i32 = (1000 * ((rem[1]-48) as i32) 1099 | + 100 * ((rem[2]-48) as i32) 1100 | + 10 * ((rem[3]-48) as i32) 1101 | + ((rem[4]-48) as i32)) * sign; 1102 | Ok((Zone(v), &rem[5..])) 1103 | } 1104 | } 1105 | impl Streamable for Zone { 1106 | fn stream(&self, w: &mut W) -> Result { 1107 | let v = if self.0 < 0 { 1108 | w.write(b" -")?; 1109 | -self.0 1110 | } else { 1111 | w.write(b" +")?; 1112 | self.0 1113 | }; 1114 | write!(w, "{:04}", v)?; 1115 | Ok(6) 1116 | } 1117 | } 1118 | impl_display!(Zone); 1119 | 1120 | // 3.3 1121 | // second = 2DIGIT / obs-second 1122 | #[derive(Debug, Clone, PartialEq)] 1123 | pub struct Second(pub u8); 1124 | impl Parsable for Second { 1125 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1126 | if input.len() == 0 { return Err(ParseError::Eof("Second")); } 1127 | if input.len() < 2 { return Err(ParseError::NotFound("Second")); } 1128 | if !is_digit(input[0]) || !is_digit(input[1]) { 1129 | return Err(ParseError::NotFound("Second")); 1130 | } 1131 | let v: u8 = (10 * (input[0]-48)) + (input[1]-48); 1132 | Ok((Second(v), &input[2..])) 1133 | } 1134 | } 1135 | impl Streamable for Second { 1136 | fn stream(&self, w: &mut W) -> Result { 1137 | write!(w, "{:02}", self.0)?; 1138 | Ok(2) 1139 | } 1140 | } 1141 | impl_display!(Second); 1142 | 1143 | // 3.3 1144 | // minute = 2DIGIT / obs-minute 1145 | #[derive(Debug, Clone, PartialEq)] 1146 | pub struct Minute(pub u8); 1147 | impl Parsable for Minute { 1148 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1149 | if input.len() == 0 { return Err(ParseError::Eof("Minute")); } 1150 | if input.len() < 2 { return Err(ParseError::NotFound("Minute")); } 1151 | if !is_digit(input[0]) || !is_digit(input[1]) { 1152 | return Err(ParseError::NotFound("Minute")); 1153 | } 1154 | let v: u8 = (10 * (input[0]-48)) + (input[1]-48); 1155 | Ok((Minute(v), &input[2..])) 1156 | } 1157 | } 1158 | impl Streamable for Minute { 1159 | fn stream(&self, w: &mut W) -> Result { 1160 | write!(w, "{:02}", self.0)?; 1161 | Ok(2) 1162 | } 1163 | } 1164 | impl_display!(Minute); 1165 | 1166 | // 3.3 1167 | // hour = 2DIGIT / obs-hour 1168 | #[derive(Debug, Clone, PartialEq)] 1169 | pub struct Hour(pub u8); 1170 | impl Parsable for Hour { 1171 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1172 | if input.len() == 0 { return Err(ParseError::Eof("Hour")); } 1173 | if input.len() < 2 { return Err(ParseError::NotFound("Hour")); } 1174 | if !is_digit(input[0]) || !is_digit(input[1]) { 1175 | return Err(ParseError::NotFound("Hour")); 1176 | } 1177 | let v: u8 = (10 * (input[0]-48)) + (input[1]-48); 1178 | Ok((Hour(v), &input[2..])) 1179 | } 1180 | } 1181 | impl Streamable for Hour { 1182 | fn stream(&self, w: &mut W) -> Result { 1183 | write!(w, "{:02}", self.0)?; 1184 | Ok(2) 1185 | } 1186 | } 1187 | impl_display!(Hour); 1188 | 1189 | // 3.3 1190 | // time-of-day = hour ":" minute [ ":" second ] 1191 | #[derive(Debug, Clone, PartialEq)] 1192 | pub struct TimeOfDay { 1193 | pub hour: Hour, 1194 | pub minute: Minute, 1195 | pub second: Option 1196 | } 1197 | impl Parsable for TimeOfDay { 1198 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1199 | if input.len() == 0 { return Err(ParseError::Eof("TimeOfDay")); } 1200 | let mut rem = input; 1201 | if let Ok(hour) = parse!(Hour, rem) { 1202 | req!(rem, b":", input); 1203 | if let Ok(minute) = parse!(Minute, rem) { 1204 | let saved = rem; 1205 | if rem.len() > 0 && rem[0]==b':' { 1206 | rem = &rem[1..]; 1207 | if let Ok(second) = parse!(Second, rem) { 1208 | return Ok((TimeOfDay { 1209 | hour: hour, 1210 | minute: minute, 1211 | second: Some(second), 1212 | }, rem)); 1213 | } 1214 | } 1215 | return Ok((TimeOfDay { 1216 | hour: hour, 1217 | minute: minute, 1218 | second: None, 1219 | }, saved)); 1220 | } 1221 | } 1222 | Err(ParseError::NotFound("TimeOfDay")) 1223 | } 1224 | } 1225 | impl Streamable for TimeOfDay { 1226 | fn stream(&self, w: &mut W) -> Result { 1227 | if self.second.is_some() { 1228 | write!(w, "{:02}:{:02}:{:02}", self.hour.0, self.minute.0, 1229 | self.second.as_ref().unwrap().0)?; 1230 | Ok(8) 1231 | } else { 1232 | write!(w, "{:02}:{:02}", self.hour.0, self.minute.0)?; 1233 | Ok(5) 1234 | } 1235 | } 1236 | } 1237 | impl_display!(TimeOfDay); 1238 | 1239 | // 3.3 1240 | // time = time-of-day zone 1241 | #[derive(Debug, Clone, PartialEq)] 1242 | pub struct Time { 1243 | pub time_of_day: TimeOfDay, 1244 | pub zone: Zone, 1245 | } 1246 | impl Parsable for Time { 1247 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1248 | if input.len() == 0 { return Err(ParseError::Eof("Time")); } 1249 | let mut rem = input; 1250 | if let Ok(tod) = parse!(TimeOfDay, rem) { 1251 | if let Ok(zone) = parse!(Zone, rem) { 1252 | return Ok((Time { 1253 | time_of_day: tod, 1254 | zone: zone 1255 | }, rem)); 1256 | } 1257 | } 1258 | Err(ParseError::NotFound("Time")) 1259 | } 1260 | } 1261 | impl Streamable for Time { 1262 | fn stream(&self, w: &mut W) -> Result { 1263 | Ok(self.time_of_day.stream(w)? + self.zone.stream(w)?) 1264 | } 1265 | } 1266 | impl_display!(Time); 1267 | 1268 | // 3.3 1269 | // year = (FWS 4*DIGIT FWS) / obs-year 1270 | #[derive(Debug, Clone, PartialEq)] 1271 | pub struct Year(pub u32); 1272 | impl Parsable for Year { 1273 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1274 | if input.len() == 0 { return Err(ParseError::Eof("Year")); } 1275 | let mut rem = input; 1276 | let fws = parse!(FWS, rem); 1277 | if fws.is_err() { return Err(ParseError::NotFound("Year")); } 1278 | if rem.len() < 5 { return Err(ParseError::NotFound("Year")); } 1279 | if !is_digit(rem[0]) || !is_digit(rem[1]) || !is_digit(rem[2]) || !is_digit(rem[3]) { 1280 | return Err(ParseError::NotFound("Year")); 1281 | } 1282 | let v: u32 = 1000 * ((rem[0]-48) as u32) 1283 | + 100 * ((rem[1]-48) as u32) 1284 | + 10 * ((rem[2]-48) as u32) 1285 | + ((rem[3]-48) as u32); 1286 | rem = &rem[4..]; 1287 | let fws = parse!(FWS, rem); 1288 | if fws.is_err() { return Err(ParseError::NotFound("Year")); } 1289 | Ok((Year(v), rem)) 1290 | } 1291 | } 1292 | impl Streamable for Year { 1293 | fn stream(&self, w: &mut W) -> Result { 1294 | write!(w, " {:04} ", self.0)?; 1295 | Ok(6) 1296 | } 1297 | } 1298 | impl_display!(Year); 1299 | 1300 | // 3.3 1301 | // month = "Jan" / "Feb" / "Mar" / "Apr" / 1302 | // "May" / "Jun" / "Jul" / "Aug" / 1303 | // "Sep" / "Oct" / "Nov" / "Dec" 1304 | #[derive(Debug, Clone, PartialEq)] 1305 | pub struct Month(pub u8); 1306 | impl Parsable for Month { 1307 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1308 | if input.len() == 0 { return Err(ParseError::Eof("Month")); } 1309 | if input.len() < 3 { return Err(ParseError::NotFound("Month")); } 1310 | let three = &input[0..3].to_ascii_lowercase(); 1311 | let rem = &input[3..]; 1312 | if three==b"jan" { Ok((Month(1), rem)) } 1313 | else if three==b"feb" { Ok((Month(2), rem)) } 1314 | else if three==b"mar" { Ok((Month(3), rem)) } 1315 | else if three==b"apr" { Ok((Month(4), rem)) } 1316 | else if three==b"may" { Ok((Month(5), rem)) } 1317 | else if three==b"jun" { Ok((Month(6), rem)) } 1318 | else if three==b"jul" { Ok((Month(7), rem)) } 1319 | else if three==b"aug" { Ok((Month(8), rem)) } 1320 | else if three==b"sep" { Ok((Month(9), rem)) } 1321 | else if three==b"oct" { Ok((Month(10), rem)) } 1322 | else if three==b"nov" { Ok((Month(11), rem)) } 1323 | else if three==b"dec" { Ok((Month(12), rem)) } 1324 | else { Err(ParseError::NotFound("Month")) } 1325 | } 1326 | } 1327 | impl Streamable for Month { 1328 | fn stream(&self, w: &mut W) -> Result { 1329 | match self.0 { 1330 | 1 => Ok(w.write(b"Jan")?), 1331 | 2 => Ok(w.write(b"Feb")?), 1332 | 3 => Ok(w.write(b"Mar")?), 1333 | 4 => Ok(w.write(b"Apr")?), 1334 | 5 => Ok(w.write(b"May")?), 1335 | 6 => Ok(w.write(b"Jun")?), 1336 | 7 => Ok(w.write(b"Jul")?), 1337 | 8 => Ok(w.write(b"Aug")?), 1338 | 9 => Ok(w.write(b"Sep")?), 1339 | 10 => Ok(w.write(b"Oct")?), 1340 | 11 => Ok(w.write(b"Nov")?), 1341 | 12 => Ok(w.write(b"Dec")?), 1342 | _ => Err(IoError::new(::std::io::ErrorKind::InvalidData, "Month out of range")) 1343 | } 1344 | } 1345 | } 1346 | impl_display!(Month); 1347 | 1348 | // 3.3 1349 | // day = ([FWS] 1*2DIGIT FWS) / obs-day 1350 | #[derive(Debug, Clone, PartialEq)] 1351 | pub struct Day(pub u8); 1352 | impl Parsable for Day { 1353 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1354 | if input.len() == 0 { return Err(ParseError::Eof("Day")); } 1355 | let mut rem = input; 1356 | let _ = parse!(FWS, rem); 1357 | if rem.len() < 3 { return Err(ParseError::NotFound("Day")); } 1358 | if !is_digit(rem[0]) || (!is_digit(rem[1]) && !is_wsp(rem[1])) { 1359 | return Err(ParseError::NotFound("Day")); 1360 | } 1361 | let mut v: u8 = rem[0] - 48; 1362 | let mut num_consumed = 1; 1363 | // the day field may be 1 or 2 digits 1364 | if is_digit(rem[1]) { 1365 | v = 10 * v + rem[1] - 48; 1366 | num_consumed += 1; 1367 | } 1368 | rem = &rem[num_consumed..]; 1369 | let fws = parse!(FWS, rem); 1370 | if fws.is_err() { return Err(ParseError::NotFound("Day")); } 1371 | Ok((Day(v), rem)) 1372 | } 1373 | } 1374 | impl Streamable for Day { 1375 | fn stream(&self, w: &mut W) -> Result { 1376 | write!(w, " {} ", self.0)?; 1377 | Ok(4) 1378 | } 1379 | } 1380 | impl_display!(Day); 1381 | 1382 | // 3.3 1383 | // date = day month year 1384 | #[derive(Debug, Clone, PartialEq)] 1385 | pub struct Date { 1386 | pub day: Day, 1387 | pub month: Month, 1388 | pub year: Year, 1389 | } 1390 | impl Parsable for Date { 1391 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1392 | if input.len() == 0 { return Err(ParseError::Eof("Date")); } 1393 | let mut rem = input; 1394 | if let Ok(day) = parse!(Day, rem) { 1395 | if let Ok(month) = parse!(Month, rem) { 1396 | if let Ok(year) = parse!(Year, rem) { 1397 | return Ok((Date { 1398 | day: day, 1399 | month: month, 1400 | year: year, 1401 | }, rem)); 1402 | } 1403 | } 1404 | } 1405 | Err(ParseError::NotFound("Date")) 1406 | } 1407 | } 1408 | impl Streamable for Date { 1409 | fn stream(&self, w: &mut W) -> Result { 1410 | Ok(self.day.stream(w)? 1411 | + self.month.stream(w)? 1412 | + self.year.stream(w)?) 1413 | } 1414 | } 1415 | impl_display!(Date); 1416 | 1417 | // 3.3 1418 | // day-name = "Mon" / "Tue" / "Wed" / "Thu" / 1419 | // "Fri" / "Sat" / "Sun" 1420 | #[derive(Debug, Clone, PartialEq)] 1421 | pub struct DayName(pub u8); 1422 | impl Parsable for DayName { 1423 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1424 | if input.len() == 0 { return Err(ParseError::Eof("DayName")); } 1425 | if input.len() < 3 { return Err(ParseError::NotFound("DayName")); } 1426 | let three = &input[0..3].to_ascii_lowercase(); 1427 | let rem = &input[3..]; 1428 | if three==b"sun" { Ok((DayName(1), rem)) } 1429 | else if three==b"mon" { Ok((DayName(2), rem)) } 1430 | else if three==b"tue" { Ok((DayName(3), rem)) } 1431 | else if three==b"wed" { Ok((DayName(4), rem)) } 1432 | else if three==b"thu" { Ok((DayName(5), rem)) } 1433 | else if three==b"fri" { Ok((DayName(6), rem)) } 1434 | else if three==b"sat" { Ok((DayName(7), rem)) } 1435 | else { Err(ParseError::NotFound("DayName")) } 1436 | } 1437 | } 1438 | impl Streamable for DayName { 1439 | fn stream(&self, w: &mut W) -> Result { 1440 | match self.0 { 1441 | 1 => Ok(w.write(b"Sun")?), 1442 | 2 => Ok(w.write(b"Mon")?), 1443 | 3 => Ok(w.write(b"Tue")?), 1444 | 4 => Ok(w.write(b"Wed")?), 1445 | 5 => Ok(w.write(b"Thu")?), 1446 | 6 => Ok(w.write(b"Fri")?), 1447 | 7 => Ok(w.write(b"Sat")?), 1448 | _ => Err(IoError::new(::std::io::ErrorKind::InvalidData, "Day out of range")) 1449 | } 1450 | } 1451 | } 1452 | impl_display!(DayName); 1453 | 1454 | // 3.3 1455 | // day-of-week = ([FWS] day-name) / obs-day-of-week 1456 | #[derive(Debug, Clone, PartialEq)] 1457 | pub struct DayOfWeek { 1458 | pub pre_fws: Option, 1459 | pub day_name: DayName, 1460 | } 1461 | impl Parsable for DayOfWeek { 1462 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1463 | if input.len() == 0 { return Err(ParseError::Eof("DayOfWeek")); } 1464 | let mut rem = input; 1465 | let pre_fws = parse!(FWS, rem); 1466 | if let Ok(dn) = parse!(DayName, rem) { 1467 | Ok((DayOfWeek { 1468 | pre_fws: pre_fws.ok(), 1469 | day_name: dn, 1470 | }, rem)) 1471 | } else { 1472 | Err(ParseError::NotFound("DayOfWeek")) 1473 | } 1474 | } 1475 | } 1476 | impl Streamable for DayOfWeek { 1477 | fn stream(&self, w: &mut W) -> Result { 1478 | let mut count: usize = 0; 1479 | if let Some(ref fws) = self.pre_fws { 1480 | count += fws.stream(w)?; 1481 | } 1482 | count += self.day_name.stream(w)?; 1483 | Ok(count) 1484 | } 1485 | } 1486 | impl_display!(DayOfWeek); 1487 | 1488 | // 3.3 1489 | // date-time = [ day-of-week "," ] date time [CFWS] 1490 | #[derive(Debug, Clone, PartialEq)] 1491 | pub struct DateTime { 1492 | pub day_of_week: Option, 1493 | pub date: Date, 1494 | pub time: Time, 1495 | pub post_cfws: Option 1496 | } 1497 | impl Parsable for DateTime { 1498 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1499 | if input.len() == 0 { return Err(ParseError::Eof("DateTime")); } 1500 | let mut rem = input; 1501 | let mut day_of_week: Option = None; 1502 | if let Ok(dow) = parse!(DayOfWeek, rem) { 1503 | if rem.len() != 0 && rem[0]==b',' { 1504 | rem = &rem[1..]; 1505 | day_of_week = Some(dow); 1506 | } else { 1507 | rem = input; 1508 | } 1509 | } 1510 | if let Ok(date) = parse!(Date, rem) { 1511 | if let Ok(time) = parse!(Time, rem) { 1512 | let post_cfws = parse!(CFWS, rem); 1513 | return Ok((DateTime { 1514 | day_of_week: day_of_week, 1515 | date: date, 1516 | time: time, 1517 | post_cfws: post_cfws.ok() 1518 | }, rem)); 1519 | } 1520 | } 1521 | Err(ParseError::NotFound("DateTime")) 1522 | } 1523 | } 1524 | impl Streamable for DateTime { 1525 | fn stream(&self, w: &mut W) -> Result { 1526 | let mut count: usize = 0; 1527 | if let Some(ref dow) = self.day_of_week { 1528 | count += dow.stream(w)?; 1529 | count += w.write(b",")?; 1530 | } 1531 | count += self.date.stream(w)?; 1532 | count += self.time.stream(w)?; 1533 | if let Some(ref cfws) = self.post_cfws { 1534 | count += cfws.stream(w)?; 1535 | } 1536 | Ok(count) 1537 | } 1538 | } 1539 | impl_display!(DateTime); 1540 | 1541 | // 3.6.4 1542 | // no-fold-literal = "[" *dtext "]" 1543 | #[derive(Debug, Clone, PartialEq)] 1544 | pub struct NoFoldLiteral(pub DText); 1545 | impl Parsable for NoFoldLiteral { 1546 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1547 | if input.len() == 0 { return Err(ParseError::Eof("No-Fold Literal")); } 1548 | let mut rem = input; 1549 | req!(rem, b"[", input); 1550 | if let Ok(dtext) = parse!(DText, rem) { 1551 | req!(rem, b"]", input); 1552 | return Ok((NoFoldLiteral(dtext), rem)); 1553 | } 1554 | Err(ParseError::NotFound("No-Fold Literal")) 1555 | } 1556 | } 1557 | impl Streamable for NoFoldLiteral { 1558 | fn stream(&self, w: &mut W) -> Result { 1559 | Ok(w.write(b"[")? 1560 | + self.0.stream(w)? 1561 | + w.write(b"]")?) 1562 | } 1563 | } 1564 | impl_display!(NoFoldLiteral); 1565 | 1566 | // 3.6.4 1567 | // id-right = dot-atom-text / no-fold-literal / obs-id-right 1568 | #[derive(Debug, Clone, PartialEq)] 1569 | pub enum IdRight { 1570 | DotAtomText(DotAtomText), 1571 | NoFoldLiteral(NoFoldLiteral), 1572 | } 1573 | impl Parsable for IdRight { 1574 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1575 | if input.len() == 0 { return Err(ParseError::Eof("Id-right")); } 1576 | if let Ok((x, rem)) = DotAtomText::parse(input) { 1577 | Ok((IdRight::DotAtomText(x), rem)) 1578 | } 1579 | else if let Ok((x, rem)) = NoFoldLiteral::parse(input) { 1580 | Ok((IdRight::NoFoldLiteral(x), rem)) 1581 | } 1582 | else { 1583 | Err(ParseError::NotFound("Id-right")) 1584 | } 1585 | } 1586 | } 1587 | impl Streamable for IdRight { 1588 | fn stream(&self, w: &mut W) -> Result { 1589 | match *self { 1590 | IdRight::DotAtomText(ref x) => x.stream(w), 1591 | IdRight::NoFoldLiteral(ref x) => x.stream(w), 1592 | } 1593 | } 1594 | } 1595 | impl_display!(IdRight); 1596 | 1597 | // 3.6.4 1598 | // id-left = dot-atom-text / obs-id-left 1599 | #[derive(Debug, Clone, PartialEq)] 1600 | pub struct IdLeft(pub DotAtomText); 1601 | impl Parsable for IdLeft { 1602 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1603 | if input.len() == 0 { return Err(ParseError::Eof("Id-left")); } 1604 | let mut rem = input; 1605 | if let Ok(dat) = parse!(DotAtomText, rem) { 1606 | return Ok((IdLeft(dat), rem)); 1607 | } 1608 | Err(ParseError::NotFound("Id-left")) 1609 | } 1610 | } 1611 | impl Streamable for IdLeft { 1612 | fn stream(&self, w: &mut W) -> Result { 1613 | Ok(self.0.stream(w)?) 1614 | } 1615 | } 1616 | impl_display!(IdLeft); 1617 | 1618 | // 3.6.4 1619 | // msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS] 1620 | #[derive(Debug, Clone, PartialEq)] 1621 | pub struct MsgId { 1622 | pub pre_cfws: Option, 1623 | pub id_left: IdLeft, 1624 | pub id_right: IdRight, 1625 | pub post_cfws: Option, 1626 | } 1627 | impl Parsable for MsgId { 1628 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1629 | if input.len() == 0 { return Err(ParseError::Eof("MsgId")); } 1630 | let mut rem = input; 1631 | let pre_cfws = parse!(CFWS, rem); 1632 | req!(rem, b"<", input); 1633 | let idl = match parse!(IdLeft, rem) { 1634 | Err(e) => return Err(e), 1635 | Ok(idl) => idl 1636 | }; 1637 | req!(rem, b"@", input); 1638 | let idr = match parse!(IdRight, rem) { 1639 | Err(e) => return Err(e), 1640 | Ok(idr) => idr 1641 | }; 1642 | req!(rem, b">", input); 1643 | let post_cfws = parse!(CFWS, rem); 1644 | Ok((MsgId { 1645 | pre_cfws: pre_cfws.ok(), 1646 | id_left: idl, 1647 | id_right: idr, 1648 | post_cfws: post_cfws.ok(), 1649 | }, rem)) 1650 | } 1651 | } 1652 | impl Streamable for MsgId { 1653 | fn stream(&self, w: &mut W) -> Result { 1654 | let mut count: usize = 0; 1655 | if let Some(ref cfws) = self.pre_cfws { 1656 | count += cfws.stream(w)?; 1657 | } 1658 | count += w.write(b"<")?; 1659 | count += self.id_left.stream(w)?; 1660 | count += w.write(b"@")?; 1661 | count += self.id_right.stream(w)?; 1662 | count += w.write(b">")?; 1663 | if let Some(ref cfws) = self.post_cfws { 1664 | count += cfws.stream(w)?; 1665 | } 1666 | Ok(count) 1667 | } 1668 | } 1669 | impl_display!(MsgId); 1670 | 1671 | // 3.6.7 1672 | // received-token = word / angle-addr / addr-spec / domain 1673 | #[derive(Debug, Clone, PartialEq)] 1674 | pub enum ReceivedToken { 1675 | Word(Word), 1676 | AngleAddr(AngleAddr), 1677 | AddrSpec(AddrSpec), 1678 | Domain(Domain), 1679 | } 1680 | impl Parsable for ReceivedToken { 1681 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1682 | if input.len() == 0 { return Err(ParseError::Eof("Received Token")); } 1683 | if let Ok((x, rem)) = Word::parse(input) { 1684 | Ok((ReceivedToken::Word(x), rem)) 1685 | } 1686 | else if let Ok((x, rem)) = AngleAddr::parse(input) { 1687 | Ok((ReceivedToken::AngleAddr(x), rem)) 1688 | } 1689 | else if let Ok((x, rem)) = AddrSpec::parse(input) { 1690 | Ok((ReceivedToken::AddrSpec(x), rem)) 1691 | } 1692 | else if let Ok((x, rem)) = Domain::parse(input) { 1693 | Ok((ReceivedToken::Domain(x), rem)) 1694 | } 1695 | else { 1696 | Err(ParseError::NotFound("Received Token")) 1697 | } 1698 | } 1699 | } 1700 | impl Streamable for ReceivedToken { 1701 | fn stream(&self, w: &mut W) -> Result { 1702 | match *self { 1703 | ReceivedToken::Word(ref x) => x.stream(w), 1704 | ReceivedToken::AngleAddr(ref x) => x.stream(w), 1705 | ReceivedToken::AddrSpec(ref x) => x.stream(w), 1706 | ReceivedToken::Domain(ref x) => x.stream(w), 1707 | } 1708 | } 1709 | } 1710 | impl_display!(ReceivedToken); 1711 | 1712 | // 3.6.7 1713 | // path = angle-addr / ([CFWS] "<" [CFWS] ">" [CFWS]) 1714 | #[derive(Debug, Clone, PartialEq)] 1715 | pub enum Path { 1716 | AngleAddr(AngleAddr), 1717 | Other(Option, Option, Option), 1718 | } 1719 | impl Parsable for Path { 1720 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1721 | let mut rem = input; 1722 | if let Ok(aa) = parse!(AngleAddr, rem) { 1723 | return Ok((Path::AngleAddr(aa), rem)); 1724 | } 1725 | let c1 = parse!(CFWS, rem).ok(); 1726 | req!(rem, b"<", input); 1727 | let c2 = parse!(CFWS, rem).ok(); 1728 | req!(rem, b">", input); 1729 | let c3 = parse!(CFWS, rem).ok(); 1730 | Ok((Path::Other(c1,c2,c3), rem)) 1731 | } 1732 | } 1733 | impl Streamable for Path { 1734 | fn stream(&self, w: &mut W) -> Result { 1735 | match *self { 1736 | Path::AngleAddr(ref aa) => aa.stream(w), 1737 | Path::Other(ref c1, ref c2, ref c3) => { 1738 | let mut count: usize = 0; 1739 | if let &Some(ref c) = c1 { 1740 | count += c.stream(w)?; 1741 | } 1742 | count += w.write(b"<")?; 1743 | if let &Some(ref c) = c2 { 1744 | count += c.stream(w)?; 1745 | } 1746 | count += w.write(b">")?; 1747 | if let &Some(ref c) = c3 { 1748 | count += c.stream(w)?; 1749 | } 1750 | Ok(count) 1751 | } 1752 | } 1753 | } 1754 | } 1755 | impl_display!(Path); 1756 | 1757 | // 3.6.8 1758 | // ftext = %d33-57 / ; Printable US-ASCII 1759 | // %d59-126 ; characters not including 1760 | // ; ":". 1761 | #[inline] 1762 | pub fn is_ftext(c: u8) -> bool { (c>=33 && c<=57) || (c>=59 && c<=126) } 1763 | def_cclass!(FText, is_ftext); 1764 | impl_display!(FText); 1765 | 1766 | // 3.6.8 1767 | // field-name = 1*ftext 1768 | #[derive(Debug, Clone, PartialEq)] 1769 | pub struct FieldName(pub FText); 1770 | impl Parsable for FieldName { 1771 | fn parse(input: &[u8]) -> Result<(Self, &[u8]), ParseError> { 1772 | let mut rem = input; 1773 | if let Ok(ftext) = parse!(FText, rem) { 1774 | Ok((FieldName(ftext), rem)) 1775 | } else { 1776 | Err(ParseError::NotFound("Field Name")) 1777 | } 1778 | } 1779 | } 1780 | impl Streamable for FieldName { 1781 | fn stream(&self, w: &mut W) -> Result { 1782 | self.0.stream(w) 1783 | } 1784 | } 1785 | impl_display!(FieldName); 1786 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | 2 | macro_rules! assert_match { 3 | ($left:expr, $right:pat) => { 4 | match $left { 5 | $right => true, 6 | _ => false 7 | } 8 | }; 9 | } 10 | 11 | use rfc5322::{Parsable, ParseError, Streamable}; 12 | 13 | #[test] 14 | fn test_alpha() { 15 | use rfc5322::types::Alpha; 16 | 17 | let (alpha, rem) = Alpha::parse(b"abcdEFZz123").unwrap(); 18 | assert_eq!(alpha, Alpha(b"abcdEFZz".to_vec())); 19 | assert_eq!(rem, b"123"); 20 | 21 | let err = Alpha::parse(b"").err().unwrap(); 22 | assert_match!(err, ParseError::Eof("Alpha")); 23 | 24 | let err = Alpha::parse(b"123").err().unwrap(); 25 | assert_match!(err, ParseError::NotFound("Alpha")); 26 | 27 | let mut output: Vec = Vec::new(); 28 | assert_eq!(alpha.stream(&mut output).unwrap(), 8); 29 | assert_eq!(output, b"abcdEFZz".to_vec()); 30 | } 31 | 32 | #[test] 33 | fn test_parse_quoted_pair() { 34 | use rfc5322::types::QuotedPair; 35 | 36 | let err = QuotedPair::parse(b"not").err().unwrap(); 37 | assert_match!(err, ParseError::NotFound("Quoted Pair")); 38 | let err = QuotedPair::parse(b"\\").err().unwrap(); 39 | assert_match!(err, ParseError::NotFound("Quoted Pair")); 40 | let (token, rem) = QuotedPair::parse(b"\\n").unwrap(); 41 | assert_eq!(token, QuotedPair(b'n')); 42 | assert_eq!(rem, b""); 43 | let qp = QuotedPair(b'n'); 44 | let mut output: Vec = Vec::new(); 45 | assert_eq!(qp.stream(&mut output).unwrap(), 2); 46 | assert_eq!(output, b"\\n"); 47 | } 48 | 49 | #[test] 50 | fn test_fws() { 51 | use rfc5322::types::FWS; 52 | 53 | let (token, rem) = FWS::parse(b" ").unwrap(); 54 | assert_eq!(token, FWS); 55 | assert_eq!(rem, b""); 56 | let (token, rem) = FWS::parse(b" \r\n \t").unwrap(); 57 | assert_eq!(token, FWS); 58 | assert_eq!(rem, b""); 59 | let (token, rem) = FWS::parse(b" \r ").unwrap(); 60 | assert_eq!(token, FWS); 61 | assert_eq!(rem, b"\r "); 62 | let err = FWS::parse(b"\n ").err().unwrap(); 63 | assert_match!(err, ParseError::NotFound("Folding White Space")); 64 | let err = FWS::parse(b"\r\n").err().unwrap(); 65 | assert_match!(err, ParseError::NotFound("Folding White Space")); 66 | let (token, rem) = FWS::parse(b"\r\n\tx").unwrap(); 67 | assert_eq!(token, FWS); 68 | assert_eq!(rem, b"x"); 69 | } 70 | 71 | #[test] 72 | fn test_ctext() { 73 | use rfc5322::types::CText; 74 | 75 | let input = b"Thi,s;1:23isCt_#ext".to_vec(); 76 | let (token, remainder) = CText::parse(input.as_slice()).unwrap(); 77 | assert_eq!(token, CText(input.clone())); 78 | assert_eq!(remainder, b""); 79 | } 80 | 81 | #[test] 82 | fn test_ccontent() { 83 | use rfc5322::types::{CContent, CText, QuotedPair}; 84 | 85 | let input = b"Thi,s;1:23isCt_#ext".to_vec(); 86 | let (token, _) = CContent::parse(input.as_slice()).unwrap(); 87 | assert_eq!(token, CContent::CText(CText(input.clone()))); 88 | 89 | let input = b"\\n".to_vec(); 90 | let (token, _) = CContent::parse(input.as_slice()).unwrap(); 91 | assert_eq!(token, CContent::QuotedPair(QuotedPair(b'n'))); 92 | 93 | let input = b"(Comments can contain whitespace and \\( quoted \\\\ characters, and even ( nesting ) with or (without) whitepsace, but must balance parenthesis)".to_vec(); 94 | let (_, remainder) = CContent::parse(input.as_slice()).unwrap(); 95 | assert_eq!(remainder, b""); 96 | } 97 | 98 | #[test] 99 | fn test_comment() { 100 | use rfc5322::types::{Comment, CContent, CText, QuotedPair}; 101 | 102 | let input = b"( a,b,c\t \\nYes (and so on) \r\n )".to_vec(); 103 | let (token, rem) = Comment::parse(input.as_slice()).unwrap(); 104 | assert_eq!(token, Comment { 105 | ccontent: vec![ 106 | (true, CContent::CText( CText(b"a,b,c".to_vec()) )), 107 | (true, CContent::QuotedPair( QuotedPair(b'n') )), 108 | (false, CContent::CText( CText(b"Yes".to_vec()) )), 109 | (true, CContent::Comment(Comment { 110 | ccontent: vec![ 111 | (false, CContent::CText( CText(b"and".to_vec()) )), 112 | (true, CContent::CText( CText(b"so".to_vec()) )), 113 | (true, CContent::CText( CText(b"on".to_vec()) )) ], 114 | trailing_ws: false 115 | }))], 116 | trailing_ws: true, 117 | }); 118 | assert_eq!(rem, b""); 119 | 120 | let mut output: Vec = Vec::new(); 121 | assert_eq!(token.stream(&mut output).unwrap(), 27); 122 | assert_eq!(output, b"( a,b,c \\nYes (and so on) )"); 123 | } 124 | 125 | #[test] 126 | fn test_cfws() { 127 | use rfc5322::types::{CFWS, Comment, CContent, CText, QuotedPair}; 128 | 129 | let input = b" \t( a,b,c\t \\nYes (and so on) \r\n ) \r\n ".to_vec(); 130 | let (token, rem) = CFWS::parse(input.as_slice()).unwrap(); 131 | assert_eq!(token, CFWS { 132 | comments: vec![ 133 | (true, Comment { 134 | ccontent: vec![ 135 | (true, CContent::CText( CText(b"a,b,c".to_vec()) )), 136 | (true, CContent::QuotedPair( QuotedPair(b'n') )), 137 | (false, CContent::CText( CText(b"Yes".to_vec()) )), 138 | (true, CContent::Comment(Comment { 139 | ccontent: vec![ 140 | (false, CContent::CText( CText(b"and".to_vec()) )), 141 | (true, CContent::CText( CText(b"so".to_vec()) )), 142 | (true, CContent::CText( CText(b"on".to_vec()) )) ], 143 | trailing_ws: false 144 | }))], 145 | trailing_ws: true, 146 | })], 147 | trailing_ws: true, 148 | }); 149 | assert_eq!(rem, b""); 150 | 151 | let mut output: Vec = Vec::new(); 152 | assert_eq!(token.stream(&mut output).unwrap(), 29); 153 | assert_eq!(output, b" ( a,b,c \\nYes (and so on) ) "); 154 | 155 | let input = b"(abc)(def\r\n )".to_vec(); 156 | let (token, _) = CFWS::parse(input.as_slice()).unwrap(); 157 | assert_eq!(token, CFWS { 158 | comments: vec![ 159 | (false, Comment { 160 | ccontent: vec![ 161 | (false, CContent::CText( CText(b"abc".to_vec()) )) ], 162 | trailing_ws: false, 163 | }), 164 | (false, Comment { 165 | ccontent: vec![ 166 | (false, CContent::CText( CText(b"def".to_vec()) )) ], 167 | trailing_ws: true, 168 | }), 169 | ], 170 | trailing_ws: false, 171 | }); 172 | } 173 | 174 | #[test] 175 | fn test_atom() { 176 | use rfc5322::types::Atom; 177 | 178 | let input = b" \t( a,b,c\t \\nYes (and so on) \r\n ) atom\r\n ".to_vec(); 179 | let (atom, remainder) = Atom::parse(input.as_slice()).unwrap(); 180 | assert_eq!(atom.atext.0, b"atom".to_vec()); 181 | assert_eq!(remainder, b""); 182 | 183 | let input = b" \t AMZamz019!#$%&'*+-/=?^_`{|}~ \t ".to_vec(); 184 | let (atom, remainder) = Atom::parse(input.as_slice()).unwrap(); 185 | assert_eq!(atom.atext.0, b"AMZamz019!#$%&'*+-/=?^_`{|}~".to_vec()); 186 | assert_eq!(remainder, b""); 187 | 188 | let input = b" John Smith ".to_vec(); 189 | let (atom, remainder) = Atom::parse(input.as_slice()).unwrap(); 190 | assert_eq!(atom.atext.0, b"John".to_vec()); 191 | assert_eq!(remainder, b"Smith "); 192 | 193 | let mut output: Vec = Vec::new(); 194 | assert_eq!(atom.stream(&mut output).unwrap(), 6); 195 | assert_eq!(output, b" John "); 196 | } 197 | 198 | #[test] 199 | fn test_dot_atom() { 200 | use rfc5322::types::{DotAtom, AText}; 201 | 202 | let input = b" \r\n www.google.com. ".to_vec(); 203 | let (dot_atom, remainder) = DotAtom::parse(input.as_slice()).unwrap(); 204 | assert_eq!(dot_atom.dot_atom_text.0, vec![ 205 | AText(b"www".to_vec()), 206 | AText(b"google".to_vec()), 207 | AText(b"com".to_vec())]); 208 | assert!(dot_atom.pre_cfws.is_some()); 209 | assert!(dot_atom.post_cfws.is_none()); 210 | assert_eq!(remainder, b". "); 211 | } 212 | 213 | #[test] 214 | fn test_qcontent() { 215 | use rfc5322::types::{QContent, QText, QuotedPair}; 216 | 217 | let input = b"!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]\ 218 | ^_`abcdefghijklmnopqrstuvwxyz{|}~".to_vec(); 219 | let input2 = input.clone(); 220 | let (token, remainder) = QContent::parse(input.as_slice()).unwrap(); 221 | assert_eq!(token, QContent::QText( QText(input2) )); 222 | assert_eq!(remainder, b""); 223 | 224 | let input = b"\\nc".to_vec(); 225 | let (token, remainder) = QContent::parse(input.as_slice()).unwrap(); 226 | assert_eq!(token, QContent::QuotedPair( QuotedPair(b'n') )); 227 | assert_eq!(remainder, b"c"); 228 | 229 | let mut output: Vec = Vec::new(); 230 | assert_eq!(token.stream(&mut output).unwrap(), 2); 231 | assert_eq!(output, b"\\n"); 232 | } 233 | 234 | #[test] 235 | fn test_quoted_string() { 236 | use rfc5322::types::{QuotedString, QContent, QText}; 237 | 238 | let input = b" \t (a comment) \" \r\n bob joe\" (fred) ".to_vec(); 239 | let (token, remainder) = QuotedString::parse(input.as_slice()).unwrap(); 240 | assert_eq!(remainder, b""); 241 | assert!(token.pre_cfws.is_some()); 242 | assert_eq!(token.qcontent, vec![ 243 | (true, QContent::QText( QText(b"bob".to_vec()) )), 244 | (true, QContent::QText( QText(b"joe".to_vec()) )), 245 | ]); 246 | assert_eq!(token.trailing_ws, false); 247 | assert!(token.post_cfws.is_some()); 248 | 249 | let unterminated = b" \t (a comment) \" \r\n bob joe (fred) ".to_vec(); 250 | assert!(QuotedString::parse(unterminated.as_slice()).is_err()); 251 | } 252 | 253 | #[test] 254 | fn test_phrase() { 255 | use rfc5322::types::Phrase; 256 | 257 | let input = b" John \"the Snake\" Stevens".to_vec(); 258 | let (phrase, remainder) = Phrase::parse(input.as_slice()).unwrap(); 259 | assert_eq!(phrase.0.len(), 3); 260 | assert_eq!(remainder, b""); 261 | 262 | let input = b" John Smith [Doctor]".to_vec(); 263 | let (phrase, remainder) = Phrase::parse(input.as_slice()).unwrap(); 264 | assert_eq!(phrase.0.len(), 2); 265 | assert_eq!(remainder, b"[Doctor]"); 266 | } 267 | 268 | #[test] 269 | fn test_unstructured() { 270 | use rfc5322::types::{Unstructured, VChar}; 271 | 272 | let input = b"This is; unstructured=5 \r\n ".to_vec(); 273 | let (u, remainder) = Unstructured::parse(input.as_slice()).unwrap(); 274 | assert_eq!(u, Unstructured { 275 | leading_ws: false, 276 | parts: vec![ 277 | VChar(b"This".to_vec()), 278 | VChar(b"is;".to_vec()), 279 | VChar(b"unstructured=5".to_vec())], 280 | trailing_ws: true, 281 | }); 282 | assert_eq!(remainder, b"\r\n "); // because trailing ws is only WSP not FWS 283 | } 284 | 285 | #[test] 286 | fn test_domain_literal() { 287 | use rfc5322::types::{DomainLiteral, DText}; 288 | 289 | let input = b"\r\n \t[ 2001:db8:85a3:8d3:1319:8a2e:370:7348]".to_vec(); 290 | let (token, _) = DomainLiteral::parse(input.as_slice()).unwrap(); 291 | assert!(token.pre_cfws.is_some()); 292 | assert_eq!(token.dtext, vec![ 293 | (true, DText(b"2001:db8:85a3:8d3:1319:8a2e:370:7348".to_vec())) 294 | ]); 295 | assert_eq!(token.trailing_ws, false); 296 | assert!(token.post_cfws.is_none()); 297 | } 298 | 299 | #[test] 300 | fn test_addr_spec() { 301 | use rfc5322::types::{AddrSpec, LocalPart, Domain, DotAtom, DotAtomText, 302 | QuotedString, QContent, DomainLiteral, AText, DText, QText}; 303 | 304 | let input = b"joe.smith@gmail.com".to_vec(); 305 | let (a, rem) = AddrSpec::parse(input.as_slice()).unwrap(); 306 | assert_eq!(a.local_part, LocalPart::DotAtom( DotAtom { 307 | pre_cfws: None, 308 | dot_atom_text: DotAtomText(vec![ AText(b"joe".to_vec()), 309 | AText(b"smith".to_vec()) ]), 310 | post_cfws: None, 311 | })); 312 | assert_eq!(a.domain, Domain::DotAtom( DotAtom { 313 | pre_cfws: None, 314 | dot_atom_text: DotAtomText(vec![ AText(b"gmail".to_vec()), 315 | AText(b"com".to_vec()) ]), 316 | post_cfws: None, 317 | })); 318 | assert_eq!(rem, b""); 319 | 320 | let mut output: Vec = Vec::new(); 321 | assert_eq!(a.stream(&mut output).unwrap(), 19); 322 | assert_eq!(output, b"joe.smith@gmail.com".to_vec()); 323 | 324 | let input = b"\"joe smith\"@[2001:db8:85a3:8d3:1319:8a2e:370:7348]".to_vec(); 325 | let (a, rem) = AddrSpec::parse(input.as_slice()).unwrap(); 326 | assert_eq!(a.local_part, LocalPart::QuotedString( QuotedString { 327 | pre_cfws: None, 328 | qcontent: vec![ (false, QContent::QText(QText(b"joe".to_vec()))), 329 | (true, QContent::QText(QText(b"smith".to_vec()))) ], 330 | trailing_ws: false, 331 | post_cfws: None, 332 | })); 333 | assert_eq!(a.domain, Domain::DomainLiteral( DomainLiteral { 334 | pre_cfws: None, 335 | dtext: vec![(false, DText(b"2001:db8:85a3:8d3:1319:8a2e:370:7348".to_vec()))], 336 | trailing_ws: false, 337 | post_cfws: None, 338 | })); 339 | assert_eq!(rem, b""); 340 | } 341 | 342 | #[test] 343 | fn test_angle_addr() { 344 | use rfc5322::types::AngleAddr; 345 | 346 | let input = b"< admin@example.com >".to_vec(); 347 | let (token, rem) = AngleAddr::parse(input.as_slice()).unwrap(); 348 | assert_eq!(rem, b""); 349 | 350 | let mut output: Vec = Vec::new(); 351 | assert_eq!(token.stream(&mut output).unwrap(), 21); 352 | assert_eq!(output, input); 353 | } 354 | 355 | #[test] 356 | fn test_name_addr() { 357 | use rfc5322::types::NameAddr; 358 | 359 | let input = b" Bruce \"The Boss\" < bruce@net> \r\n ".to_vec(); 360 | let (token, rem) = NameAddr::parse(input.as_slice()).unwrap(); 361 | assert_eq!(rem, b""); 362 | 363 | let mut output: Vec = Vec::new(); 364 | assert_eq!(token.stream(&mut output).unwrap(), 31); 365 | assert_eq!(output, b" Bruce \"The Boss\" < bruce@net> ".to_vec()); 366 | } 367 | 368 | #[test] 369 | fn test_mailbox_list() { 370 | use rfc5322::types::{MailboxList, Mailbox}; 371 | 372 | let input = b"a@b.c, \"j p\" ,,".to_vec(); 373 | let (mbl, rem) = MailboxList::parse(input.as_slice()).unwrap(); 374 | assert_eq!(mbl.0.len(), 2); 375 | let mb2 = &mbl.0[1]; 376 | assert_eq!(match mb2 { 377 | &Mailbox::NameAddr(_) => true, 378 | &Mailbox::AddrSpec(_) => false, 379 | }, true); 380 | assert_eq!(rem, b",,"); 381 | 382 | let mut output: Vec = Vec::new(); 383 | assert_eq!(mbl.stream(&mut output).unwrap(), 22); 384 | assert_eq!(output, b"a@b.c, \"j p\" ".to_vec()); 385 | } 386 | 387 | #[test] 388 | fn test_zone() { 389 | use rfc5322::types::Zone; 390 | 391 | let input = b" +1135".to_vec(); 392 | let (v, rem) = Zone::parse(input.as_slice()).unwrap(); 393 | assert_eq!(rem, b""); 394 | assert_eq!(v.0, 1135_i32); 395 | 396 | let input = b" \r\n -0700".to_vec(); 397 | let (v, rem) = Zone::parse(input.as_slice()).unwrap(); 398 | assert_eq!(rem, b""); 399 | assert_eq!(v.0, -700_i32); 400 | 401 | let mut output: Vec = Vec::new(); 402 | assert_eq!(v.stream(&mut output).unwrap(), 6); 403 | assert_eq!(output, b" -0700".to_vec()); 404 | } 405 | 406 | #[test] 407 | fn test_time_of_day() { 408 | use rfc5322::types::TimeOfDay; 409 | 410 | let input = b"17:25:049".to_vec(); 411 | let (t, rem) = TimeOfDay::parse(input.as_slice()).unwrap(); 412 | assert_eq!(rem, b"9"); 413 | assert_eq!(t.hour.0, 17); 414 | assert_eq!(t.minute.0, 25); 415 | assert_eq!(t.second.as_ref().unwrap().0, 4); 416 | 417 | let mut output: Vec = Vec::new(); 418 | assert_eq!(t.stream(&mut output).unwrap(), 8); 419 | assert_eq!(output, b"17:25:04".to_vec()); 420 | 421 | let input = b"01:019".to_vec(); 422 | let (t, rem) = TimeOfDay::parse(input.as_slice()).unwrap(); 423 | assert_eq!(rem, b"9"); 424 | assert_eq!(t.hour.0, 1); 425 | assert_eq!(t.minute.0, 1); 426 | assert_eq!(t.second, None); 427 | 428 | let mut output: Vec = Vec::new(); 429 | assert_eq!(t.stream(&mut output).unwrap(), 5); 430 | assert_eq!(output, b"01:01".to_vec()); 431 | } 432 | 433 | #[test] 434 | fn test_date() { 435 | use rfc5322::types::Date; 436 | 437 | let input = b" 22 Sep 2016 ".to_vec(); 438 | let (t, rem) = Date::parse(input.as_slice()).unwrap(); 439 | assert_eq!(rem, b""); 440 | assert_eq!(t.day.0, 22); 441 | assert_eq!(t.month.0, 9); 442 | assert_eq!(t.year.0, 2016); 443 | 444 | let mut output: Vec = Vec::new(); 445 | assert_eq!(t.stream(&mut output).unwrap(), 13); 446 | assert_eq!(output, b" 22 Sep 2016 ".to_vec()); 447 | } 448 | 449 | #[cfg(feature="chrono")] 450 | #[test] 451 | fn test_date_from_chrono() 452 | { 453 | use TryFrom; 454 | use rfc5322::headers::OrigDate; 455 | use chrono::{TimeZone, Local}; 456 | 457 | let input = Local.ymd(2014, 11, 28).and_hms(12, 0, 9); 458 | let _: OrigDate = TryFrom::try_from(&input).unwrap(); 459 | } 460 | 461 | #[cfg(feature="time")] 462 | #[test] 463 | fn test_date_from_time() 464 | { 465 | use TryFrom; 466 | use rfc5322::headers::OrigDate; 467 | 468 | let input = ::time::now(); 469 | let _: OrigDate = TryFrom::try_from(&input).unwrap(); 470 | } 471 | 472 | #[test] 473 | fn test_date_time() { 474 | use rfc5322::types::DateTime; 475 | 476 | let input = b"suN, 01 DEC 2000 12:12:12 -1300 (or thereabouts) \r\n ".to_vec(); 477 | let (t, rem) = DateTime::parse(input.as_slice()).unwrap(); 478 | assert_eq!(rem, b""); 479 | 480 | // like previous case, but check that 1-digit dates are parseable too 481 | let input = b"suN, 1 DEC 2000 12:12:12 -1300 (or thereabouts) \r\n ".to_vec(); 482 | let (_, rem) = DateTime::parse(input.as_slice()).unwrap(); 483 | assert_eq!(rem, b""); 484 | 485 | let mut output: Vec = Vec::new(); 486 | assert_eq!(t.stream(&mut output).unwrap(), 49); 487 | assert_eq!(output, b"Sun, 1 Dec 2000 12:12:12 -1300 (or thereabouts) ".to_vec()); 488 | } 489 | 490 | #[test] 491 | fn test_orig_date() { 492 | use rfc5322::headers::OrigDate; 493 | 494 | let input = b"DATE: SAT, 11 Jan 2000 00:00:00 +0000\r\n".to_vec(); 495 | let (od, rem) = OrigDate::parse(input.as_slice()).unwrap(); 496 | assert_eq!(rem, b""); 497 | 498 | let mut output: Vec = Vec::new(); 499 | assert_eq!(od.stream(&mut output).unwrap(), 39); 500 | assert_eq!(output, b"Date: Sat, 11 Jan 2000 00:00:00 +0000\r\n".to_vec()); 501 | } 502 | 503 | #[test] 504 | fn test_from() { 505 | use rfc5322::headers::From; 506 | 507 | let input = b"froM:steven@a.b.c\r\n".to_vec(); 508 | let (from, rem) = From::parse(input.as_slice()).unwrap(); 509 | assert_eq!(rem, b""); 510 | 511 | let mut output: Vec = Vec::new(); 512 | assert_eq!(from.stream(&mut output).unwrap(), 19); 513 | assert_eq!(output, b"From:steven@a.b.c\r\n".to_vec()); 514 | } 515 | 516 | #[test] 517 | fn test_bcc() { 518 | use rfc5322::headers::Bcc; 519 | 520 | let input1 = b"bcc: (hah)\r\n".to_vec(); 521 | let (token, rem) = Bcc::parse(input1.as_slice()).unwrap(); 522 | assert_eq!(rem, b""); 523 | assert!(match token { 524 | Bcc::AddressList(_) => false, 525 | Bcc::CFWS(_) => true, 526 | Bcc::Empty => false, 527 | }); 528 | 529 | let input1 = b"bcc: a@b,c@d\r\n".to_vec(); 530 | let (token, rem) = Bcc::parse(input1.as_slice()).unwrap(); 531 | assert_eq!(rem, b""); 532 | assert!(match token { 533 | Bcc::AddressList(_) => true, 534 | Bcc::CFWS(_) => false, 535 | Bcc::Empty => false, 536 | }); 537 | } 538 | 539 | #[test] 540 | fn test_msg_id() { 541 | use rfc5322::types::{MsgId, IdLeft, IdRight, DotAtomText, AText}; 542 | 543 | let input = b"<950910bae2c7eff8d34297870a93dbb8@a.b.co.nz>".to_vec(); 544 | let (msgid, rem) = MsgId::parse(input.as_slice()).unwrap(); 545 | assert_eq!(rem, b""); 546 | assert_eq!(msgid, MsgId { 547 | pre_cfws: None, 548 | id_left: IdLeft(DotAtomText(vec![ 549 | AText("950910bae2c7eff8d34297870a93dbb8".as_bytes().to_owned()), 550 | ])), 551 | id_right: IdRight::DotAtomText(DotAtomText(vec![ 552 | AText("a".as_bytes().to_owned()), 553 | AText("b".as_bytes().to_owned()), 554 | AText("co".as_bytes().to_owned()), 555 | AText("nz".as_bytes().to_owned()), 556 | ])), 557 | post_cfws: None, 558 | }); 559 | } 560 | 561 | #[test] 562 | fn test_body() { 563 | use rfc5322::Body; 564 | 565 | let input = b"This is a test email".to_vec(); 566 | let (body, rem) = Body::parse(input.as_slice()).unwrap(); 567 | assert_eq!(rem, b""); 568 | assert_eq!(body.0, input); 569 | 570 | let input = b"This is a test email\r\n".to_vec(); 571 | let (body, rem) = Body::parse(input.as_slice()).unwrap(); 572 | assert_eq!(rem, b""); 573 | assert_eq!(body.0, input); 574 | 575 | let input = b"This is a test email\r\nVery simple, though.\r\n".to_vec(); 576 | let (body, rem) = Body::parse(input.as_slice()).unwrap(); 577 | assert_eq!(rem, b""); 578 | assert_eq!(body.0, input); 579 | 580 | let input = b"This is a test email\r\n\r\nok\r\n".to_vec(); 581 | let (body, rem) = Body::parse(input.as_slice()).unwrap(); 582 | assert_eq!(rem, b""); 583 | assert_eq!(body.0, input); 584 | 585 | let input = b"This is a test email\r\n\r\nbad\rbad\r\n".to_vec(); 586 | assert_match!(Body::parse(input.as_slice()), Err(_)); 587 | } 588 | 589 | #[test] 590 | fn test_message_1() { 591 | use rfc5322::{Message, Fields, Field, Body}; 592 | use rfc5322::headers::{Subject, From, To}; 593 | use rfc5322::types::{Unstructured, MailboxList, VChar, AddrSpec, DotAtom, CFWS, 594 | AText, DotAtomText, LocalPart, Domain, 595 | Mailbox, Address, AddressList}; 596 | 597 | let input = b"Subject: This is a test\r\n\ 598 | From: me@mydomain.net\r\n\ 599 | To: you@yourdomain.net\r\n\ 600 | \r\n\ 601 | This is the body.\r\n\ 602 | Simple.".to_vec(); 603 | 604 | let (message, rem) = Message::parse(input.as_slice()).unwrap(); 605 | assert_eq!(rem, b""); 606 | assert_eq!(message, Message { 607 | fields: Fields { 608 | trace_blocks: vec![], 609 | fields: vec![ 610 | Field::Subject(Subject(Unstructured { 611 | leading_ws: true, 612 | parts: vec![VChar(b"This".to_vec()), 613 | VChar(b"is".to_vec()), 614 | VChar(b"a".to_vec()), 615 | VChar(b"test".to_vec())], 616 | trailing_ws: false, 617 | })), 618 | Field::From(From(MailboxList(vec![Mailbox::AddrSpec(AddrSpec { 619 | local_part: LocalPart::DotAtom(DotAtom { 620 | pre_cfws: Some(CFWS { 621 | comments: vec![], 622 | trailing_ws: true, 623 | }), 624 | dot_atom_text: DotAtomText(vec![AText(b"me".to_vec())]), 625 | post_cfws: None, 626 | }), 627 | domain: Domain::DotAtom(DotAtom { 628 | pre_cfws: None, 629 | dot_atom_text: DotAtomText(vec![AText(b"mydomain".to_vec()), 630 | AText(b"net".to_vec())]), 631 | post_cfws: None }) 632 | })]))), 633 | Field::To(To(AddressList( vec![ 634 | Address::Mailbox( 635 | Mailbox::AddrSpec(AddrSpec { 636 | local_part: LocalPart::DotAtom(DotAtom { 637 | pre_cfws: Some(CFWS { 638 | comments: vec![], 639 | trailing_ws: true, 640 | }), 641 | dot_atom_text: DotAtomText(vec![AText(b"you".to_vec())]), 642 | post_cfws: None, 643 | }), 644 | domain: Domain::DotAtom(DotAtom { 645 | pre_cfws: None, 646 | dot_atom_text: DotAtomText(vec![AText(b"yourdomain".to_vec()), 647 | AText(b"net".to_vec())]), 648 | post_cfws: None }) 649 | }))]))), 650 | ] 651 | }, 652 | body: Some(Body(b"This is the body.\r\nSimple.".to_vec())), 653 | }); 654 | } 655 | 656 | #[test] 657 | fn test_email_struct_functions() { 658 | use ::Email; 659 | let mut email = Email::new("mike@sample.com", 660 | "Wed, 5 Jan 2015 15:13:05 +1300").unwrap(); 661 | 662 | email.set_date("Wed, 6 Jan 2015 15:13:05 +1300".as_bytes()).unwrap(); 663 | let date1 = email.get_date(); 664 | email.set_date("Fri, 30 Dec 2000 09:11:56 -1100").unwrap(); 665 | let date2 = email.get_date(); 666 | assert!(date1 != date2); 667 | email.set_date(date2).unwrap(); 668 | 669 | email.set_from("mike@sample.com".as_bytes()).unwrap(); 670 | let from1 = email.get_from(); 671 | email.set_from("mike@sample2.com").unwrap(); 672 | let from2 = email.get_from(); 673 | assert!(from1 != from2); 674 | email.set_from(from2).unwrap(); 675 | 676 | assert!(email.get_sender().is_none()); 677 | email.set_sender("mike@sample.com".as_bytes()).unwrap(); 678 | let sender1 = email.get_sender().unwrap(); 679 | email.set_sender("mike@sample2.com").unwrap(); 680 | let sender2 = email.get_sender().unwrap(); 681 | assert!(sender1 != sender2); 682 | email.set_sender(sender2).unwrap(); 683 | 684 | assert!(email.get_reply_to().is_none()); 685 | email.set_reply_to("mike@sample.com".as_bytes()).unwrap(); 686 | let reply_to1 = email.get_reply_to().unwrap(); 687 | email.set_reply_to("mike@sample2.com").unwrap(); 688 | let reply_to2 = email.get_reply_to().unwrap(); 689 | assert!(reply_to1 != reply_to2); 690 | email.set_reply_to(reply_to2).unwrap(); 691 | 692 | assert!(email.get_to().is_none()); 693 | email.set_to("mike@sample.com".as_bytes()).unwrap(); 694 | let to1 = email.get_to().unwrap(); 695 | email.set_to("mike@sample2.com").unwrap(); 696 | let to2 = email.get_to().unwrap(); 697 | assert!(to1 != to2); 698 | email.set_to(to2).unwrap(); 699 | 700 | assert!(email.get_cc().is_none()); 701 | email.set_cc("mike@sample.com, webmaster@sample.com".as_bytes()).unwrap(); 702 | let cc1 = email.get_cc().unwrap(); 703 | email.set_cc("mike@sample2.com, mike@sample.com").unwrap(); 704 | let cc2 = email.get_cc().unwrap(); 705 | assert!(cc1 != cc2); 706 | email.set_cc(cc2).unwrap(); 707 | } 708 | 709 | #[test] 710 | fn test_email_example() { 711 | use ::Email; 712 | 713 | let mut email = Email::new( 714 | "myself@mydomain.com", // "From:" 715 | "Wed, 5 Jan 2015 15:13:05 +1300" // "Date:" 716 | ).unwrap(); 717 | email.set_sender("from_myself@mydomain.com").unwrap(); 718 | email.set_reply_to("My Mailer ").unwrap(); 719 | email.set_to("You ").unwrap(); 720 | email.set_cc("Our Friend ").unwrap(); 721 | email.set_message_id("").unwrap(); 722 | email.set_subject("Hello Friend").unwrap(); 723 | email.set_body("Good to hear from you.\r\n\ 724 | I wish you the best.\r\n\ 725 | \r\n\ 726 | Your Friend").unwrap(); 727 | 728 | 729 | let mut output: Vec = Vec::new(); 730 | email.stream(&mut output).unwrap(); 731 | 732 | assert_eq!(output, 733 | "Date:Wed, 5 Jan 2015 15:13:05 +1300\r\n\ 734 | From:myself@mydomain.com\r\n\ 735 | Sender:from_myself@mydomain.com\r\n\ 736 | Reply-To:My Mailer \r\n\ 737 | To:You \r\n\ 738 | Cc:Our Friend \r\n\ 739 | Message-ID:\r\n\ 740 | Subject:Hello Friend\r\n\ 741 | \r\n\ 742 | Good to hear from you.\r\n\ 743 | I wish you the best.\r\n\ 744 | \r\n\ 745 | Your Friend".as_bytes()); 746 | } 747 | 748 | #[test] 749 | fn test_email_parse_stream() { 750 | use ::Email; 751 | use ::rfc5322::{Parsable, Streamable}; 752 | 753 | let input = "Date: Wed, 15 Jan 2015 15:13:05 +1300\r\n\ 754 | From: myself@mydomain.com\r\n\ 755 | Sender: from_myself@mydomain.com\r\n\ 756 | Reply-To: My Mailer \r\n\ 757 | To: You \r\n\ 758 | Cc: Our Friend \r\n\ 759 | Message-ID: \r\n\ 760 | Subject: Hello Friend\r\n\ 761 | \r\n\ 762 | Good to hear from you.\r\n\ 763 | I wish you the best.\r\n\ 764 | \r\n\ 765 | Your Friend".as_bytes(); 766 | 767 | let (email, remainder) = Email::parse(&input).unwrap(); 768 | assert_eq!(remainder.len(), 0); 769 | 770 | let mut output: Vec = Vec::new(); 771 | email.stream(&mut output).unwrap(); 772 | 773 | assert_eq!(input, &*output); 774 | } 775 | 776 | #[test] 777 | #[should_panic] 778 | fn test_trailing_input() { 779 | use ::TryFrom; 780 | use ::rfc5322::headers::Sender; 781 | 782 | let _: Sender = TryFrom::try_from("mike@optcomp.nz[.xyz]").unwrap(); 783 | } 784 | 785 | #[test] 786 | fn test_optional_fields() { 787 | use ::Email; 788 | use ::rfc5322::{Parsable, Streamable}; 789 | 790 | let input = "Date: Wed, 5 Jan 2015 15:13:05 +1300\r\n\ 791 | From: myself@mydomain.com\r\n\ 792 | Sender: from_myself@mydomain.com\r\n\ 793 | My-Crazy-Field: this is my field\r\n\ 794 | Subject: Hello Friend\r\n\ 795 | \r\n\ 796 | Good to hear from you.\r\n\ 797 | I wish you the best.\r\n\ 798 | \r\n\ 799 | Your Friend".as_bytes(); 800 | 801 | let (mut email, remainder) = Email::parse(&input).unwrap(); 802 | assert_eq!(remainder.len(), 0); 803 | 804 | assert_eq!(email.get_optional_fields().len(), 1); 805 | 806 | let mut output: Vec = Vec::new(); 807 | email.stream(&mut output).unwrap(); 808 | 809 | assert_eq!(input, &*output); 810 | 811 | email.add_optional_field(("Another-Crazy-Field", "has other content")).unwrap(); 812 | 813 | assert_eq!(email.get_optional_fields().len(), 2); 814 | 815 | email.clear_optional_fields(); 816 | assert_eq!(email.get_optional_fields().len(), 0); 817 | } 818 | 819 | #[cfg(feature="lettre")] 820 | #[test] 821 | fn test_as_sendable_email() { 822 | use ::Email; 823 | use ::rfc5322::Parsable; 824 | 825 | let input = "Date: Wed, 5 Jan 2015 15:13:05 +1300\r\n\ 826 | From: myself@mydomain.com\r\n\ 827 | Sender: from_myself@mydomain.com\r\n\ 828 | To: target@publicdomain.com\r\n\ 829 | Bcc: accomplice@secretdomain.com\r\n\ 830 | Message-ID: \r\n\ 831 | Subject: Hello Friend\r\n\ 832 | \r\n\ 833 | Good to hear from you.\r\n\ 834 | I wish you the best.\r\n\ 835 | \r\n\ 836 | Your Friend".as_bytes(); 837 | 838 | let (mut email, remainder) = Email::parse(&input).unwrap(); 839 | assert_eq!(remainder.len(), 0); 840 | 841 | let ssemail = email.as_sendable_email().unwrap(); 842 | 843 | // verify Bcc line is still in email 844 | assert_eq!( &*format!("{}",email.get_bcc().unwrap()), 845 | "Bcc: accomplice@secretdomain.com\r\n" ); 846 | 847 | assert_eq!( &*ssemail.message_id(), 848 | "id/20161128115731.29084.maelstrom@mydomain.com" ); 849 | 850 | // verify the Bcc line is NOT in the ssemail.message 851 | assert_eq!( ssemail.message_to_string().unwrap().contains("accomplice"), 852 | false ); 853 | 854 | } 855 | -------------------------------------------------------------------------------- /tests/unicode.rs.fails: -------------------------------------------------------------------------------- 1 | extern crate email_format; 2 | 3 | use email_format::Email; 4 | 5 | #[test] 6 | fn main() { 7 | 8 | let body = "Good to hear from you, Hans Müeller.\r\n\ 9 | I wish you the best.\r\n\ 10 | \r\n\ 11 | Your Friend,\r\n\ 12 | 黛安娜"; 13 | 14 | let mut email = Email::new( 15 | "myself@mydomain.com", // "From:" 16 | "Wed, 05 Jan 2015 15:13:05 +1300" // "Date:" 17 | ).unwrap(); 18 | email.set_sender("from_myself@mydomain.com").unwrap(); 19 | email.set_reply_to("My Mailer ").unwrap(); 20 | email.set_to("You ").unwrap(); 21 | email.set_cc("Our Friend ").unwrap(); 22 | email.set_message_id("").unwrap(); 23 | email.set_subject("Hello Friend").unwrap(); 24 | email.add_optional_field(("MIME-Version", "1.0")).unwrap(); 25 | email.add_optional_field(("Content-Type", "text/plain; charset=\"utf8\"")).unwrap(); 26 | email.set_body(body).unwrap(); 27 | 28 | println!("{}", email); 29 | } 30 | --------------------------------------------------------------------------------