├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── app.rs └── src ├── error.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | /1.txt 4 | /my_docs -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | notifications: 4 | email: false 5 | 6 | branches: 7 | only: 8 | - master 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atarashii_imap" 3 | version = "1.0.0" 4 | authors = ["Alex Maslakov , "] 5 | license = "Apache-2.0" 6 | 7 | repository = "https://github.com/GildedHonour/atarashii_imap" 8 | homepage = "https://github.com/GildedHonour/atarashii_imap" 9 | documentation = "https://github.com/GildedHonour/atarashii_imap" 10 | 11 | description = "IMAP client written in Rust" 12 | keywords = ["imap", "email", "mail"] 13 | readme = "README.md" 14 | 15 | [dependencies] 16 | native-tls = "0.1.4" 17 | regex = "0.2.1" 18 | 19 | [lib] 20 | name = "atarashii_imap" 21 | path = "src/lib.rs" 22 | 23 | [[bin]] 24 | name = "atarashii_imap" 25 | path = "examples/app.rs" 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 2016 Alex Maslakov 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 新しい IMAP client [![Build Status](https://travis-ci.org/GildedHonour/atarashii_imap.svg?branch=master)](https://travis-ci.org/GildedHonour/atarashii_imap) [![crates.io](https://img.shields.io/crates/v/atarashii_imap.svg)](https://crates.io/crates/atarashii_imap) 2 | ================================================ 3 | 4 | 新しい (atarashii/new) IMAP client in Rust. It supports plain and secure connections. 5 | 6 | 7 | ## In progress 8 | 9 | It's under development... 10 | 11 | 12 | ## Usage 13 | 14 | Put this in your `Cargo.toml`: 15 | 16 | ```toml 17 | [dependencies] 18 | atarashii_imap = "" 19 | ``` 20 | 21 | 22 | ### Example 23 | ```rust 24 | extern crate atarashii_imap; 25 | extern crate openssl; 26 | 27 | use atarashii_imap::{Client, Response, SslMode}; 28 | use native_tls::{TlsConnector, TlsConnectorBuilder, TlsStream, SslMethod, SslConnectorBuilder}; 29 | //....... 30 | 31 | match Client::connect("imap.gmail.com") { 32 | Ok(mut client) => { 33 | match conn.authenticate("login123@gmail.com", "password") { 34 | //todo 35 | 36 | // doing stuff with client 37 | // ............ 38 | 39 | client.disconnect(); 40 | }, 41 | 42 | Err(e) => println!("authentication error") 43 | } 44 | }, 45 | 46 | Err(e) => panic!("connection error") 47 | } 48 | 49 | ``` 50 | 51 | ## Commands supported 52 | * select(mailbox_name: &str) 53 | * examine(mailbox_name: &str) 54 | * create(mailbox_name: &str) 55 | * delete(mailbox_name: &str) 56 | * rename(current_name: &str, new_name: &str) 57 | * subscribe(mailbox_name: &str) 58 | * unsubscribe(mailbox_name: &str) 59 | * close 60 | * logout 61 | * capability 62 | * fetch 63 | * copy(seq_set: String, mailbox_name: String) 64 | * list(folder_name: &str, search_pattern: &str) 65 | * lsub(folder_name: &str, search_pattern: &str) 66 | * expunge 67 | * check 68 | * noop 69 | 70 | 71 | ## Author 72 | Alex Maslakov | me@gildedhonour.com 73 | 74 | ## License 75 | Apache 2.0 76 | -------------------------------------------------------------------------------- /examples/app.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Alex Maslakov, , 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * For questions and comments about this product, please see the project page at: 17 | * 18 | * https://github.com/GildedHonour/atarashii_imap 19 | * 20 | */ 21 | 22 | extern crate atarashii_imap; 23 | extern crate openssl; 24 | 25 | use atarashii_imap::{Connection, Response}; 26 | use openssl::ssl::{SslContext, SslStream}; 27 | use openssl::ssl::{SslMethod, SslConnectorBuilder}; 28 | 29 | fn main() { 30 | //match Connection::open_secure("imap.gmail.com", "gmail_login@gmail.com", "password") { 31 | match Connection::open_secure("imap.gmail.com", "gildedhonour@gmail.com", """26p9VfamA^X`ys"L""") { 32 | Ok(mut conn) => { 33 | match conn.select("inbox") { 34 | Ok(sel_res) => println!("select cmd result: {}", sel_res), 35 | _ => panic!("select cmd error") 36 | }; 37 | 38 | println!("\r\n"); 39 | match conn.capability() { 40 | Ok(Response::Ok(data)) => { 41 | for x in data.iter() { 42 | println!("capability cmd item: {}", x); 43 | } 44 | }, 45 | _ => panic!("capability cmd error") 46 | }; 47 | 48 | println!("\r\n"); 49 | match conn.list_by_search_query("%") { 50 | Ok(Response::Ok(data)) => { 51 | for x in data.iter() { 52 | println!("list cmd item: {}", x); 53 | } 54 | }, 55 | _ => panic!("list cmd error") 56 | }; 57 | }, 58 | 59 | Err(_) => panic!("Unable to open connection") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Alex Maslakov, , 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * For questions and comments about this product, please see the project page at: 17 | * 18 | * https://github.com/GildedHonour/atarashii_imap 19 | * 20 | */ 21 | 22 | pub enum Error { 23 | Append, 24 | Search, 25 | Fetch, 26 | Store, 27 | Copy, 28 | UnknownCommandOrInvalidArgs, 29 | NoSuchMailbox, 30 | InvalidCredentials, 31 | UnableToCreateMailbox, 32 | UnableToDeleteMailbox, 33 | UnableToRenameMailbox, 34 | UnableToSubscribeToMailbox, 35 | Login, 36 | Generic, 37 | Connect, 38 | SendCommand 39 | } 40 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Alex Maslakov, , 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * For questions and comments about this product, please see the project page at: 17 | * 18 | * https://github.com/GildedHonour/atarashii_imap 19 | * 20 | */ 21 | 22 | extern crate native_tls; 23 | extern crate regex; 24 | 25 | use regex::Regex; 26 | use std::net::TcpStream; 27 | use std::io::{Read, Write}; 28 | use std::result; 29 | use std::cell::Cell; 30 | use std::fmt; 31 | use native_tls::{TlsConnector, TlsConnectorBuilder, TlsStream}; 32 | 33 | mod error; 34 | 35 | pub enum SslMode { 36 | None, 37 | Auto, 38 | SslOnConnect, 39 | StartTls, 40 | StartTlsIfSupported 41 | } 42 | 43 | impl SslMode { 44 | fn port(&self) -> u16 { 45 | match *self { 46 | SslMode::None | SslMode::Explicit => 143, 47 | SslMode::Implicit => 993 48 | } 49 | } 50 | } 51 | 52 | pub enum Authentication { 53 | NormalPassword, 54 | EncryptedPassword, 55 | Ntlm, 56 | Kerberos, 57 | TlsCertificate, 58 | GssApi, 59 | Skey, 60 | Oauth2 61 | } 62 | 63 | pub enum SaslMechanism { 64 | } 65 | 66 | pub enum Response { 67 | Ok(Vec), 68 | No(Vec), 69 | Bad(Vec) 70 | } 71 | 72 | pub enum ResponseOptional { 73 | Referral, 74 | Alert, 75 | Badcharset, 76 | Parse, 77 | Permanentflags, 78 | ReadOnly, 79 | ReadWrite, 80 | Trycreate, 81 | Uidnext, 82 | Uidvalidity, 83 | Unseen, 84 | UnknownCte, 85 | Uidnotsticky, 86 | Appenduid, 87 | Copyuid, 88 | Urlmech, 89 | Toobig, 90 | Badurl, 91 | Highestmodseq, 92 | Nomodseq, 93 | Modified, 94 | Compressionactive, 95 | Closed, 96 | Notsaved, 97 | Badcomparator, 98 | Annotate, 99 | Annotations, 100 | Tempfail, 101 | Maxconvertmessages, 102 | Maxconvertparts, 103 | Noupdate, 104 | Metadata, 105 | Notificationoverflow, 106 | Badevent, 107 | UndefinedFilter, 108 | Unavailable, 109 | Authenticationfailed, 110 | Authorizationfailed, 111 | Expired, 112 | Privacyrequired, 113 | Contactadmin, 114 | Noperm, 115 | Inuse, 116 | Expungeissued, 117 | Corruption, 118 | Serverbug, 119 | Clientbug, 120 | Cannot, 121 | Limit, 122 | Overquota, 123 | Alreadyexists, 124 | Nonexistent 125 | } 126 | 127 | pub struct EmailBox { 128 | pub flags: Vec, 129 | pub permanent_flags: Vec, 130 | pub exists_num: u32, 131 | pub recent_num: u32, 132 | pub unseen_num: u32, 133 | pub uid_next: u32, 134 | pub uid_validity: u32 135 | } 136 | 137 | impl Default for EmailBox { 138 | fn default() -> EmailBox { 139 | EmailBox { 140 | flags: vec![], 141 | permanent_flags: vec![], 142 | exists_num: 0, 143 | recent_num: 0, 144 | unseen_num: 0, 145 | uid_next: 0, 146 | uid_validity: 0 147 | } 148 | } 149 | } 150 | 151 | impl fmt::Display for EmailBox { 152 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 153 | write!(f, "Exists: {}\r\nRecent: {}\r\nUnseen: {}\r\nUid validity: {}\r\nUid next: {}\r\nFlags: {}\r\nPermanent flags: {}", 154 | self.exists_num, 155 | self.recent_num, 156 | self.unseen_num, 157 | self.uid_validity, 158 | self.uid_next, 159 | self.flags.join(", "), 160 | self.permanent_flags.join(", ")) 161 | } 162 | } 163 | 164 | pub struct Client { 165 | host: String, 166 | tag_sequence_number: Cell, 167 | stream: T 168 | } 169 | 170 | const CARRIAGE_RETURN_CODE: u8 = 0x0D; 171 | const NEW_LINE_CODE: u8 = 0x0A; 172 | const NEW_LINE_FULL_CODE: [u8; 2] = [CARRIAGE_RETURN_CODE, NEW_LINE_CODE]; 173 | const NEW_LINE_FULL_CODE_LEN: usize = 2; 174 | 175 | impl fmt::Display for Connection { 176 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 177 | write!(f, "Connection: host: {}, tag prefix: {}, tag sequence number: {}", 178 | self.host, Connection::tag_prefix(), self.tag_sequence_number.get()) 179 | } 180 | } 181 | 182 | //todo 183 | impl Client { 184 | pub fn connect(host: &str, ssl_mode: SslMode) -> result::Result { 185 | 186 | let mut conn = match ssl_mode { 187 | SslMode::None => { 188 | match TcpStream::connect((host, SslMode::None.port())) { 189 | Ok(mut unsec_conn) => { 190 | let mut str_buf = String::new(); 191 | match unsec_conn.read_to_string(&mut str_buf) { 192 | Ok(bytes_read) => { 193 | //TODO: if OK exists then success 194 | //read greating 195 | 196 | }, 197 | 198 | Err(e) => panic!() 199 | } 200 | }, 201 | 202 | Err(e) => panic!() 203 | } 204 | }, 205 | 206 | SslMode::Implicit => { 207 | let connector = TlsConnector::builder().unwrap().build().unwrap(); 208 | //todo: match 209 | let pl_stream = TcpStream::connect(format!("{}:{}", host, SslMode::None.port())).unwrap(); 210 | connector.connect(host, pl_stream).unwrap(); 211 | }, 212 | 213 | // todo 214 | SslMode::Explicit => { 215 | match TcpStream::connect((host, SslMode::Explicit.port())) { 216 | Ok(unsec_conn) => { 217 | Connection::verify_greeting(&mut unsec_conn); 218 | 219 | 220 | 221 | //todo 222 | //match 223 | let tls_cmd = start_tls(unsec_conn); 224 | let connector = TlsConnector::builder().unwrap().build().unwrap(); 225 | let mut sec_conn = connector.connect(host, unsec_conn).unwrap(); 226 | }, 227 | Err(e) => panic!("{}", format!("Unable to connect: {}", e)) 228 | } 229 | } 230 | }; 231 | 232 | 233 | //todo 234 | match conn.login(credentials) { 235 | Ok(login_res) => Ok(Connection{ host: host.to_string(), 236 | tag_sequence_number: Cell::new(1), stream: conn}), 237 | 238 | Err(e) => Err(error::Error::Login) 239 | } 240 | } 241 | 242 | fn tag_prefix() -> &'static str { 243 | "TAG" 244 | } 245 | 246 | fn verify_greeting(&mut net_stream: T) { 247 | let byte_buf: &mut [u8] = &mut [0]; 248 | let mut greet_buf = Vec::new(); 249 | loop { 250 | if greet_buf.len() >= NEW_LINE_FULL_CODE_LEN && 251 | &greet_buf[greet_buf.len() - NEW_LINE_FULL_CODE_LEN..] == 252 | &NEW_LINE_FULL_CODE[..] { 253 | break; 254 | } 255 | 256 | match net_stream.read(byte_buf) { 257 | Ok(_) => greet_buf.push(byte_buf[0]), 258 | Err(e) => panic!("Unable to read greeting data from a socket: {}", e) 259 | }; 260 | } 261 | 262 | let greeting_re = Regex::new(r"^[*] OK").unwrap(); 263 | let maybe_greeting = String::from_utf8(greet_buf).unwrap(); 264 | if !greeting_re.is_match(&maybe_greeting) { 265 | panic!("Greeting doesn't have the correct format"); 266 | } 267 | } 268 | 269 | fn select_generic(&mut self, emailbox_name: &str, cmd: &str) -> Result { 270 | match self.execute_command(&format!("{} {}", cmd, emailbox_name)) { 271 | Ok(Response::Ok(data)) => { 272 | let re_flags = Regex::new(r"FLAGS\s\((.+)\)").unwrap(); 273 | let re_perm_flags = Regex::new(r"\[PERMANENTFLAGS\s\((.+)\)\]").unwrap(); 274 | let re_uid_validity = Regex::new(r"\[UIDVALIDITY\s(\d+)\]").unwrap(); 275 | let re_exists_num = Regex::new(r"(\d+)\sEXISTS").unwrap(); 276 | let re_recent_num = Regex::new(r"(\d+)\sRECENT").unwrap(); 277 | let re_unseen_num = Regex::new(r"\[UNSEEN\s(\d+)\]").unwrap(); 278 | let re_uid_next = Regex::new(r"\[UIDNEXT\s(\d+)\]").unwrap(); 279 | let re_tag_and_res = Regex::new(&format!(r"{}\s(OK|NO|BAD){{1}}", self.get_current_tag())).unwrap(); 280 | 281 | let mut scr = EmailBox::default(); 282 | for x in data.iter() { 283 | if re_flags.is_match(&x) { 284 | let cp = re_flags.captures(&x).unwrap(); 285 | let flg1 = cp[1].to_string(); 286 | let flg2: Vec<&str> = flg1.split(" ").collect(); 287 | scr.flags = flg2.iter().map(|x| x.to_string()).collect(); 288 | } 289 | 290 | if re_perm_flags.is_match(&x) { 291 | let cp = re_perm_flags.captures(&x).unwrap(); 292 | let flg1 = cp[1].to_string(); 293 | let flg2: Vec<&str> = flg1.split(" ").collect(); 294 | scr.permanent_flags = flg2.iter().map(|x| x.to_string()).collect(); 295 | } 296 | 297 | if re_exists_num.is_match(&x) { 298 | let cp = re_exists_num.captures(&x).unwrap(); 299 | scr.exists_num = cp[1].parse::().unwrap(); 300 | } 301 | 302 | if re_recent_num.is_match(&x) { 303 | let cp = re_recent_num.captures(&x).unwrap(); 304 | scr.recent_num = cp[1].parse::().unwrap(); 305 | } 306 | 307 | if re_uid_next.is_match(&x) { 308 | let cp = re_uid_next.captures(&x).unwrap(); 309 | scr.uid_next = cp[1].parse::().unwrap(); 310 | } 311 | 312 | if re_uid_validity.is_match(&x) { 313 | let cp = re_uid_validity.captures(&x).unwrap(); 314 | scr.uid_validity = cp[1].parse::().unwrap(); 315 | } 316 | 317 | if re_unseen_num.is_match(&x) { 318 | let cp = re_unseen_num.captures(&x).unwrap(); 319 | scr.unseen_num = cp[1].parse::().unwrap(); 320 | } 321 | } 322 | 323 | Ok(scr) 324 | }, 325 | 326 | _ => unimplemented!(), 327 | /* 328 | Ok(Response::No(data)) => unimplemented!(), 329 | Ok(Response::Bad(data)) => { 330 | for x in data.iter() { 331 | println!("select bad resp item: {:?}", x); 332 | } 333 | 334 | unimplemented!() 335 | }, 336 | Err(e) => panic!("select cmd error123") 337 | 338 | */ 339 | } 340 | } 341 | 342 | //commands 343 | 344 | pub fn create(&mut self, mailbox_name: &str) -> Result { 345 | self.execute_command(&format!("create {}", mailbox_name)) 346 | } 347 | 348 | pub fn delete(&mut self, mailbox_name: &str) -> Result { 349 | self.execute_command(&format!("delete {}", mailbox_name)) 350 | } 351 | 352 | pub fn rename(&mut self, current_name: &str, new_name: &str) -> Result { 353 | self.execute_command(&format!("rename {} {}", current_name, new_name)) 354 | } 355 | 356 | pub fn subscribe(&mut self, mailbox_name: &str) -> Result { 357 | self.execute_command(&format!("subscribe {}", mailbox_name)) 358 | } 359 | 360 | pub fn unsubscribe(&mut self, mailbox_name: &str) -> Result { 361 | self.execute_command(&format!("unsubscribe {}", mailbox_name)) 362 | } 363 | 364 | pub fn close(&mut self) -> Result { 365 | self.execute_command(&"close") 366 | } 367 | 368 | fn logout(&mut self) -> Result { 369 | match self.execute_command(&"logout") { 370 | Ok(Response::Ok(data)) => { 371 | for x in data.iter() { 372 | if x.contains("BYE") { 373 | return Ok(Response::Ok(Vec::default())) 374 | } 375 | } 376 | 377 | Ok(Response::Bad(vec!["The response of the server doesn't contain 'BYE'".to_string()])) 378 | }, 379 | 380 | _ => Ok(Response::Bad(Vec::default())) 381 | } 382 | } 383 | 384 | pub fn capability(&mut self) -> Result { 385 | self.execute_command(&"capability") //todo -- parse response, remove redundant stuff 386 | } 387 | 388 | pub fn fetch(&mut self, seq_set: &str, message_data_query: &str) -> Result { 389 | self.execute_command(&format!("fetch {} {}", seq_set, message_data_query)) 390 | } 391 | 392 | pub fn copy(&mut self, seq_set: String, mailbox_name: String) -> Result { 393 | self.execute_command (&format!("copy {} {}", seq_set, mailbox_name)) 394 | } 395 | 396 | pub fn list_all(&mut self) -> Result { 397 | self.list("", "") 398 | } 399 | 400 | pub fn list_by_search_query(&mut self, search_pattern: &str) -> Result { 401 | self.list("", search_pattern) 402 | } 403 | 404 | pub fn list_by_folder_name(&mut self, folder_name: &str) -> Result { 405 | self.list(folder_name, "") 406 | } 407 | 408 | pub fn list(&mut self, folder_name: &str, search_pattern: &str) -> Result { 409 | self.execute_command(&format!("list \"{}\" \"{}\"", folder_name, search_pattern)) 410 | } 411 | 412 | pub fn lsub(&mut self, folder_name: &str, search_pattern: &str) -> Result { 413 | self.execute_command(&format!("lsub \"{}\" \"{}\"", folder_name, search_pattern)) 414 | } 415 | 416 | pub fn select(&mut self, mailbox_name: &str) -> Result { 417 | self.select_generic(mailbox_name, "select") 418 | } 419 | 420 | pub fn examine(&mut self, mailbox_name: &str) -> Result { 421 | self.select_generic(mailbox_name, "examine") 422 | } 423 | 424 | pub fn expunge(&mut self) -> Result { 425 | self.execute_command(&"expunge") 426 | } 427 | 428 | pub fn check(&mut self) -> Result { 429 | self.execute_command(&"check") 430 | } 431 | 432 | pub fn noop(&mut self) -> Result { 433 | self.execute_command(&"noop") 434 | } 435 | 436 | fn execute_command(&mut self, cmd: &str) -> Result { 437 | let tag = self.generate_tag(); 438 | match self.stream.write(format!("{} {}\r\n", tag, cmd).as_bytes()) { 439 | Ok(_) => { 440 | let byte_buf: &mut [u8] = &mut [0]; 441 | let mut read_buf: Vec = Vec::new(); 442 | let regex_str = format!(r"{}\s(OK|NO|BAD){{1}}", tag); 443 | let cmd_resp_re = Regex::new(®ex_str).unwrap(); 444 | loop { 445 | if read_buf.len() >= NEW_LINE_FULL_CODE.len() && 446 | &read_buf[read_buf.len() - NEW_LINE_FULL_CODE.len()..] == 447 | &NEW_LINE_FULL_CODE[..] { 448 | //todo 449 | let m1 = String::from_utf8(read_buf.clone()).unwrap(); 450 | if cmd_resp_re.is_match(&m1) { 451 | break; 452 | } 453 | } 454 | 455 | match self.stream.read(byte_buf) { 456 | Ok(_) => read_buf.push(byte_buf[0]), 457 | Err(e) => panic!("Error reading bytes from the socket: {}", e) 458 | } 459 | } 460 | 461 | let resp = String::from_utf8(read_buf.clone()).unwrap(); 462 | let caps = cmd_resp_re.captures(&resp).unwrap(); 463 | let data = resp.split("\r\n").map(|x| x.to_string()).collect(); 464 | Ok(match &caps[1] { 465 | "OK" => Response::Ok(data), 466 | "NO" => Response::No(data), 467 | "BAD" => Response::Bad(data), 468 | _ => panic!("Invalid response") 469 | }) 470 | }, 471 | _ => Err(error::Error::SendCommand) 472 | } 473 | } 474 | 475 | pub fn authenticate(&mut self, saslMech: SaslMechenism) -> result::Result { 476 | 477 | self.execute_command(&format!("authenticate {}", ???)) 478 | } 479 | 480 | 481 | 482 | 483 | 484 | fn login(&mut self, credentials: (&str, &str)) -> result::Result { 485 | let (usr_lgn, pass) = credentials; 486 | self.execute_command(&format!("login {} {}", usr_lgn, pass)) 487 | } 488 | 489 | fn start_tls(&mut self) -> Result { 490 | self.execute_command("starttls") 491 | } 492 | 493 | //end commands 494 | 495 | 496 | fn generate_tag(&self) -> String { 497 | let v = self.tag_sequence_number.get(); 498 | self.tag_sequence_number.set(v + 1); 499 | format!("{}_{}", Connection::tag_prefix(), self.tag_sequence_number.get()) 500 | } 501 | 502 | fn get_current_tag(&self) -> String { 503 | format!("{}_{}", Connection::tag_prefix(), self.tag_sequence_number.get()) 504 | } 505 | 506 | 507 | //todo 508 | pub fn disconnect(&mut self) -> result::Result { 509 | unimplemented!() 510 | } 511 | } 512 | 513 | #[cfg(test)] 514 | mod tests { 515 | #[test] 516 | fn it_works() { 517 | } 518 | } 519 | --------------------------------------------------------------------------------