summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2022-10-22 18:42:07 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2022-10-22 21:14:53 +0300
commit803d3414fd73743ff5bfc0fefe5e3d76d88e58cb (patch)
treea81e60346996a0acde0a6e3051fdaf55add0beea
parent3697b7d960cc9dbe602fa84f861cea854b600b73 (diff)
downloadmeli-803d3414fd73743ff5bfc0fefe5e3d76d88e58cb.zip
melib/imap/managesieve: implement some rfc5804 commands
Try with managesieve REPL in src/managesieve.rs: cargo run --bin managesieve-client ~/.config/meli/config.toml "accountname" rfc5804 <https://www.rfc-editor.org/rfc/rfc5804.html>
-rw-r--r--Cargo.toml6
-rw-r--r--melib/src/backends/imap/managesieve.rs491
-rw-r--r--melib/src/backends/imap/protocol_parser.rs4
-rw-r--r--src/managesieve.rs135
4 files changed, 526 insertions, 110 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 3a42e116..9f027db9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,9 +21,9 @@ path = "src/main.rs"
name = "meli"
path = "src/lib.rs"
-#[[bin]]
-#name = "managesieve-meli"
-#path = "src/managesieve.rs"
+[[bin]]
+name = "managesieve-client"
+path = "src/managesieve.rs"
#[[bin]]
#name = "async"
diff --git a/melib/src/backends/imap/managesieve.rs b/melib/src/backends/imap/managesieve.rs
index 56e3edd9..67c036c6 100644
--- a/melib/src/backends/imap/managesieve.rs
+++ b/melib/src/backends/imap/managesieve.rs
@@ -21,16 +21,22 @@
use super::{ImapConnection, ImapProtocol, ImapServerConf, UIDStore};
use crate::conf::AccountSettings;
+use crate::email::parser::IResult;
use crate::error::{MeliError, Result};
use crate::get_conf_val;
+use crate::imap::RequiredResponses;
use nom::{
- branch::alt, bytes::complete::tag, combinator::map, error::Error as NomError, error::ErrorKind,
- multi::separated_list1, sequence::separated_pair, IResult,
+ branch::alt, bytes::complete::tag, combinator::map, multi::separated_list1,
+ sequence::separated_pair,
};
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
+pub struct ManageSieveConnection {
+ pub inner: ImapConnection,
+}
+
pub fn managesieve_capabilities(input: &[u8]) -> Result<Vec<(&[u8], &[u8])>> {
let (_, ret) = separated_list1(
tag(b"\r\n"),
@@ -42,26 +48,225 @@ pub fn managesieve_capabilities(input: &[u8]) -> Result<Vec<(&[u8], &[u8])>> {
Ok(ret)
}
-#[test]
-fn test_managesieve_capabilities() {
- assert_eq!(managesieve_capabilities(b"\"IMPLEMENTATION\" \"Dovecot Pigeonhole\"\r\n\"SIEVE\" \"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext\"\r\n\"NOTIFY\" \"mailto\"\r\n\"SASL\" \"PLAIN\"\r\n\"STARTTLS\"\r\n\"VERSION\" \"1.0\"\r\n").unwrap(), vec![
- (&b"IMPLEMENTATION"[..],&b"Dovecot Pigeonhole"[..]),
- (&b"SIEVE"[..],&b"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext"[..]),
- (&b"NOTIFY"[..],&b"mailto"[..]),
- (&b"SASL"[..],&b"PLAIN"[..]),
- (&b"STARTTLS"[..], &b""[..]),
- (&b"VERSION"[..],&b"1.0"[..])]
-
- );
+#[derive(PartialEq, Eq, Debug, Copy, Clone)]
+pub enum ManageSieveResponse<'a> {
+ Ok {
+ code: Option<&'a [u8]>,
+ message: Option<&'a [u8]>,
+ },
+ NoBye {
+ code: Option<&'a [u8]>,
+ message: Option<&'a [u8]>,
+ },
+}
+
+mod parser {
+ use super::*;
+ use nom::bytes::complete::tag;
+ pub use nom::bytes::complete::{is_not, tag_no_case};
+ use nom::character::complete::crlf;
+ use nom::combinator::{iterator, map, opt};
+ pub use nom::sequence::{delimited, pair, preceded, terminated};
+
+ pub fn sieve_name(input: &[u8]) -> IResult<&[u8], &[u8]> {
+ crate::backends::imap::protocol_parser::string_token(input)
+ }
+
+ // *(sieve-name [SP "ACTIVE"] CRLF)
+ // response-oknobye
+ pub fn listscripts(input: &[u8]) -> IResult<&[u8], Vec<(&[u8], bool)>> {
+ let mut it = iterator(
+ input,
+ alt((
+ terminated(
+ map(terminated(sieve_name, tag_no_case(b" ACTIVE")), |r| {
+ (r, true)
+ }),
+ crlf,
+ ),
+ terminated(map(sieve_name, |r| (r, false)), crlf),
+ )),
+ );
+
+ let parsed = (&mut it).collect::<Vec<(&[u8], bool)>>();
+ let res: IResult<_, _> = it.finish();
+ let (rest, _) = res?;
+ Ok((rest, parsed))
+ }
+
+ // response-getscript = (sieve-script CRLF response-ok) /
+ // response-nobye
+ pub fn getscript(input: &[u8]) -> IResult<&[u8], &[u8]> {
+ sieve_name(input)
+ }
+
+ pub fn response_oknobye(input: &[u8]) -> IResult<&[u8], ManageSieveResponse> {
+ alt((
+ map(
+ terminated(
+ pair(
+ preceded(
+ tag_no_case(b"ok"),
+ opt(preceded(
+ tag(b" "),
+ delimited(tag(b"("), is_not(")"), tag(b")")),
+ )),
+ ),
+ opt(preceded(tag(b" "), sieve_name)),
+ ),
+ crlf,
+ ),
+ |(code, message)| ManageSieveResponse::Ok { code, message },
+ ),
+ map(
+ terminated(
+ pair(
+ preceded(
+ alt((tag_no_case(b"no"), tag_no_case(b"bye"))),
+ opt(preceded(
+ tag(b" "),
+ delimited(tag(b"("), is_not(")"), tag(b")")),
+ )),
+ ),
+ opt(preceded(tag(b" "), sieve_name)),
+ ),
+ crlf,
+ ),
+ |(code, message)| ManageSieveResponse::NoBye { code, message },
+ ),
+ ))(input)
+ }
+
+ #[test]
+ fn test_managesieve_listscripts() {
+ let input_1 = b"\"summer_script\"\r\n\"vacation_script\"\r\n{13}\r\nclever\"script\r\n\"main_script\" ACTIVE\r\nOK";
+ assert_eq!(
+ terminated(listscripts, tag_no_case(b"OK"))(input_1),
+ Ok((
+ &b""[..],
+ vec![
+ (&b"summer_script"[..], false),
+ (&b"vacation_script"[..], false),
+ (&b"clever\"script"[..], false),
+ (&b"main_script"[..], true)
+ ]
+ ))
+ );
+
+ let input_2 = b"\"summer_script\"\r\n\"main_script\" active\r\nok";
+ assert_eq!(
+ terminated(listscripts, tag_no_case(b"OK"))(input_2),
+ Ok((
+ &b""[..],
+ vec![(&b"summer_script"[..], false), (&b"main_script"[..], true)]
+ ))
+ );
+ let input_3 = b"ok";
+ assert_eq!(
+ terminated(listscripts, tag_no_case(b"OK"))(input_3),
+ Ok((&b""[..], vec![]))
+ );
+ }
+
+ #[test]
+ fn test_managesieve_general() {
+ assert_eq!(managesieve_capabilities(b"\"IMPLEMENTATION\" \"Dovecot Pigeonhole\"\r\n\"SIEVE\" \"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext\"\r\n\"NOTIFY\" \"mailto\"\r\n\"SASL\" \"PLAIN\"\r\n\"STARTTLS\"\r\n\"VERSION\" \"1.0\"\r\n").unwrap(), vec![
+ (&b"IMPLEMENTATION"[..],&b"Dovecot Pigeonhole"[..]),
+ (&b"SIEVE"[..],&b"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext"[..]),
+ (&b"NOTIFY"[..],&b"mailto"[..]),
+ (&b"SASL"[..],&b"PLAIN"[..]),
+ (&b"STARTTLS"[..], &b""[..]),
+ (&b"VERSION"[..],&b"1.0"[..])]
+
+ );
+
+ let response_ok = b"OK (WARNINGS) \"line 8: server redirect action limit is 2, this redirect might be ignored\"\r\n";
+ assert_eq!(
+ response_oknobye(response_ok),
+ Ok((
+ &b""[..],
+ ManageSieveResponse::Ok {
+ code: Some(&b"WARNINGS"[..]),
+ message: Some(&b"line 8: server redirect action limit is 2, this redirect might be ignored"[..]),
+ }
+ ))
+ );
+ let response_ok = b"OK (WARNINGS)\r\n";
+ assert_eq!(
+ response_oknobye(response_ok),
+ Ok((
+ &b""[..],
+ ManageSieveResponse::Ok {
+ code: Some(&b"WARNINGS"[..]),
+ message: None,
+ }
+ ))
+ );
+ let response_ok =
+ b"OK \"line 8: server redirect action limit is 2, this redirect might be ignored\"\r\n";
+ assert_eq!(
+ response_oknobye(response_ok),
+ Ok((
+ &b""[..],
+ ManageSieveResponse::Ok {
+ code: None,
+ message: Some(&b"line 8: server redirect action limit is 2, this redirect might be ignored"[..]),
+ }
+ ))
+ );
+ let response_ok = b"Ok\r\n";
+ assert_eq!(
+ response_oknobye(response_ok),
+ Ok((
+ &b""[..],
+ ManageSieveResponse::Ok {
+ code: None,
+ message: None,
+ }
+ ))
+ );
+
+ let response_nobye = b"No (NONEXISTENT) \"There is no script by that name\"\r\n";
+ assert_eq!(
+ response_oknobye(response_nobye),
+ Ok((
+ &b""[..],
+ ManageSieveResponse::NoBye {
+ code: Some(&b"NONEXISTENT"[..]),
+ message: Some(&b"There is no script by that name"[..]),
+ }
+ ))
+ );
+ let response_nobye = b"No (NONEXISTENT) {31}\r\nThere is no script by that name\r\n";
+ assert_eq!(
+ response_oknobye(response_nobye),
+ Ok((
+ &b""[..],
+ ManageSieveResponse::NoBye {
+ code: Some(&b"NONEXISTENT"[..]),
+ message: Some(&b"There is no script by that name"[..]),
+ }
+ ))
+ );
+
+ let response_nobye = b"No\r\n";
+ assert_eq!(
+ response_oknobye(response_nobye),
+ Ok((
+ &b""[..],
+ ManageSieveResponse::NoBye {
+ code: None,
+ message: None,
+ }
+ ))
+ );
+ }
}
// Return a byte sequence surrounded by "s and decoded if necessary
pub fn quoted_raw(input: &[u8]) -> IResult<&[u8], &[u8]> {
if input.is_empty() || input[0] != b'"' {
- return Err(nom::Err::Error(NomError {
- input,
- code: ErrorKind::Tag,
- }));
+ return Err(nom::Err::Error((input, "empty").into()));
}
let mut i = 1;
@@ -72,91 +277,199 @@ pub fn quoted_raw(input: &[u8]) -> IResult<&[u8], &[u8]> {
i += 1;
}
- Err(nom::Err::Error(NomError {
- input,
- code: ErrorKind::Tag,
- }))
-}
-
-pub trait ManageSieve {
- fn havespace(&mut self) -> Result<()>;
- fn putscript(&mut self) -> Result<()>;
-
- fn listscripts(&mut self) -> Result<()>;
- fn setactive(&mut self) -> Result<()>;
-
- fn getscript(&mut self) -> Result<()>;
-
- fn deletescript(&mut self) -> Result<()>;
- fn renamescript(&mut self) -> Result<()>;
+ Err(nom::Err::Error((input, "no quotes").into()))
}
-pub fn new_managesieve_connection(
- account_hash: crate::backends::AccountHash,
- account_name: String,
- s: &AccountSettings,
- event_consumer: crate::backends::BackendEventConsumer,
-) -> Result<ImapConnection> {
- let server_hostname = get_conf_val!(s["server_hostname"])?;
- let server_username = get_conf_val!(s["server_username"])?;
- let server_password = get_conf_val!(s["server_password"])?;
- let server_port = get_conf_val!(s["server_port"], 4190)?;
- let danger_accept_invalid_certs: bool = get_conf_val!(s["danger_accept_invalid_certs"], false)?;
- let timeout = get_conf_val!(s["timeout"], 16_u64)?;
- let timeout = if timeout == 0 {
- None
- } else {
- Some(std::time::Duration::from_secs(timeout))
- };
- let server_conf = ImapServerConf {
- server_hostname: server_hostname.to_string(),
- server_username: server_username.to_string(),
- server_password: server_password.to_string(),
- server_port,
- use_starttls: true,
- use_tls: true,
- danger_accept_invalid_certs,
- protocol: ImapProtocol::ManageSieve,
- timeout,
- };
- let uid_store = Arc::new(UIDStore {
- is_online: Arc::new(Mutex::new((
- SystemTime::now(),
- Err(MeliError::new("Account is uninitialised.")),
- ))),
- ..UIDStore::new(
- account_hash,
- Arc::new(account_name),
- event_consumer,
- server_conf.timeout,
- )
- });
- Ok(ImapConnection::new_connection(&server_conf, uid_store))
-}
+impl ManageSieveConnection {
+ pub fn new(
+ account_hash: crate::backends::AccountHash,
+ account_name: String,
+ s: &AccountSettings,
+ event_consumer: crate::backends::BackendEventConsumer,
+ ) -> Result<Self> {
+ let server_hostname = get_conf_val!(s["server_hostname"])?;
+ let server_username = get_conf_val!(s["server_username"])?;
+ let server_password = get_conf_val!(s["server_password"])?;
+ let server_port = get_conf_val!(s["server_port"], 4190)?;
+ let danger_accept_invalid_certs: bool =
+ get_conf_val!(s["danger_accept_invalid_certs"], false)?;
+ let timeout = get_conf_val!(s["timeout"], 16_u64)?;
+ let timeout = if timeout == 0 {
+ None
+ } else {
+ Some(std::time::Duration::from_secs(timeout))
+ };
+ let server_conf = ImapServerConf {
+ server_hostname: server_hostname.to_string(),
+ server_username: server_username.to_string(),
+ server_password: server_password.to_string(),
+ server_port,
+ use_starttls: true,
+ use_tls: true,
+ danger_accept_invalid_certs,
+ protocol: ImapProtocol::ManageSieve,
+ timeout,
+ };
+ let uid_store = Arc::new(UIDStore {
+ is_online: Arc::new(Mutex::new((
+ SystemTime::now(),
+ Err(MeliError::new("Account is uninitialised.")),
+ ))),
+ ..UIDStore::new(
+ account_hash,
+ Arc::new(account_name),
+ event_consumer,
+ server_conf.timeout,
+ )
+ });
+ Ok(Self {
+ inner: ImapConnection::new_connection(&server_conf, uid_store),
+ })
+ }
-impl ManageSieve for ImapConnection {
- fn havespace(&mut self) -> Result<()> {
+ pub async fn havespace(&mut self) -> Result<()> {
Ok(())
}
- fn putscript(&mut self) -> Result<()> {
- Ok(())
+
+ pub async fn putscript(&mut self, script_name: &[u8], script: &[u8]) -> Result<()> {
+ let mut ret = Vec::new();
+ self.inner
+ .send_literal(format!("Putscript {{{len}+}}\r\n", len = script_name.len()).as_bytes())
+ .await?;
+ self.inner.send_literal(script_name).await?;
+ self.inner
+ .send_literal(format!(" {{{len}+}}\r\n", len = script.len()).as_bytes())
+ .await?;
+ self.inner.send_literal(script).await?;
+ self.inner
+ .read_response(&mut ret, RequiredResponses::empty())
+ .await?;
+ let (_rest, response) = parser::response_oknobye(&ret)?;
+ match response {
+ ManageSieveResponse::Ok { .. } => Ok(()),
+ ManageSieveResponse::NoBye { code, message } => Err(format!(
+ "Could not upload script: {} {}",
+ code.map(|b| String::from_utf8_lossy(b)).unwrap_or_default(),
+ message
+ .map(|b| String::from_utf8_lossy(b))
+ .unwrap_or_default()
+ )
+ .into()),
+ }
}
- fn listscripts(&mut self) -> Result<()> {
- Ok(())
+ pub async fn listscripts(&mut self) -> Result<Vec<(Vec<u8>, bool)>> {
+ let mut ret = Vec::new();
+ self.inner.send_command(b"Listscripts").await?;
+ self.inner
+ .read_response(&mut ret, RequiredResponses::empty())
+ .await?;
+ let (_rest, scripts) =
+ parser::terminated(parser::listscripts, parser::tag_no_case(b"OK"))(&ret)?;
+ Ok(scripts
+ .into_iter()
+ .map(|(n, a)| (n.to_vec(), a))
+ .collect::<Vec<(Vec<u8>, bool)>>())
}
- fn setactive(&mut self) -> Result<()> {
- Ok(())
+
+ pub async fn checkscript(&mut self, script: &[u8]) -> Result<()> {
+ let mut ret = Vec::new();
+ self.inner
+ .send_literal(format!("Checkscript {{{len}+}}\r\n", len = script.len()).as_bytes())
+ .await?;
+ self.inner.send_literal(script).await?;
+ self.inner
+ .read_response(&mut ret, RequiredResponses::empty())
+ .await?;
+ let (_rest, response) = parser::response_oknobye(&ret)?;
+ match response {
+ ManageSieveResponse::Ok { .. } => Ok(()),
+ ManageSieveResponse::NoBye { code, message } => Err(format!(
+ "Checkscript reply: {} {}",
+ code.map(|b| String::from_utf8_lossy(b)).unwrap_or_default(),
+ message
+ .map(|b| String::from_utf8_lossy(b))
+ .unwrap_or_default()
+ )
+ .into()),
+ }
}
- fn getscript(&mut self) -> Result<()> {
- Ok(())
+ pub async fn setactive(&mut self, script_name: &[u8]) -> Result<()> {
+ let mut ret = Vec::new();
+ self.inner
+ .send_literal(format!("Setactive {{{len}+}}\r\n", len = script_name.len()).as_bytes())
+ .await?;
+ self.inner.send_literal(script_name).await?;
+ self.inner
+ .read_response(&mut ret, RequiredResponses::empty())
+ .await?;
+ let (_rest, response) = parser::response_oknobye(&ret)?;
+ match response {
+ ManageSieveResponse::Ok { .. } => Ok(()),
+ ManageSieveResponse::NoBye { code, message } => Err(format!(
+ "Could not set active script: {} {}",
+ code.map(|b| String::from_utf8_lossy(b)).unwrap_or_default(),
+ message
+ .map(|b| String::from_utf8_lossy(b))
+ .unwrap_or_default()
+ )
+ .into()),
+ }
}
- fn deletescript(&mut self) -> Result<()> {
- Ok(())
+ pub async fn getscript(&mut self, script_name: &[u8]) -> Result<Vec<u8>> {
+ let mut ret = Vec::new();
+ self.inner
+ .send_literal(format!("Getscript {{{len}+}}\r\n", len = script_name.len()).as_bytes())
+ .await?;
+ self.inner.send_literal(script_name).await?;
+ self.inner
+ .read_response(&mut ret, RequiredResponses::empty())
+ .await?;
+ if let Ok((_, ManageSieveResponse::NoBye { code, message })) =
+ parser::response_oknobye(&ret)
+ {
+ return Err(format!(
+ "Could not set active script: {} {}",
+ code.map(|b| String::from_utf8_lossy(b)).unwrap_or_default(),
+ message
+ .map(|b| String::from_utf8_lossy(b))
+ .unwrap_or_default()
+ )
+ .into());
+ }
+ let (_rest, script) =
+ parser::terminated(parser::getscript, parser::tag_no_case(b"OK"))(&ret)?;
+ Ok(script.to_vec())
}
- fn renamescript(&mut self) -> Result<()> {
+
+ pub async fn deletescript(&mut self, script_name: &[u8]) -> Result<()> {
+ let mut ret = Vec::new();
+ self.inner
+ .send_literal(
+ format!("Deletescript {{{len}+}}\r\n", len = script_name.len()).as_bytes(),
+ )
+ .await?;
+ self.inner.send_literal(script_name).await?;
+ self.inner
+ .read_response(&mut ret, RequiredResponses::empty())
+ .await?;
+ let (_rest, response) = parser::response_oknobye(&ret)?;
+ match response {
+ ManageSieveResponse::Ok { .. } => Ok(()),
+ ManageSieveResponse::NoBye { code, message } => Err(format!(
+ "Could not delete script: {} {}",
+ code.map(|b| String::from_utf8_lossy(b)).unwrap_or_default(),
+ message
+ .map(|b| String::from_utf8_lossy(b))
+ .unwrap_or_default()
+ )
+ .into()),
+ }
+ }
+
+ pub async fn renamescript(&mut self) -> Result<()> {
Ok(())
}
}
diff --git a/melib/src/backends/imap/protocol_parser.rs b/melib/src/backends/imap/protocol_parser.rs
index d546078e..b58e1b06 100644
--- a/melib/src/backends/imap/protocol_parser.rs
+++ b/melib/src/backends/imap/protocol_parser.rs
@@ -1626,12 +1626,12 @@ pub fn mailbox_token(input: &'_ [u8]) -> IResult<&'_ [u8], std::borrow::Cow<'_,
}
// astring = 1*ASTRING-CHAR / string
-fn astring_token(input: &[u8]) -> IResult<&[u8], &[u8]> {
+pub fn astring_token(input: &[u8]) -> IResult<&[u8], &[u8]> {
alt((string_token, astring_char))(input)
}
// string = quoted / literal
-fn string_token(input: &[u8]) -> IResult<&[u8], &[u8]> {
+pub fn string_token(input: &[u8]) -> IResult<&[u8], &[u8]> {
if let Ok((r, o)) = literal(input) {
return Ok((r, o));
}
diff --git a/src/managesieve.rs b/src/managesieve.rs
index f26353ee..340978a9 100644
--- a/src/managesieve.rs
+++ b/src/managesieve.rs
@@ -24,17 +24,15 @@ extern crate melib;
use melib::*;
use std::collections::VecDeque;
-extern crate xdg_utils;
#[macro_use]
extern crate serde_derive;
extern crate linkify;
-extern crate uuid;
extern crate serde_json;
extern crate smallvec;
extern crate termion;
-use melib::backends::imap::managesieve::new_managesieve_connection;
+use melib::backends::imap::managesieve::ManageSieveConnection;
use melib::Result;
#[macro_use]
@@ -64,7 +62,7 @@ pub mod sqlite3;
pub mod jobs;
pub mod mailcap;
-pub mod plugins;
+//pub mod plugins;
use futures::executor::block_on;
@@ -84,10 +82,7 @@ fn main() -> Result<()> {
std::process::exit(1);
}
- let (config_path, account_name) = (
- std::mem::replace(&mut args[0], String::new()),
- std::mem::replace(&mut args[1], String::new()),
- );
+ let (config_path, account_name) = (std::mem::take(&mut args[0]), std::mem::take(&mut args[1]));
std::env::set_var("MELI_CONFIG", config_path);
let settings = conf::Settings::new()?;
if !settings.accounts.contains_key(&account_name) {
@@ -102,12 +97,47 @@ fn main() -> Result<()> {
);
std::process::exit(1);
}
- let mut conn = new_managesieve_connection(&settings.accounts[&account_name].account)?;
- block_on(conn.connect())?;
- let mut res = String::with_capacity(8 * 1024);
+ let account = &settings.accounts[&account_name].account;
+ let mut conn = ManageSieveConnection::new(
+ 0,
+ account_name.clone(),
+ account,
+ melib::backends::BackendEventConsumer::new(std::sync::Arc::new(|_, _| {})),
+ )?;
+ block_on(conn.inner.connect())?;
let mut input = String::new();
- println!("managesieve shell: use 'logout'");
+ const AVAILABLE_COMMANDS: &[&str] = &[
+ "help",
+ "logout",
+ "listscripts",
+ "checkscript",
+ "putscript",
+ "setactive",
+ "getscript",
+ "deletescript",
+ ];
+ const COMMANDS_HELP: &[&str] = &[
+ "help",
+ "logout",
+ "listscripts and whether they are active",
+ "paste a script to check for validity without uploading it",
+ "upload a script",
+ "set a script as active",
+ "get a script by its name",
+ "delete a script by its name",
+ ];
+ println!("managesieve shell: use 'help' for available commands");
+ enum PrevCmd {
+ None,
+ Checkscript,
+ PutscriptName,
+ PutscriptString(String),
+ SetActiveName,
+ GetScriptName,
+ }
+ use PrevCmd::*;
+ let mut prev_cmd: PrevCmd = None;
loop {
use std::io;
use std::io::Write;
@@ -116,12 +146,85 @@ fn main() -> Result<()> {
io::stdout().flush().unwrap();
match io::stdin().read_line(&mut input) {
Ok(_) => {
- if input.trim().eq_ignore_ascii_case("logout") {
+ let input = input.trim();
+ if input.eq_ignore_ascii_case("logout") {
break;
}
- block_on(conn.send_command(input.as_bytes()))?;
- block_on(conn.read_lines(&mut res, String::new()))?;
- println!("out: {}", res.trim());
+ if input.eq_ignore_ascii_case("help") {
+ println!("available commands: [{}]", AVAILABLE_COMMANDS.join(", "));
+ continue;
+ }
+ if input.len() >= "help ".len()
+ && input[0.."help ".len()].eq_ignore_ascii_case("help ")
+ {
+ if let Some(i) = AVAILABLE_COMMANDS
+ .iter()
+ .position(|cmd| cmd.eq_ignore_ascii_case(&input["help ".len()..]))
+ {
+ println!("{}", COMMANDS_HELP[i]);
+ } else {
+ println!("invalid command `{}`", &input["help ".len()..]);
+ }
+ continue;
+ }
+ if input.eq_ignore_ascii_case("listscripts") {
+ let scripts = block_on(conn.listscripts())?;
+ println!("Got {} scripts:", scripts.len());
+ for (script, active) in scripts {
+ println!(
+ "{}active: {}",
+ if active { "" } else { "in" },
+ String::from_utf8_lossy(&script)
+ );
+ }
+ } else if input.eq_ignore_ascii_case("checkscript") {
+ prev_cmd = Checkscript;
+ println!("insert file path of script");
+ } else if input.eq_ignore_ascii_case("putscript") {
+ prev_cmd = PutscriptName;
+ println!("Insert script name");
+ } else if input.eq_ignore_ascii_case("setactive") {
+ prev_cmd = SetActiveName;
+ } else if input.eq_ignore_ascii_case("getscript") {
+ prev_cmd = GetScriptName;
+ } else if input.eq_ignore_ascii_case("deletescript") {
+ println!("unimplemented `{}`", input);
+ } else {
+ match prev_cmd {
+ None => println!("invalid command `{}`", input),
+ Checkscript => {
+ let content = std::fs::read_to_string(&input).unwrap();
+ let result = block_on(conn.checkscript(content.as_bytes()));
+ println!("Got {:?}", result);
+ prev_cmd = None;
+ }
+ PutscriptName => {
+ prev_cmd = PutscriptString(input.to_string());
+ println!("insert file path of script");
+ }
+ PutscriptString(name) => {
+ prev_cmd = None;
+ let content = std::fs::read_to_string(&input).unwrap();
+ let result =
+ block_on(conn.putscript(name.as_bytes(), content.as_bytes()));
+ println!("Got {:?}", result);
+ }
+ SetActiveName => {
+ prev_cmd = None;
+ let result = block_on(conn.setactive(input.as_bytes()));
+ println!("Got {:?}", result);
+ }
+ GetScriptName => {
+ prev_cmd = None;
+ let result = block_on(conn.getscript(input.as_bytes()));
+ println!("Got {:?}", result);
+ }
+ }
+ }
+
+ //block_on(conn.send_command(input.as_bytes()))?;
+ //block_on(conn.read_lines(&mut res, String::new()))?;
+ //println!("out: {}", res.trim());
}
Err(error) => println!("error: {}", error),
}