mirror of https://git.meli.delivery/meli/meli
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
130 lines
4.2 KiB
Rust
130 lines
4.2 KiB
Rust
use anyhow::{Context, Error};
|
|
use futures::{SinkExt, StreamExt};
|
|
use imap_codec::imap_types::{
|
|
command::{Command, CommandBody},
|
|
core::Tag,
|
|
response::{Response, Status},
|
|
};
|
|
use tokio::{self, net::TcpStream};
|
|
use tokio_support::client::{Event, ImapClientCodec};
|
|
use tokio_util::codec::Decoder;
|
|
|
|
// Poor human's terminal color support.
|
|
const BLUE: &str = "\x1b[34m";
|
|
const RED: &str = "\x1b[31m";
|
|
const RESET: &str = "\x1b[0m";
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Error> {
|
|
let addr = std::env::args()
|
|
.nth(1)
|
|
.context("USAGE: tokio-client <host>:<port>")?;
|
|
|
|
let mut framed = {
|
|
let stream = TcpStream::connect(&addr)
|
|
.await
|
|
.context(format!("Could not connect to `{addr}`"))?;
|
|
// This is for demonstration purposes only, and we probably want a bigger number.
|
|
let max_literal_size = 1024;
|
|
|
|
ImapClientCodec::new(max_literal_size).framed(stream)
|
|
};
|
|
|
|
// First, we read the server greeting.
|
|
match framed
|
|
.next()
|
|
.await
|
|
// We get an `Option<Result<...>>` here that denotes ...
|
|
// 1) if we got something from the server, and
|
|
// 2) if it was valid.
|
|
.context("Connection closed unexpectedly")?
|
|
.context("Failed to obtain next message")?
|
|
{
|
|
Event::Greeting(greeting) => {
|
|
println!("S: {BLUE}{greeting:#?}{RESET}");
|
|
}
|
|
Event::Response(response) => {
|
|
return Err(Error::msg(format!("Expected greeting, got `{response:?}`")));
|
|
}
|
|
};
|
|
|
|
// Then, we send a login command to the server ...
|
|
let tag_login = Tag::unvalidated("A1");
|
|
let cmd = Command {
|
|
tag: tag_login.clone(),
|
|
body: CommandBody::login("alice", "password").context("Could not create command")?,
|
|
};
|
|
framed.send(&cmd).await.context("Could not send command")?;
|
|
println!("C: {RED}{cmd:#?}{RESET}");
|
|
|
|
// ... and process the response(s). We must read zero or many data responses before we can
|
|
// finally examine the status response that tells us whether the login succeeded.
|
|
loop {
|
|
let frame = framed
|
|
.next()
|
|
.await
|
|
.context("Connection closed unexpectedly")?
|
|
.context("Failed to obtain next message")?;
|
|
println!("S: {BLUE}{frame:#?}{RESET}");
|
|
|
|
match frame {
|
|
Event::Greeting(greeting) => {
|
|
return Err(Error::msg(format!("Expected response, got `{greeting:?}`")));
|
|
}
|
|
Event::Response(response) => match response {
|
|
Response::Status(ref status) if status.tag() == Some(&tag_login) => {
|
|
if matches!(status, Status::Ok { .. }) {
|
|
println!("[!] got login done (successful)");
|
|
} else {
|
|
println!("[!] got login done (failed)");
|
|
}
|
|
|
|
break;
|
|
}
|
|
_ => {
|
|
println!("[!] unexpected response");
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
let tag_logout = Tag::unvalidated("A2");
|
|
let cmd = Command {
|
|
tag: tag_logout.clone(),
|
|
body: CommandBody::Logout,
|
|
};
|
|
framed.send(&cmd).await.context("Could not send command")?;
|
|
println!("C: {RED}{cmd:#?}{RESET}");
|
|
|
|
loop {
|
|
let frame = framed
|
|
.next()
|
|
.await
|
|
.context("Connection closed unexpectedly")?
|
|
.context("Failed to obtain next message")?;
|
|
println!("S: {BLUE}{frame:#?}{RESET}");
|
|
|
|
match frame {
|
|
Event::Greeting(greeting) => {
|
|
return Err(Error::msg(format!("Expected response, got `{greeting:?}`")));
|
|
}
|
|
Event::Response(response) => match response {
|
|
Response::Status(Status::Bye { .. }) => {
|
|
println!("[!] got bye");
|
|
}
|
|
Response::Status(Status::Ok {
|
|
tag: Some(ref tag), ..
|
|
}) if *tag == tag_logout => {
|
|
println!("[!] got logout done");
|
|
break;
|
|
}
|
|
_ => {
|
|
println!("[!] unexpected response");
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|