Compare commits

..

No commits in common. "f7274363cb16d3c9efad8bc8c0360b94cba42af7" and "0f57af374669148a04a49f9151f83d4f5cb2ef34" have entirely different histories.

17 changed files with 597 additions and 914 deletions

View file

@ -84,10 +84,10 @@ jobs:
submodules: true submodules: true
- name: Install nightly - name: Install nightly
uses: dtolnay/rust-toolchain@nightly uses: dtolnay/rust-toolchain@nightly
- name: Install cargo-docs-rs - name: cargo doc
uses: dtolnay/install@cargo-docs-rs run: cargo doc --no-deps --all-features
- name: cargo docs-rs env:
run: cargo docs-rs RUSTDOCFLAGS: --cfg docsrs
hack: hack:
# cargo-hack checks combinations of feature flags to ensure that features are all additive # cargo-hack checks combinations of feature flags to ensure that features are all additive
# which is required for feature unification # which is required for feature unification
@ -112,7 +112,7 @@ jobs:
# https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
strategy: strategy:
matrix: matrix:
msrv: ["1.80.0"] msrv: ["1.65.0"]
name: ubuntu / ${{ matrix.msrv }} name: ubuntu / ${{ matrix.msrv }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View file

@ -197,7 +197,7 @@ jobs:
- name: Record Rust version - name: Record Rust version
run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV"
- name: Upload to codecov.io - name: Upload to codecov.io
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v4
with: with:
fail_ci_if_error: true fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}

1112
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "imap" name = "imap"
version = "3.0.0-alpha.15" version = "3.0.0-alpha.14"
authors = ["Jon Gjengset <jon@thesquareplanet.com>", authors = ["Jon Gjengset <jon@thesquareplanet.com>",
"Matt McCoy <mattnenterprise@yahoo.com>"] "Matt McCoy <mattnenterprise@yahoo.com>"]
documentation = "https://docs.rs/imap/" documentation = "https://docs.rs/imap/"
@ -23,19 +23,19 @@ test-full-imap = []
[dependencies] [dependencies]
native-tls = { version = "0.2.2", optional = true } native-tls = { version = "0.2.2", optional = true }
rustls-connector = { version = "0.22.0", optional = true } rustls-connector = { version = "0.19.0", optional = true }
regex = "1.0" regex = "1.0"
bufstream = "0.1.3" bufstream = "0.1.3"
imap-proto = "0.16.1" imap-proto = "0.16.1"
nom = { version = "7.1.0", default-features = false } nom = { version = "7.1.0", default-features = false }
base64 = "0.22" base64 = "0.22"
chrono = { version = "0.4.37", default-features = false, features = ["std"]} chrono = { version = "0.4", default-features = false, features = ["std"]}
lazy_static = "1.4" lazy_static = "1.4"
ouroboros = "0.18.0" ouroboros = "0.18.0"
[dev-dependencies] [dev-dependencies]
lettre = "0.11" lettre = "0.11"
rustls-connector = "0.22.0" rustls-connector = "0.19.0"
clap = { version = "4.5.4", features = ["derive"] } clap = { version = "4.5.4", features = ["derive"] }
# to make -Zminimal-versions work # to make -Zminimal-versions work

View file

@ -1,9 +1,12 @@
<!-- this file uses https://github.com/livioribeiro/cargo-readme -->
<!-- do not manually edit README.md, instead edit README.tpl or src/lib.rs -->
# imap # imap
[![Crates.io](https://img.shields.io/crates/v/imap.svg)](https://crates.io/crates/imap) [![Crates.io](https://img.shields.io/crates/v/imap.svg)](https://crates.io/crates/imap)
[![Documentation](https://docs.rs/imap/badge.svg)](https://docs.rs/imap/) [![Documentation](https://docs.rs/imap/badge.svg)](https://docs.rs/imap/)
[![Crate License](https://img.shields.io/crates/l/imap.svg)](https://crates.io/crates/imap) [![Crate License](https://img.shields.io/crates/l/imap.svg)](https://crates.io/crates/imap)
[![codecov](https://codecov.io/gh/jonhoo/rust-imap/graph/badge.svg?token=2cO5sjtmyR)](https://codecov.io/gh/jonhoo/rust-imap) [![Codecov](https://codecov.io/github/jonhoo/rust-imap/coverage.svg?branch=master)](https://codecov.io/gh/jonhoo/rust-imap)
[![Dependency status](https://deps.rs/repo/github/jonhoo/rust-imap/status.svg)](https://deps.rs/repo/github/jonhoo/rust-imap) [![Dependency status](https://deps.rs/repo/github/jonhoo/rust-imap/status.svg)](https://deps.rs/repo/github/jonhoo/rust-imap)
This crate lets you connect to and interact with servers that implement the IMAP protocol ([RFC This crate lets you connect to and interact with servers that implement the IMAP protocol ([RFC

View file

@ -3,10 +3,10 @@ use bufstream::BufStream;
use chrono::{DateTime, FixedOffset}; use chrono::{DateTime, FixedOffset};
use imap_proto::Response; use imap_proto::Response;
use std::collections::HashSet; use std::collections::HashSet;
use std::collections::VecDeque;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::str; use std::str;
use std::sync::mpsc;
use super::authenticator::Authenticator; use super::authenticator::Authenticator;
use super::error::{Bad, Bye, Error, No, ParseError, Result, TagMismatch, ValidateError}; use super::error::{Bad, Bye, Error, No, ParseError, Result, TagMismatch, ValidateError};
@ -141,9 +141,11 @@ fn validate_sequence_set(
#[derive(Debug)] #[derive(Debug)]
pub struct Session<T: Read + Write> { pub struct Session<T: Read + Write> {
conn: Connection<T>, conn: Connection<T>,
pub(crate) unsolicited_responses_tx: mpsc::Sender<UnsolicitedResponse>,
/// Server responses that are not related to the current command. See also the note on /// Server responses that are not related to the current command. See also the note on
/// [unilateral server responses in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). /// [unilateral server responses in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7).
pub(crate) unsolicited_responses: VecDeque<UnsolicitedResponse>, pub unsolicited_responses: mpsc::Receiver<UnsolicitedResponse>,
} }
/// An (unauthenticated) handle to talk to an IMAP server. This is what you get when first /// An (unauthenticated) handle to talk to an IMAP server. This is what you get when first
@ -260,7 +262,7 @@ impl<'a, T: Read + Write> AppendCmd<'a, T> {
self.session.stream.flush()?; self.session.stream.flush()?;
self.session self.session
.read_response() .read_response()
.and_then(|(lines, _)| parse_append(&lines, &mut self.session.unsolicited_responses)) .and_then(|(lines, _)| parse_append(&lines, &mut self.session.unsolicited_responses_tx))
} }
} }
@ -368,10 +370,10 @@ impl<T: Read + Write> Client<T> {
/// ///
/// This allows reading capabilities before authentication. /// This allows reading capabilities before authentication.
pub fn capabilities(&mut self) -> Result<Capabilities> { pub fn capabilities(&mut self) -> Result<Capabilities> {
// Create a temporary vec deque as we do not care about out of band responses before login // Create a temporary channel as we do not care about out of band responses before login
let mut unsolicited_responses = VecDeque::new(); let (mut tx, _rx) = mpsc::channel();
self.run_command_and_read_response("CAPABILITY") self.run_command_and_read_response("CAPABILITY")
.and_then(|lines| Capabilities::parse(lines, &mut unsolicited_responses)) .and_then(|lines| Capabilities::parse(lines, &mut tx))
} }
/// Log in to the IMAP server. Upon success a [`Session`](struct.Session.html) instance is /// Log in to the IMAP server. Upon success a [`Session`](struct.Session.html) instance is
@ -501,7 +503,7 @@ impl<T: Read + Write> Client<T> {
let data = let data =
ok_or_unauth_client_err!(parse_authenticate_response(line_str), self); ok_or_unauth_client_err!(parse_authenticate_response(line_str), self);
ok_or_unauth_client_err!( ok_or_unauth_client_err!(
general_purpose::STANDARD general_purpose::STANDARD_NO_PAD
.decode(data) .decode(data)
.map_err(|e| Error::Parse(ParseError::Authentication( .map_err(|e| Error::Parse(ParseError::Authentication(
data.to_string(), data.to_string(),
@ -512,7 +514,7 @@ impl<T: Read + Write> Client<T> {
}; };
let raw_response = &authenticator.process(&challenge); let raw_response = &authenticator.process(&challenge);
let auth_response = general_purpose::STANDARD.encode(raw_response); let auth_response = general_purpose::STANDARD_NO_PAD.encode(raw_response);
ok_or_unauth_client_err!( ok_or_unauth_client_err!(
self.write_line(auth_response.into_bytes().as_slice()), self.write_line(auth_response.into_bytes().as_slice()),
self self
@ -528,17 +530,14 @@ impl<T: Read + Write> Client<T> {
impl<T: Read + Write> Session<T> { impl<T: Read + Write> Session<T> {
// not public, just to avoid duplicating the channel creation code // not public, just to avoid duplicating the channel creation code
fn new(conn: Connection<T>) -> Self { fn new(conn: Connection<T>) -> Self {
let (tx, rx) = mpsc::channel();
Session { Session {
conn, conn,
unsolicited_responses: VecDeque::new(), unsolicited_responses: rx,
unsolicited_responses_tx: tx,
} }
} }
/// Takes all the unsolicited responses received thus far.
pub fn take_all_unsolicited(&mut self) -> impl ExactSizeIterator<Item = UnsolicitedResponse> {
std::mem::take(&mut self.unsolicited_responses).into_iter()
}
/// Selects a mailbox /// Selects a mailbox
/// ///
/// The `SELECT` command selects a mailbox so that messages in the mailbox can be accessed. /// The `SELECT` command selects a mailbox so that messages in the mailbox can be accessed.
@ -562,7 +561,7 @@ impl<T: Read + Write> Session<T> {
"SELECT {}", "SELECT {}",
validate_str("SELECT", "mailbox", mailbox_name.as_ref())? validate_str("SELECT", "mailbox", mailbox_name.as_ref())?
)) ))
.and_then(|(lines, _)| parse_mailbox(&lines[..], &mut self.unsolicited_responses)) .and_then(|(lines, _)| parse_mailbox(&lines[..], &mut self.unsolicited_responses_tx))
} }
/// The `EXAMINE` command is identical to [`Session::select`] and returns the same output; /// The `EXAMINE` command is identical to [`Session::select`] and returns the same output;
@ -574,7 +573,7 @@ impl<T: Read + Write> Session<T> {
"EXAMINE {}", "EXAMINE {}",
validate_str("EXAMINE", "mailbox", mailbox_name.as_ref())? validate_str("EXAMINE", "mailbox", mailbox_name.as_ref())?
)) ))
.and_then(|(lines, _)| parse_mailbox(&lines[..], &mut self.unsolicited_responses)) .and_then(|(lines, _)| parse_mailbox(&lines[..], &mut self.unsolicited_responses_tx))
} }
/// Fetch retrieves data associated with a set of messages in the mailbox. /// Fetch retrieves data associated with a set of messages in the mailbox.
@ -641,7 +640,7 @@ impl<T: Read + Write> Session<T> {
query: impl AsRef<str>, query: impl AsRef<str>,
) -> Result<Fetches> { ) -> Result<Fetches> {
if sequence_set.as_ref().is_empty() { if sequence_set.as_ref().is_empty() {
Fetches::parse(vec![], &mut self.unsolicited_responses) Fetches::parse(vec![], &mut self.unsolicited_responses_tx)
} else { } else {
let synopsis = "FETCH"; let synopsis = "FETCH";
self.run_command_and_read_response(&format!( self.run_command_and_read_response(&format!(
@ -649,7 +648,7 @@ impl<T: Read + Write> Session<T> {
validate_sequence_set(synopsis, "seq", sequence_set.as_ref())?, validate_sequence_set(synopsis, "seq", sequence_set.as_ref())?,
validate_str_noquote(synopsis, "query", query.as_ref())? validate_str_noquote(synopsis, "query", query.as_ref())?
)) ))
.and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses)) .and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx))
} }
} }
@ -661,7 +660,7 @@ impl<T: Read + Write> Session<T> {
query: impl AsRef<str>, query: impl AsRef<str>,
) -> Result<Fetches> { ) -> Result<Fetches> {
if uid_set.as_ref().is_empty() { if uid_set.as_ref().is_empty() {
Fetches::parse(vec![], &mut self.unsolicited_responses) Fetches::parse(vec![], &mut self.unsolicited_responses_tx)
} else { } else {
let synopsis = "UID FETCH"; let synopsis = "UID FETCH";
self.run_command_and_read_response(&format!( self.run_command_and_read_response(&format!(
@ -669,14 +668,14 @@ impl<T: Read + Write> Session<T> {
validate_sequence_set(synopsis, "seq", uid_set.as_ref())?, validate_sequence_set(synopsis, "seq", uid_set.as_ref())?,
validate_str_noquote(synopsis, "query", query.as_ref())? validate_str_noquote(synopsis, "query", query.as_ref())?
)) ))
.and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses)) .and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx))
} }
} }
/// Noop always succeeds, and it does nothing. /// Noop always succeeds, and it does nothing.
pub fn noop(&mut self) -> Result<()> { pub fn noop(&mut self) -> Result<()> {
self.run_command_and_read_response("NOOP") self.run_command_and_read_response("NOOP")
.and_then(|lines| parse_noop(lines, &mut self.unsolicited_responses)) .and_then(|lines| parse_noop(lines, &mut self.unsolicited_responses_tx))
} }
/// Logout informs the server that the client is done with the connection. /// Logout informs the server that the client is done with the connection.
@ -808,7 +807,7 @@ impl<T: Read + Write> Session<T> {
/// one of the listed capabilities. See [`Capabilities`] for further details. /// one of the listed capabilities. See [`Capabilities`] for further details.
pub fn capabilities(&mut self) -> Result<Capabilities> { pub fn capabilities(&mut self) -> Result<Capabilities> {
self.run_command_and_read_response("CAPABILITY") self.run_command_and_read_response("CAPABILITY")
.and_then(|lines| Capabilities::parse(lines, &mut self.unsolicited_responses)) .and_then(|lines| Capabilities::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`EXPUNGE` command](https://tools.ietf.org/html/rfc3501#section-6.4.3) permanently /// The [`EXPUNGE` command](https://tools.ietf.org/html/rfc3501#section-6.4.3) permanently
@ -817,7 +816,7 @@ impl<T: Read + Write> Session<T> {
pub fn expunge(&mut self) -> Result<Deleted> { pub fn expunge(&mut self) -> Result<Deleted> {
self.run_command("EXPUNGE")?; self.run_command("EXPUNGE")?;
self.read_response() self.read_response()
.and_then(|(lines, _)| parse_expunge(lines, &mut self.unsolicited_responses)) .and_then(|(lines, _)| parse_expunge(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`UID EXPUNGE` command](https://tools.ietf.org/html/rfc4315#section-2.1) permanently /// The [`UID EXPUNGE` command](https://tools.ietf.org/html/rfc4315#section-2.1) permanently
@ -845,7 +844,7 @@ impl<T: Read + Write> Session<T> {
pub fn uid_expunge(&mut self, uid_set: impl AsRef<str>) -> Result<Deleted> { pub fn uid_expunge(&mut self, uid_set: impl AsRef<str>) -> Result<Deleted> {
self.run_command(&format!("UID EXPUNGE {}", uid_set.as_ref()))?; self.run_command(&format!("UID EXPUNGE {}", uid_set.as_ref()))?;
self.read_response() self.read_response()
.and_then(|(lines, _)| parse_expunge(lines, &mut self.unsolicited_responses)) .and_then(|(lines, _)| parse_expunge(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`CHECK` command](https://tools.ietf.org/html/rfc3501#section-6.4.1) requests a /// The [`CHECK` command](https://tools.ietf.org/html/rfc3501#section-6.4.1) requests a
@ -935,7 +934,7 @@ impl<T: Read + Write> Session<T> {
sequence_set.as_ref(), sequence_set.as_ref(),
query.as_ref() query.as_ref()
)) ))
.and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses)) .and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// Equivalent to [`Session::store`], except that all identifiers in `sequence_set` are /// Equivalent to [`Session::store`], except that all identifiers in `sequence_set` are
@ -950,7 +949,7 @@ impl<T: Read + Write> Session<T> {
uid_set.as_ref(), uid_set.as_ref(),
query.as_ref() query.as_ref()
)) ))
.and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses)) .and_then(|lines| Fetches::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`COPY` command](https://tools.ietf.org/html/rfc3501#section-6.4.7) copies the /// The [`COPY` command](https://tools.ietf.org/html/rfc3501#section-6.4.7) copies the
@ -1085,7 +1084,7 @@ impl<T: Read + Write> Session<T> {
quote!(reference_name.unwrap_or("")), quote!(reference_name.unwrap_or("")),
mailbox_pattern.unwrap_or("\"\"") mailbox_pattern.unwrap_or("\"\"")
)) ))
.and_then(|lines| Names::parse(lines, &mut self.unsolicited_responses)) .and_then(|lines| Names::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`LSUB` command](https://tools.ietf.org/html/rfc3501#section-6.3.9) returns a subset of /// The [`LSUB` command](https://tools.ietf.org/html/rfc3501#section-6.3.9) returns a subset of
@ -1113,7 +1112,7 @@ impl<T: Read + Write> Session<T> {
quote!(reference_name.unwrap_or("")), quote!(reference_name.unwrap_or("")),
mailbox_pattern.unwrap_or("") mailbox_pattern.unwrap_or("")
)) ))
.and_then(|lines| Names::parse(lines, &mut self.unsolicited_responses)) .and_then(|lines| Names::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`STATUS` command](https://tools.ietf.org/html/rfc3501#section-6.3.10) requests the /// The [`STATUS` command](https://tools.ietf.org/html/rfc3501#section-6.3.10) requests the
@ -1162,7 +1161,9 @@ impl<T: Read + Write> Session<T> {
validate_str("STATUS", "mailbox", mailbox_name)?, validate_str("STATUS", "mailbox", mailbox_name)?,
data_items.as_ref() data_items.as_ref()
)) ))
.and_then(|lines| parse_status(&lines[..], mailbox_name, &mut self.unsolicited_responses)) .and_then(|lines| {
parse_status(&lines[..], mailbox_name, &mut self.unsolicited_responses_tx)
})
} }
/// This method returns a handle that lets you use the [`IDLE` /// This method returns a handle that lets you use the [`IDLE`
@ -1263,7 +1264,7 @@ impl<T: Read + Write> Session<T> {
/// - `SINCE <date>`: Messages whose internal date (disregarding time and timezone) is within or later than the specified date. /// - `SINCE <date>`: Messages whose internal date (disregarding time and timezone) is within or later than the specified date.
pub fn search(&mut self, query: impl AsRef<str>) -> Result<HashSet<Seq>> { pub fn search(&mut self, query: impl AsRef<str>) -> Result<HashSet<Seq>> {
self.run_command_and_read_response(&format!("SEARCH {}", query.as_ref())) self.run_command_and_read_response(&format!("SEARCH {}", query.as_ref()))
.and_then(|lines| parse_id_set(&lines, &mut self.unsolicited_responses)) .and_then(|lines| parse_id_set(&lines, &mut self.unsolicited_responses_tx))
} }
/// Equivalent to [`Session::search`], except that the returned identifiers /// Equivalent to [`Session::search`], except that the returned identifiers
@ -1271,7 +1272,7 @@ impl<T: Read + Write> Session<T> {
/// command](https://tools.ietf.org/html/rfc3501#section-6.4.8). /// command](https://tools.ietf.org/html/rfc3501#section-6.4.8).
pub fn uid_search(&mut self, query: impl AsRef<str>) -> Result<HashSet<Uid>> { pub fn uid_search(&mut self, query: impl AsRef<str>) -> Result<HashSet<Uid>> {
self.run_command_and_read_response(&format!("UID SEARCH {}", query.as_ref())) self.run_command_and_read_response(&format!("UID SEARCH {}", query.as_ref()))
.and_then(|lines| parse_id_set(&lines, &mut self.unsolicited_responses)) .and_then(|lines| parse_id_set(&lines, &mut self.unsolicited_responses_tx))
} }
/// This issues the [SORT command](https://tools.ietf.org/html/rfc5256#section-3), /// This issues the [SORT command](https://tools.ietf.org/html/rfc5256#section-3),
@ -1291,7 +1292,7 @@ impl<T: Read + Write> Session<T> {
charset, charset,
query.as_ref() query.as_ref()
)) ))
.and_then(|lines| parse_id_seq(&lines, &mut self.unsolicited_responses)) .and_then(|lines| parse_id_seq(&lines, &mut self.unsolicited_responses_tx))
} }
/// Equivalent to [`Session::sort`], except that it returns [`Uid`]s. /// Equivalent to [`Session::sort`], except that it returns [`Uid`]s.
@ -1309,7 +1310,7 @@ impl<T: Read + Write> Session<T> {
charset, charset,
query.as_ref() query.as_ref()
)) ))
.and_then(|lines| parse_id_seq(&lines, &mut self.unsolicited_responses)) .and_then(|lines| parse_id_seq(&lines, &mut self.unsolicited_responses_tx))
} }
/// The [`SETACL` command](https://datatracker.ietf.org/doc/html/rfc4314#section-3.1) /// The [`SETACL` command](https://datatracker.ietf.org/doc/html/rfc4314#section-3.1)
@ -1372,7 +1373,7 @@ impl<T: Read + Write> Session<T> {
"GETACL {}", "GETACL {}",
validate_str("GETACL", "mailbox", mailbox_name.as_ref())? validate_str("GETACL", "mailbox", mailbox_name.as_ref())?
)) ))
.and_then(|lines| AclResponse::parse(lines, &mut self.unsolicited_responses)) .and_then(|lines| AclResponse::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`LISTRIGHTS` command](https://datatracker.ietf.org/doc/html/rfc4314#section-3.4) /// The [`LISTRIGHTS` command](https://datatracker.ietf.org/doc/html/rfc4314#section-3.4)
@ -1393,7 +1394,7 @@ impl<T: Read + Write> Session<T> {
validate_str("LISTRIGHTS", "mailbox", mailbox_name.as_ref())?, validate_str("LISTRIGHTS", "mailbox", mailbox_name.as_ref())?,
validate_str("LISTRIGHTS", "identifier", identifier.as_ref())? validate_str("LISTRIGHTS", "identifier", identifier.as_ref())?
)) ))
.and_then(|lines| ListRightsResponse::parse(lines, &mut self.unsolicited_responses)) .and_then(|lines| ListRightsResponse::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`MYRIGHTS` command](https://datatracker.ietf.org/doc/html/rfc4314#section-3.5) /// The [`MYRIGHTS` command](https://datatracker.ietf.org/doc/html/rfc4314#section-3.5)
@ -1407,7 +1408,7 @@ impl<T: Read + Write> Session<T> {
"MYRIGHTS {}", "MYRIGHTS {}",
validate_str("MYRIGHTS", "mailbox", mailbox_name.as_ref())?, validate_str("MYRIGHTS", "mailbox", mailbox_name.as_ref())?,
)) ))
.and_then(|lines| MyRightsResponse::parse(lines, &mut self.unsolicited_responses)) .and_then(|lines| MyRightsResponse::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`SETQUOTA` command](https://datatracker.ietf.org/doc/html/rfc2087#section-4.1) /// The [`SETQUOTA` command](https://datatracker.ietf.org/doc/html/rfc2087#section-4.1)
@ -1427,7 +1428,7 @@ impl<T: Read + Write> Session<T> {
validate_str("SETQUOTA", "quota_root", quota_root.as_ref())?, validate_str("SETQUOTA", "quota_root", quota_root.as_ref())?,
limits, limits,
)) ))
.and_then(|lines| QuotaResponse::parse(lines, &mut self.unsolicited_responses)) .and_then(|lines| QuotaResponse::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`GETQUOTA` command](https://datatracker.ietf.org/doc/html/rfc2087#section-4.2) /// The [`GETQUOTA` command](https://datatracker.ietf.org/doc/html/rfc2087#section-4.2)
@ -1438,7 +1439,7 @@ impl<T: Read + Write> Session<T> {
"GETQUOTA {}", "GETQUOTA {}",
validate_str("GETQUOTA", "quota_root", quota_root.as_ref())? validate_str("GETQUOTA", "quota_root", quota_root.as_ref())?
)) ))
.and_then(|lines| QuotaResponse::parse(lines, &mut self.unsolicited_responses)) .and_then(|lines| QuotaResponse::parse(lines, &mut self.unsolicited_responses_tx))
} }
/// The [`GETQUOTAROOT` command](https://datatracker.ietf.org/doc/html/rfc2087#section-4.3) /// The [`GETQUOTAROOT` command](https://datatracker.ietf.org/doc/html/rfc2087#section-4.3)
@ -1449,7 +1450,7 @@ impl<T: Read + Write> Session<T> {
"GETQUOTAROOT {}", "GETQUOTAROOT {}",
validate_str("GETQUOTAROOT", "mailbox", mailbox_name.as_ref())? validate_str("GETQUOTAROOT", "mailbox", mailbox_name.as_ref())?
)) ))
.and_then(|lines| QuotaRootResponse::parse(lines, &mut self.unsolicited_responses)) .and_then(|lines| QuotaRootResponse::parse(lines, &mut self.unsolicited_responses_tx))
} }
// these are only here because they are public interface, the rest is in `Connection` // these are only here because they are public interface, the rest is in `Connection`

View file

@ -64,8 +64,8 @@ impl rustls::client::danger::ServerCertVerifier for NoCertVerification {
lazy_static! { lazy_static! {
static ref CACERTS: RootCertStore = { static ref CACERTS: RootCertStore = {
let mut store = RootCertStore::empty(); let mut store = RootCertStore::empty();
for cert in load_native_certs().certs { for cert in load_native_certs().unwrap_or_else(|_| vec![]) {
let _ = store.add(cert); if let Ok(_) = store.add(cert) {}
} }
store store
}; };

View file

@ -7,9 +7,9 @@ use crate::parse::try_handle_unilateral;
use crate::types::{Mailbox, Name, UnsolicitedResponse}; use crate::types::{Mailbox, Name, UnsolicitedResponse};
use imap_proto::types::{MailboxDatum, Response, StatusAttribute}; use imap_proto::types::{MailboxDatum, Response, StatusAttribute};
use ouroboros::self_referencing; use ouroboros::self_referencing;
use std::collections::VecDeque;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::slice::Iter; use std::slice::Iter;
use std::sync::mpsc;
/// A wrapper for one or more [`Name`] responses paired with optional [`Mailbox`] responses. /// A wrapper for one or more [`Name`] responses paired with optional [`Mailbox`] responses.
/// ///
@ -27,7 +27,7 @@ impl ExtendedNames {
/// Parse one or more LIST-STATUS responses from a response buffer /// Parse one or more LIST-STATUS responses from a response buffer
pub(crate) fn parse( pub(crate) fn parse(
owned: Vec<u8>, owned: Vec<u8>,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> core::result::Result<Self, Error> { ) -> core::result::Result<Self, Error> {
ExtendedNamesTryBuilder { ExtendedNamesTryBuilder {
data: owned, data: owned,
@ -145,13 +145,13 @@ impl<T: Read + Write> Session<T> {
data_items: &str, data_items: &str,
) -> Result<ExtendedNames> { ) -> Result<ExtendedNames> {
let reference = validate_str("LIST-STATUS", "reference", reference_name.unwrap_or(""))?; let reference = validate_str("LIST-STATUS", "reference", reference_name.unwrap_or(""))?;
let lines = self.run_command_and_read_response(format!( self.run_command_and_read_response(&format!(
"LIST {} {} RETURN (STATUS {})", "LIST {} {} RETURN (STATUS {})",
&reference, &reference,
mailbox_pattern.unwrap_or("\"\""), mailbox_pattern.unwrap_or("\"\""),
data_items data_items
))?; ))
ExtendedNames::parse(lines, &mut self.unsolicited_responses) .and_then(|lines| ExtendedNames::parse(lines, &mut self.unsolicited_responses_tx))
} }
} }
@ -171,9 +171,9 @@ mod tests {
* LIST (\\UnMarked) \".\" feeds\r\n\ * LIST (\\UnMarked) \".\" feeds\r\n\
* LIST () \".\" feeds.test\r\n\ * LIST () \".\" feeds.test\r\n\
* STATUS feeds.test (HIGHESTMODSEQ 757)\r\n"; * STATUS feeds.test (HIGHESTMODSEQ 757)\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let fetches = ExtendedNames::parse(lines.to_vec(), &mut queue).unwrap(); let fetches = ExtendedNames::parse(lines.to_vec(), &mut send).unwrap();
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
assert!(!fetches.is_empty()); assert!(!fetches.is_empty());
assert_eq!(fetches.len(), 4); assert_eq!(fetches.len(), 4);
let (name, status) = fetches.get(0).unwrap(); let (name, status) = fetches.get(0).unwrap();

View file

@ -15,8 +15,8 @@ use crate::error::{Error, ParseError, Result};
use crate::parse::try_handle_unilateral; use crate::parse::try_handle_unilateral;
use crate::types::*; use crate::types::*;
use imap_proto::types::{MailboxDatum, Metadata, Response, ResponseCode}; use imap_proto::types::{MailboxDatum, Metadata, Response, ResponseCode};
use std::collections::VecDeque;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::sync::mpsc;
// for intra-doc links // for intra-doc links
#[allow(unused_imports)] #[allow(unused_imports)]
@ -83,7 +83,7 @@ impl MetadataDepth {
fn parse_metadata<'a>( fn parse_metadata<'a>(
mut lines: &'a [u8], mut lines: &'a [u8],
unsolicited: &'a mut VecDeque<UnsolicitedResponse>, unsolicited: &'a mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Vec<Metadata>> { ) -> Result<Vec<Metadata>> {
let mut res: Vec<Metadata> = Vec::new(); let mut res: Vec<Metadata> = Vec::new();
loop { loop {
@ -197,7 +197,7 @@ impl<T: Read + Write> Session<T> {
.as_str(), .as_str(),
); );
let (lines, ok) = self.run(command)?; let (lines, ok) = self.run(command)?;
let meta = parse_metadata(&lines[..ok], &mut self.unsolicited_responses)?; let meta = parse_metadata(&lines[..ok], &mut self.unsolicited_responses_tx)?;
let missed = if maxsize.is_some() { let missed = if maxsize.is_some() {
if let Ok((_, Response::Done { code, .. })) = if let Ok((_, Response::Done { code, .. })) =
imap_proto::parser::parse_response(&lines[ok..]) imap_proto::parser::parse_response(&lines[ok..])

View file

@ -2,9 +2,7 @@ use imap_proto::{MailboxDatum, Response, ResponseCode, StatusAttribute};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use std::collections::HashSet; use std::collections::HashSet;
use std::collections::VecDeque; use std::sync::mpsc;
use std::convert::TryFrom;
use std::iter::Extend;
use super::error::{Error, ParseError, Result}; use super::error::{Error, ParseError, Result};
use super::types::*; use super::types::*;
@ -37,7 +35,7 @@ pub(crate) enum MapOrNot<'a, T> {
pub(crate) fn parse_many_into<'input, T, F>( pub(crate) fn parse_many_into<'input, T, F>(
input: &'input [u8], input: &'input [u8],
into: &mut impl Extend<T>, into: &mut impl Extend<T>,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
mut map: F, mut map: F,
) -> Result<()> ) -> Result<()>
where where
@ -79,7 +77,7 @@ pub(crate) fn parse_many_into2<'input, T, U, F, IU, IT>(
input: &'input [u8], input: &'input [u8],
into1: &mut IT, into1: &mut IT,
into2: &mut IU, into2: &mut IU,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
mut map: F, mut map: F,
) -> Result<()> ) -> Result<()>
where where
@ -120,7 +118,7 @@ where
fn parse_until_done_internal<'input, T, F>( fn parse_until_done_internal<'input, T, F>(
input: &'input [u8], input: &'input [u8],
optional: bool, optional: bool,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
map: F, map: F,
) -> Result<Option<T>> ) -> Result<Option<T>>
where where
@ -143,7 +141,7 @@ where
/// If more than one `T` are found then [`Error::Parse`] is returned /// If more than one `T` are found then [`Error::Parse`] is returned
pub(crate) fn parse_until_done_optional<'input, T, F>( pub(crate) fn parse_until_done_optional<'input, T, F>(
input: &'input [u8], input: &'input [u8],
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
map: F, map: F,
) -> Result<Option<T>> ) -> Result<Option<T>>
where where
@ -158,7 +156,7 @@ where
/// If zero or more than one `T` are found then [`Error::Parse`] is returned. /// If zero or more than one `T` are found then [`Error::Parse`] is returned.
pub(crate) fn parse_until_done<'input, T, F>( pub(crate) fn parse_until_done<'input, T, F>(
input: &'input [u8], input: &'input [u8],
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
map: F, map: F,
) -> Result<T> ) -> Result<T>
where where
@ -171,7 +169,7 @@ where
pub fn parse_expunge( pub fn parse_expunge(
lines: Vec<u8>, lines: Vec<u8>,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Deleted> { ) -> Result<Deleted> {
let mut lines: &[u8] = &lines; let mut lines: &[u8] = &lines;
let mut expunged = Vec::new(); let mut expunged = Vec::new();
@ -224,7 +222,7 @@ pub fn parse_expunge(
pub fn parse_append( pub fn parse_append(
mut lines: &[u8], mut lines: &[u8],
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Appended> { ) -> Result<Appended> {
let mut appended = Appended::default(); let mut appended = Appended::default();
@ -256,7 +254,10 @@ pub fn parse_append(
} }
} }
pub fn parse_noop(lines: Vec<u8>, unsolicited: &mut VecDeque<UnsolicitedResponse>) -> Result<()> { pub fn parse_noop(
lines: Vec<u8>,
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<()> {
let mut lines: &[u8] = &lines; let mut lines: &[u8] = &lines;
loop { loop {
@ -280,7 +281,7 @@ pub fn parse_noop(lines: Vec<u8>, unsolicited: &mut VecDeque<UnsolicitedResponse
pub fn parse_mailbox( pub fn parse_mailbox(
mut lines: &[u8], mut lines: &[u8],
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Mailbox> { ) -> Result<Mailbox> {
let mut mailbox = Mailbox::default(); let mut mailbox = Mailbox::default();
@ -330,10 +331,12 @@ pub fn parse_mailbox(
match m { match m {
MailboxDatum::Status { mailbox, status } => { MailboxDatum::Status { mailbox, status } => {
unsolicited.push_back(UnsolicitedResponse::Status { unsolicited
.send(UnsolicitedResponse::Status {
mailbox: mailbox.into(), mailbox: mailbox.into(),
attributes: status, attributes: status,
}) })
.unwrap();
} }
MailboxDatum::Exists(e) => { MailboxDatum::Exists(e) => {
mailbox.exists = e; mailbox.exists = e;
@ -349,7 +352,7 @@ pub fn parse_mailbox(
} }
Ok((rest, Response::Expunge(n))) => { Ok((rest, Response::Expunge(n))) => {
lines = rest; lines = rest;
unsolicited.push_back(UnsolicitedResponse::Expunge(n)) unsolicited.send(UnsolicitedResponse::Expunge(n)).unwrap();
} }
Ok((_, resp)) => { Ok((_, resp)) => {
break Err(resp.into()); break Err(resp.into());
@ -368,7 +371,7 @@ pub fn parse_mailbox(
pub fn parse_status( pub fn parse_status(
mut lines: &[u8], mut lines: &[u8],
mailbox_name: &str, mailbox_name: &str,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Mailbox> { ) -> Result<Mailbox> {
let mut mailbox = Mailbox::default(); let mut mailbox = Mailbox::default();
let mut got_anything = false; let mut got_anything = false;
@ -414,7 +417,7 @@ pub fn parse_status(
fn parse_ids_with<T: Extend<u32>>( fn parse_ids_with<T: Extend<u32>>(
lines: &[u8], lines: &[u8],
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
mut collection: T, mut collection: T,
) -> Result<T> { ) -> Result<T> {
let mut lines = lines; let mut lines = lines;
@ -447,14 +450,14 @@ fn parse_ids_with<T: Extend<u32>>(
pub fn parse_id_set( pub fn parse_id_set(
lines: &[u8], lines: &[u8],
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<HashSet<u32>> { ) -> Result<HashSet<u32>> {
parse_ids_with(lines, unsolicited, HashSet::new()) parse_ids_with(lines, unsolicited, HashSet::new())
} }
pub fn parse_id_seq( pub fn parse_id_seq(
lines: &[u8], lines: &[u8],
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Vec<u32>> { ) -> Result<Vec<u32>> {
parse_ids_with(lines, unsolicited, Vec::new()) parse_ids_with(lines, unsolicited, Vec::new())
} }
@ -479,11 +482,11 @@ pub fn parse_idle(lines: &[u8]) -> (&[u8], Option<Result<UnsolicitedResponse>>)
// Returns `None` if the response was handled, `Some(res)` if not. // Returns `None` if the response was handled, `Some(res)` if not.
pub(crate) fn try_handle_unilateral<'a>( pub(crate) fn try_handle_unilateral<'a>(
res: Response<'a>, res: Response<'a>,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Option<Response<'a>> { ) -> Option<Response<'a>> {
match UnsolicitedResponse::try_from(res) { match UnsolicitedResponse::try_from(res) {
Ok(response) => { Ok(response) => {
unsolicited.push_back(response); unsolicited.send(response).ok();
None None
} }
Err(unhandled) => Some(unhandled), Err(unhandled) => Some(unhandled),
@ -505,10 +508,10 @@ mod tests {
Capability::Atom(Cow::Borrowed("LOGINDISABLED")), Capability::Atom(Cow::Borrowed("LOGINDISABLED")),
]; ];
let lines = b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n"; let lines = b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let capabilities = Capabilities::parse(lines.to_vec(), &mut queue).unwrap(); let capabilities = Capabilities::parse(lines.to_vec(), &mut send).unwrap();
// shouldn't be any unexpected responses parsed // shouldn't be any unexpected responses parsed
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
assert_eq!(capabilities.len(), 4); assert_eq!(capabilities.len(), 4);
for e in expected_capabilities { for e in expected_capabilities {
assert!(capabilities.has(&e)); assert!(capabilities.has(&e));
@ -523,10 +526,10 @@ mod tests {
Capability::Atom(Cow::Borrowed("STARTTLS")), Capability::Atom(Cow::Borrowed("STARTTLS")),
]; ];
let lines = b"* CAPABILITY IMAP4REV1 STARTTLS\r\n"; let lines = b"* CAPABILITY IMAP4REV1 STARTTLS\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let capabilities = Capabilities::parse(lines.to_vec(), &mut queue).unwrap(); let capabilities = Capabilities::parse(lines.to_vec(), &mut send).unwrap();
// shouldn't be any unexpected responses parsed // shouldn't be any unexpected responses parsed
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
assert_eq!(capabilities.len(), 2); assert_eq!(capabilities.len(), 2);
for e in expected_capabilities { for e in expected_capabilities {
assert!(capabilities.has(&e)); assert!(capabilities.has(&e));
@ -536,18 +539,18 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn parse_capability_invalid_test() { fn parse_capability_invalid_test() {
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let lines = b"* JUNK IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n"; let lines = b"* JUNK IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n";
Capabilities::parse(lines.to_vec(), &mut queue).unwrap(); Capabilities::parse(lines.to_vec(), &mut send).unwrap();
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
} }
#[test] #[test]
fn parse_names_test() { fn parse_names_test() {
let lines = b"* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n"; let lines = b"* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let names = Names::parse(lines.to_vec(), &mut queue).unwrap(); let names = Names::parse(lines.to_vec(), &mut send).unwrap();
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
assert_eq!(names.len(), 1); assert_eq!(names.len(), 1);
let first = names.get(0).unwrap(); let first = names.get(0).unwrap();
assert_eq!( assert_eq!(
@ -561,9 +564,9 @@ mod tests {
#[test] #[test]
fn parse_fetches_empty() { fn parse_fetches_empty() {
let lines = b""; let lines = b"";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let fetches = Fetches::parse(lines.to_vec(), &mut queue).unwrap(); let fetches = Fetches::parse(lines.to_vec(), &mut send).unwrap();
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
assert!(fetches.is_empty()); assert!(fetches.is_empty());
} }
@ -572,9 +575,9 @@ mod tests {
let lines = b"\ let lines = b"\
* 24 FETCH (FLAGS (\\Seen) UID 4827943)\r\n\ * 24 FETCH (FLAGS (\\Seen) UID 4827943)\r\n\
* 25 FETCH (FLAGS (\\Seen))\r\n"; * 25 FETCH (FLAGS (\\Seen))\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let fetches = Fetches::parse(lines.to_vec(), &mut queue).unwrap(); let fetches = Fetches::parse(lines.to_vec(), &mut send).unwrap();
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
assert_eq!(fetches.len(), 2); assert_eq!(fetches.len(), 2);
let first = fetches.get(0).unwrap(); let first = fetches.get(0).unwrap();
assert_eq!(first.message, 24); assert_eq!(first.message, 24);
@ -596,9 +599,9 @@ mod tests {
let lines = b"\ let lines = b"\
* 37 FETCH (UID 74)\r\n\ * 37 FETCH (UID 74)\r\n\
* 1 RECENT\r\n"; * 1 RECENT\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let fetches = Fetches::parse(lines.to_vec(), &mut queue).unwrap(); let fetches = Fetches::parse(lines.to_vec(), &mut send).unwrap();
assert_eq!(queue.pop_front(), Some(UnsolicitedResponse::Recent(1))); assert_eq!(recv.try_recv(), Ok(UnsolicitedResponse::Recent(1)));
assert_eq!(fetches.len(), 1); assert_eq!(fetches.len(), 1);
let first = fetches.get(0).unwrap(); let first = fetches.get(0).unwrap();
assert_eq!(first.message, 37); assert_eq!(first.message, 37);
@ -613,11 +616,11 @@ mod tests {
let lines = b"\ let lines = b"\
* OK Searched 91% of the mailbox, ETA 0:01\r\n\ * OK Searched 91% of the mailbox, ETA 0:01\r\n\
* 37 FETCH (UID 74)\r\n"; * 37 FETCH (UID 74)\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let fetches = Fetches::parse(lines.to_vec(), &mut queue).unwrap(); let fetches = Fetches::parse(lines.to_vec(), &mut send).unwrap();
assert_eq!( assert_eq!(
queue.pop_front(), recv.try_recv(),
Some(UnsolicitedResponse::Ok { Ok(UnsolicitedResponse::Ok {
code: None, code: None,
information: Some(String::from("Searched 91% of the mailbox, ETA 0:01")), information: Some(String::from("Searched 91% of the mailbox, ETA 0:01")),
}) })
@ -633,10 +636,10 @@ mod tests {
let lines = b"\ let lines = b"\
* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n\ * LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n\
* 4 EXPUNGE\r\n"; * 4 EXPUNGE\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let names = Names::parse(lines.to_vec(), &mut queue).unwrap(); let names = Names::parse(lines.to_vec(), &mut send).unwrap();
assert_eq!(queue.pop_front(), Some(UnsolicitedResponse::Expunge(4))); assert_eq!(recv.try_recv().unwrap(), UnsolicitedResponse::Expunge(4));
assert_eq!(names.len(), 1); assert_eq!(names.len(), 1);
let first = names.get(0).unwrap(); let first = names.get(0).unwrap();
@ -660,8 +663,8 @@ mod tests {
* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n\ * CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n\
* STATUS dev.github (MESSAGES 10 UIDNEXT 11 UIDVALIDITY 1408806928 UNSEEN 0)\r\n\ * STATUS dev.github (MESSAGES 10 UIDNEXT 11 UIDVALIDITY 1408806928 UNSEEN 0)\r\n\
* 4 EXISTS\r\n"; * 4 EXISTS\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let capabilities = Capabilities::parse(lines.to_vec(), &mut queue).unwrap(); let capabilities = Capabilities::parse(lines.to_vec(), &mut send).unwrap();
assert_eq!(capabilities.len(), 4); assert_eq!(capabilities.len(), 4);
for e in expected_capabilities { for e in expected_capabilities {
@ -669,8 +672,8 @@ mod tests {
} }
assert_eq!( assert_eq!(
queue.pop_front(), recv.try_recv().unwrap(),
Some(UnsolicitedResponse::Status { UnsolicitedResponse::Status {
mailbox: "dev.github".to_string(), mailbox: "dev.github".to_string(),
attributes: vec![ attributes: vec![
StatusAttribute::Messages(10), StatusAttribute::Messages(10),
@ -678,9 +681,9 @@ mod tests {
StatusAttribute::UidValidity(1408806928), StatusAttribute::UidValidity(1408806928),
StatusAttribute::Unseen(0) StatusAttribute::Unseen(0)
] ]
}) }
); );
assert_eq!(queue.pop_front(), Some(UnsolicitedResponse::Exists(4))); assert_eq!(recv.try_recv().unwrap(), UnsolicitedResponse::Exists(4));
} }
#[test] #[test]
@ -689,14 +692,14 @@ mod tests {
* SEARCH 23 42 4711\r\n\ * SEARCH 23 42 4711\r\n\
* 1 RECENT\r\n\ * 1 RECENT\r\n\
* STATUS INBOX (MESSAGES 10 UIDNEXT 11 UIDVALIDITY 1408806928 UNSEEN 0)\r\n"; * STATUS INBOX (MESSAGES 10 UIDNEXT 11 UIDVALIDITY 1408806928 UNSEEN 0)\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let ids = parse_id_set(lines, &mut queue).unwrap(); let ids = parse_id_set(lines, &mut send).unwrap();
assert_eq!(ids, [23, 42, 4711].iter().cloned().collect()); assert_eq!(ids, [23, 42, 4711].iter().cloned().collect());
assert_eq!(queue.pop_front().unwrap(), UnsolicitedResponse::Recent(1)); assert_eq!(recv.try_recv().unwrap(), UnsolicitedResponse::Recent(1));
assert_eq!( assert_eq!(
queue.pop_front().unwrap(), recv.try_recv().unwrap(),
UnsolicitedResponse::Status { UnsolicitedResponse::Status {
mailbox: "INBOX".to_string(), mailbox: "INBOX".to_string(),
attributes: vec![ attributes: vec![
@ -713,9 +716,9 @@ mod tests {
fn parse_ids_test() { fn parse_ids_test() {
let lines = b"* SEARCH 1600 1698 1739 1781 1795 1885 1891 1892 1893 1898 1899 1901 1911 1926 1932 1933 1993 1994 2007 2032 2033 2041 2053 2062 2063 2065 2066 2072 2078 2079 2082 2084 2095 2100 2101 2102 2103 2104 2107 2116 2120 2135 2138 2154 2163 2168 2172 2189 2193 2198 2199 2205 2212 2213 2221 2227 2267 2275 2276 2295 2300 2328 2330 2332 2333 2334\r\n\ let lines = b"* SEARCH 1600 1698 1739 1781 1795 1885 1891 1892 1893 1898 1899 1901 1911 1926 1932 1933 1993 1994 2007 2032 2033 2041 2053 2062 2063 2065 2066 2072 2078 2079 2082 2084 2095 2100 2101 2102 2103 2104 2107 2116 2120 2135 2138 2154 2163 2168 2172 2189 2193 2198 2199 2205 2212 2213 2221 2227 2267 2275 2276 2295 2300 2328 2330 2332 2333 2334\r\n\
* SEARCH 2335 2336 2337 2338 2339 2341 2342 2347 2349 2350 2358 2359 2362 2369 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2390 2392 2397 2400 2401 2403 2405 2409 2411 2414 2417 2419 2420 2424 2426 2428 2439 2454 2456 2467 2468 2469 2490 2515 2519 2520 2521\r\n"; * SEARCH 2335 2336 2337 2338 2339 2341 2342 2347 2349 2350 2358 2359 2362 2369 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2390 2392 2397 2400 2401 2403 2405 2409 2411 2414 2417 2419 2420 2424 2426 2428 2439 2454 2456 2467 2468 2469 2490 2515 2519 2520 2521\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let ids = parse_id_set(lines, &mut queue).unwrap(); let ids = parse_id_set(lines, &mut send).unwrap();
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
let ids: HashSet<u32> = ids.iter().cloned().collect(); let ids: HashSet<u32> = ids.iter().cloned().collect();
assert_eq!( assert_eq!(
ids, ids,
@ -736,16 +739,16 @@ mod tests {
); );
let lines = b"* SEARCH\r\n"; let lines = b"* SEARCH\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let ids = parse_id_set(lines, &mut queue).unwrap(); let ids = parse_id_set(lines, &mut send).unwrap();
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
let ids: HashSet<u32> = ids.iter().cloned().collect(); let ids: HashSet<u32> = ids.iter().cloned().collect();
assert_eq!(ids, HashSet::<u32>::new()); assert_eq!(ids, HashSet::<u32>::new());
let lines = b"* SORT\r\n"; let lines = b"* SORT\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let ids = parse_id_seq(lines, &mut queue).unwrap(); let ids = parse_id_seq(lines, &mut send).unwrap();
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
let ids: Vec<u32> = ids.iter().cloned().collect(); let ids: Vec<u32> = ids.iter().cloned().collect();
assert_eq!(ids, Vec::<u32>::new()); assert_eq!(ids, Vec::<u32>::new());
} }
@ -757,8 +760,8 @@ mod tests {
// two cases the VANISHED response will be a different type than expected // two cases the VANISHED response will be a different type than expected
// and so goes into the unsolicited responses channel. // and so goes into the unsolicited responses channel.
let lines = b"* VANISHED 3\r\n"; let lines = b"* VANISHED 3\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let resp = parse_expunge(lines.to_vec(), &mut queue).unwrap(); let resp = parse_expunge(lines.to_vec(), &mut send).unwrap();
// Should be not empty, and have no seqs // Should be not empty, and have no seqs
assert!(!resp.is_empty()); assert!(!resp.is_empty());
@ -770,14 +773,14 @@ mod tests {
assert_eq!(None, uids.next()); assert_eq!(None, uids.next());
// Should be nothing in the unsolicited responses channel // Should be nothing in the unsolicited responses channel
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
// Test VANISHED mixed with FETCH // Test VANISHED mixed with FETCH
let lines = b"* VANISHED (EARLIER) 3:8,12,50:60\r\n\ let lines = b"* VANISHED (EARLIER) 3:8,12,50:60\r\n\
* 49 FETCH (UID 117 FLAGS (\\Seen \\Answered) MODSEQ (90060115194045001))\r\n"; * 49 FETCH (UID 117 FLAGS (\\Seen \\Answered) MODSEQ (90060115194045001))\r\n";
let fetches = Fetches::parse(lines.to_vec(), &mut queue).unwrap(); let fetches = Fetches::parse(lines.to_vec(), &mut send).unwrap();
match queue.pop_front().unwrap() { match recv.try_recv().unwrap() {
UnsolicitedResponse::Vanished { earlier, uids } => { UnsolicitedResponse::Vanished { earlier, uids } => {
assert!(earlier); assert!(earlier);
assert_eq!(uids.len(), 3); assert_eq!(uids.len(), 3);
@ -790,7 +793,7 @@ mod tests {
} }
what => panic!("Unexpected response in unsolicited responses: {:?}", what), what => panic!("Unexpected response in unsolicited responses: {:?}", what),
} }
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
assert_eq!(fetches.len(), 1); assert_eq!(fetches.len(), 1);
let first = fetches.get(0).unwrap(); let first = fetches.get(0).unwrap();
assert_eq!(first.message, 49); assert_eq!(first.message, 49);
@ -806,16 +809,16 @@ mod tests {
// SELECT/EXAMINE (QRESYNC); UID FETCH (VANISHED); or EXPUNGE commands. In the latter // SELECT/EXAMINE (QRESYNC); UID FETCH (VANISHED); or EXPUNGE commands. In the latter
// case, the VANISHED responses will be parsed with the response and the list of // case, the VANISHED responses will be parsed with the response and the list of
// expunged message is included in the returned struct. // expunged message is included in the returned struct.
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
// Test VANISHED mixed with FETCH // Test VANISHED mixed with FETCH
let lines = b"* VANISHED 3:5,12\r\n\ let lines = b"* VANISHED 3:5,12\r\n\
B202 OK [HIGHESTMODSEQ 20010715194045319] expunged\r\n"; B202 OK [HIGHESTMODSEQ 20010715194045319] expunged\r\n";
let deleted = parse_expunge(lines.to_vec(), &mut queue).unwrap(); let deleted = parse_expunge(lines.to_vec(), &mut send).unwrap();
// No unsolicited responses, they are aggregated in the returned type // No unsolicited responses, they are aggregated in the returned type
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
assert_eq!(deleted.mod_seq, Some(20010715194045319)); assert_eq!(deleted.mod_seq, Some(20010715194045319));
let mut del = deleted.uids(); let mut del = deleted.uids();
@ -833,10 +836,10 @@ mod tests {
// UID assigned to the appended message in the destination mailbox. // UID assigned to the appended message in the destination mailbox.
// If the MULTIAPPEND extension is also used, there can be multiple UIDs. // If the MULTIAPPEND extension is also used, there can be multiple UIDs.
let lines = b"A003 OK [APPENDUID 38505 3955] APPEND completed\r\n"; let lines = b"A003 OK [APPENDUID 38505 3955] APPEND completed\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let resp = parse_append(lines, &mut queue).unwrap(); let resp = parse_append(lines, &mut send).unwrap();
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
assert_eq!(resp.uid_validity, Some(38505)); assert_eq!(resp.uid_validity, Some(38505));
match resp.uids { match resp.uids {
Some(uid_list) => { Some(uid_list) => {
@ -855,10 +858,10 @@ mod tests {
// UID assigned to the appended message in the destination mailbox. // UID assigned to the appended message in the destination mailbox.
// If the MULTIAPPEND extension is also used, there can be multiple UIDs. // If the MULTIAPPEND extension is also used, there can be multiple UIDs.
let lines = b"A003 OK [APPENDUID 38505 3955:3957] APPEND completed\r\n"; let lines = b"A003 OK [APPENDUID 38505 3955:3957] APPEND completed\r\n";
let mut queue = VecDeque::new(); let (mut send, recv) = mpsc::channel();
let resp = parse_append(lines, &mut queue).unwrap(); let resp = parse_append(lines, &mut send).unwrap();
assert_eq!(queue.pop_front(), None); assert!(recv.try_recv().is_err());
assert_eq!(resp.uid_validity, Some(38505)); assert_eq!(resp.uid_validity, Some(38505));
match resp.uids { match resp.uids {
Some(uid_list) => { Some(uid_list) => {

View file

@ -19,7 +19,7 @@ use crate::{extensions::list_status::ExtendedNames, types::*};
/// Methods to build a [`Capabilities`] response object /// Methods to build a [`Capabilities`] response object
pub mod capabilities { pub mod capabilities {
use crate::types::Capabilities; use crate::types::Capabilities;
use std::collections::VecDeque; use std::sync::mpsc;
/// Builds an [`Capabilities`] based on the provided input /// Builds an [`Capabilities`] based on the provided input
/// ///
@ -30,16 +30,16 @@ pub mod capabilities {
/// let response = imap::testing::capabilities::parse(input); /// let response = imap::testing::capabilities::parse(input);
/// ``` /// ```
pub fn parse(input: impl Into<Vec<u8>>) -> Capabilities { pub fn parse(input: impl Into<Vec<u8>>) -> Capabilities {
let mut unsolicited_responses = VecDeque::new(); let (mut tx, _rx) = mpsc::channel();
Capabilities::parse(input.into(), &mut unsolicited_responses).unwrap() Capabilities::parse(input.into(), &mut tx).unwrap()
} }
} }
/// Methods to build a [`Fetches`] response object /// Methods to build a [`Fetches`] response object
pub mod fetches { pub mod fetches {
use crate::types::Fetches; use crate::types::Fetches;
use std::collections::VecDeque; use std::sync::mpsc;
/// Builds an [`Fetches`] based on the provided input /// Builds an [`Fetches`] based on the provided input
/// ///
@ -53,16 +53,16 @@ pub mod fetches {
/// let response = imap::testing::fetches::parse(input); /// let response = imap::testing::fetches::parse(input);
/// ``` /// ```
pub fn parse(input: impl Into<Vec<u8>>) -> Fetches { pub fn parse(input: impl Into<Vec<u8>>) -> Fetches {
let mut unsolicited_responses = VecDeque::new(); let (mut tx, _rx) = mpsc::channel();
Fetches::parse(input.into(), &mut unsolicited_responses).unwrap() Fetches::parse(input.into(), &mut tx).unwrap()
} }
} }
/// Methods to build a [`Names`] response object /// Methods to build a [`Names`] response object
pub mod names { pub mod names {
use crate::types::Names; use crate::types::Names;
use std::collections::VecDeque; use std::sync::mpsc;
/// Builds an [`Names`] based on the provided input /// Builds an [`Names`] based on the provided input
/// Example input. /// Example input.
@ -74,16 +74,16 @@ pub mod names {
/// let response = imap::testing::names::parse(input); /// let response = imap::testing::names::parse(input);
///``` ///```
pub fn parse(input: impl Into<Vec<u8>>) -> Names { pub fn parse(input: impl Into<Vec<u8>>) -> Names {
let mut unsolicited_responses = VecDeque::new(); let (mut tx, _rx) = mpsc::channel();
Names::parse(input.into(), &mut unsolicited_responses).unwrap() Names::parse(input.into(), &mut tx).unwrap()
} }
} }
/// Methods to build a [`ExtendedNames`] response object /// Methods to build a [`ExtendedNames`] response object
pub mod extended_names { pub mod extended_names {
use crate::extensions::list_status::ExtendedNames; use crate::extensions::list_status::ExtendedNames;
use std::collections::VecDeque; use std::sync::mpsc;
/// Builds an [`ExtendedNames`] based on the provided input /// Builds an [`ExtendedNames`] based on the provided input
/// ///
@ -102,16 +102,16 @@ pub mod extended_names {
/// let response = imap::testing::extended_names::parse(input); /// let response = imap::testing::extended_names::parse(input);
/// ``` /// ```
pub fn parse(input: impl Into<Vec<u8>>) -> ExtendedNames { pub fn parse(input: impl Into<Vec<u8>>) -> ExtendedNames {
let mut unsolicited_responses = VecDeque::new(); let (mut tx, _rx) = mpsc::channel();
ExtendedNames::parse(input.into(), &mut unsolicited_responses).unwrap() ExtendedNames::parse(input.into(), &mut tx).unwrap()
} }
} }
/// Methods to build a [`AclResponse`] response object /// Methods to build a [`AclResponse`] response object
pub mod acl_response { pub mod acl_response {
use crate::types::AclResponse; use crate::types::AclResponse;
use std::collections::VecDeque; use std::sync::mpsc;
/// Builds an [`AclResponse`] based on the provided input /// Builds an [`AclResponse`] based on the provided input
/// ///
@ -122,16 +122,16 @@ pub mod acl_response {
/// let response = imap::testing::acl_response::parse(input); /// let response = imap::testing::acl_response::parse(input);
/// ``` /// ```
pub fn parse(input: impl Into<Vec<u8>>) -> AclResponse { pub fn parse(input: impl Into<Vec<u8>>) -> AclResponse {
let mut unsolicited_responses = VecDeque::new(); let (mut tx, _rx) = mpsc::channel();
AclResponse::parse(input.into(), &mut unsolicited_responses).unwrap() AclResponse::parse(input.into(), &mut tx).unwrap()
} }
} }
/// Methods to build a [`ListRightsResponse`] response object /// Methods to build a [`ListRightsResponse`] response object
pub mod list_rights_response { pub mod list_rights_response {
use crate::types::ListRightsResponse; use crate::types::ListRightsResponse;
use std::collections::VecDeque; use std::sync::mpsc;
/// Builds an [`ListRightsResponse`] based on the provided input /// Builds an [`ListRightsResponse`] based on the provided input
/// ///
@ -142,16 +142,16 @@ pub mod list_rights_response {
/// let response = imap::testing::list_rights_response::parse(input); /// let response = imap::testing::list_rights_response::parse(input);
///``` ///```
pub fn parse(input: impl Into<Vec<u8>>) -> ListRightsResponse { pub fn parse(input: impl Into<Vec<u8>>) -> ListRightsResponse {
let mut unsolicited_responses = VecDeque::new(); let (mut tx, _rx) = mpsc::channel();
ListRightsResponse::parse(input.into(), &mut unsolicited_responses).unwrap() ListRightsResponse::parse(input.into(), &mut tx).unwrap()
} }
} }
/// Methods to build a [`MyRightsResponse`] response object /// Methods to build a [`MyRightsResponse`] response object
pub mod my_rights_response { pub mod my_rights_response {
use crate::types::MyRightsResponse; use crate::types::MyRightsResponse;
use std::collections::VecDeque; use std::sync::mpsc;
/// Builds an [`MyRightsResponse`] based on the provided input /// Builds an [`MyRightsResponse`] based on the provided input
/// ///
@ -162,16 +162,16 @@ pub mod my_rights_response {
/// let response = imap::testing::my_rights_response::parse(input); /// let response = imap::testing::my_rights_response::parse(input);
/// ``` /// ```
pub fn parse(input: impl Into<Vec<u8>>) -> MyRightsResponse { pub fn parse(input: impl Into<Vec<u8>>) -> MyRightsResponse {
let mut unsolicited_responses = VecDeque::new(); let (mut tx, _rx) = mpsc::channel();
MyRightsResponse::parse(input.into(), &mut unsolicited_responses).unwrap() MyRightsResponse::parse(input.into(), &mut tx).unwrap()
} }
} }
/// Methods to build a [`QuotaResponse`] response object /// Methods to build a [`QuotaResponse`] response object
pub mod quota_response { pub mod quota_response {
use crate::types::QuotaResponse; use crate::types::QuotaResponse;
use std::collections::VecDeque; use std::sync::mpsc;
/// Builds an [`QuotaResponse`] based on the provided input /// Builds an [`QuotaResponse`] based on the provided input
/// ///
@ -182,16 +182,16 @@ pub mod quota_response {
/// let response = imap::testing::quota_response::parse(input); /// let response = imap::testing::quota_response::parse(input);
/// ``` /// ```
pub fn parse(input: impl Into<Vec<u8>>) -> QuotaResponse { pub fn parse(input: impl Into<Vec<u8>>) -> QuotaResponse {
let mut unsolicited_responses = VecDeque::new(); let (mut tx, _rx) = mpsc::channel();
QuotaResponse::parse(input.into(), &mut unsolicited_responses).unwrap() QuotaResponse::parse(input.into(), &mut tx).unwrap()
} }
} }
/// Methods to build a [`QuotaRootResponse`] response object /// Methods to build a [`QuotaRootResponse`] response object
pub mod quota_root_response { pub mod quota_root_response {
use crate::types::QuotaRootResponse; use crate::types::QuotaRootResponse;
use std::collections::VecDeque; use std::sync::mpsc;
/// Builds an [`QuotaRootResponse`] based on the provided input /// Builds an [`QuotaRootResponse`] based on the provided input
/// ///
@ -205,8 +205,8 @@ pub mod quota_root_response {
/// let response = imap::testing::quota_root_response::parse(input); /// let response = imap::testing::quota_root_response::parse(input);
/// ``` /// ```
pub fn parse(input: impl Into<Vec<u8>>) -> QuotaRootResponse { pub fn parse(input: impl Into<Vec<u8>>) -> QuotaRootResponse {
let mut unsolicited_responses = VecDeque::new(); let (mut tx, _rx) = mpsc::channel();
QuotaRootResponse::parse(input.into(), &mut unsolicited_responses).unwrap() QuotaRootResponse::parse(input.into(), &mut tx).unwrap()
} }
} }

View file

@ -8,8 +8,8 @@ use imap_proto::Response;
use ouroboros::self_referencing; use ouroboros::self_referencing;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashSet; use std::collections::HashSet;
use std::collections::VecDeque;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::sync::mpsc;
/// Specifies how [`Session::set_acl`] should modify an existing permission set. /// Specifies how [`Session::set_acl`] should modify an existing permission set.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -117,7 +117,7 @@ impl AclResponse {
/// Parse the given input into a [`Acl`] response. /// Parse the given input into a [`Acl`] response.
pub(crate) fn parse( pub(crate) fn parse(
owned: Vec<u8>, owned: Vec<u8>,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
AclResponseTryBuilder { AclResponseTryBuilder {
data: owned, data: owned,
@ -200,7 +200,7 @@ impl ListRightsResponse {
/// Parse the given input into a [`ListRights`] response. /// Parse the given input into a [`ListRights`] response.
pub(crate) fn parse( pub(crate) fn parse(
owned: Vec<u8>, owned: Vec<u8>,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
ListRightsResponseTryBuilder { ListRightsResponseTryBuilder {
data: owned, data: owned,
@ -280,7 +280,7 @@ impl MyRightsResponse {
/// Parse the given input into a [`MyRights`] response. /// Parse the given input into a [`MyRights`] response.
pub(crate) fn parse( pub(crate) fn parse(
owned: Vec<u8>, owned: Vec<u8>,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
MyRightsResponseTryBuilder { MyRightsResponseTryBuilder {
data: owned, data: owned,

View file

@ -5,7 +5,7 @@ use imap_proto::{Capability, Response};
use ouroboros::self_referencing; use ouroboros::self_referencing;
use std::collections::hash_set::Iter; use std::collections::hash_set::Iter;
use std::collections::HashSet; use std::collections::HashSet;
use std::collections::VecDeque; use std::sync::mpsc;
const IMAP4REV1_CAPABILITY: &str = "IMAP4rev1"; const IMAP4REV1_CAPABILITY: &str = "IMAP4rev1";
const AUTH_CAPABILITY_PREFIX: &str = "AUTH="; const AUTH_CAPABILITY_PREFIX: &str = "AUTH=";
@ -47,7 +47,7 @@ impl Capabilities {
/// Parse the given input into one or more [`Capabilitity`] responses. /// Parse the given input into one or more [`Capabilitity`] responses.
pub(crate) fn parse( pub(crate) fn parse(
owned: Vec<u8>, owned: Vec<u8>,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
CapabilitiesTryBuilder { CapabilitiesTryBuilder {
data: owned, data: owned,

View file

@ -7,8 +7,8 @@ use imap_proto::types::{
AttributeValue, BodyStructure, Envelope, MessageSection, Response, SectionPath, AttributeValue, BodyStructure, Envelope, MessageSection, Response, SectionPath,
}; };
use ouroboros::self_referencing; use ouroboros::self_referencing;
use std::collections::VecDeque;
use std::slice::Iter; use std::slice::Iter;
use std::sync::mpsc;
/// Format of Date and Time as defined RFC3501. /// Format of Date and Time as defined RFC3501.
/// See `date-time` element in [Formal Syntax](https://tools.ietf.org/html/rfc3501#section-9) /// See `date-time` element in [Formal Syntax](https://tools.ietf.org/html/rfc3501#section-9)
@ -28,7 +28,7 @@ impl Fetches {
/// Parse one or more [`Fetch`] responses from a response buffer. /// Parse one or more [`Fetch`] responses from a response buffer.
pub(crate) fn parse( pub(crate) fn parse(
owned: Vec<u8>, owned: Vec<u8>,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
FetchesTryBuilder { FetchesTryBuilder {
data: owned, data: owned,

View file

@ -4,8 +4,8 @@ use crate::types::UnsolicitedResponse;
use imap_proto::{MailboxDatum, NameAttribute, Response}; use imap_proto::{MailboxDatum, NameAttribute, Response};
use ouroboros::self_referencing; use ouroboros::self_referencing;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::VecDeque;
use std::slice::Iter; use std::slice::Iter;
use std::sync::mpsc;
/// A wrapper for one or more [`Name`] responses. /// A wrapper for one or more [`Name`] responses.
#[self_referencing] #[self_referencing]
@ -20,7 +20,7 @@ impl Names {
/// Parse one or more [`Name`] from a response buffer /// Parse one or more [`Name`] from a response buffer
pub(crate) fn parse( pub(crate) fn parse(
owned: Vec<u8>, owned: Vec<u8>,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
NamesTryBuilder { NamesTryBuilder {
data: owned, data: owned,

View file

@ -4,8 +4,8 @@ use crate::types::UnsolicitedResponse;
use imap_proto::Response; use imap_proto::Response;
use ouroboros::self_referencing; use ouroboros::self_referencing;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
use std::sync::mpsc;
/// From [SETQUOTA Resource limit](https://datatracker.ietf.org/doc/html/rfc2087#section-4.1) /// From [SETQUOTA Resource limit](https://datatracker.ietf.org/doc/html/rfc2087#section-4.1)
/// ///
@ -105,7 +105,7 @@ impl QuotaResponse {
/// Parse the [`Quota`] response from a response buffer. /// Parse the [`Quota`] response from a response buffer.
pub(crate) fn parse( pub(crate) fn parse(
owned: Vec<u8>, owned: Vec<u8>,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
QuotaResponseTryBuilder { QuotaResponseTryBuilder {
data: owned, data: owned,
@ -200,7 +200,7 @@ impl QuotaRootResponse {
/// Parse the [`QuotaRoot`] response from a response buffer. /// Parse the [`QuotaRoot`] response from a response buffer.
pub(crate) fn parse( pub(crate) fn parse(
owned: Vec<u8>, owned: Vec<u8>,
unsolicited: &mut VecDeque<UnsolicitedResponse>, unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
QuotaRootResponseTryBuilder { QuotaRootResponseTryBuilder {
data: owned, data: owned,

View file

@ -5,7 +5,6 @@ extern crate native_tls;
use chrono::{FixedOffset, TimeZone}; use chrono::{FixedOffset, TimeZone};
use lettre::Transport; use lettre::Transport;
use std::collections::VecDeque;
use std::net::TcpStream; use std::net::TcpStream;
use crate::imap::extensions::sort::{SortCharset, SortCriterion}; use crate::imap::extensions::sort::{SortCharset, SortCriterion};
@ -207,7 +206,10 @@ fn inbox() {
// we should also get two unsolicited responses: Exists and Recent // we should also get two unsolicited responses: Exists and Recent
c.noop().unwrap(); c.noop().unwrap();
let unsolicited: VecDeque<_> = c.take_all_unsolicited().collect(); let mut unsolicited = Vec::new();
while let Ok(m) = c.unsolicited_responses.try_recv() {
unsolicited.push(m);
}
assert_eq!(unsolicited.len(), 2); assert_eq!(unsolicited.len(), 2);
assert!(unsolicited assert!(unsolicited
.iter() .iter()
@ -322,7 +324,10 @@ fn inbox_uid() {
// we should also get two unsolicited responses: Exists and Recent // we should also get two unsolicited responses: Exists and Recent
c.noop().unwrap(); c.noop().unwrap();
let unsolicited: VecDeque<_> = c.take_all_unsolicited().collect(); let mut unsolicited = Vec::new();
while let Ok(m) = c.unsolicited_responses.try_recv() {
unsolicited.push(m);
}
assert_eq!(unsolicited.len(), 2); assert_eq!(unsolicited.len(), 2);
assert!(unsolicited assert!(unsolicited
.iter() .iter()
@ -497,9 +502,12 @@ fn append_with_flags_and_date() {
let mbox = "INBOX"; let mbox = "INBOX";
c.select(mbox).unwrap(); c.select(mbox).unwrap();
// append // append
#[allow(deprecated)]
// ymd_opt is deprecated in chrono 0.4.23 and replace with new with_ymd_and_hms
let date = FixedOffset::east_opt(8 * 3600) let date = FixedOffset::east_opt(8 * 3600)
.unwrap() .unwrap()
.with_ymd_and_hms(2020, 12, 13, 13, 36, 36) .ymd_opt(2020, 12, 13)
.and_hms_opt(13, 36, 36)
.unwrap(); .unwrap();
c.append(mbox, &e.formatted()) c.append(mbox, &e.formatted())
.flag(Flag::Seen) .flag(Flag::Seen)