From 304a25ab3fe6656731cb8054c22cf4dee1e3470e Mon Sep 17 00:00:00 2001 From: tkirishima Date: Mon, 13 May 2024 18:51:14 +0000 Subject: [PATCH 01/15] chore(bump): version of the crate --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index db4458a9..e4d695dd 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Making your own project? [Submit a pull request](https://github.com/aatxe/irc/pu ## Getting Started -To start using the irc crate with cargo, you can add `irc = "0.15"` to your dependencies in +To start using the irc crate with cargo, you can add `irc = "1.0.0"` to your dependencies in or you can use the comman `cargo add irc`. your Cargo.toml file. The high-level API can be found in [`irc::client::prelude`][irc-prelude]. You'll find a number of examples to help you get started in `examples/`, throughout the documentation, and below. @@ -89,7 +89,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -irc = "0.15.0" +irc = "1.0.0" tokio = { version = "1.0.0", features = ["rt", "rt-multi-thread", "macros", "net", "time"] } futures = "0.3.0" failure = "0.1.8" From 5b94c53558f3843d171292df64315476537b95f2 Mon Sep 17 00:00:00 2001 From: tkirishima Date: Mon, 13 May 2024 18:51:55 +0000 Subject: [PATCH 02/15] fix(README): proxy_port is a u16 not &str --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e4d695dd..d13acc24 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ port = 6697 password = "" proxy_type = "None" proxy_server = "127.0.0.1" -proxy_port = "1080" +proxy_port = 1080 proxy_username = "" proxy_password = "" use_tls = true From 1aa1d39b3e68a3c996746205335b75afdf4cd2bb Mon Sep 17 00:00:00 2001 From: kpcyrd Date: Tue, 2 Jul 2024 01:34:49 +0200 Subject: [PATCH 03/15] Update tokio-rustls dependency --- Cargo.toml | 6 +-- src/client/conn.rs | 121 ++++++++++++++++++++++++++++++--------------- src/error.rs | 2 +- 3 files changed, 84 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6d20f66d..14a74b0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,10 +66,10 @@ tokio-socks = { version = "0.5.1", optional = true } # Feature - TLS native-tls = { version = "0.2.11", optional = true } -tokio-rustls = { version = "0.24.0", features = ["dangerous_configuration"], optional = true } -rustls-pemfile = { version = "1.0.2", optional = true } +tokio-rustls = { version = "0.26.0", optional = true } +rustls-pemfile = { version = "2", optional = true } tokio-native-tls = { version = "0.3.1", optional = true } -webpki-roots = { version = "0.23.0", optional = true } +webpki-roots = { version = "0.26.0", optional = true } [dev-dependencies] diff --git a/src/client/conn.rs b/src/client/conn.rs index a90f2b83..d5b12da6 100644 --- a/src/client/conn.rs +++ b/src/client/conn.rs @@ -3,6 +3,7 @@ use futures_util::{sink::Sink, stream::Stream}; use pin_project::pin_project; use std::{ fmt, + iter::FromIterator, pin::Pin, task::{Context, Poll}, }; @@ -25,8 +26,6 @@ use native_tls::{Certificate, Identity, TlsConnector}; #[cfg(all(feature = "tls-native", not(feature = "tls-rust")))] use tokio_native_tls::{self, TlsStream}; -#[cfg(feature = "tls-rust")] -use rustls_pemfile::certs; #[cfg(feature = "tls-rust")] use std::{ convert::TryFrom, @@ -38,10 +37,11 @@ use std::{ use tokio_rustls::client::TlsStream; #[cfg(feature = "tls-rust")] use tokio_rustls::{ - rustls::client::{ServerCertVerified, ServerCertVerifier}, - rustls::{ - self, Certificate, ClientConfig, OwnedTrustAnchor, PrivateKey, RootCertStore, ServerName, + rustls::client::danger::{ServerCertVerified, ServerCertVerifier}, + rustls::pki_types::{ + CertificateDer as Certificate, PrivateKeyDer as PrivateKey, ServerName, UnixTime, }, + rustls::{self, ClientConfig, RootCertStore}, TlsConnector, }; @@ -223,6 +223,7 @@ impl Connection { config: &Config, tx: UnboundedSender, ) -> error::Result>> { + #[derive(Debug)] struct DangerousAcceptAllVerifier; impl ServerCertVerifier for DangerousAcceptAllVerifier { @@ -231,31 +232,71 @@ impl Connection { _: &Certificate, _: &[Certificate], _: &ServerName, - _: &mut dyn Iterator, _: &[u8], - _: std::time::SystemTime, + _: UnixTime, ) -> Result { return Ok(ServerCertVerified::assertion()); } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &Certificate<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result + { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &Certificate<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result + { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + vec![ + rustls::SignatureScheme::RSA_PKCS1_SHA1, + rustls::SignatureScheme::ECDSA_SHA1_Legacy, + rustls::SignatureScheme::RSA_PKCS1_SHA256, + rustls::SignatureScheme::ECDSA_NISTP256_SHA256, + rustls::SignatureScheme::RSA_PKCS1_SHA384, + rustls::SignatureScheme::ECDSA_NISTP384_SHA384, + rustls::SignatureScheme::RSA_PKCS1_SHA512, + rustls::SignatureScheme::ECDSA_NISTP521_SHA512, + rustls::SignatureScheme::RSA_PSS_SHA256, + rustls::SignatureScheme::RSA_PSS_SHA384, + rustls::SignatureScheme::RSA_PSS_SHA512, + rustls::SignatureScheme::ED25519, + rustls::SignatureScheme::ED448, + ] + } } enum ClientAuth { - SingleCert(Vec, PrivateKey), + SingleCert(Vec>, PrivateKey<'static>), NoClientAuth, } let client_auth = if let Some(client_cert_path) = config.client_cert_path() { if let Ok(file) = File::open(client_cert_path) { - let client_cert_data = certs(&mut BufReader::new(file)).map_err(|_| { - error::Error::Io(Error::new(ErrorKind::InvalidInput, "invalid cert")) - })?; + let client_cert_data = + rustls_pemfile::certs(&mut BufReader::new(file)).collect::>()?; - let client_cert_data = client_cert_data - .into_iter() - .map(Certificate) - .collect::>(); - - let client_cert_pass = PrivateKey(Vec::from(config.client_cert_pass())); + let client_cert_pass = config.client_cert_pass(); + let client_cert_pass = rustls_pemfile::private_key( + &mut client_cert_pass.as_bytes(), + )? + .ok_or_else(|| error::Error::InvalidConfig { + path: config.path(), + cause: error::ConfigError::UnknownConfigFormat { + format: "Failed to parse private key".to_string(), + }, + })?; log::info!( "Using {} for client certificate authentication.", @@ -279,7 +320,7 @@ impl Connection { ($builder:expr) => { match client_auth { ClientAuth::SingleCert(data, pass) => { - $builder.with_single_cert(data, pass).map_err(|err| { + $builder.with_client_auth_cert(data, pass).map_err(|err| { error::Error::Io(Error::new(ErrorKind::InvalidInput, err)) })? } @@ -288,35 +329,33 @@ impl Connection { }; } - let builder = ClientConfig::builder() - .with_safe_default_cipher_suites() - .with_safe_default_kx_groups() - .with_safe_default_protocol_versions()?; + let builder = ClientConfig::builder(); let tls_config = if config.dangerously_accept_invalid_certs() { - let builder = - builder.with_custom_certificate_verifier(Arc::new(DangerousAcceptAllVerifier)); + let builder = builder + .dangerous() + .with_custom_certificate_verifier(Arc::new(DangerousAcceptAllVerifier)); make_client_auth!(builder) } else { - let mut root_store = RootCertStore::empty(); - - root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map( - |ta| { - OwnedTrustAnchor::from_subject_spki_name_constraints( - ta.subject, - ta.spki, - ta.name_constraints, - ) - }, - )); + let mut root_store = RootCertStore::from_iter( + webpki_roots::TLS_SERVER_ROOTS + .iter() + .map(|ta| ta.to_owned()), + ); if let Some(cert_path) = config.cert_path() { - if let Ok(data) = std::fs::read(cert_path) { - root_store.add(&Certificate(data)).map_err(|_| { - error::Error::Io(Error::new(ErrorKind::InvalidInput, "invalid cert")) - })?; + if let Ok(file) = File::open(cert_path) { + let certificates = rustls_pemfile::certs(&mut BufReader::new(file)) + .collect::, _>>()?; + let (added, ignored) = root_store.add_parsable_certificates(certificates); + + if ignored > 0 { + log::warn!("Failed to parse some certificates in {}", cert_path); + } - log::info!("Added {} to trusted certificates.", cert_path); + if added > 0 { + log::info!("Added {} to trusted certificates.", cert_path); + } } else { return Err(error::Error::InvalidConfig { path: config.path(), @@ -332,7 +371,7 @@ impl Connection { }; let connector = TlsConnector::from(Arc::new(tls_config)); - let domain = ServerName::try_from(config.server()?)?; + let domain = ServerName::try_from(config.server()?)?.to_owned(); let stream = Self::new_stream(config).await?; let stream = connector.connect(domain, stream).await?; let framed = Framed::new(stream, IrcCodec::new(config.encoding())?); diff --git a/src/error.rs b/src/error.rs index 391340df..21b25b86 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,7 +6,7 @@ use std::sync::mpsc::RecvError; use thiserror::Error; use tokio::sync::mpsc::error::{SendError, TrySendError}; #[cfg(feature = "tls-rust")] -use tokio_rustls::rustls::client::InvalidDnsNameError; +use tokio_rustls::rustls::pki_types::InvalidDnsNameError; use crate::proto::error::{MessageParseError, ProtocolError}; From 62dd1f8c1e6ddf6d453dbbf373369dce13087d07 Mon Sep 17 00:00:00 2001 From: kpcyrd Date: Wed, 3 Jul 2024 01:59:06 +0200 Subject: [PATCH 04/15] Make DangerousAcceptAllVerifier wrap a CryptoProvider Co-authored-by: Daniel McCarney --- src/client/conn.rs | 75 ++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/src/client/conn.rs b/src/client/conn.rs index d5b12da6..cc695d5e 100644 --- a/src/client/conn.rs +++ b/src/client/conn.rs @@ -3,7 +3,6 @@ use futures_util::{sink::Sink, stream::Stream}; use pin_project::pin_project; use std::{ fmt, - iter::FromIterator, pin::Pin, task::{Context, Poll}, }; @@ -38,6 +37,7 @@ use tokio_rustls::client::TlsStream; #[cfg(feature = "tls-rust")] use tokio_rustls::{ rustls::client::danger::{ServerCertVerified, ServerCertVerifier}, + rustls::crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}, rustls::pki_types::{ CertificateDer as Certificate, PrivateKeyDer as PrivateKey, ServerName, UnixTime, }, @@ -224,56 +224,60 @@ impl Connection { tx: UnboundedSender, ) -> error::Result>> { #[derive(Debug)] - struct DangerousAcceptAllVerifier; + struct DangerousAcceptAllVerifier(Arc); + + impl DangerousAcceptAllVerifier { + fn new() -> Self { + DangerousAcceptAllVerifier(CryptoProvider::get_default() + .expect("no process default crypto provider has been set - application must call CryptoProvider::install_default()") + .clone()) + } + } impl ServerCertVerifier for DangerousAcceptAllVerifier { fn verify_server_cert( &self, - _: &Certificate, - _: &[Certificate], - _: &ServerName, - _: &[u8], - _: UnixTime, + _end_entity: &Certificate, + _intermediates: &[Certificate], + _server_name: &ServerName, + _oscp: &[u8], + _now: UnixTime, ) -> Result { return Ok(ServerCertVerified::assertion()); } fn verify_tls12_signature( &self, - _message: &[u8], - _cert: &Certificate<'_>, - _dss: &rustls::DigitallySignedStruct, + message: &[u8], + cert: &Certificate<'_>, + dss: &rustls::DigitallySignedStruct, ) -> Result { - Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + verify_tls12_signature( + message, + cert, + dss, + &self.0.signature_verification_algorithms, + ) } fn verify_tls13_signature( &self, - _message: &[u8], - _cert: &Certificate<'_>, - _dss: &rustls::DigitallySignedStruct, + message: &[u8], + cert: &Certificate<'_>, + dss: &rustls::DigitallySignedStruct, ) -> Result { - Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + verify_tls13_signature( + message, + cert, + dss, + &self.0.signature_verification_algorithms, + ) } fn supported_verify_schemes(&self) -> Vec { - vec![ - rustls::SignatureScheme::RSA_PKCS1_SHA1, - rustls::SignatureScheme::ECDSA_SHA1_Legacy, - rustls::SignatureScheme::RSA_PKCS1_SHA256, - rustls::SignatureScheme::ECDSA_NISTP256_SHA256, - rustls::SignatureScheme::RSA_PKCS1_SHA384, - rustls::SignatureScheme::ECDSA_NISTP384_SHA384, - rustls::SignatureScheme::RSA_PKCS1_SHA512, - rustls::SignatureScheme::ECDSA_NISTP521_SHA512, - rustls::SignatureScheme::RSA_PSS_SHA256, - rustls::SignatureScheme::RSA_PSS_SHA384, - rustls::SignatureScheme::RSA_PSS_SHA512, - rustls::SignatureScheme::ED25519, - rustls::SignatureScheme::ED448, - ] + self.0.signature_verification_algorithms.supported_schemes() } } @@ -334,14 +338,13 @@ impl Connection { let tls_config = if config.dangerously_accept_invalid_certs() { let builder = builder .dangerous() - .with_custom_certificate_verifier(Arc::new(DangerousAcceptAllVerifier)); + .with_custom_certificate_verifier(Arc::new(DangerousAcceptAllVerifier::new())); make_client_auth!(builder) } else { - let mut root_store = RootCertStore::from_iter( - webpki_roots::TLS_SERVER_ROOTS - .iter() - .map(|ta| ta.to_owned()), - ); + let mut root_store = webpki_roots::TLS_SERVER_ROOTS + .iter() + .cloned() + .collect::(); if let Some(cert_path) = config.cert_path() { if let Ok(file) = File::open(cert_path) { From 30cb2fd38b39485e89f41e5ad7336f77328e686a Mon Sep 17 00:00:00 2001 From: Daniel Cheng Date: Tue, 24 Sep 2024 00:58:18 -0700 Subject: [PATCH 05/15] Do not separate mode flags with spaces when stringifying MODE commands RFC2812 expects all mode flags to be in a single parameter, but irc-proto was sending each mode flag as a separate parameter, e.g. MODE user +i +x Instead of: MODE user +i+x --- irc-proto/src/command.rs | 26 +++++++++++++------------- irc-proto/src/mode.rs | 27 ++++++++++++++++++++++----- src/client/mod.rs | 27 ++++++++++++++++++++++++--- 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/irc-proto/src/command.rs b/irc-proto/src/command.rs index 8f96a8bc..098b8781 100644 --- a/irc-proto/src/command.rs +++ b/irc-proto/src/command.rs @@ -227,15 +227,13 @@ impl<'a> From<&'a Command> for String { Command::NICK(ref n) => stringify("NICK", &[n]), Command::USER(ref u, ref m, ref r) => stringify("USER", &[u, m, "*", r]), Command::OPER(ref u, ref p) => stringify("OPER", &[u, p]), - Command::UserMODE(ref u, ref m) => format!( - "MODE {}{}", - u, - m.iter().fold(String::new(), |mut acc, mode| { - acc.push(' '); - acc.push_str(&mode.to_string()); + Command::UserMODE(ref u, ref m) => { + // User modes never have arguments. + m.iter().fold(format!("MODE {u} "), |mut acc, m| { + acc.push_str(&m.flag()); acc }) - ), + } Command::SERVICE(ref nick, ref r0, ref dist, ref typ, ref r1, ref info) => { stringify("SERVICE", &[nick, r0, dist, typ, r1, info]) } @@ -248,15 +246,17 @@ impl<'a> From<&'a Command> for String { Command::JOIN(ref c, None, None) => stringify("JOIN", &[c]), Command::PART(ref c, Some(ref m)) => stringify("PART", &[c, m]), Command::PART(ref c, None) => stringify("PART", &[c]), - Command::ChannelMODE(ref u, ref m) => format!( - "MODE {}{}", - u, - m.iter().fold(String::new(), |mut acc, mode| { + Command::ChannelMODE(ref c, ref m) => { + let cmd = m.iter().fold(format!("MODE {c} "), |mut acc, m| { + acc.push_str(&m.flag()); + acc + }); + m.iter().filter_map(|m| m.arg()).fold(cmd, |mut acc, arg| { acc.push(' '); - acc.push_str(&mode.to_string()); + acc.push_str(arg); acc }) - ), + } Command::TOPIC(ref c, Some(ref t)) => stringify("TOPIC", &[c, t]), Command::TOPIC(ref c, None) => stringify("TOPIC", &[c]), Command::NAMES(Some(ref c), Some(ref t)) => stringify("NAMES", &[c, t]), diff --git a/irc-proto/src/mode.rs b/irc-proto/src/mode.rs index 778e0a76..07ea335a 100644 --- a/irc-proto/src/mode.rs +++ b/irc-proto/src/mode.rs @@ -238,6 +238,24 @@ where pub fn no_prefix(inner: T) -> Mode { Mode::NoPrefix(inner) } + + /// Gets the mode flag associated with this mode with a + or - prefix as needed. + pub fn flag(&self) -> String { + match self { + Mode::Plus(mode, _) => format!("+{}", mode), + Mode::Minus(mode, _) => format!("-{}", mode), + Mode::NoPrefix(mode) => mode.to_string(), + } + } + + /// Gets the arg associated with this mode, if any. Only some channel modes support arguments, + /// e.g. b (ban) or o (oper). + pub fn arg(&self) -> Option<&str> { + match self { + Mode::Plus(_, arg) | Mode::Minus(_, arg) => arg.as_deref(), + _ => None, + } + } } impl fmt::Display for Mode @@ -246,11 +264,10 @@ where { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Mode::Plus(ref mode, Some(ref arg)) => write!(f, "+{} {}", mode, arg), - Mode::Minus(ref mode, Some(ref arg)) => write!(f, "-{} {}", mode, arg), - Mode::Plus(ref mode, None) => write!(f, "+{}", mode), - Mode::Minus(ref mode, None) => write!(f, "-{}", mode), - Mode::NoPrefix(ref mode) => write!(f, "{}", mode), + Mode::Plus(_, Some(ref arg)) | Mode::Minus(_, Some(ref arg)) => { + write!(f, "{} {}", self.flag(), arg) + } + _ => write!(f, "{}", self.flag()), } } } diff --git a/src/client/mod.rs b/src/client/mod.rs index 0fffaee7..8c3f08c6 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1101,7 +1101,7 @@ mod test { error::Error, proto::{ command::Command::{Raw, PRIVMSG}, - ChannelMode, IrcCodec, Mode, + ChannelMode, IrcCodec, Mode, UserMode, }, }; use anyhow::Result; @@ -1807,10 +1807,31 @@ mod test { let mut client = Client::from_config(test_config()).await?; client.send_mode( "#test", - &[Mode::Plus(ChannelMode::Oper, Some("test".to_owned()))], + &[ + Mode::Plus(ChannelMode::Oper, Some("test".to_owned())), + Mode::Minus(ChannelMode::Oper, Some("test2".to_owned())), + ], )?; client.stream()?.collect().await?; - assert_eq!(&get_client_value(client)[..], "MODE #test +o test\r\n"); + assert_eq!( + &get_client_value(client)[..], + "MODE #test +o-o test test2\r\n" + ); + Ok(()) + } + + #[tokio::test] + async fn send_umode() -> Result<()> { + let mut client = Client::from_config(test_config()).await?; + client.send_mode( + "test", + &[ + Mode::Plus(UserMode::Invisible, None), + Mode::Plus(UserMode::MaskedHost, None), + ], + )?; + client.stream()?.collect().await?; + assert_eq!(&get_client_value(client)[..], "MODE test +i+x\r\n"); Ok(()) } From 3f2737caea8bf58edfdc7c383a500c573c743e53 Mon Sep 17 00:00:00 2001 From: kpcyrd Date: Fri, 8 Nov 2024 16:45:01 +0100 Subject: [PATCH 06/15] Fix gating of tokio feature --- irc-proto/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/irc-proto/Cargo.toml b/irc-proto/Cargo.toml index be9c457d..487867b5 100644 --- a/irc-proto/Cargo.toml +++ b/irc-proto/Cargo.toml @@ -15,7 +15,8 @@ categories = ["network-programming"] travis-ci = { repository = "aatxe/irc" } [features] -default = ["bytes", "tokio", "tokio-util"] +default = ["tokio"] +tokio = ["bytes", "dep:tokio", "tokio-util"] [dependencies] encoding = "0.2.33" From 43086054ec40c7a12e16715370162ae8b8599133 Mon Sep 17 00:00:00 2001 From: Steve Fan <29133953+stevefan1999-personal@users.noreply.github.com> Date: Wed, 11 Dec 2024 01:10:51 +0800 Subject: [PATCH 07/15] make encoding optional --- Cargo.toml | 6 +-- irc-proto/Cargo.toml | 2 +- irc-proto/src/line.rs | 92 +++++++++++++++++++++++++++---------------- src/client/conn.rs | 35 ++++++++++------ 4 files changed, 85 insertions(+), 50 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 14a74b0e..47967bd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ "./", "irc-proto/" ] [features] -default = ["ctcp", "tls-native", "channel-lists", "toml_config"] +default = ["ctcp", "tls-native", "channel-lists", "toml_config", "encoding"] ctcp = [] channel-lists = [] @@ -39,11 +39,11 @@ proxy = ["tokio-socks"] tls-native = ["native-tls", "tokio-native-tls"] tls-rust = ["tokio-rustls", "webpki-roots", "rustls-pemfile"] - +encoding = ["dep:encoding", "irc-proto/encoding"] [dependencies] chrono = { version = "0.4.24", default-features = false, features = ["clock", "std"] } -encoding = "0.2.33" +encoding = { version = "0.2.33", optional = true } futures-util = { version = "0.3.30", default-features = false, features = ["alloc", "sink"] } irc-proto = { version = "1.0.0", path = "irc-proto" } log = "0.4.21" diff --git a/irc-proto/Cargo.toml b/irc-proto/Cargo.toml index be9c457d..a6a1c6bc 100644 --- a/irc-proto/Cargo.toml +++ b/irc-proto/Cargo.toml @@ -18,7 +18,7 @@ travis-ci = { repository = "aatxe/irc" } default = ["bytes", "tokio", "tokio-util"] [dependencies] -encoding = "0.2.33" +encoding = { version = "0.2.33", optional = true } thiserror = "1.0.40" bytes = { version = "1.4.0", optional = true } diff --git a/irc-proto/src/line.rs b/irc-proto/src/line.rs index 8c83cdd7..3953fef0 100644 --- a/irc-proto/src/line.rs +++ b/irc-proto/src/line.rs @@ -3,7 +3,9 @@ use std::io; use bytes::BytesMut; +#[cfg(feature = "encoding")] use encoding::label::encoding_from_whatwg_label; +#[cfg(feature = "encoding")] use encoding::{DecoderTrap, EncoderTrap, EncodingRef}; use tokio_util::codec::{Decoder, Encoder}; @@ -11,6 +13,7 @@ use crate::error; /// A line-based codec parameterized by an encoding. pub struct LineCodec { + #[cfg(feature = "encoding")] encoding: EncodingRef, next_index: usize, } @@ -18,18 +21,19 @@ pub struct LineCodec { impl LineCodec { /// Creates a new instance of LineCodec from the specified encoding. pub fn new(label: &str) -> error::Result { - encoding_from_whatwg_label(label) - .map(|enc| LineCodec { - encoding: enc, - next_index: 0, - }) - .ok_or_else(|| { - io::Error::new( - io::ErrorKind::InvalidInput, - &format!("Attempted to use unknown codec {}.", label)[..], - ) - .into() - }) + Ok(LineCodec { + #[cfg(feature = "encoding")] + encoding: match encoding_from_whatwg_label(label) { + Some(x) => x, + None => { + return Err(error::ProtocolError::Io(io::Error::new( + io::ErrorKind::InvalidInput, + &format!("Attempted to use unknown codec {}.", label)[..], + ))); + } + }, + next_index: 0, + }) } } @@ -45,14 +49,29 @@ impl Decoder for LineCodec { // Set the search start index back to 0 since we found a newline. self.next_index = 0; - // Decode the line using the codec's encoding. - match self.encoding.decode(line.as_ref(), DecoderTrap::Replace) { - Ok(data) => Ok(Some(data)), - Err(data) => Err(io::Error::new( - io::ErrorKind::InvalidInput, - &format!("Failed to decode {} as {}.", data, self.encoding.name())[..], - ) - .into()), + #[cfg(feature = "encoding")] + { + // Decode the line using the codec's encoding. + match self.encoding.decode(line.as_ref(), DecoderTrap::Replace) { + Ok(data) => Ok(Some(data)), + Err(data) => Err(io::Error::new( + io::ErrorKind::InvalidInput, + &format!("Failed to decode {} as {}.", data, self.encoding.name())[..], + ) + .into()), + } + } + + #[cfg(not(feature = "encoding"))] + { + match String::from_utf8(line.to_vec()) { + Ok(data) => Ok(Some(data)), + Err(data) => Err(io::Error::new( + io::ErrorKind::InvalidInput, + &format!("Failed to decode {} as UTF-8.", data)[..], + ) + .into()), + } } } else { // Set the search start index to the current length since we know that none of the @@ -67,20 +86,27 @@ impl Encoder for LineCodec { type Error = error::ProtocolError; fn encode(&mut self, msg: String, dst: &mut BytesMut) -> error::Result<()> { - // Encode the message using the codec's encoding. - let data: error::Result> = self - .encoding - .encode(&msg, EncoderTrap::Replace) - .map_err(|data| { - io::Error::new( - io::ErrorKind::InvalidInput, - &format!("Failed to encode {} as {}.", data, self.encoding.name())[..], - ) - .into() - }); + #[cfg(feature = "encoding")] + { + // Encode the message using the codec's encoding. + let data: error::Result> = self + .encoding + .encode(&msg, EncoderTrap::Replace) + .map_err(|data| { + io::Error::new( + io::ErrorKind::InvalidInput, + &format!("Failed to encode {} as {}.", data, self.encoding.name())[..], + ) + .into() + }); + // Write the encoded message to the output buffer. + dst.extend(&data?); + } - // Write the encoded message to the output buffer. - dst.extend(&data?); + #[cfg(not(feature = "encoding"))] + { + dst.extend(msg.into_bytes()); + } Ok(()) } diff --git a/src/client/conn.rs b/src/client/conn.rs index cc695d5e..e0f1c104 100644 --- a/src/client/conn.rs +++ b/src/client/conn.rs @@ -386,22 +386,31 @@ impl Connection { config: &Config, tx: UnboundedSender, ) -> error::Result> { - use encoding::{label::encoding_from_whatwg_label, EncoderTrap}; + let init_str = config.mock_initial_value(); - let encoding = encoding_from_whatwg_label(config.encoding()).ok_or_else(|| { - error::Error::UnknownCodec { - codec: config.encoding().to_owned(), - } - })?; + let initial = { + #[cfg(feature = "encoding")] + { + use encoding::{label::encoding_from_whatwg_label, EncoderTrap}; - let init_str = config.mock_initial_value(); - let initial = encoding - .encode(init_str, EncoderTrap::Replace) - .map_err(|data| error::Error::CodecFailed { - codec: encoding.name(), - data: data.into_owned(), - })?; + let encoding = encoding_from_whatwg_label(config.encoding()).ok_or_else(|| { + error::Error::UnknownCodec { + codec: config.encoding().to_owned(), + } + })?; + encoding + .encode(init_str, EncoderTrap::Replace) + .map_err(|data| error::Error::CodecFailed { + codec: encoding.name(), + data: data.into_owned(), + })? + } + #[cfg(not(feature = "encoding"))] + { + init_str.as_bytes() + } + }; let stream = MockStream::new(&initial); let framed = Framed::new(stream, IrcCodec::new(config.encoding())?); From 111ed0b4b2e072fc83fee65e72485b5522fbd320 Mon Sep 17 00:00:00 2001 From: Zachary Corvidae Date: Sat, 11 Jan 2025 12:17:49 -0800 Subject: [PATCH 08/15] Update README examples Update editions to 2021 and change out failure for anyhow --- README.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d13acc24..c0f5f498 100644 --- a/README.md +++ b/README.md @@ -52,20 +52,18 @@ documentation, and below. The release of v0.14 replaced all existing APIs with one based on async/await. -```rust,no_run,edition2018 -use irc::client::prelude::*; +```rust,no_run,edition2021 use futures::prelude::*; +use irc::client::prelude::*; #[tokio::main] -async fn main() -> Result<(), failure::Error> { - // We can also load the Config at runtime via Config::load("path/to/config.toml") +async fn main() -> Result<(), anyhow::Error> { let config = Config { nickname: Some("the-irc-crate".to_owned()), server: Some("chat.freenode.net".to_owned()), channels: vec!["#test".to_owned()], ..Config::default() }; - let mut client = Client::from_config(config).await?; client.identify()?; @@ -80,19 +78,25 @@ async fn main() -> Result<(), failure::Error> { ``` Example Cargo.toml file: -```rust,no_run,edition2018 +```rust,no_run,edition2021 [package] name = "example" version = "0.1.0" -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0" +futures = "0.3" irc = "1.0.0" -tokio = { version = "1.0.0", features = ["rt", "rt-multi-thread", "macros", "net", "time"] } -futures = "0.3.0" -failure = "0.1.8" +tokio = { version = "1.0", features = [ + "rt", + "rt-multi-thread", + "macros", + "net", + "time", +] } ``` ## Configuring IRC Clients From a2979df520524dbe7c10e3fd1a42d8c4131ae4e6 Mon Sep 17 00:00:00 2001 From: Zachary Corvidae Date: Sat, 11 Jan 2025 12:19:41 -0800 Subject: [PATCH 09/15] Re-add Config::load comment --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c0f5f498..1a6befed 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ use irc::client::prelude::*; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { + // We can also load the Config at runtime via Config::load("path/to/config.toml") let config = Config { nickname: Some("the-irc-crate".to_owned()), server: Some("chat.freenode.net".to_owned()), From e0f6703b9698c98018f9e6609c6045eb8a976016 Mon Sep 17 00:00:00 2001 From: ariel Date: Sun, 19 Jan 2025 17:41:20 -0500 Subject: [PATCH 10/15] Update name in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a6befed..1a34303d 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ tool should make it easier for users to migrate their old configurations to TOML ## Contributing the irc crate is a free, open source library that relies on contributions from its maintainers, -Aaron Weiss ([@aatxe][awe]) and Peter Atashian ([@retep998][bun]), as well as the broader Rust +Ariel Weiss ([@aatxe][awe]) and Peter Atashian ([@retep998][bun]), as well as the broader Rust community. It's licensed under the Mozilla Public License 2.0 whose text can be found in `LICENSE.md`. To foster an inclusive community around the irc crate, we have adopted a Code of Conduct whose text can be found in `CODE_OF_CONDUCT.md`. You can find details about how to From 070b93a3548963ba388962601da537cb9f6e0ad7 Mon Sep 17 00:00:00 2001 From: kpcyrd Date: Fri, 24 Jan 2025 14:15:11 +0100 Subject: [PATCH 11/15] Add native-certs support --- Cargo.toml | 7 ++++--- src/client/conn.rs | 13 +++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 47967bd1..0ea0e1c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ yaml = ["yaml_config"] proxy = ["tokio-socks"] tls-native = ["native-tls", "tokio-native-tls"] -tls-rust = ["tokio-rustls", "webpki-roots", "rustls-pemfile"] +tls-rust = ["rustls-native-certs", "rustls-pemfile", "tokio-rustls", "webpki-roots"] encoding = ["dep:encoding", "irc-proto/encoding"] [dependencies] @@ -66,9 +66,10 @@ tokio-socks = { version = "0.5.1", optional = true } # Feature - TLS native-tls = { version = "0.2.11", optional = true } -tokio-rustls = { version = "0.26.0", optional = true } -rustls-pemfile = { version = "2", optional = true } tokio-native-tls = { version = "0.3.1", optional = true } +rustls-native-certs = { version = "0.8", optional = true } +rustls-pemfile = { version = "2", optional = true } +tokio-rustls = { version = "0.26.0", optional = true } webpki-roots = { version = "0.26.0", optional = true } diff --git a/src/client/conn.rs b/src/client/conn.rs index e0f1c104..7fd2cbbe 100644 --- a/src/client/conn.rs +++ b/src/client/conn.rs @@ -341,10 +341,15 @@ impl Connection { .with_custom_certificate_verifier(Arc::new(DangerousAcceptAllVerifier::new())); make_client_auth!(builder) } else { - let mut root_store = webpki_roots::TLS_SERVER_ROOTS - .iter() - .cloned() - .collect::(); + let mut root_store = RootCertStore::empty(); + + #[cfg(feature = "webpki-roots")] + root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + + let native_certs = rustls_native_certs::load_native_certs(); + for cert in native_certs.certs { + root_store.add(cert.into())?; + } if let Some(cert_path) = config.cert_path() { if let Ok(file) = File::open(cert_path) { From 2135612d8de244d7f17304129b45187c2a8e9fae Mon Sep 17 00:00:00 2001 From: kpcyrd Date: Fri, 24 Jan 2025 14:25:03 +0100 Subject: [PATCH 12/15] Replace serde_derive with serde/derive --- Cargo.toml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 47967bd1..3e779ae6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,9 +28,9 @@ default = ["ctcp", "tls-native", "channel-lists", "toml_config", "encoding"] ctcp = [] channel-lists = [] -json_config = ["serde", "serde/derive", "serde_derive", "serde_json"] -toml_config = ["serde", "serde/derive", "serde_derive", "toml"] -yaml_config = ["serde", "serde/derive", "serde_derive", "serde_yaml"] +json_config = ["serde", "serde_json"] +toml_config = ["serde", "toml"] +yaml_config = ["serde", "serde_yaml"] # Temporary transitionary features json = ["json_config"] yaml = ["yaml_config"] @@ -55,8 +55,7 @@ tokio-stream = "0.1.12" tokio-util = { version = "0.7.7", features = ["codec"] } # Feature - Config -serde = { version = "1.0.160", optional = true } -serde_derive = { version = "1.0.160", optional = true } +serde = { version = "1.0.160", features = ["derive"], optional = true } serde_json = { version = "1.0.95", optional = true } serde_yaml = { version = "0.9.21", optional = true } toml = { version = "0.7.3", optional = true } From b04602dc27f8c969b16d3059286cdb013d0342ca Mon Sep 17 00:00:00 2001 From: ariel weiss Date: Mon, 24 Mar 2025 09:24:19 -0700 Subject: [PATCH 13/15] cargo.toml edits for new release --- Cargo.toml | 31 ++++++++++++++++++++++++------- irc-proto/Cargo.toml | 4 ++-- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b3532f7a..43569519 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "irc" -version = "1.0.0" -authors = ["Aaron Weiss "] +version = "1.1.0" +authors = ["Ariel Weiss "] edition = "2018" rust-version = "1.71" description = "the irc crate – usable, async IRC for Rust" @@ -20,7 +20,7 @@ is-it-maintained-open-issues = { repository = "aatxe/irc" } [workspace] -members = [ "./", "irc-proto/" ] +members = ["./", "irc-proto/"] [features] @@ -38,13 +38,24 @@ yaml = ["yaml_config"] proxy = ["tokio-socks"] tls-native = ["native-tls", "tokio-native-tls"] -tls-rust = ["rustls-native-certs", "rustls-pemfile", "tokio-rustls", "webpki-roots"] +tls-rust = [ + "rustls-native-certs", + "rustls-pemfile", + "tokio-rustls", + "webpki-roots", +] encoding = ["dep:encoding", "irc-proto/encoding"] [dependencies] -chrono = { version = "0.4.24", default-features = false, features = ["clock", "std"] } +chrono = { version = "0.4.24", default-features = false, features = [ + "clock", + "std", +] } encoding = { version = "0.2.33", optional = true } -futures-util = { version = "0.3.30", default-features = false, features = ["alloc", "sink"] } +futures-util = { version = "0.3.30", default-features = false, features = [ + "alloc", + "sink", +] } irc-proto = { version = "1.0.0", path = "irc-proto" } log = "0.4.21" parking_lot = "0.12.1" @@ -78,7 +89,13 @@ args = "2.2.0" env_logger = "0.11.0" futures = "0.3.30" getopts = "0.2.21" -tokio = { version = "1.27.0", features = ["rt", "rt-multi-thread", "macros", "net", "time"] } +tokio = { version = "1.27.0", features = [ + "rt", + "rt-multi-thread", + "macros", + "net", + "time", +] } [[example]] diff --git a/irc-proto/Cargo.toml b/irc-proto/Cargo.toml index 940da754..c95ce15e 100644 --- a/irc-proto/Cargo.toml +++ b/irc-proto/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "irc-proto" -version = "1.0.0" -authors = ["Aaron Weiss "] +version = "1.1.0" +authors = ["Ariel Weiss "] edition = "2018" rust-version = "1.60" description = "The IRC protocol distilled." From d6b80f48ea2fdb36c617f2fd9edcf012b660150d Mon Sep 17 00:00:00 2001 From: ariel weiss Date: Mon, 24 Mar 2025 09:28:11 -0700 Subject: [PATCH 14/15] bump msrv to 1.80 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 43569519..fbea1f38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "irc" version = "1.1.0" authors = ["Ariel Weiss "] edition = "2018" -rust-version = "1.71" +rust-version = "1.80" description = "the irc crate – usable, async IRC for Rust" documentation = "https://docs.rs/irc/" readme = "README.md" From fb6ace269967bb076b3ac6bebc7d6fd06b6caaf3 Mon Sep 17 00:00:00 2001 From: ariel weiss Date: Mon, 24 Mar 2025 09:29:06 -0700 Subject: [PATCH 15/15] updated ci job for msrv --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f6290f4..751e24d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - rust: ["1.71", stable] + rust: ["1.80", stable] steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master