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>pull/161/head
parent
3697b7d960
commit
803d3414fd
|
@ -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"
|
||||
|
|
|
@ -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,8 +48,128 @@ pub fn managesieve_capabilities(input: &[u8]) -> Result<Vec<(&[u8], &[u8])>> {
|
|||
Ok(ret)
|
||||
}
|
||||
|
||||
#[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_capabilities() {
|
||||
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"[..]),
|
||||
|
@ -53,15 +179,94 @@ fn test_managesieve_capabilities() {
|
|||
(&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,36 +277,22 @@ pub fn quoted_raw(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
i += 1;
|
||||
}
|
||||
|
||||
Err(nom::Err::Error(NomError {
|
||||
input,
|
||||
code: ErrorKind::Tag,
|
||||
}))
|
||||
Err(nom::Err::Error((input, "no quotes").into()))
|
||||
}
|
||||
|
||||
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<()>;
|
||||
}
|
||||
|
||||
pub fn new_managesieve_connection(
|
||||
impl ManageSieveConnection {
|
||||
pub fn new(
|
||||
account_hash: crate::backends::AccountHash,
|
||||
account_name: String,
|
||||
s: &AccountSettings,
|
||||
event_consumer: crate::backends::BackendEventConsumer,
|
||||
) -> Result<ImapConnection> {
|
||||
) -> 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 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
|
||||
|
@ -131,32 +322,154 @@ pub fn new_managesieve_connection(
|
|||
server_conf.timeout,
|
||||
)
|
||||
});
|
||||
Ok(ImapConnection::new_connection(&server_conf, uid_store))
|
||||
Ok(Self {
|
||||
inner: ImapConnection::new_connection(&server_conf, uid_store),
|
||||
})
|
||||
}
|
||||
|
||||
impl ManageSieve for ImapConnection {
|
||||
fn havespace(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn putscript(&mut self) -> Result<()> {
|
||||
pub async fn havespace(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn listscripts(&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 setactive(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn getscript(&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 deletescript(&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 renamescript(&mut self) -> Result<()> {
|
||||
}
|
||||
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue