Skip to content

Commit 95d3a5f

Browse files
Merge pull request #36 from avitex/fix-auth
2 parents 6e11d2c + a66bb1d commit 95d3a5f

File tree

9 files changed

+165
-152
lines changed

9 files changed

+165
-152
lines changed

examples/basic.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ fn main() -> Result<()> {
1919

2020
async fn fetch_inbox_top(imap_server: &str, login: &str, password: &str) -> Result<Option<String>> {
2121
let tls = async_native_tls::TlsConnector::new();
22+
let imap_addr = (imap_server, 993);
2223

2324
// we pass in the imap_server twice to check that the server's TLS
2425
// certificate is valid for the imap_server we're connecting to.
25-
let client = async_imap::connect((imap_server, 993), imap_server, tls).await?;
26-
println!("-- connected to {}:{}", imap_server, 993);
26+
let client = async_imap::connect(imap_addr, imap_server, tls).await?;
27+
println!("-- connected to {}:{}", imap_addr.0, imap_addr.1);
2728

2829
// the client we have here is unauthenticated.
2930
// to do anything useful with the e-mails, we need to log in

examples/gmail_oauth2.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ struct GmailOAuth2 {
77
access_token: String,
88
}
99

10-
impl async_imap::Authenticator for GmailOAuth2 {
10+
impl async_imap::Authenticator for &GmailOAuth2 {
1111
type Response = String;
12-
#[allow(unused_variables)]
13-
fn process(&self, data: &[u8]) -> Self::Response {
12+
13+
fn process(&mut self, _data: &[u8]) -> Self::Response {
1414
format!(
1515
"user={}\x01auth=Bearer {}\x01\x01",
1616
self.user, self.access_token

examples/idle.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ fn main() -> Result<()> {
2121

2222
async fn fetch_and_idle(imap_server: &str, login: &str, password: &str) -> Result<()> {
2323
let tls = async_native_tls::TlsConnector::new();
24+
let imap_addr = (imap_server, 993);
2425

2526
// we pass in the imap_server twice to check that the server's TLS
2627
// certificate is valid for the imap_server we're connecting to.
27-
let client = async_imap::connect((imap_server, 993), imap_server, tls).await?;
28-
println!("-- connected to {}:{}", imap_server, 993);
28+
let client = async_imap::connect(imap_addr, imap_server, tls).await?;
29+
println!("-- connected to {}:{}", imap_addr.0, imap_addr.1);
2930

3031
// the client we have here is unauthenticated.
3132
// to do anything useful with the e-mails, we need to log in

src/authenticator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ pub trait Authenticator {
66

77
/// Each base64-decoded server challenge is passed to `process`.
88
/// The returned byte-string is base64-encoded and then sent back to the server.
9-
fn process(&self, challenge: &[u8]) -> Self::Response;
9+
fn process(&mut self, challenge: &[u8]) -> Self::Response;
1010
}

src/client.rs

Lines changed: 99 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,6 @@ pub struct Client<T: Read + Write + Unpin + fmt::Debug> {
6464
pub struct Connection<T: Read + Write + Unpin + fmt::Debug> {
6565
pub(crate) stream: ImapStream<T>,
6666

67-
/// Enable debug mode for this connection so that all client-server interactions are printed to
68-
/// `STDERR`.
69-
pub debug: bool,
70-
7167
/// Manages the request ids.
7268
pub(crate) request_ids: IdGenerator,
7369
}
@@ -192,7 +188,6 @@ impl<T: Read + Write + Unpin + fmt::Debug + Send> Client<T> {
192188
Client {
193189
conn: Connection {
194190
stream,
195-
debug: false,
196191
request_ids: IdGenerator::new(),
197192
},
198193
}
@@ -260,9 +255,9 @@ impl<T: Read + Write + Unpin + fmt::Debug + Send> Client<T> {
260255
/// access_token: String,
261256
/// }
262257
///
263-
/// impl async_imap::Authenticator for OAuth2 {
258+
/// impl async_imap::Authenticator for &OAuth2 {
264259
/// type Response = String;
265-
/// fn process(&self, _: &[u8]) -> Self::Response {
260+
/// fn process(&mut self, _: &[u8]) -> Self::Response {
266261
/// format!(
267262
/// "user={}\x01auth=Bearer {}\x01\x01",
268263
/// self.user, self.access_token
@@ -297,60 +292,59 @@ impl<T: Read + Write + Unpin + fmt::Debug + Send> Client<T> {
297292
pub async fn authenticate<A: Authenticator, S: AsRef<str>>(
298293
mut self,
299294
auth_type: S,
300-
authenticator: &A,
295+
authenticator: A,
301296
) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
302-
ok_or_unauth_client_err!(
297+
let id = ok_or_unauth_client_err!(
303298
self.run_command(&format!("AUTHENTICATE {}", auth_type.as_ref()))
304299
.await,
305300
self
306301
);
307-
let session = self.do_auth_handshake(authenticator).await?;
308-
302+
let session = self.do_auth_handshake(id, authenticator).await?;
309303
Ok(session)
310304
}
311305

312306
/// This func does the handshake process once the authenticate command is made.
313307
async fn do_auth_handshake<A: Authenticator>(
314308
mut self,
315-
authenticator: &A,
309+
id: RequestId,
310+
mut authenticator: A,
316311
) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
317312
// explicit match blocks neccessary to convert error to tuple and not bind self too
318313
// early (see also comment on `login`)
319-
if let Some(res) = self.read_response().await {
320-
// FIXME: Some servers will only send `+\r\n` need to handle that in imap_proto.
321-
// https://github.com/djc/tokio-imap/issues/67
322-
let res = ok_or_unauth_client_err!(res.map_err(Into::into), self);
323-
match res.parsed() {
324-
Response::Continue { information, .. } => {
325-
let challenge = if let Some(text) = information {
314+
loop {
315+
if let Some(res) = self.read_response().await {
316+
let res = ok_or_unauth_client_err!(res.map_err(Into::into), self);
317+
match res.parsed() {
318+
Response::Continue { information, .. } => {
319+
let challenge = if let Some(text) = information {
320+
ok_or_unauth_client_err!(
321+
base64::decode(text).map_err(|e| Error::Parse(
322+
ParseError::Authentication((*text).to_string(), Some(e))
323+
)),
324+
self
325+
)
326+
} else {
327+
Vec::new()
328+
};
329+
let raw_response = &mut authenticator.process(&challenge);
330+
let auth_response = base64::encode(raw_response);
331+
326332
ok_or_unauth_client_err!(
327-
base64::decode(text).map_err(|e| Error::Parse(
328-
ParseError::Authentication((*text).to_string(), Some(e))
329-
)),
333+
self.conn.run_command_untagged(&auth_response).await,
330334
self
331-
)
332-
} else {
333-
Vec::new()
334-
};
335-
let raw_response = &authenticator.process(&challenge);
336-
let auth_response = base64::encode(raw_response);
337-
338-
ok_or_unauth_client_err!(
339-
self.conn.run_command_untagged(&auth_response).await,
340-
self
341-
);
342-
Ok(Session::new(self.conn))
343-
}
344-
_ => {
345-
if self.read_response().await.is_some() {
346-
Ok(Session::new(self.conn))
347-
} else {
348-
Err((Error::ConnectionLost, self))
335+
);
336+
}
337+
_ => {
338+
ok_or_unauth_client_err!(
339+
self.check_done_ok_from(&id, None, res).await,
340+
self
341+
);
342+
return Ok(Session::new(self.conn));
349343
}
350344
}
345+
} else {
346+
return Err((Error::ConnectionLost, self));
351347
}
352-
} else {
353-
Err((Error::ConnectionLost, self))
354348
}
355349
}
356350
}
@@ -1322,63 +1316,81 @@ impl<T: Read + Write + Unpin + fmt::Debug> Connection<T> {
13221316
unsolicited: Option<sync::Sender<UnsolicitedResponse>>,
13231317
) -> Result<()> {
13241318
let id = self.run_command(command).await?;
1325-
self.check_ok(id, unsolicited).await?;
1319+
self.check_done_ok(&id, unsolicited).await?;
13261320

13271321
Ok(())
13281322
}
13291323

1330-
pub(crate) async fn check_ok(
1324+
pub(crate) async fn check_done_ok(
13311325
&mut self,
1332-
id: RequestId,
1326+
id: &RequestId,
1327+
unsolicited: Option<sync::Sender<UnsolicitedResponse>>,
1328+
) -> Result<()> {
1329+
if let Some(first_res) = self.stream.next().await {
1330+
self.check_done_ok_from(id, unsolicited, first_res?).await
1331+
} else {
1332+
Err(Error::ConnectionLost)
1333+
}
1334+
}
1335+
1336+
pub(crate) async fn check_done_ok_from(
1337+
&mut self,
1338+
id: &RequestId,
13331339
unsolicited: Option<sync::Sender<UnsolicitedResponse>>,
1340+
mut response: ResponseData,
13341341
) -> Result<()> {
1335-
while let Some(res) = self.stream.next().await {
1336-
let res = res?;
1342+
loop {
13371343
if let Response::Done {
13381344
status,
13391345
code,
13401346
information,
13411347
tag,
1342-
} = res.parsed()
1348+
} = response.parsed()
13431349
{
1344-
use imap_proto::Status;
1345-
match status {
1346-
Status::Ok => {
1347-
if tag != &id {
1348-
if let Some(unsolicited) = unsolicited.clone() {
1349-
handle_unilateral(res, unsolicited).await;
1350-
}
1351-
continue;
1352-
}
1353-
1354-
return Ok(());
1355-
}
1356-
Status::Bad => {
1357-
return Err(Error::Bad(format!(
1358-
"code: {:?}, info: {:?}",
1359-
code, information
1360-
)))
1361-
}
1362-
Status::No => {
1363-
return Err(Error::No(format!(
1364-
"code: {:?}, info: {:?}",
1365-
code, information
1366-
)))
1367-
}
1368-
_ => {
1369-
return Err(Error::Io(io::Error::new(
1370-
io::ErrorKind::Other,
1371-
format!(
1372-
"status: {:?}, code: {:?}, information: {:?}",
1373-
status, code, information
1374-
),
1375-
)));
1376-
}
1350+
self.check_status_ok(status, code.as_ref(), *information)?;
1351+
1352+
if tag == id {
1353+
return Ok(());
13771354
}
13781355
}
1356+
1357+
if let Some(unsolicited) = unsolicited.clone() {
1358+
handle_unilateral(response, unsolicited).await;
1359+
}
1360+
1361+
if let Some(res) = self.stream.next().await {
1362+
response = res?;
1363+
} else {
1364+
return Err(Error::ConnectionLost);
1365+
}
13791366
}
1367+
}
13801368

1381-
Err(Error::ConnectionLost)
1369+
pub(crate) fn check_status_ok(
1370+
&self,
1371+
status: &imap_proto::Status,
1372+
code: Option<&imap_proto::ResponseCode<'_>>,
1373+
information: Option<&str>,
1374+
) -> Result<()> {
1375+
use imap_proto::Status;
1376+
match status {
1377+
Status::Ok => Ok(()),
1378+
Status::Bad => Err(Error::Bad(format!(
1379+
"code: {:?}, info: {:?}",
1380+
code, information
1381+
))),
1382+
Status::No => Err(Error::No(format!(
1383+
"code: {:?}, info: {:?}",
1384+
code, information
1385+
))),
1386+
_ => Err(Error::Io(io::Error::new(
1387+
io::ErrorKind::Other,
1388+
format!(
1389+
"status: {:?}, code: {:?}, information: {:?}",
1390+
status, code, information
1391+
),
1392+
))),
1393+
}
13821394
}
13831395
}
13841396

@@ -1484,9 +1496,9 @@ mod tests {
14841496
enum Authenticate {
14851497
Auth,
14861498
};
1487-
impl Authenticator for Authenticate {
1499+
impl Authenticator for &Authenticate {
14881500
type Response = Vec<u8>;
1489-
fn process(&self, challenge: &[u8]) -> Self::Response {
1501+
fn process(&mut self, challenge: &[u8]) -> Self::Response {
14901502
assert!(challenge == b"bar", "Invalid authenticate challenge");
14911503
b"foo".to_vec()
14921504
}
@@ -1525,7 +1537,7 @@ mod tests {
15251537
#[async_std::test]
15261538
async fn logout() {
15271539
let response = b"A0001 OK Logout completed.\r\n".to_vec();
1528-
let command = format!("A0001 LOGOUT\r\n");
1540+
let command = "A0001 LOGOUT\r\n";
15291541
let mock_stream = MockStream::new(response);
15301542
let mut session = mock_session!(mock_stream);
15311543
session.logout().await.unwrap();
@@ -2027,7 +2039,7 @@ mod tests {
20272039
#[test]
20282040
fn validate_newline() {
20292041
if let Err(ref e) = validate_str("test\nstring") {
2030-
if let &Error::Validate(ref ve) = e {
2042+
if let Error::Validate(ref ve) = e {
20312043
if ve.0 == '\n' {
20322044
return;
20332045
}
@@ -2041,7 +2053,7 @@ mod tests {
20412053
#[allow(unreachable_patterns)]
20422054
fn validate_carriage_return() {
20432055
if let Err(ref e) = validate_str("test\rstring") {
2044-
if let &Error::Validate(ref ve) = e {
2056+
if let Error::Validate(ref ve) = e {
20452057
if ve.0 == '\r' {
20462058
return;
20472059
}

src/extensions/idle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ impl<T: Read + Write + Unpin + fmt::Debug + Send> Handle<T> {
207207
self.session.run_command_untagged("DONE").await?;
208208
let sender = self.session.unsolicited_responses_tx.clone();
209209
self.session
210-
.check_ok(self.id.expect("invalid setup"), Some(sender))
210+
.check_done_ok(&self.id.expect("invalid setup"), Some(sender))
211211
.await?;
212212

213213
Ok(self.session)

0 commit comments

Comments
 (0)