From 347be54305c60350b055a1da3a1abfa4d33d3f22 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Tue, 4 Oct 2022 15:49:34 +0300 Subject: [PATCH] melib/error: add NetworkErrorKind enum --- melib/src/backends/imap/connection.rs | 122 +++------- melib/src/backends/jmap.rs | 3 + melib/src/backends/nntp/connection.rs | 49 ++-- melib/src/connections.rs | 4 +- melib/src/error.rs | 329 +++++++++++++++++++++++++- melib/src/smtp.rs | 96 +++----- src/components/notifications.rs | 2 +- 7 files changed, 411 insertions(+), 194 deletions(-) diff --git a/melib/src/backends/imap/connection.rs b/melib/src/backends/imap/connection.rs index faec21563..d581e31d4 100644 --- a/melib/src/backends/imap/connection.rs +++ b/melib/src/backends/imap/connection.rs @@ -134,26 +134,19 @@ impl ImapStream { } let connector = connector .build() - .chain_err_kind(crate::error::ErrorKind::Network)?; + .chain_err_kind(crate::error::ErrorKind::Network( + crate::error::NetworkErrorKind::InvalidTLSConnection, + ))?; - let addr = if let Ok(a) = lookup_ipv4(path, server_conf.server_port) { - a - } else { - return Err(MeliError::new(format!( - "Could not lookup address {}", - &path - ))); - }; + let addr = lookup_ipv4(path, server_conf.server_port)?; let mut socket = AsyncWrapper::new(Connection::Tcp( if let Some(timeout) = server_conf.timeout { - TcpStream::connect_timeout(&addr, timeout) - .chain_err_kind(crate::error::ErrorKind::Network)? + TcpStream::connect_timeout(&addr, timeout)? } else { - TcpStream::connect(&addr).chain_err_kind(crate::error::ErrorKind::Network)? + TcpStream::connect(&addr)? }, - )) - .chain_err_kind(crate::error::ErrorKind::Network)?; + ))?; if server_conf.use_starttls { let err_fn = || { if server_conf.server_port == 993 { @@ -167,36 +160,22 @@ impl ImapStream { ImapProtocol::IMAP { .. } => socket .write_all(format!("M{} STARTTLS\r\n", cmd_id).as_bytes()) .await - .chain_err_summary(err_fn) - .chain_err_kind(crate::error::ErrorKind::Network)?, + .chain_err_summary(err_fn)?, ImapProtocol::ManageSieve => { - socket - .read(&mut buf) - .await - .chain_err_summary(err_fn) - .chain_err_kind(crate::error::ErrorKind::Network)?; + socket.read(&mut buf).await.chain_err_summary(err_fn)?; socket .write_all(b"STARTTLS\r\n") .await - .chain_err_summary(err_fn) - .chain_err_kind(crate::error::ErrorKind::Network)?; + .chain_err_summary(err_fn)?; } } - socket - .flush() - .await - .chain_err_summary(err_fn) - .chain_err_kind(crate::error::ErrorKind::Network)?; + socket.flush().await.chain_err_summary(err_fn)?; let mut response = Vec::with_capacity(1024); let mut broken = false; let now = Instant::now(); while now.elapsed().as_secs() < 3 { - let len = socket - .read(&mut buf) - .await - .chain_err_summary(err_fn) - .chain_err_kind(crate::error::ErrorKind::Network)?; + let len = socket.read(&mut buf).await.chain_err_summary(err_fn)?; response.extend_from_slice(&buf[0..len]); match server_conf.protocol { ImapProtocol::IMAP { .. } => { @@ -229,9 +208,7 @@ impl ImapStream { { // FIXME: This is blocking - let socket = socket - .into_inner() - .chain_err_kind(crate::error::ErrorKind::Network)?; + let socket = socket.into_inner()?; let mut conn_result = connector.connect(path, socket); if let Err(native_tls::HandshakeError::WouldBlock(midhandshake_stream)) = conn_result @@ -247,20 +224,17 @@ impl ImapStream { midhandshake_stream = Some(stream); } p => { - p.chain_err_kind(crate::error::ErrorKind::Network)?; + p.chain_err_kind(crate::error::ErrorKind::Network( + crate::error::NetworkErrorKind::InvalidTLSConnection, + ))?; } } } } - AsyncWrapper::new(Connection::Tls( - conn_result - .chain_err_summary(|| { - format!("Could not initiate TLS negotiation to {}.", path) - }) - .chain_err_kind(crate::error::ErrorKind::Network)?, - )) - .chain_err_summary(|| format!("Could not initiate TLS negotiation to {}.", path)) - .chain_err_kind(crate::error::ErrorKind::Network)? + AsyncWrapper::new(Connection::Tls(conn_result.chain_err_summary(|| { + format!("Could not initiate TLS negotiation to {}.", path) + })?)) + .chain_err_summary(|| format!("Could not initiate TLS negotiation to {}.", path))? } } else { let addr = if let Ok(a) = lookup_ipv4(path, server_conf.server_port) { @@ -273,13 +247,11 @@ impl ImapStream { }; AsyncWrapper::new(Connection::Tcp( if let Some(timeout) = server_conf.timeout { - TcpStream::connect_timeout(&addr, timeout) - .chain_err_kind(crate::error::ErrorKind::Network)? + TcpStream::connect_timeout(&addr, timeout)? } else { - TcpStream::connect(&addr).chain_err_kind(crate::error::ErrorKind::Network)? + TcpStream::connect(&addr)? }, - )) - .chain_err_kind(crate::error::ErrorKind::Network)? + ))? }; if let Err(err) = stream .get_ref() @@ -507,8 +479,8 @@ impl ImapStream { last_line_idx += pos + b"\r\n".len(); } } - Err(e) => { - return Err(MeliError::from(e).set_err_kind(crate::error::ErrorKind::Network)); + Err(err) => { + return Err(MeliError::from(err)); } } } @@ -524,7 +496,7 @@ impl ImapStream { } pub async fn send_command(&mut self, command: &[u8]) -> Result<()> { - if let Err(err) = timeout( + _ = timeout( self.timeout, try_await(async move { let command = command.trim(); @@ -558,42 +530,22 @@ impl ImapStream { Ok(()) }), ) - .await - { - Err(err.set_err_kind(crate::error::ErrorKind::Network)) - } else { - Ok(()) - } + .await?; + Ok(()) } pub async fn send_literal(&mut self, data: &[u8]) -> Result<()> { - if let Err(err) = try_await(async move { - self.stream.write_all(data).await?; - self.stream.write_all(b"\r\n").await?; - self.stream.flush().await?; - Ok(()) - }) - .await - { - Err(err.set_err_kind(crate::error::ErrorKind::Network)) - } else { - Ok(()) - } + self.stream.write_all(data).await?; + self.stream.write_all(b"\r\n").await?; + self.stream.flush().await?; + Ok(()) } pub async fn send_raw(&mut self, raw: &[u8]) -> Result<()> { - if let Err(err) = try_await(async move { - self.stream.write_all(raw).await?; - self.stream.write_all(b"\r\n").await?; - self.stream.flush().await?; - Ok(()) - }) - .await - { - Err(err.set_err_kind(crate::error::ErrorKind::Network)) - } else { - Ok(()) - } + self.stream.write_all(raw).await?; + self.stream.write_all(b"\r\n").await?; + self.stream.flush().await?; + Ok(()) } } @@ -1152,7 +1104,7 @@ async fn read( *prev_failure = None; } Err(_err) => { - *err = Some(Into::::into(_err).set_kind(crate::error::ErrorKind::Network)); + *err = Some(Into::::into(_err)); *break_flag = true; *prev_failure = Some(SystemTime::now()); } diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index 0c26e4e4d..f8de9321e 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -337,6 +337,9 @@ impl MailBackend for JmapType { &store, mailbox_hash, ).await?; + if res.is_empty() { + return; + } yield res; })) } diff --git a/melib/src/backends/nntp/connection.rs b/melib/src/backends/nntp/connection.rs index 3cd0e3807..ee76ccc05 100644 --- a/melib/src/backends/nntp/connection.rs +++ b/melib/src/backends/nntp/connection.rs @@ -90,11 +90,10 @@ impl NntpStream { let stream = { let addr = lookup_ipv4(path, server_conf.server_port)?; - AsyncWrapper::new(Connection::Tcp( - TcpStream::connect_timeout(&addr, std::time::Duration::new(16, 0)) - .chain_err_kind(crate::error::ErrorKind::Network)?, - )) - .chain_err_kind(crate::error::ErrorKind::Network)? + AsyncWrapper::new(Connection::Tcp(TcpStream::connect_timeout( + &addr, + std::time::Duration::new(16, 0), + )?))? }; let mut res = String::with_capacity(8 * 1024); let mut ret = NntpStream { @@ -109,9 +108,7 @@ impl NntpStream { if server_conf.danger_accept_invalid_certs { connector.danger_accept_invalid_certs(true); } - let connector = connector - .build() - .chain_err_kind(crate::error::ErrorKind::Network)?; + let connector = connector.build()?; if server_conf.use_starttls { ret.read_response(&mut res, false, &["200 ", "201 "]) @@ -147,14 +144,8 @@ impl NntpStream { &server_conf.server_hostname ))); } - ret.stream - .write_all(b"STARTTLS\r\n") - .await - .chain_err_kind(crate::error::ErrorKind::Network)?; - ret.stream - .flush() - .await - .chain_err_kind(crate::error::ErrorKind::Network)?; + ret.stream.write_all(b"STARTTLS\r\n").await?; + ret.stream.flush().await?; ret.read_response(&mut res, false, command_to_replycodes("STARTTLS")) .await?; if !res.starts_with("382 ") { @@ -167,10 +158,7 @@ impl NntpStream { { // FIXME: This is blocking - let socket = ret - .stream - .into_inner() - .chain_err_kind(crate::error::ErrorKind::Network)?; + let socket = ret.stream.into_inner()?; let mut conn_result = connector.connect(path, socket); if let Err(native_tls::HandshakeError::WouldBlock(midhandshake_stream)) = conn_result @@ -186,16 +174,17 @@ impl NntpStream { midhandshake_stream = Some(stream); } p => { - p.chain_err_kind(crate::error::ErrorKind::Network)?; + p.chain_err_kind(crate::error::ErrorKind::Network( + crate::error::NetworkErrorKind::InvalidTLSConnection, + ))?; } } } } - ret.stream = AsyncWrapper::new(Connection::Tls( - conn_result.chain_err_kind(crate::error::ErrorKind::Network)?, - )) - .chain_err_summary(|| format!("Could not initiate TLS negotiation to {}.", path)) - .chain_err_kind(crate::error::ErrorKind::Network)?; + ret.stream = + AsyncWrapper::new(Connection::Tls(conn_result?)).chain_err_summary(|| { + format!("Could not initiate TLS negotiation to {}.", path) + })?; } } //ret.send_command( @@ -367,8 +356,8 @@ impl NntpStream { last_line_idx += pos + "\r\n".len(); } } - Err(e) => { - return Err(MeliError::from(e).set_err_kind(crate::error::ErrorKind::Network)); + Err(err) => { + return Err(MeliError::from(err)); } } } @@ -393,7 +382,7 @@ impl NntpStream { .await { debug!("stream send_command err {:?}", err); - Err(err.set_err_kind(crate::error::ErrorKind::Network)) + Err(err) } else { Ok(()) } @@ -427,7 +416,7 @@ impl NntpStream { .await { debug!("stream send_multiline_data_block err {:?}", err); - Err(err.set_err_kind(crate::error::ErrorKind::Network)) + Err(err) } else { Ok(()) } diff --git a/melib/src/connections.rs b/melib/src/connections.rs index 0f40766db..f1a2f8e3c 100644 --- a/melib/src/connections.rs +++ b/melib/src/connections.rs @@ -271,7 +271,9 @@ pub fn lookup_ipv4(host: &str, port: u16) -> crate::Result Err( crate::error::MeliError::new(format!("Could not lookup address {}:{}", host, port)) - .set_kind(crate::error::ErrorKind::Network), + .set_kind(crate::error::ErrorKind::Network( + crate::error::NetworkErrorKind::HostLookupFailed, + )), ) } diff --git a/melib/src/error.rs b/melib/src/error.rs index 229633757..06debaac6 100644 --- a/melib/src/error.rs +++ b/melib/src/error.rs @@ -34,6 +34,284 @@ use std::sync::Arc; pub type Result = result::Result; +#[derive(Debug, Copy, PartialEq, Clone)] +pub enum NetworkErrorKind { + /// Unspecified + None, + /// Name lookup of host failed. + HostLookupFailed, + /// Bad client Certificate + BadClientCertificate, + /// Bad server certificate + BadServerCertificate, + /// Client initialization + ClientInitialization, + /// Connection failed + ConnectionFailed, + /// Invalid content encoding + InvalidContentEncoding, + /// Invalid credentials + InvalidCredentials, + /// Invalid request + InvalidRequest, + /// IO Error + Io, + /// Name resolution + NameResolution, + /// Protocol violation + ProtocolViolation, + /// Request body not rewindable + RequestBodyNotRewindable, + /// Connection (not request) timeout. + Timeout, + /// TooManyRedirects + TooManyRedirects, + /// Invalid TLS connection + InvalidTLSConnection, + /// Equivalent to HTTP status code 400 Bad Request + /// [[RFC7231, Section 6.5.1](https://tools.ietf.org/html/rfc7231#section-6.5.1)] + BadRequest, + /// Equivalent to HTTP status code 401 Unauthorized + /// [[RFC7235, Section 3.1](https://tools.ietf.org/html/rfc7235#section-3.1)] + Unauthorized, + /// Equivalent to HTTP status code 402 Payment Required + /// [[RFC7231, Section 6.5.2](https://tools.ietf.org/html/rfc7231#section-6.5.2)] + PaymentRequired, + /// Equivalent to HTTP status code 403 Forbidden + /// [[RFC7231, Section 6.5.3](https://tools.ietf.org/html/rfc7231#section-6.5.3)] + Forbidden, + /// Equivalent to HTTP status code 404 Not Found + /// [[RFC7231, Section 6.5.4](https://tools.ietf.org/html/rfc7231#section-6.5.4)] + NotFound, + /// Equivalent to HTTP status code 405 Method Not Allowed + /// [[RFC7231, Section 6.5.5](https://tools.ietf.org/html/rfc7231#section-6.5.5)] + MethodNotAllowed, + /// Equivalent to HTTP status code 406 Not Acceptable + /// [[RFC7231, Section 6.5.6](https://tools.ietf.org/html/rfc7231#section-6.5.6)] + NotAcceptable, + /// Equivalent to HTTP status code 407 Proxy Authentication Required + /// [[RFC7235, Section 3.2](https://tools.ietf.org/html/rfc7235#section-3.2)] + ProxyAuthenticationRequired, + /// Equivalent to HTTP status code 408 Request Timeout + /// [[RFC7231, Section 6.5.7](https://tools.ietf.org/html/rfc7231#section-6.5.7)] + RequestTimeout, + /// Equivalent to HTTP status code 409 Conflict + /// [[RFC7231, Section 6.5.8](https://tools.ietf.org/html/rfc7231#section-6.5.8)] + Conflict, + /// Equivalent to HTTP status code 410 Gone + /// [[RFC7231, Section 6.5.9](https://tools.ietf.org/html/rfc7231#section-6.5.9)] + Gone, + /// Equivalent to HTTP status code 411 Length Required + /// [[RFC7231, Section 6.5.10](https://tools.ietf.org/html/rfc7231#section-6.5.10)] + LengthRequired, + /// Equivalent to HTTP status code 412 Precondition Failed + /// [[RFC7232, Section 4.2](https://tools.ietf.org/html/rfc7232#section-4.2)] + PreconditionFailed, + /// Equivalent to HTTP status code 413 Payload Too Large + /// [[RFC7231, Section 6.5.11](https://tools.ietf.org/html/rfc7231#section-6.5.11)] + PayloadTooLarge, + /// Equivalent to HTTP status code 414 URI Too Long + /// [[RFC7231, Section 6.5.12](https://tools.ietf.org/html/rfc7231#section-6.5.12)] + URITooLong, + /// Equivalent to HTTP status code 415 Unsupported Media Type + /// [[RFC7231, Section 6.5.13](https://tools.ietf.org/html/rfc7231#section-6.5.13)] + UnsupportedMediaType, + /// Equivalent to HTTP status code 416 Range Not Satisfiable + /// [[RFC7233, Section 4.4](https://tools.ietf.org/html/rfc7233#section-4.4)] + RangeNotSatisfiable, + /// Equivalent to HTTP status code 417 Expectation Failed + /// [[RFC7231, Section 6.5.14](https://tools.ietf.org/html/rfc7231#section-6.5.14)] + ExpectationFailed, + /// Equivalent to HTTP status code 421 Misdirected Request + /// [RFC7540, Section 9.1.2](http://tools.ietf.org/html/rfc7540#section-9.1.2) + MisdirectedRequest, + /// Equivalent to HTTP status code 422 Unprocessable Entity + /// [[RFC4918](https://tools.ietf.org/html/rfc4918)] + UnprocessableEntity, + /// Equivalent to HTTP status code 423 Locked + /// [[RFC4918](https://tools.ietf.org/html/rfc4918)] + Locked, + /// Equivalent to HTTP status code 424 Failed Dependency + /// [[RFC4918](https://tools.ietf.org/html/rfc4918)] + FailedDependency, + + /// Equivalent to HTTP status code 426 Upgrade Required + /// [[RFC7231, Section 6.5.15](https://tools.ietf.org/html/rfc7231#section-6.5.15)] + UpgradeRequired, + + /// Equivalent to HTTP status code 428 Precondition Required + /// [[RFC6585](https://tools.ietf.org/html/rfc6585)] + PreconditionRequired, + /// Equivalent to HTTP status code 429 Too Many Requests + /// [[RFC6585](https://tools.ietf.org/html/rfc6585)] + TooManyRequests, + + /// Equivalent to HTTP status code 431 Request Header Fields Too Large + /// [[RFC6585](https://tools.ietf.org/html/rfc6585)] + RequestHeaderFieldsTooLarge, + + /// Equivalent to HTTP status code 451 Unavailable For Legal Reasons + /// [[RFC7725](http://tools.ietf.org/html/rfc7725)] + UnavailableForLegalReasons, + + /// Equivalent to HTTP status code 500 Internal Server Error + /// [[RFC7231, Section 6.6.1](https://tools.ietf.org/html/rfc7231#section-6.6.1)] + InternalServerError, + /// Equivalent to HTTP status code 501 Not Implemented + /// [[RFC7231, Section 6.6.2](https://tools.ietf.org/html/rfc7231#section-6.6.2)] + NotImplemented, + /// Equivalent to HTTP status code 502 Bad Gateway + /// [[RFC7231, Section 6.6.3](https://tools.ietf.org/html/rfc7231#section-6.6.3)] + BadGateway, + /// Equivalent to HTTP status code 503 Service Unavailable + /// [[RFC7231, Section 6.6.4](https://tools.ietf.org/html/rfc7231#section-6.6.4)] + ServiceUnavailable, + /// Equivalent to HTTP status code 504 Gateway Timeout + /// [[RFC7231, Section 6.6.5](https://tools.ietf.org/html/rfc7231#section-6.6.5)] + GatewayTimeout, + /// Equivalent to HTTP status code 505 HTTP Version Not Supported + /// [[RFC7231, Section 6.6.6](https://tools.ietf.org/html/rfc7231#section-6.6.6)] + HTTPVersionNotSupported, + /// Equivalent to HTTP status code 506 Variant Also Negotiates + /// [[RFC2295](https://tools.ietf.org/html/rfc2295)] + VariantAlsoNegotiates, + /// Equivalent to HTTP status code 507 Insufficient Storage + /// [[RFC4918](https://tools.ietf.org/html/rfc4918)] + InsufficientStorage, + /// Equivalent to HTTP status code 508 Loop Detected + /// [[RFC5842](https://tools.ietf.org/html/rfc5842)] + LoopDetected, + /// Equivalent to HTTP status code 510 Not Extended + /// [[RFC2774](https://tools.ietf.org/html/rfc2774)] + NotExtended, + /// Equivalent to HTTP status code 511 Network Authentication Required + /// [[RFC6585](https://tools.ietf.org/html/rfc6585)] + NetworkAuthenticationRequired, +} + +impl NetworkErrorKind { + pub fn as_str(&self) -> &'static str { + use NetworkErrorKind::*; + match self { + None => "Network", + HostLookupFailed => "Name lookup of host failed.", + BadClientCertificate => "Bad client Certificate", + BadServerCertificate => "Bad server certificate", + ClientInitialization => "Client initialization", + ConnectionFailed => "Connection failed", + InvalidContentEncoding => "Invalid content encoding", + InvalidCredentials => "Invalid credentials", + InvalidRequest => "Invalid request", + Io => "IO Error", + NameResolution => "Name resolution", + ProtocolViolation => "Protocol violation", + RequestBodyNotRewindable => "Request body not rewindable", + Timeout => "Connection (not request) timeout.", + TooManyRedirects => "TooManyRedirects", + InvalidTLSConnection => "Invalid TLS connection", + BadRequest => "Bad Request", + Unauthorized => "Unauthorized", + PaymentRequired => "Payment Required", + Forbidden => "Forbidden", + NotFound => "Not Found", + MethodNotAllowed => "Method Not Allowed", + NotAcceptable => "Not Acceptable", + ProxyAuthenticationRequired => "Proxy Authentication Required", + RequestTimeout => "Request Timeout", + Conflict => "Conflict", + Gone => "Gone", + LengthRequired => "Length Required", + PreconditionFailed => "Precondition Failed", + PayloadTooLarge => "Payload Too Large", + URITooLong => "URI Too Long", + UnsupportedMediaType => "Unsupported Media Type", + RangeNotSatisfiable => "Range Not Satisfiable", + ExpectationFailed => "Expectation Failed", + MisdirectedRequest => "Misdirected Request", + UnprocessableEntity => "Unprocessable Entity", + Locked => "Locked", + FailedDependency => "Failed Dependency", + UpgradeRequired => "Upgrade Required", + PreconditionRequired => "Precondition Required", + TooManyRequests => "Too Many Requests", + RequestHeaderFieldsTooLarge => "Request Header Fields Too Large", + UnavailableForLegalReasons => "Unavailable For Legal Reasons", + InternalServerError => "Internal Server Error", + NotImplemented => "Not Implemented", + BadGateway => "Bad Gateway", + ServiceUnavailable => "Service Unavailable", + GatewayTimeout => "Gateway Timeout", + HTTPVersionNotSupported => "HTTP Version Not Supported", + VariantAlsoNegotiates => "Variant Also Negotiates", + InsufficientStorage => "Insufficient Storage", + LoopDetected => "Loop Detected", + NotExtended => "Not Extended", + NetworkAuthenticationRequired => "Network Authentication Required", + } + } +} + +impl Default for NetworkErrorKind { + fn default() -> Self { + Self::None + } +} + +#[cfg(feature = "http")] +impl From for NetworkErrorKind { + fn from(val: isahc::http::StatusCode) -> Self { + match val { + isahc::http::StatusCode::BAD_REQUEST => Self::BadRequest, + isahc::http::StatusCode::UNAUTHORIZED => Self::Unauthorized, + isahc::http::StatusCode::PAYMENT_REQUIRED => Self::PaymentRequired, + isahc::http::StatusCode::FORBIDDEN => Self::Forbidden, + isahc::http::StatusCode::NOT_FOUND => Self::NotFound, + isahc::http::StatusCode::METHOD_NOT_ALLOWED => Self::MethodNotAllowed, + isahc::http::StatusCode::NOT_ACCEPTABLE => Self::NotAcceptable, + isahc::http::StatusCode::PROXY_AUTHENTICATION_REQUIRED => { + Self::ProxyAuthenticationRequired + } + isahc::http::StatusCode::REQUEST_TIMEOUT => Self::RequestTimeout, + isahc::http::StatusCode::CONFLICT => Self::Conflict, + isahc::http::StatusCode::GONE => Self::Gone, + isahc::http::StatusCode::LENGTH_REQUIRED => Self::LengthRequired, + isahc::http::StatusCode::PRECONDITION_FAILED => Self::PreconditionFailed, + isahc::http::StatusCode::PAYLOAD_TOO_LARGE => Self::PayloadTooLarge, + isahc::http::StatusCode::URI_TOO_LONG => Self::URITooLong, + isahc::http::StatusCode::UNSUPPORTED_MEDIA_TYPE => Self::UnsupportedMediaType, + isahc::http::StatusCode::RANGE_NOT_SATISFIABLE => Self::RangeNotSatisfiable, + isahc::http::StatusCode::EXPECTATION_FAILED => Self::ExpectationFailed, + isahc::http::StatusCode::MISDIRECTED_REQUEST => Self::MisdirectedRequest, + isahc::http::StatusCode::UNPROCESSABLE_ENTITY => Self::UnprocessableEntity, + isahc::http::StatusCode::LOCKED => Self::Locked, + isahc::http::StatusCode::FAILED_DEPENDENCY => Self::FailedDependency, + isahc::http::StatusCode::UPGRADE_REQUIRED => Self::UpgradeRequired, + isahc::http::StatusCode::PRECONDITION_REQUIRED => Self::PreconditionRequired, + isahc::http::StatusCode::TOO_MANY_REQUESTS => Self::TooManyRequests, + isahc::http::StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE => { + Self::RequestHeaderFieldsTooLarge + } + isahc::http::StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS => { + Self::UnavailableForLegalReasons + } + isahc::http::StatusCode::INTERNAL_SERVER_ERROR => Self::InternalServerError, + isahc::http::StatusCode::NOT_IMPLEMENTED => Self::NotImplemented, + isahc::http::StatusCode::BAD_GATEWAY => Self::BadGateway, + isahc::http::StatusCode::SERVICE_UNAVAILABLE => Self::ServiceUnavailable, + isahc::http::StatusCode::GATEWAY_TIMEOUT => Self::GatewayTimeout, + isahc::http::StatusCode::HTTP_VERSION_NOT_SUPPORTED => Self::HTTPVersionNotSupported, + isahc::http::StatusCode::VARIANT_ALSO_NEGOTIATES => Self::VariantAlsoNegotiates, + isahc::http::StatusCode::INSUFFICIENT_STORAGE => Self::InsufficientStorage, + isahc::http::StatusCode::LOOP_DETECTED => Self::LoopDetected, + isahc::http::StatusCode::NOT_EXTENDED => Self::NotExtended, + isahc::http::StatusCode::NETWORK_AUTHENTICATION_REQUIRED => { + Self::NetworkAuthenticationRequired + } + _ => Self::default(), + } + } +} + #[derive(Debug, Copy, PartialEq, Clone)] pub enum ErrorKind { None, @@ -41,7 +319,7 @@ pub enum ErrorKind { Authentication, Configuration, Bug, - Network, + Network(NetworkErrorKind), Timeout, OSError, NotImplemented, @@ -58,7 +336,7 @@ impl fmt::Display for ErrorKind { ErrorKind::External => "External", ErrorKind::Authentication => "Authentication", ErrorKind::Bug => "Bug, please report this!", - ErrorKind::Network => "Network", + ErrorKind::Network(ref inner) => inner.as_str(), ErrorKind::Timeout => "Timeout", ErrorKind::OSError => "OS Error", ErrorKind::Configuration => "Configuration", @@ -71,7 +349,7 @@ impl fmt::Display for ErrorKind { impl ErrorKind { pub fn is_network(&self) -> bool { - matches!(self, ErrorKind::Network) + matches!(self, ErrorKind::Network(_)) } pub fn is_timeout(&self) -> bool { @@ -279,7 +557,7 @@ impl From) -> MeliError { MeliError::new(kind.to_string()) .set_source(Some(Arc::new(kind))) - .set_kind(ErrorKind::Network) + .set_kind(ErrorKind::Network(NetworkErrorKind::InvalidTLSConnection)) } } @@ -289,7 +567,7 @@ impl From for MeliError { fn from(kind: native_tls::Error) -> MeliError { MeliError::new(kind.to_string()) .set_source(Some(Arc::new(kind))) - .set_kind(ErrorKind::Network) + .set_kind(ErrorKind::Network(NetworkErrorKind::InvalidTLSConnection)) } } @@ -300,11 +578,46 @@ impl From for MeliError { } } -#[cfg(feature = "jmap_backend")] +#[cfg(feature = "http")] +impl From<&isahc::error::ErrorKind> for NetworkErrorKind { + #[inline] + fn from(val: &isahc::error::ErrorKind) -> NetworkErrorKind { + use isahc::error::ErrorKind::*; + match val { + BadClientCertificate => NetworkErrorKind::BadClientCertificate, + BadServerCertificate => NetworkErrorKind::BadServerCertificate, + ClientInitialization => NetworkErrorKind::ClientInitialization, + ConnectionFailed => NetworkErrorKind::ConnectionFailed, + InvalidContentEncoding => NetworkErrorKind::InvalidContentEncoding, + InvalidCredentials => NetworkErrorKind::InvalidCredentials, + InvalidRequest => NetworkErrorKind::BadRequest, + Io => NetworkErrorKind::Io, + NameResolution => NetworkErrorKind::HostLookupFailed, + ProtocolViolation => NetworkErrorKind::ProtocolViolation, + RequestBodyNotRewindable => NetworkErrorKind::RequestBodyNotRewindable, + Timeout => NetworkErrorKind::Timeout, + TlsEngine => NetworkErrorKind::InvalidTLSConnection, + TooManyRedirects => NetworkErrorKind::TooManyRedirects, + _ => NetworkErrorKind::None, + } + } +} + +impl From for ErrorKind { + #[inline] + fn from(kind: NetworkErrorKind) -> ErrorKind { + ErrorKind::Network(kind) + } +} + +#[cfg(feature = "http")] impl From for MeliError { #[inline] - fn from(kind: isahc::Error) -> MeliError { - MeliError::new(kind.to_string()).set_source(Some(Arc::new(kind))) + fn from(val: isahc::Error) -> MeliError { + let kind: NetworkErrorKind = val.kind().into(); + MeliError::new(val.to_string()) + .set_source(Some(Arc::new(val))) + .set_kind(ErrorKind::Network(kind)) } } diff --git a/melib/src/smtp.rs b/melib/src/smtp.rs index eb3e335a3..b9643dd15 100644 --- a/melib/src/smtp.rs +++ b/melib/src/smtp.rs @@ -268,16 +268,13 @@ impl SmtpConnection { if danger_accept_invalid_certs { connector.danger_accept_invalid_certs(true); } - let connector = connector - .build() - .chain_err_kind(crate::error::ErrorKind::Network)?; + let connector = connector.build()?; let addr = lookup_ipv4(path, server_conf.port)?; - let mut socket = AsyncWrapper::new(Connection::Tcp( - TcpStream::connect_timeout(&addr, std::time::Duration::new(4, 0)) - .chain_err_kind(crate::error::ErrorKind::Network)?, - )) - .chain_err_kind(crate::error::ErrorKind::Network)?; + let mut socket = AsyncWrapper::new(Connection::Tcp(TcpStream::connect_timeout( + &addr, + std::time::Duration::new(4, 0), + )?))?; let pre_ehlo_extensions_reply = read_lines( &mut socket, &mut res, @@ -300,10 +297,7 @@ impl SmtpConnection { return Err(MeliError::new("Please specify what SMTP security transport to use explicitly instead of `auto`.")); } } - socket - .write_all(b"EHLO meli.delivery\r\n") - .await - .chain_err_kind(crate::error::ErrorKind::Network)?; + socket.write_all(b"EHLO meli.delivery\r\n").await?; if let SmtpSecurity::StartTLS { .. } = server_conf.security { let pre_tls_extensions_reply = read_lines( &mut socket, @@ -314,10 +308,7 @@ impl SmtpConnection { .await?; drop(pre_tls_extensions_reply); //debug!(pre_tls_extensions_reply); - socket - .write_all(b"STARTTLS\r\n") - .await - .chain_err_kind(crate::error::ErrorKind::Network)?; + socket.write_all(b"STARTTLS\r\n").await?; let _post_starttls_extensions_reply = read_lines( &mut socket, &mut res, @@ -329,15 +320,11 @@ impl SmtpConnection { } let mut ret = { - let socket = socket - .into_inner() - .chain_err_kind(crate::error::ErrorKind::Network)?; + let socket = socket.into_inner()?; let _path = path.clone(); socket.set_nonblocking(false)?; - let conn = unblock(move || connector.connect(&_path, socket)) - .await - .chain_err_kind(crate::error::ErrorKind::Network)?; + let conn = unblock(move || connector.connect(&_path, socket)).await?; /* if let Err(native_tls::HandshakeError::WouldBlock(midhandshake_stream)) = conn_result @@ -359,21 +346,17 @@ impl SmtpConnection { } } */ - AsyncWrapper::new(Connection::Tls(conn)) - .chain_err_kind(crate::error::ErrorKind::Network)? + AsyncWrapper::new(Connection::Tls(conn))? }; - ret.write_all(b"EHLO meli.delivery\r\n") - .await - .chain_err_kind(crate::error::ErrorKind::Network)?; + ret.write_all(b"EHLO meli.delivery\r\n").await?; ret } SmtpSecurity::None => { let addr = lookup_ipv4(path, server_conf.port)?; - let mut ret = AsyncWrapper::new(Connection::Tcp( - TcpStream::connect_timeout(&addr, std::time::Duration::new(4, 0)) - .chain_err_kind(crate::error::ErrorKind::Network)?, - )) - .chain_err_kind(crate::error::ErrorKind::Network)?; + let mut ret = AsyncWrapper::new(Connection::Tcp(TcpStream::connect_timeout( + &addr, + std::time::Duration::new(4, 0), + )?))?; res.clear(); let reply = read_lines( &mut ret, @@ -391,9 +374,7 @@ impl SmtpConnection { Reply::new(&res, code) ))); } - ret.write_all(b"EHLO meli.delivery\r\n") - .await - .chain_err_kind(crate::error::ErrorKind::Network)?; + ret.write_all(b"EHLO meli.delivery\r\n").await?; ret } }; @@ -601,15 +582,10 @@ impl SmtpConnection { // .trim() //); for c in command { - self.stream - .write_all(c) - .await - .chain_err_kind(crate::error::ErrorKind::Network)?; + self.stream.write_all(c).await?; } - self.stream - .write_all(b"\r\n") - .await - .chain_err_kind(crate::error::ErrorKind::Network) + self.stream.write_all(b"\r\n").await?; + Ok(()) } /// Sends mail @@ -697,10 +673,7 @@ impl SmtpConnection { let mail_length = format!("{}", mail.as_bytes().len()); self.send_command(&[b"BDAT", mail_length.as_bytes(), b"LAST"]) .await?; - self.stream - .write_all(mail.as_bytes()) - .await - .chain_err_kind(crate::error::ErrorKind::Network)?; + self.stream.write_all(mail.as_bytes()).await?; } else { //The third step in the procedure is the DATA command //(or some alternative specified in a service extension). @@ -737,35 +710,20 @@ impl SmtpConnection { //line.If it is a period, one additional period is inserted at the beginning of the line. for line in mail.lines() { if line.starts_with('.') { - self.stream - .write_all(b".") - .await - .chain_err_kind(crate::error::ErrorKind::Network)?; + self.stream.write_all(b".").await?; } - self.stream - .write_all(line.as_bytes()) - .await - .chain_err_kind(crate::error::ErrorKind::Network)?; - self.stream - .write_all(b"\r\n") - .await - .chain_err_kind(crate::error::ErrorKind::Network)?; + self.stream.write_all(line.as_bytes()).await?; + self.stream.write_all(b"\r\n").await?; } if !mail.ends_with('\n') { - self.stream - .write_all(b".\r\n") - .await - .chain_err_kind(crate::error::ErrorKind::Network)?; + self.stream.write_all(b".\r\n").await?; } //The mail data are terminated by a line containing only a period, that is, the character //sequence ".", where the first is actually the terminator of the //previous line (see Section 4.5.2). This is the end of mail data indication. - self.stream - .write_all(b".\r\n") - .await - .chain_err_kind(crate::error::ErrorKind::Network)?; + self.stream.write_all(b".\r\n").await?; } //The end of mail data indicator also confirms the mail transaction and tells the SMTP @@ -1041,8 +999,8 @@ async fn read_lines<'r>( Ok(b) => { ret.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..b]) }); } - Err(e) => { - return Err(MeliError::from(e).set_kind(crate::error::ErrorKind::Network)); + Err(err) => { + return Err(MeliError::from(err)); } } } diff --git a/src/components/notifications.rs b/src/components/notifications.rs index cbc5fb89b..3d959a6a0 100644 --- a/src/components/notifications.rs +++ b/src/components/notifications.rs @@ -100,7 +100,7 @@ mod dbus { | Some(NotificationType::Error(melib::ErrorKind::External)) => { notification.icon("dialog-error"); } - Some(NotificationType::Error(melib::ErrorKind::Network)) => { + Some(NotificationType::Error(melib::ErrorKind::Network(_))) => { notification.icon("network-error"); } Some(NotificationType::Error(melib::ErrorKind::Timeout)) => {