melib/nntp: add AUTH support
parent
ce45cf5f17
commit
1bdecd62c7
|
@ -36,7 +36,7 @@ use crate::async_workers::{Async, WorkContext};
|
||||||
use crate::backends::*;
|
use crate::backends::*;
|
||||||
use crate::conf::AccountSettings;
|
use crate::conf::AccountSettings;
|
||||||
use crate::email::*;
|
use crate::email::*;
|
||||||
use crate::error::{MeliError, Result};
|
use crate::error::{MeliError, Result, ResultIntoMeliError};
|
||||||
use futures::lock::Mutex as FutureMutex;
|
use futures::lock::Mutex as FutureMutex;
|
||||||
use futures::stream::Stream;
|
use futures::stream::Stream;
|
||||||
use std::collections::{hash_map::DefaultHasher, BTreeMap};
|
use std::collections::{hash_map::DefaultHasher, BTreeMap};
|
||||||
|
@ -59,10 +59,11 @@ pub static SUPPORTED_CAPABILITIES: &[&str] = &[
|
||||||
pub struct NntpServerConf {
|
pub struct NntpServerConf {
|
||||||
pub server_hostname: String,
|
pub server_hostname: String,
|
||||||
pub server_username: String,
|
pub server_username: String,
|
||||||
//pub server_password: String,
|
pub server_password: String,
|
||||||
pub server_port: u16,
|
pub server_port: u16,
|
||||||
pub use_starttls: bool,
|
pub use_starttls: bool,
|
||||||
pub use_tls: bool,
|
pub use_tls: bool,
|
||||||
|
pub require_auth: bool,
|
||||||
pub danger_accept_invalid_certs: bool,
|
pub danger_accept_invalid_certs: bool,
|
||||||
pub extension_use: NntpExtensionUse,
|
pub extension_use: NntpExtensionUse,
|
||||||
}
|
}
|
||||||
|
@ -404,14 +405,24 @@ impl NntpType {
|
||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
let server_port = get_conf_val!(s["server_port"], 119)?;
|
let server_port = get_conf_val!(s["server_port"], 119)?;
|
||||||
let use_tls = get_conf_val!(s["use_tls"], true)?;
|
let use_tls = get_conf_val!(s["use_tls"], server_port == 563)?;
|
||||||
let use_starttls = use_tls && get_conf_val!(s["use_starttls"], !(server_port == 993))?;
|
let use_starttls = use_tls && get_conf_val!(s["use_starttls"], !(server_port == 563))?;
|
||||||
let danger_accept_invalid_certs: bool =
|
let danger_accept_invalid_certs: bool =
|
||||||
get_conf_val!(s["danger_accept_invalid_certs"], false)?;
|
get_conf_val!(s["danger_accept_invalid_certs"], false)?;
|
||||||
|
let require_auth = get_conf_val!(s["require_auth"], true)?;
|
||||||
let server_conf = NntpServerConf {
|
let server_conf = NntpServerConf {
|
||||||
server_hostname: server_hostname.to_string(),
|
server_hostname: server_hostname.to_string(),
|
||||||
server_username: String::new(),
|
server_username: if require_auth {
|
||||||
//server_password,
|
get_conf_val!(s["server_username"])?.to_string()
|
||||||
|
} else {
|
||||||
|
get_conf_val!(s["server_username"], String::new())?
|
||||||
|
},
|
||||||
|
server_password: if require_auth {
|
||||||
|
get_conf_val!(s["server_password"])?.to_string()
|
||||||
|
} else {
|
||||||
|
get_conf_val!(s["server_password"], String::new())?
|
||||||
|
},
|
||||||
|
require_auth,
|
||||||
server_port,
|
server_port,
|
||||||
use_tls,
|
use_tls,
|
||||||
use_starttls,
|
use_starttls,
|
||||||
|
@ -451,7 +462,7 @@ impl NntpType {
|
||||||
let uid_store: Arc<UIDStore> = Arc::new(UIDStore {
|
let uid_store: Arc<UIDStore> = Arc::new(UIDStore {
|
||||||
account_hash,
|
account_hash,
|
||||||
account_name,
|
account_name,
|
||||||
offline_cache: get_conf_val!(s["X_header_caching"], false)?,
|
offline_cache: false, //get_conf_val!(s["X_header_caching"], false)?,
|
||||||
mailboxes: Arc::new(FutureMutex::new(mailboxes)),
|
mailboxes: Arc::new(FutureMutex::new(mailboxes)),
|
||||||
..UIDStore::default()
|
..UIDStore::default()
|
||||||
});
|
});
|
||||||
|
@ -482,13 +493,14 @@ impl NntpType {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
conn.send_command(command.as_bytes()).await?;
|
conn.send_command(command.as_bytes()).await?;
|
||||||
conn.read_response(&mut res, true).await?;
|
conn.read_response(&mut res, true, &["215 "])
|
||||||
if !res.starts_with("215 ") {
|
.await
|
||||||
return Err(MeliError::new(format!(
|
.chain_err_summary(|| {
|
||||||
"Could not get newsgroups {}: expected LIST ACTIVE response but got: {}",
|
format!(
|
||||||
&conn.uid_store.account_name, res
|
"Could not get newsgroups {}: expected LIST ACTIVE response but got: {}",
|
||||||
)));
|
&conn.uid_store.account_name, res
|
||||||
}
|
)
|
||||||
|
})?;
|
||||||
debug!(&res);
|
debug!(&res);
|
||||||
let mut mailboxes_lck = conn.uid_store.mailboxes.lock().await;
|
let mut mailboxes_lck = conn.uid_store.mailboxes.lock().await;
|
||||||
for l in res.split_rn().skip(1) {
|
for l in res.split_rn().skip(1) {
|
||||||
|
@ -517,14 +529,23 @@ impl NntpType {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
let server_port = get_conf_val!(s["server_port"], 119)?;
|
let server_port = get_conf_val!(s["server_port"], 119)?;
|
||||||
let use_tls = get_conf_val!(s["use_tls"], true)?;
|
let use_tls = get_conf_val!(s["use_tls"], server_port == 563)?;
|
||||||
let use_starttls = get_conf_val!(s["use_starttls"], !(server_port == 993))?;
|
let use_starttls = get_conf_val!(s["use_starttls"], !(server_port == 563))?;
|
||||||
if !use_tls && use_starttls {
|
if !use_tls && use_starttls {
|
||||||
return Err(MeliError::new(format!(
|
return Err(MeliError::new(format!(
|
||||||
"Configuration error ({}): incompatible use_tls and use_starttls values: use_tls = false, use_starttls = true",
|
"Configuration error ({}): incompatible use_tls and use_starttls values: use_tls = false, use_starttls = true",
|
||||||
s.name.as_str(),
|
s.name.as_str(),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "deflate_compression")]
|
||||||
|
get_conf_val!(s["use_deflate"], true)?;
|
||||||
|
#[cfg(not(feature = "deflate_compression"))]
|
||||||
|
if s.extra.contains_key("use_deflate") {
|
||||||
|
return Err(MeliError::new(format!(
|
||||||
|
"Configuration error ({}): setting `use_deflate` is set but this version of meli isn't compiled with DEFLATE support.",
|
||||||
|
s.name.as_str(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
get_conf_val!(s["danger_accept_invalid_certs"], false)?;
|
get_conf_val!(s["danger_accept_invalid_certs"], false)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -552,13 +573,14 @@ async fn fetch_envs(
|
||||||
.to_string();
|
.to_string();
|
||||||
conn.send_command(format!("GROUP {}", path).as_bytes())
|
conn.send_command(format!("GROUP {}", path).as_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
conn.read_response(&mut res, false).await?;
|
conn.read_response(&mut res, false, &["211 "])
|
||||||
if !res.starts_with("211 ") {
|
.await
|
||||||
return Err(MeliError::new(format!(
|
.chain_err_summary(|| {
|
||||||
"{} Could not select newsgroup {}: expected GROUP response but got: {}",
|
format!(
|
||||||
&uid_store.account_name, path, res
|
"{} Could not select newsgroup {}: expected GROUP response but got: {}",
|
||||||
)));
|
&uid_store.account_name, path, res
|
||||||
}
|
)
|
||||||
|
})?;
|
||||||
/*
|
/*
|
||||||
* Parameters
|
* Parameters
|
||||||
group Name of newsgroup
|
group Name of newsgroup
|
||||||
|
@ -578,22 +600,22 @@ async fn fetch_envs(
|
||||||
let high = usize::from_str(&s[3]).unwrap_or(0);
|
let high = usize::from_str(&s[3]).unwrap_or(0);
|
||||||
drop(s);
|
drop(s);
|
||||||
|
|
||||||
conn.send_command(format!("OVER {}-{}", high.saturating_sub(250), high).as_bytes())
|
conn.send_command(format!("OVER {}-{}", high.saturating_sub(100), high).as_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
conn.read_response(&mut res, true).await?;
|
conn.read_response(&mut res, true, &["224 "])
|
||||||
if !res.starts_with("224 ") {
|
.await
|
||||||
return Err(MeliError::new(format!(
|
.chain_err_summary(|| {
|
||||||
"{} Could not select newsgroup {}: expected OVER response but got: {}",
|
format!(
|
||||||
&uid_store.account_name, path, res
|
"{} Could not select newsgroup {}: expected OVER response but got: {}",
|
||||||
)));
|
&uid_store.account_name, path, res
|
||||||
}
|
)
|
||||||
|
})?;
|
||||||
let mut ret = Vec::with_capacity(total);
|
let mut ret = Vec::with_capacity(total);
|
||||||
//hash_index: Arc<Mutex<HashMap<EnvelopeHash, (UID, MailboxHash)>>>,
|
//hash_index: Arc<Mutex<HashMap<EnvelopeHash, (UID, MailboxHash)>>>,
|
||||||
//uid_index: Arc<Mutex<HashMap<(MailboxHash, UID), EnvelopeHash>>>,
|
//uid_index: Arc<Mutex<HashMap<(MailboxHash, UID), EnvelopeHash>>>,
|
||||||
let mut hash_index_lck = uid_store.hash_index.lock().unwrap();
|
let mut hash_index_lck = uid_store.hash_index.lock().unwrap();
|
||||||
let mut uid_index_lck = uid_store.uid_index.lock().unwrap();
|
let mut uid_index_lck = uid_store.uid_index.lock().unwrap();
|
||||||
for l in res.split_rn().skip(1) {
|
for l in res.split_rn().skip(1) {
|
||||||
debug!(&l);
|
|
||||||
let (_, (num, env)) = debug!(protocol_parser::over_article(&l))?;
|
let (_, (num, env)) = debug!(protocol_parser::over_article(&l))?;
|
||||||
hash_index_lck.insert(env.hash(), (num, mailbox_hash));
|
hash_index_lck.insert(env.hash(), (num, mailbox_hash));
|
||||||
uid_index_lck.insert((mailbox_hash, num), env.hash());
|
uid_index_lck.insert((mailbox_hash, num), env.hash());
|
||||||
|
|
|
@ -103,27 +103,6 @@ impl NntpStream {
|
||||||
current_mailbox: MailboxSelection::None,
|
current_mailbox: MailboxSelection::None,
|
||||||
};
|
};
|
||||||
|
|
||||||
ret.read_response(&mut res, false).await?;
|
|
||||||
ret.send_command(b"CAPABILITIES").await?;
|
|
||||||
ret.read_response(&mut res, true).await?;
|
|
||||||
if !res.starts_with("101 ") {
|
|
||||||
return Err(MeliError::new(format!(
|
|
||||||
"Could not connect to {}: expected CAPABILITIES response but got:{}",
|
|
||||||
&server_conf.server_hostname, res
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
let capabilities: Vec<&str> = res.lines().skip(1).collect();
|
|
||||||
|
|
||||||
if !capabilities
|
|
||||||
.iter()
|
|
||||||
.any(|cap| cap.eq_ignore_ascii_case("VERSION 2"))
|
|
||||||
{
|
|
||||||
return Err(MeliError::new(format!(
|
|
||||||
"Could not connect to {}: server is not NNTP compliant",
|
|
||||||
&server_conf.server_hostname
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if server_conf.use_tls {
|
if server_conf.use_tls {
|
||||||
let mut connector = TlsConnector::builder();
|
let mut connector = TlsConnector::builder();
|
||||||
if server_conf.danger_accept_invalid_certs {
|
if server_conf.danger_accept_invalid_certs {
|
||||||
|
@ -134,6 +113,37 @@ impl NntpStream {
|
||||||
.chain_err_kind(crate::error::ErrorKind::Network)?;
|
.chain_err_kind(crate::error::ErrorKind::Network)?;
|
||||||
|
|
||||||
if server_conf.use_starttls {
|
if server_conf.use_starttls {
|
||||||
|
ret.read_response(&mut res, false, &["200 ", "201 "])
|
||||||
|
.await?;
|
||||||
|
ret.send_command(b"CAPABILITIES").await?;
|
||||||
|
ret.read_response(&mut res, true, command_to_replycodes("CAPABILITIES"))
|
||||||
|
.await?;
|
||||||
|
if !res.starts_with("101 ") {
|
||||||
|
return Err(MeliError::new(format!(
|
||||||
|
"Could not connect to {}: expected CAPABILITIES response but got:{}",
|
||||||
|
&server_conf.server_hostname, res
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let capabilities: Vec<&str> = res.lines().skip(1).collect();
|
||||||
|
|
||||||
|
if !capabilities
|
||||||
|
.iter()
|
||||||
|
.any(|cap| cap.eq_ignore_ascii_case("VERSION 2"))
|
||||||
|
{
|
||||||
|
return Err(MeliError::new(format!(
|
||||||
|
"Could not connect to {}: server is not NNTP compliant",
|
||||||
|
&server_conf.server_hostname
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if !capabilities
|
||||||
|
.iter()
|
||||||
|
.any(|cap| cap.eq_ignore_ascii_case("STARTTLS"))
|
||||||
|
{
|
||||||
|
return Err(MeliError::new(format!(
|
||||||
|
"Could not connect to {}: server does not support STARTTLS",
|
||||||
|
&server_conf.server_hostname
|
||||||
|
)));
|
||||||
|
}
|
||||||
ret.stream
|
ret.stream
|
||||||
.write_all(b"STARTTLS\r\n")
|
.write_all(b"STARTTLS\r\n")
|
||||||
.await
|
.await
|
||||||
|
@ -142,7 +152,8 @@ impl NntpStream {
|
||||||
.flush()
|
.flush()
|
||||||
.await
|
.await
|
||||||
.chain_err_kind(crate::error::ErrorKind::Network)?;
|
.chain_err_kind(crate::error::ErrorKind::Network)?;
|
||||||
ret.read_response(&mut res, false).await?;
|
ret.read_response(&mut res, false, command_to_replycodes("STARTTLS"))
|
||||||
|
.await?;
|
||||||
if !res.starts_with("382 ") {
|
if !res.starts_with("382 ") {
|
||||||
return Err(MeliError::new(format!(
|
return Err(MeliError::new(format!(
|
||||||
"Could not connect to {}: could not begin TLS negotiation, got: {}",
|
"Could not connect to {}: could not begin TLS negotiation, got: {}",
|
||||||
|
@ -192,8 +203,11 @@ impl NntpStream {
|
||||||
//)
|
//)
|
||||||
//.await?;
|
//.await?;
|
||||||
|
|
||||||
|
ret.read_response(&mut res, false, &["200 ", "201 "])
|
||||||
|
.await?;
|
||||||
ret.send_command(b"CAPABILITIES").await?;
|
ret.send_command(b"CAPABILITIES").await?;
|
||||||
ret.read_response(&mut res, true).await?;
|
ret.read_response(&mut res, true, command_to_replycodes("CAPABILITIES"))
|
||||||
|
.await?;
|
||||||
if !res.starts_with("101 ") {
|
if !res.starts_with("101 ") {
|
||||||
return Err(MeliError::new(format!(
|
return Err(MeliError::new(format!(
|
||||||
"Could not connect to {}: expected CAPABILITIES response but got:{}",
|
"Could not connect to {}: expected CAPABILITIES response but got:{}",
|
||||||
|
@ -201,46 +215,92 @@ impl NntpStream {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
let capabilities: HashSet<String> = res.lines().skip(1).map(|l| l.to_string()).collect();
|
let capabilities: HashSet<String> = res.lines().skip(1).map(|l| l.to_string()).collect();
|
||||||
#[cfg(feature = "deflate_compression")]
|
if !capabilities
|
||||||
|
.iter()
|
||||||
|
.any(|cap| cap.eq_ignore_ascii_case("VERSION 2"))
|
||||||
|
{
|
||||||
|
return Err(MeliError::new(format!(
|
||||||
|
"Could not connect to {}: server is not NNTP compliant",
|
||||||
|
&server_conf.server_hostname
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if server_conf.require_auth {
|
||||||
|
if capabilities.iter().any(|c| c.starts_with("AUTHINFO USER")) {
|
||||||
|
ret.send_command(
|
||||||
|
format!("AUTHINFO USER {}", server_conf.server_username).as_bytes(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
ret.read_response(&mut res, false, command_to_replycodes("AUTHINFO USER"))
|
||||||
|
.await
|
||||||
|
.chain_err_summary(|| format!("Authentication state error: {}", res))
|
||||||
|
.chain_err_kind(ErrorKind::Authentication)?;
|
||||||
|
if res.starts_with("381 ") {
|
||||||
|
ret.send_command(
|
||||||
|
format!("AUTHINFO PASS {}", server_conf.server_password).as_bytes(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
ret.read_response(&mut res, false, command_to_replycodes("AUTHINFO PASS"))
|
||||||
|
.await
|
||||||
|
.chain_err_summary(|| format!("Authentication state error: {}", res))
|
||||||
|
.chain_err_kind(ErrorKind::Authentication)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(MeliError::new(format!(
|
||||||
|
"Could not connect: no supported auth mechanisms in server capabilities: {:?}",
|
||||||
|
capabilities
|
||||||
|
))
|
||||||
|
.set_err_kind(ErrorKind::Authentication));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "deflate_compression")]
|
#[cfg(feature = "deflate_compression")]
|
||||||
if capabilities.contains("COMPRESS DEFLATE") && ret.extension_use.deflate {
|
if capabilities.contains("COMPRESS DEFLATE") && ret.extension_use.deflate {
|
||||||
ret.send_command(b"COMPRESS DEFLATE").await?;
|
ret.send_command(b"COMPRESS DEFLATE").await?;
|
||||||
ret.read_response(&mut res, false).await?;
|
ret.read_response(&mut res, false, command_to_replycodes("COMPRESS DEFLATE"))
|
||||||
if !res.starts_with("206 ") {
|
.await
|
||||||
crate::log(
|
.chain_err_summary(|| {
|
||||||
format!(
|
format!(
|
||||||
"Could not use COMPRESS=DEFLATE in account `{}`: server replied with `{}`",
|
"Could not use COMPRESS DEFLATE in account `{}`: server replied with `{}`",
|
||||||
server_conf.server_hostname, res
|
server_conf.server_hostname, res
|
||||||
),
|
)
|
||||||
crate::LoggingLevel::WARN,
|
})?;
|
||||||
);
|
let NntpStream {
|
||||||
} else {
|
stream,
|
||||||
let NntpStream {
|
extension_use,
|
||||||
stream,
|
current_mailbox,
|
||||||
|
} = ret;
|
||||||
|
let stream = stream.into_inner()?;
|
||||||
|
return Ok((
|
||||||
|
capabilities,
|
||||||
|
NntpStream {
|
||||||
|
stream: AsyncWrapper::new(stream.deflate())?,
|
||||||
extension_use,
|
extension_use,
|
||||||
current_mailbox,
|
current_mailbox,
|
||||||
} = ret;
|
},
|
||||||
let stream = stream.into_inner()?;
|
));
|
||||||
return Ok((
|
|
||||||
capabilities,
|
|
||||||
NntpStream {
|
|
||||||
stream: AsyncWrapper::new(stream.deflate())?,
|
|
||||||
extension_use,
|
|
||||||
current_mailbox,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((capabilities, ret))
|
Ok((capabilities, ret))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_response(&mut self, ret: &mut String, is_multiline: bool) -> Result<()> {
|
pub async fn read_response(
|
||||||
self.read_lines(ret, is_multiline).await?;
|
&mut self,
|
||||||
|
ret: &mut String,
|
||||||
|
is_multiline: bool,
|
||||||
|
expected_reply_code: &[&str],
|
||||||
|
) -> Result<()> {
|
||||||
|
self.read_lines(ret, is_multiline, expected_reply_code)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_lines(&mut self, ret: &mut String, is_multiline: bool) -> Result<()> {
|
pub async fn read_lines(
|
||||||
|
&mut self,
|
||||||
|
ret: &mut String,
|
||||||
|
is_multiline: bool,
|
||||||
|
expected_reply_code: &[&str],
|
||||||
|
) -> Result<()> {
|
||||||
let mut buf: Vec<u8> = vec![0; Connection::IO_BUF_SIZE];
|
let mut buf: Vec<u8> = vec![0; Connection::IO_BUF_SIZE];
|
||||||
ret.clear();
|
ret.clear();
|
||||||
let mut last_line_idx: usize = 0;
|
let mut last_line_idx: usize = 0;
|
||||||
|
@ -249,6 +309,24 @@ impl NntpStream {
|
||||||
Ok(0) => break,
|
Ok(0) => break,
|
||||||
Ok(b) => {
|
Ok(b) => {
|
||||||
ret.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..b]) });
|
ret.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..b]) });
|
||||||
|
if ret.len() > 4 {
|
||||||
|
if ret.starts_with("205 ") {
|
||||||
|
return Err(MeliError::new(format!("Disconnected: {}", ret)));
|
||||||
|
} else if ret.starts_with("501 ") || ret.starts_with("500 ") {
|
||||||
|
return Err(MeliError::new(format!("Syntax error: {}", ret)));
|
||||||
|
} else if ret.starts_with("403 ") {
|
||||||
|
return Err(MeliError::new(format!("Internal error: {}", ret)));
|
||||||
|
} else if ret.starts_with("502 ")
|
||||||
|
|| ret.starts_with("480 ")
|
||||||
|
|| ret.starts_with("483 ")
|
||||||
|
|| ret.starts_with("401 ")
|
||||||
|
{
|
||||||
|
return Err(MeliError::new(format!("Connection state error: {}", ret))
|
||||||
|
.set_err_kind(ErrorKind::Authentication));
|
||||||
|
} else if !expected_reply_code.iter().any(|r| ret.starts_with(r)) {
|
||||||
|
return Err(MeliError::new(format!("Unexpected reply code: {}", ret)));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(mut pos) = ret[last_line_idx..].rfind("\r\n") {
|
if let Some(mut pos) = ret[last_line_idx..].rfind("\r\n") {
|
||||||
if !is_multiline {
|
if !is_multiline {
|
||||||
break;
|
break;
|
||||||
|
@ -256,9 +334,6 @@ impl NntpStream {
|
||||||
ret.replace_range(pos + "\r\n".len()..pos + "\r\n.\r\n".len(), "");
|
ret.replace_range(pos + "\r\n".len()..pos + "\r\n.\r\n".len(), "");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ret[last_line_idx..].starts_with("205 ") {
|
|
||||||
return Err(MeliError::new(format!("Disconnected: {}", ret)));
|
|
||||||
}
|
|
||||||
if let Some(prev_line) =
|
if let Some(prev_line) =
|
||||||
ret[last_line_idx..pos + last_line_idx].rfind("\r\n")
|
ret[last_line_idx..pos + last_line_idx].rfind("\r\n")
|
||||||
{
|
{
|
||||||
|
@ -339,15 +414,27 @@ impl NntpConnection {
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
ret: &'a mut String,
|
ret: &'a mut String,
|
||||||
is_multiline: bool,
|
is_multiline: bool,
|
||||||
|
expected_reply_code: &'static [&str],
|
||||||
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> {
|
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
ret.clear();
|
ret.clear();
|
||||||
self.stream.as_mut()?.read_response(ret, is_multiline).await
|
self.stream
|
||||||
|
.as_mut()?
|
||||||
|
.read_response(ret, is_multiline, expected_reply_code)
|
||||||
|
.await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_lines(&mut self, ret: &mut String, is_multiline: bool) -> Result<()> {
|
pub async fn read_lines(
|
||||||
self.stream.as_mut()?.read_lines(ret, is_multiline).await?;
|
&mut self,
|
||||||
|
ret: &mut String,
|
||||||
|
is_multiline: bool,
|
||||||
|
expected_reply_code: &[&str],
|
||||||
|
) -> Result<()> {
|
||||||
|
self.stream
|
||||||
|
.as_mut()?
|
||||||
|
.read_lines(ret, is_multiline, expected_reply_code)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,3 +464,31 @@ impl NntpConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn command_to_replycodes(c: &str) -> &'static [&'static str] {
|
||||||
|
if c.starts_with("OVER") {
|
||||||
|
&["224 "]
|
||||||
|
} else if c.starts_with("LIST") {
|
||||||
|
&["215 "]
|
||||||
|
} else if c.starts_with("STARTTLS") {
|
||||||
|
&["382 "]
|
||||||
|
} else if c.starts_with("GROUP") {
|
||||||
|
&["211 "]
|
||||||
|
} else if c.starts_with("CAPABILITIES") {
|
||||||
|
&["101 "]
|
||||||
|
} else if c.starts_with("ARTICLE") {
|
||||||
|
&["220 "]
|
||||||
|
} else if c.starts_with("DATE") {
|
||||||
|
&["111 "]
|
||||||
|
} else if c.starts_with("NEWNEWS") {
|
||||||
|
&["230 "]
|
||||||
|
} else if c.starts_with("AUTHINFO USER") {
|
||||||
|
&["281 ", "381 "]
|
||||||
|
} else if c.starts_with("AUTHINFO PASS") {
|
||||||
|
&["281 "]
|
||||||
|
} else if c.starts_with("COMPRESS DEFLATE") {
|
||||||
|
&["206 "]
|
||||||
|
} else {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ impl BackendOp for NntpOp {
|
||||||
.to_string();
|
.to_string();
|
||||||
conn.send_command(format!("GROUP {}", path).as_bytes())
|
conn.send_command(format!("GROUP {}", path).as_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
conn.read_response(&mut res, false).await?;
|
conn.read_response(&mut res, false, &["211 "]).await?;
|
||||||
if !res.starts_with("211 ") {
|
if !res.starts_with("211 ") {
|
||||||
return Err(MeliError::new(format!(
|
return Err(MeliError::new(format!(
|
||||||
"{} Could not select newsgroup {}: expected GROUP response but got: {}",
|
"{} Could not select newsgroup {}: expected GROUP response but got: {}",
|
||||||
|
@ -74,7 +74,7 @@ impl BackendOp for NntpOp {
|
||||||
}
|
}
|
||||||
conn.send_command(format!("ARTICLE {}", uid).as_bytes())
|
conn.send_command(format!("ARTICLE {}", uid).as_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
conn.read_response(&mut res, true).await?;
|
conn.read_response(&mut res, true, &["220 "]).await?;
|
||||||
if !res.starts_with("220 ") {
|
if !res.starts_with("220 ") {
|
||||||
return Err(MeliError::new(format!(
|
return Err(MeliError::new(format!(
|
||||||
"{} Could not select article {}: expected ARTICLE response but got: {}",
|
"{} Could not select article {}: expected ARTICLE response but got: {}",
|
||||||
|
|
Loading…
Reference in New Issue