#![expect(private_interfaces)]
use crate::config::setopt::{EasyHandle, SetOpt, SetOptError};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[cfg(unix)]
pub use unix::*;
pub trait Selector {
#[doc(hidden)]
fn into_interface_string(self, _: Sealed) -> InterfaceString;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Any;
impl Selector for Any {
fn into_interface_string(self, _: Sealed) -> InterfaceString {
InterfaceString(None)
}
}
impl Selector for IpAddr {
fn into_interface_string(self, _: Sealed) -> InterfaceString {
InterfaceString(Some(format!("host!{}", self)))
}
}
impl Selector for Ipv4Addr {
fn into_interface_string(self, _: Sealed) -> InterfaceString {
IpAddr::from(self).into_interface_string(Sealed)
}
}
impl Selector for Ipv6Addr {
fn into_interface_string(self, _: Sealed) -> InterfaceString {
IpAddr::from(self).into_interface_string(Sealed)
}
}
#[derive(Clone, Debug)]
pub(crate) struct InterfaceString(Option<String>);
impl<T: Selector> From<T> for InterfaceString {
fn from(selector: T) -> Self {
selector.into_interface_string(Sealed)
}
}
impl SetOpt for InterfaceString {
fn set_opt(&self, easy: &mut EasyHandle) -> Result<(), SetOptError> {
match self.0.as_ref() {
Some(interface) => easy.interface(interface).map_err(Into::into),
None => unsafe {
match curl_sys::curl_easy_setopt(easy.raw(), curl_sys::CURLOPT_INTERFACE, 0) {
curl_sys::CURLE_OK => Ok(()),
code => Err(curl::Error::new(code).into()),
}
},
}
}
}
struct Sealed;
#[cfg(unix)]
mod unix {
use super::*;
use std::fmt;
pub struct Name<T: AsRef<str>>(pub T);
impl<T: AsRef<str> + Clone> Clone for Name<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T: AsRef<str>> fmt::Debug for Name<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name: &str = self.0.as_ref();
f.debug_tuple("InterfaceName").field(&name).finish()
}
}
impl<T: AsRef<str>> Selector for Name<T> {
fn into_interface_string(self, _: Sealed) -> InterfaceString {
InterfaceString(Some(format!("if!{}", self.0.as_ref())))
}
}
impl<T, U> Selector for (Name<T>, U)
where
T: AsRef<str>,
U: Into<IpAddr> + Selector,
{
fn into_interface_string(self, _: Sealed) -> InterfaceString {
InterfaceString(Some(format!(
"ifhost!{}!{}",
self.0.0.as_ref(),
self.1.into()
)))
}
}
impl<T, U> Selector for (T, Name<U>)
where
T: Into<IpAddr> + Selector,
U: AsRef<str>,
{
fn into_interface_string(self, _: Sealed) -> InterfaceString {
(self.1, self.0).into_interface_string(Sealed)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_any_interface() {
assert!(Any.into_interface_string(Sealed).0.is_none());
}
#[test]
#[cfg(unix)]
fn test_interface_name() {
let selector = Name("eth0");
assert_eq!(selector.into_interface_string(Sealed).0.unwrap(), "if!eth0");
}
#[test]
fn test_interface_ip_addr() {
let selector = Ipv4Addr::new(192, 168, 1, 1);
assert_eq!(
selector.into_interface_string(Sealed).0.unwrap(),
"host!192.168.1.1"
);
}
#[test]
#[cfg(unix)]
fn test_interface_name_and_ip_addr() {
let selector = (Name("eth0"), Ipv4Addr::new(192, 168, 1, 1));
assert_eq!(
selector.into_interface_string(Sealed).0.unwrap(),
"ifhost!eth0!192.168.1.1"
);
}
}