diff --git a/CHANGELOG.md b/CHANGELOG.md index 201e8ffd1..2f1ea5692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,23 @@ # Changelog -## Unreleased - +## 10.0.0 (7 June 2024) + +- string values are `&str`, not `CStr` + - returning `CStr` was ugly but necessary while c-ares made no promises about + the values that it returned + - from its release 1.17.2 c-ares verifies that hostnames are strings, and from + its 1.30.0 it makes the same check for other DNS strings + - therefore this wrapper now prefers to return native rust `&str` + - when using a new enough version of c-ares we trust the c-ares validation, + and make unchecked conversions from C strings to `&str` + - when using older c-ares we make a fallible conversion, and `unwrap()` the + result + - therefore if you are using old c-ares and new rust-c-ares and you are + talking to a server that returns invalid DNS strings: you may see panics + - solve this by using a newer c-ares, or an older rust-c-ares, or by fixing + your DNS server - CAA record value is bytes, not a string +- c-ares 1.30.0 ## 9.2.1 (26 May 2024) diff --git a/Cargo.toml b/Cargo.toml index 07bda8503..9ace100ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "c-ares" license = "MIT" -version = "9.2.1" +version = "10.0.0" authors = ["David Hotham"] description = """ A Rust wrapper for the c-ares library, for asynchronous DNS requests. diff --git a/build.rs b/build.rs index 50caf3598..ba0718473 100644 --- a/build.rs +++ b/build.rs @@ -14,6 +14,11 @@ fn main() { println!("cargo:rustc-cfg=cares1_17"); } + println!("cargo::rustc-check-cfg=cfg(cares1_17_2)"); + if version >= 0x1_11_02 { + println!("cargo:rustc-cfg=cares1_17_2"); + } + println!("cargo::rustc-check-cfg=cfg(cares1_19)"); if version >= 0x1_13_00 { println!("cargo:rustc-cfg=cares1_19"); @@ -43,5 +48,10 @@ fn main() { if version >= 0x1_1d_00 { println!("cargo:rustc-cfg=cares1_29"); } + + println!("cargo::rustc-check-cfg=cfg(cares1_30)"); + if version >= 0x1_1e_00 { + println!("cargo:rustc-cfg=cares1_30"); + } } } diff --git a/c-ares-sys/CHANGELOG.md b/c-ares-sys/CHANGELOG.md index ea692ee79..4ba0f46ea 100644 --- a/c-ares-sys/CHANGELOG.md +++ b/c-ares-sys/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 9.3.0 (07 June 2024) + +- c-ares 1.30.0 + ## 9.2.1 (26 May 2024) - Include the whole API in docs diff --git a/c-ares-sys/Cargo.toml b/c-ares-sys/Cargo.toml index 354eb3cd7..83baa4990 100644 --- a/c-ares-sys/Cargo.toml +++ b/c-ares-sys/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "c-ares-sys" license = "MIT" -version = "9.2.1" +version = "9.3.0" authors = ["David Hotham"] build = "build/main.rs" links = "cares" diff --git a/c-ares-sys/c-ares b/c-ares-sys/c-ares index bf4c5fd22..f90e73304 160000 --- a/c-ares-sys/c-ares +++ b/c-ares-sys/c-ares @@ -1 +1 @@ -Subproject commit bf4c5fd223e71776e3d0c5a6a8d5598ffb82597c +Subproject commit f90e73304a2d9f5c56d2255c19f9efaaa5b2c16c diff --git a/c-ares-sys/ffi.patch b/c-ares-sys/ffi.patch index e30c3d54e..5036a4fa5 100644 --- a/c-ares-sys/ffi.patch +++ b/c-ares-sys/ffi.patch @@ -1,5 +1,5 @@ ---- src/ffi.rs.orig 2024-05-22 18:19:48.078618559 +0100 -+++ src/ffi.rs 2024-05-22 18:22:28.529371085 +0100 +--- src/ffi.rs.orig 2024-06-07 18:30:42.815463862 +0100 ++++ src/ffi.rs 2024-06-07 18:30:42.835463835 +0100 @@ -1,13 +1,24 @@ /* automatically generated by rust-bindgen 0.69.4 */ +#![allow(non_camel_case_types, non_snake_case)] @@ -51,7 +51,7 @@ pub server_failover_opts: ares_server_failover_options, } #[repr(C)] -@@ -1451,6 +1469,7 @@ +@@ -1471,6 +1489,7 @@ pub struct ares_addrinfo { pub cnames: *mut ares_addrinfo_cname, pub nodes: *mut ares_addrinfo_node, @@ -59,7 +59,7 @@ pub name: *mut ::std::os::raw::c_char, } #[repr(C)] -@@ -1661,3 +1680,17 @@ +@@ -1681,3 +1700,17 @@ #[doc = " Retrieve the total number of active queries pending answers from servers.\n Some c-ares requests may spawn multiple queries, such as ares_getaddrinfo()\n when using AF_UNSPEC, which will be reflected in this number.\n\n \\param[in] channel Initialized ares channel\n \\return Number of active queries to servers"] pub fn ares_queue_active_queries(channel: *const ares_channel_t) -> usize; } diff --git a/c-ares-sys/src/ffi.rs b/c-ares-sys/src/ffi.rs index 5ffad4e5c..c4ff85abd 100644 --- a/c-ares-sys/src/ffi.rs +++ b/c-ares-sys/src/ffi.rs @@ -177,6 +177,8 @@ pub enum ares_dns_rec_type_t { ARES_REC_TYPE_MX = 15, #[doc = "< Text strings."] ARES_REC_TYPE_TXT = 16, + #[doc = "< RFC 2535 / RFC 2931. SIG Record"] + ARES_REC_TYPE_SIG = 24, #[doc = "< RFC 3596. Ip6 Address."] ARES_REC_TYPE_AAAA = 28, #[doc = "< RFC 2782. Server Selection."] @@ -366,6 +368,24 @@ pub enum ares_dns_rr_key_t { ARES_RR_MX_EXCHANGE = 1502, #[doc = " TXT Record. Data. Datatype: BINP"] ARES_RR_TXT_DATA = 1601, + #[doc = " SIG Record. Type Covered. Datatype: U16"] + ARES_RR_SIG_TYPE_COVERED = 2401, + #[doc = " SIG Record. Algorithm. Datatype: U8"] + ARES_RR_SIG_ALGORITHM = 2402, + #[doc = " SIG Record. Labels. Datatype: U8"] + ARES_RR_SIG_LABELS = 2403, + #[doc = " SIG Record. Original TTL. Datatype: U32"] + ARES_RR_SIG_ORIGINAL_TTL = 2404, + #[doc = " SIG Record. Signature Expiration. Datatype: U32"] + ARES_RR_SIG_EXPIRATION = 2405, + #[doc = " SIG Record. Signature Inception. Datatype: U32"] + ARES_RR_SIG_INCEPTION = 2406, + #[doc = " SIG Record. Key Tag. Datatype: U16"] + ARES_RR_SIG_KEY_TAG = 2407, + #[doc = " SIG Record. Signers Name. Datatype: NAME"] + ARES_RR_SIG_SIGNERS_NAME = 2408, + #[doc = " SIG Record. Signature. Datatype: BIN"] + ARES_RR_SIG_SIGNATURE = 2409, #[doc = " AAAA Record. Address. Datatype: INADDR6"] ARES_RR_AAAA_ADDR = 2801, #[doc = " SRV Record. Priority. Datatype: U16"] diff --git a/examples/epoll.rs b/examples/epoll.rs index 59244ae65..959fe99a3 100644 --- a/examples/epoll.rs +++ b/examples/epoll.rs @@ -65,7 +65,7 @@ mod example { for srv_result in srv_results { println!( "host: {} (port: {}), priority: {}, weight: {}", - srv_result.host().to_string_lossy(), + srv_result.host(), srv_result.port(), srv_result.priority(), srv_result.weight() diff --git a/examples/select.rs b/examples/select.rs index 7dd3710a3..092acb423 100644 --- a/examples/select.rs +++ b/examples/select.rs @@ -21,11 +21,8 @@ mod example { } Ok(ref soa_result) => { println!("Successful SOA lookup..."); - println!( - "Name server: {}", - soa_result.name_server().to_string_lossy() - ); - println!("Hostmaster: {}", soa_result.hostmaster().to_string_lossy()); + println!("Name server: {}", soa_result.name_server()); + println!("Hostmaster: {}", soa_result.hostmaster()); println!("Serial: {}", soa_result.serial()); println!("Retry: {}", soa_result.retry()); println!("Expire: {}", soa_result.expire()); @@ -42,10 +39,10 @@ mod example { Ok(ref name_info_result) => { println!("Successful name info lookup..."); if let Some(node) = name_info_result.node() { - println!("Node: {}", node.to_string_lossy()); + println!("Node: {}", node); } if let Some(service) = name_info_result.service() { - println!("Service: {}", service.to_string_lossy()); + println!("Service: {}", service); } } } diff --git a/src/caa.rs b/src/caa.rs index 4d98a48eb..e333b0ce9 100644 --- a/src/caa.rs +++ b/src/caa.rs @@ -1,4 +1,3 @@ -use std::ffi::CStr; use std::marker::PhantomData; use std::os::raw::{c_int, c_uchar, c_void}; use std::{fmt, ptr, slice, str}; @@ -7,6 +6,7 @@ use itertools::Itertools; use crate::error::{Error, Result}; use crate::panic; +use crate::utils::dns_string_as_str; /// The result of a successful CAA lookup. #[derive(Debug)] @@ -103,12 +103,8 @@ impl<'a> CAAResult<'a> { } /// The property represented by this `CAAResult`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn property(self) -> &'a CStr { - unsafe { CStr::from_ptr(self.caa_reply.property.cast()) } + pub fn property(self) -> &'a str { + unsafe { dns_string_as_str(self.caa_reply.property.cast()) } } /// The value represented by this `CAAResult`. @@ -120,11 +116,7 @@ impl<'a> CAAResult<'a> { impl<'a> fmt::Display for CAAResult<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "Critical: {}, ", self.critical())?; - write!( - fmt, - "Property: {}, ", - self.property().to_str().unwrap_or("") - )?; + write!(fmt, "Property: {}, ", self.property())?; let value = str::from_utf8(self.value()).unwrap_or(""); write!(fmt, "Value: {}", value) } diff --git a/src/channel.rs b/src/channel.rs index fefe62d08..aa2eacb3f 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -1,5 +1,4 @@ -#[allow(unused_imports)] -use std::ffi::{CStr, CString}; +use std::ffi::CString; use std::marker::PhantomData; use std::mem; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; @@ -30,8 +29,10 @@ use crate::string::AresString; use crate::txt::{query_txt_callback, TXTResults}; use crate::types::{AddressFamily, DnsClass, QueryType, Socket}; use crate::uri::{query_uri_callback, URIResults}; +#[allow(unused_imports)] use crate::utils::{ - ipv4_as_in_addr, ipv6_as_in6_addr, socket_addrv4_as_sockaddr_in, socket_addrv6_as_sockaddr_in6, + c_string_as_str_unchecked, ipv4_as_in_addr, ipv6_as_in6_addr, socket_addrv4_as_sockaddr_in, + socket_addrv6_as_sockaddr_in6, }; use crate::Flags; #[cfg(cares1_29)] @@ -1160,8 +1161,7 @@ unsafe extern "C" fn server_state_callback( F: FnMut(&str, bool, ServerStateFlags) + Send + 'static, { let handler = data.cast::(); - let c_str = CStr::from_ptr(server_string); - let server = c_str.to_str().unwrap(); + let server = c_string_as_str_unchecked(server_string); panic::catch(|| { (*handler)( server, diff --git a/src/cname.rs b/src/cname.rs index fbb079239..dda09a779 100644 --- a/src/cname.rs +++ b/src/cname.rs @@ -1,4 +1,3 @@ -use std::ffi::CStr; use std::fmt; use std::os::raw::{c_int, c_uchar, c_void}; use std::ptr; @@ -42,11 +41,7 @@ impl CNameResults { } /// Returns the hostname from this `CNameResults`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn hostname(&self) -> &CStr { + pub fn hostname(&self) -> &str { self.hostent.hostname() } diff --git a/src/error.rs b/src/error.rs index 0af2c8a39..eb2fb164e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,9 +1,9 @@ use std::error; -use std::ffi::CStr; use std::fmt; use std::os::raw::c_int; use std::result; -use std::str; + +use crate::utils::c_string_as_str_unchecked; /// Error codes that the library might return. #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord)] @@ -96,8 +96,7 @@ impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { let text = unsafe { let ptr = c_ares_sys::ares_strerror(*self as c_int); - let buf = CStr::from_ptr(ptr).to_bytes(); - str::from_utf8_unchecked(buf) + c_string_as_str_unchecked(ptr) }; fmt.write_str(text) } diff --git a/src/host.rs b/src/host.rs index fc5c94fdb..c569476f7 100644 --- a/src/host.rs +++ b/src/host.rs @@ -1,4 +1,3 @@ -use std::ffi::CStr; use std::fmt; use std::os::raw::{c_int, c_void}; @@ -20,11 +19,7 @@ impl<'a> HostResults<'a> { } /// Returns the hostname from this `HostResults`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn hostname(self) -> &'a CStr { + pub fn hostname(self) -> &'a str { self.hostent.hostname() } diff --git a/src/hostent.rs b/src/hostent.rs index 6d250ff28..420117463 100644 --- a/src/hostent.rs +++ b/src/hostent.rs @@ -1,4 +1,3 @@ -use std::ffi::CStr; use std::marker::PhantomData; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::os::raw::c_char; @@ -7,10 +6,10 @@ use std::{fmt, ptr, slice}; use itertools::Itertools; use crate::types::AddressFamily; -use crate::utils::address_family; +use crate::utils::{address_family, hostname_as_str}; -fn hostname(hostent: &c_types::hostent) -> &CStr { - unsafe { CStr::from_ptr(hostent.h_name.cast()) } +fn hostname(hostent: &c_types::hostent) -> &str { + unsafe { hostname_as_str(hostent.h_name.cast()) } } fn addresses(hostent: &c_types::hostent) -> HostAddressResultsIter { @@ -28,23 +27,17 @@ fn aliases(hostent: &c_types::hostent) -> HostAliasResultsIter { } fn display(hostent: &c_types::hostent, fmt: &mut fmt::Formatter) -> fmt::Result { - write!( - fmt, - "Hostname: {}, ", - hostname(hostent).to_str().unwrap_or("") - )?; + write!(fmt, "Hostname: {}, ", hostname(hostent))?; let addresses = addresses(hostent).format(", "); write!(fmt, "Addresses: [{addresses}], ")?; - let aliases = aliases(hostent) - .map(|cstr| cstr.to_str().unwrap_or("")) - .format(", "); + let aliases = aliases(hostent).format(", "); write!(fmt, "Aliases: [{aliases}]") } pub trait HasHostent<'a>: Sized { fn hostent(self) -> &'a c_types::hostent; - fn hostname(self) -> &'a CStr { + fn hostname(self) -> &'a str { let hostent = self.hostent(); hostname(hostent) } @@ -171,28 +164,22 @@ impl<'a> Iterator for HostAddressResultsIter<'a> { unsafe impl<'a> Send for HostAddressResultsIter<'a> {} unsafe impl<'a> Sync for HostAddressResultsIter<'a> {} -/// Iterator of `&'a CStr`s. -/// -/// Each item is very likely to be a valid UTF-8 string, but the underlying `c-ares` library does -/// not guarantee this - so we leave it to users to decide whether they prefer a fallible -/// conversion, a lossy conversion, or something else altogether. +/// Iterator of `&'a str`s. #[derive(Clone, Copy, Debug)] pub struct HostAliasResultsIter<'a> { next: &'a *const c_char, } impl<'a> Iterator for HostAliasResultsIter<'a> { - type Item = &'a CStr; + type Item = &'a str; fn next(&mut self) -> Option { let h_alias = *self.next; if h_alias.is_null() { None } else { - unsafe { - self.next = &*ptr::from_ref(self.next).offset(1); - let c_str = CStr::from_ptr(h_alias); - Some(c_str) - } + self.next = unsafe { &*ptr::from_ref(self.next).offset(1) }; + let string = unsafe { hostname_as_str(h_alias) }; + Some(string) } } } diff --git a/src/mx.rs b/src/mx.rs index 8152710da..42c1d4453 100644 --- a/src/mx.rs +++ b/src/mx.rs @@ -1,4 +1,3 @@ -use std::ffi::CStr; use std::fmt; use std::marker::PhantomData; use std::os::raw::{c_int, c_uchar, c_void}; @@ -9,6 +8,7 @@ use itertools::Itertools; use crate::error::{Error, Result}; use crate::panic; +use crate::utils::hostname_as_str; /// The result of a successful MX lookup. #[derive(Debug)] @@ -99,12 +99,8 @@ unsafe impl<'a> Sync for MXResultsIter<'a> {} impl<'a> MXResult<'a> { /// Returns the hostname in this `MXResult`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn host(self) -> &'a CStr { - unsafe { CStr::from_ptr(self.mx_reply.host) } + pub fn host(self) -> &'a str { + unsafe { hostname_as_str(self.mx_reply.host) } } /// Returns the priority from this `MXResult`. @@ -115,11 +111,7 @@ impl<'a> MXResult<'a> { impl<'a> fmt::Display for MXResult<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!( - fmt, - "Hostname: {}, ", - self.host().to_str().unwrap_or("") - )?; + write!(fmt, "Hostname: {}, ", self.host())?; write!(fmt, "Priority: {}", self.priority()) } } diff --git a/src/nameinfo.rs b/src/nameinfo.rs index 0005d4c2b..6db96b8e3 100644 --- a/src/nameinfo.rs +++ b/src/nameinfo.rs @@ -1,9 +1,9 @@ -use std::ffi::CStr; -use std::fmt; use std::os::raw::{c_char, c_int, c_void}; +use std::{fmt, str}; use crate::error::{Error, Result}; use crate::panic; +use crate::utils::{c_string_as_str_unchecked, hostname_as_str}; /// The result of a successful name-info lookup. #[derive(Clone, Copy, Debug)] @@ -18,35 +18,22 @@ impl<'a> NameInfoResult<'a> { } /// Returns the node from this `NameInfoResult`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn node(&self) -> Option<&CStr> { - self.node.map(|string| unsafe { CStr::from_ptr(string) }) + pub fn node(&self) -> Option<&str> { + self.node.map(|string| unsafe { hostname_as_str(string) }) } /// Returns the service from this `NameInfoResult`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn service(&self) -> Option<&CStr> { - self.service.map(|string| unsafe { CStr::from_ptr(string) }) + pub fn service(&self) -> Option<&str> { + self.service + .map(|string| unsafe { c_string_as_str_unchecked(string) }) } } impl<'a> fmt::Display for NameInfoResult<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - let node = self - .node() - .map(|cstr| cstr.to_str().unwrap_or("")) - .unwrap_or(""); + let node = self.node().unwrap_or(""); write!(fmt, "Node: {node}, ")?; - let service = self - .service() - .map(|cstr| cstr.to_str().unwrap_or("")) - .unwrap_or(""); + let service = self.service().unwrap_or(""); write!(fmt, "Service: {service}") } } diff --git a/src/naptr.rs b/src/naptr.rs index 2fe5cad5b..2748a2935 100644 --- a/src/naptr.rs +++ b/src/naptr.rs @@ -1,4 +1,3 @@ -use std::ffi::CStr; use std::fmt; use std::marker::PhantomData; use std::os::raw::{c_int, c_uchar, c_void}; @@ -9,6 +8,7 @@ use itertools::Itertools; use crate::error::{Error, Result}; use crate::panic; +use crate::utils::{dns_string_as_str, hostname_as_str}; /// The result of a successful NAPTR lookup. #[derive(Debug)] @@ -99,39 +99,23 @@ unsafe impl<'a> Sync for NAPTRResultsIter<'a> {} impl<'a> NAPTRResult<'a> { /// Returns the flags in this `NAPTRResult`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn flags(self) -> &'a CStr { - unsafe { CStr::from_ptr(self.naptr_reply.flags.cast()) } + pub fn flags(self) -> &'a str { + unsafe { dns_string_as_str(self.naptr_reply.flags.cast()) } } /// Returns the service name in this `NAPTRResult`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn service_name(self) -> &'a CStr { - unsafe { CStr::from_ptr(self.naptr_reply.service.cast()) } + pub fn service_name(self) -> &'a str { + unsafe { dns_string_as_str(self.naptr_reply.service.cast()) } } /// Returns the regular expression in this `NAPTRResult`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn reg_exp(self) -> &'a CStr { - unsafe { CStr::from_ptr(self.naptr_reply.regexp.cast()) } + pub fn reg_exp(self) -> &'a str { + unsafe { dns_string_as_str(self.naptr_reply.regexp.cast()) } } /// Returns the replacement pattern in this `NAPTRResult`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn replacement_pattern(self) -> &'a CStr { - unsafe { CStr::from_ptr(self.naptr_reply.replacement) } + pub fn replacement_pattern(self) -> &'a str { + unsafe { hostname_as_str(self.naptr_reply.replacement) } } /// Returns the order value in this `NAPTRResult`. @@ -147,26 +131,10 @@ impl<'a> NAPTRResult<'a> { impl<'a> fmt::Display for NAPTRResult<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!( - fmt, - "Flags: {}, ", - self.flags().to_str().unwrap_or("") - )?; - write!( - fmt, - "Service name: {}, ", - self.service_name().to_str().unwrap_or("") - )?; - write!( - fmt, - "Regular expression: {}, ", - self.reg_exp().to_str().unwrap_or("") - )?; - write!( - fmt, - "Replacement pattern: {}, ", - self.replacement_pattern().to_str().unwrap_or("") - )?; + write!(fmt, "Flags: {}, ", self.flags())?; + write!(fmt, "Service name: {}, ", self.service_name())?; + write!(fmt, "Regular expression: {}, ", self.reg_exp())?; + write!(fmt, "Replacement pattern: {}, ", self.replacement_pattern())?; write!(fmt, "Order: {}, ", self.order())?; write!(fmt, "Preference: {}", self.preference()) } diff --git a/src/ns.rs b/src/ns.rs index 741171576..a0de2a839 100644 --- a/src/ns.rs +++ b/src/ns.rs @@ -1,4 +1,3 @@ -use std::ffi::CStr; use std::fmt; use std::os::raw::{c_int, c_uchar, c_void}; use std::ptr; @@ -38,11 +37,7 @@ impl NSResults { } /// Returns the hostname from this `NSResults`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn hostname(&self) -> &CStr { + pub fn hostname(&self) -> &str { self.hostent.hostname() } @@ -54,15 +49,8 @@ impl NSResults { impl fmt::Display for NSResults { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!( - fmt, - "Hostname: {}, ", - self.hostname().to_str().unwrap_or("") - )?; - let aliases = self - .aliases() - .map(|cstr| cstr.to_str().unwrap_or("")) - .format(", "); + write!(fmt, "Hostname: {}, ", self.hostname())?; + let aliases = self.aliases().format(", "); write!(fmt, "Aliases: [{aliases}]") } } diff --git a/src/ptr.rs b/src/ptr.rs index dc547d204..ca443a6e1 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -1,4 +1,3 @@ -use std::ffi::CStr; use std::fmt; use std::os::raw::{c_int, c_uchar, c_void}; use std::ptr; @@ -47,11 +46,7 @@ impl PTRResults { } /// Returns the hostname from this `PTRResults`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn hostname(&self) -> &CStr { + pub fn hostname(&self) -> &str { self.hostent.hostname() } @@ -63,15 +58,8 @@ impl PTRResults { impl fmt::Display for PTRResults { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!( - fmt, - "Hostname: {}, ", - self.hostname().to_str().unwrap_or("") - )?; - let aliases = self - .aliases() - .map(|cstr| cstr.to_str().unwrap_or("")) - .format(", "); + write!(fmt, "Hostname: {}, ", self.hostname())?; + let aliases = self.aliases().format(", "); write!(fmt, "Aliases: [{aliases}]") } } diff --git a/src/soa.rs b/src/soa.rs index 089215c56..8e321ec51 100644 --- a/src/soa.rs +++ b/src/soa.rs @@ -1,4 +1,3 @@ -use std::ffi::CStr; use std::fmt; use std::marker::PhantomData; use std::os::raw::{c_int, c_uchar, c_void}; @@ -7,6 +6,7 @@ use std::slice; use crate::error::{Error, Result}; use crate::panic; +use crate::utils::hostname_as_str; /// The result of a successful SOA lookup. #[derive(Debug)] @@ -38,21 +38,13 @@ impl SOAResult { } /// Returns the name server from this `SOAResult`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn name_server(&self) -> &CStr { - unsafe { CStr::from_ptr((*self.soa_reply).nsname) } + pub fn name_server(&self) -> &str { + unsafe { hostname_as_str((*self.soa_reply).nsname) } } /// Returns the hostmaster from this `SOAResult`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn hostmaster(&self) -> &CStr { - unsafe { CStr::from_ptr((*self.soa_reply).hostmaster) } + pub fn hostmaster(&self) -> &str { + unsafe { hostname_as_str((*self.soa_reply).hostmaster) } } /// Returns the serial number from this `SOAResult`. @@ -83,16 +75,8 @@ impl SOAResult { impl fmt::Display for SOAResult { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!( - fmt, - "Name server: {}, ", - self.name_server().to_str().unwrap_or("") - )?; - write!( - fmt, - "Hostmaster: {}, ", - self.hostmaster().to_str().unwrap_or("") - )?; + write!(fmt, "Name server: {}, ", self.name_server())?; + write!(fmt, "Hostmaster: {}, ", self.hostmaster())?; write!(fmt, "Serial: {}, ", self.serial())?; write!(fmt, "Refresh: {}, ", self.refresh())?; write!(fmt, "Retry: {}, ", self.retry())?; diff --git a/src/srv.rs b/src/srv.rs index 443663803..3e4b60581 100644 --- a/src/srv.rs +++ b/src/srv.rs @@ -1,4 +1,3 @@ -use std::ffi::CStr; use std::fmt; use std::marker::PhantomData; use std::os::raw::{c_int, c_uchar, c_void}; @@ -9,6 +8,7 @@ use itertools::Itertools; use crate::error::{Error, Result}; use crate::panic; +use crate::utils::hostname_as_str; /// The result of a successful SRV lookup. #[derive(Debug)] @@ -100,12 +100,8 @@ unsafe impl<'a> Sync for SRVResultsIter<'a> {} impl<'a> SRVResult<'a> { /// Returns the hostname in this `SRVResult`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn host(self) -> &'a CStr { - unsafe { CStr::from_ptr(self.srv_reply.host) } + pub fn host(self) -> &'a str { + unsafe { hostname_as_str(self.srv_reply.host) } } /// Returns the weight in this `SRVResult`. @@ -126,11 +122,7 @@ impl<'a> SRVResult<'a> { impl<'a> fmt::Display for SRVResult<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!( - fmt, - "Host: {}, ", - self.host().to_str().unwrap_or("") - )?; + write!(fmt, "Host: {}, ", self.host())?; write!(fmt, "Port: {}, ", self.port())?; write!(fmt, "Priority: {}, ", self.priority())?; write!(fmt, "Weight: {}", self.weight()) diff --git a/src/string.rs b/src/string.rs index 14de76230..7471d3e2b 100644 --- a/src/string.rs +++ b/src/string.rs @@ -1,6 +1,8 @@ -use std::ffi::CStr; use std::ops::Deref; use std::os::raw::c_char; +use std::str; + +use crate::utils::c_string_as_str_unchecked; /// A smart pointer wrapping a string as allocated by c-ares. pub struct AresString { @@ -11,8 +13,7 @@ pub struct AresString { impl AresString { #[allow(dead_code)] pub(crate) fn new(ares_string: *mut c_char) -> Self { - let c_str = unsafe { CStr::from_ptr(ares_string) }; - let rust_str = c_str.to_str().unwrap(); + let rust_str = unsafe { c_string_as_str_unchecked(ares_string) }; AresString { ares_string, rust_str, diff --git a/src/uri.rs b/src/uri.rs index 612641a9b..d86813aec 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -1,4 +1,3 @@ -use std::ffi::CStr; use std::fmt; use std::marker::PhantomData; use std::os::raw::{c_int, c_uchar, c_void}; @@ -9,6 +8,7 @@ use itertools::Itertools; use crate::error::{Error, Result}; use crate::panic; +use crate::utils::dns_string_as_str; /// The result of a successful URI lookup. #[derive(Debug)] @@ -110,12 +110,8 @@ impl<'a> URIResult<'a> { } /// Returns the uri in this `URIResult`. - /// - /// In practice this is very likely to be a valid UTF-8 string, but the underlying `c-ares` - /// library does not guarantee this - so we leave it to users to decide whether they prefer a - /// fallible conversion, a lossy conversion, or something else altogether. - pub fn uri(self) -> &'a CStr { - unsafe { CStr::from_ptr(self.uri_reply.uri) } + pub fn uri(self) -> &'a str { + unsafe { dns_string_as_str(self.uri_reply.uri) } } /// Returns the time-to-live in this `URIResult`. @@ -128,11 +124,7 @@ impl<'a> URIResult<'a> { impl<'a> fmt::Display for URIResult<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!( - fmt, - "URI: {}, ", - self.uri().to_str().unwrap_or("") - )?; + write!(fmt, "URI: {}, ", self.uri())?; write!(fmt, "Priority: {}, ", self.priority())?; write!(fmt, "Weight: {}, ", self.weight())?; write!(fmt, "TTL: {}", self.ttl()) diff --git a/src/utils.rs b/src/utils.rs index 494ffa5c1..f51a9cd4e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,5 @@ use crate::types::AddressFamily; -use std::ffi::CStr; +use std::ffi::{c_char, CStr}; use std::mem; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6}; use std::os::raw::c_int; @@ -155,6 +155,37 @@ pub fn socket_addrv6_as_sockaddr_in6(sock_v6: &SocketAddrV6) -> c_types::sockadd sockaddr_in6 } +pub unsafe fn c_string_as_str_unchecked<'a>(c_str: *const c_char) -> &'a str { + let bytes = CStr::from_ptr(c_str).to_bytes(); + str::from_utf8_unchecked(bytes) +} + +#[cfg(not(cares1_30))] +pub unsafe fn c_string_as_str_checked<'a>(c_str: *const c_char) -> &'a str { + let c_str = CStr::from_ptr(c_str); + c_str.to_str().unwrap() +} + +#[cfg(not(cares1_17_2))] +pub unsafe fn hostname_as_str<'a>(hostname: *const c_char) -> &'a str { + c_string_as_str_checked(hostname) +} + +#[cfg(cares1_17_2)] +pub unsafe fn hostname_as_str<'a>(hostname: *const c_char) -> &'a str { + c_string_as_str_unchecked(hostname) +} + +#[cfg(not(cares1_30))] +pub unsafe fn dns_string_as_str<'a>(hostname: *const c_char) -> &'a str { + c_string_as_str_checked(hostname) +} + +#[cfg(cares1_30)] +pub unsafe fn dns_string_as_str<'a>(hostname: *const c_char) -> &'a str { + c_string_as_str_unchecked(hostname) +} + /// Get the version number of the underlying `c-ares` library. /// /// The version is returned as both a string and an integer. The integer is built up as 24bit @@ -164,8 +195,7 @@ pub fn version() -> (&'static str, u32) { let mut int_version: c_int = 0; let str_version = unsafe { let ptr = c_ares_sys::ares_version(&mut int_version); - let buf = CStr::from_ptr(ptr).to_bytes(); - str::from_utf8_unchecked(buf) + c_string_as_str_unchecked(ptr) }; (str_version, int_version as u32) }