-
-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Marc-André Lureau <[email protected]>
- Loading branch information
Showing
14 changed files
with
967 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
use std::{borrow::Cow, collections::HashSet, fmt}; | ||
|
||
use crate::{Error, Guid, Result}; | ||
|
||
use super::{ | ||
percent::{decode_percents_str, Encodable}, | ||
transport::Transport, | ||
}; | ||
|
||
#[derive(Debug, PartialEq, Eq)] | ||
pub struct Address<'a> { | ||
pub(super) addr: &'a str, | ||
} | ||
|
||
impl<'a> Address<'a> { | ||
pub fn guid(&self) -> Option<Result<Guid>> { | ||
match self.get_string("guid") { | ||
Some(Ok(v)) => Some(Guid::try_from(v.as_ref())), | ||
Some(Err(e)) => Some(Err(e)), | ||
_ => None, | ||
} | ||
} | ||
|
||
pub fn transport(&self) -> Result<Transport<'_>> { | ||
self.try_into() | ||
} | ||
|
||
fn from_str(addr: &'a str) -> Result<Self> { | ||
let addr = Self { addr }; | ||
addr.transport()?; | ||
let mut set = HashSet::new(); | ||
for (k, _) in addr.key_val_iter() { | ||
if !set.insert(k) { | ||
return Err(Error::Address(format!("Duplicate key: {k}"))); | ||
} | ||
} | ||
Ok(addr) | ||
} | ||
|
||
pub(super) fn key_val_iter(&self) -> KeyValIter<'a> { | ||
let mut split = self.addr.splitn(2, ':'); | ||
// skip transport:.. | ||
split.next(); | ||
let kv = split.next().unwrap_or(&""); | ||
KeyValIter::new(kv) | ||
} | ||
|
||
fn get_string(&self, key: &str) -> Option<Result<Cow<'a, str>>> { | ||
for (k, v) in self.key_val_iter() { | ||
if key == k { | ||
return v.map(decode_percents_str); | ||
} | ||
} | ||
None | ||
} | ||
} | ||
|
||
impl<'a> TryFrom<&'a str> for Address<'a> { | ||
type Error = Error; | ||
|
||
fn try_from(addr: &'a str) -> Result<Self> { | ||
Self::from_str(addr) | ||
} | ||
} | ||
|
||
impl fmt::Display for Address<'_> { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
let kv = KeyValFmt::new().add("guid", self.guid().map(|v| v.ok()).flatten()); | ||
let t = self.transport().map_err(|_| fmt::Error)?; | ||
let kv = t.key_val_fmt_add(kv); | ||
write!(f, "{t}:{kv}")?; | ||
Ok(()) | ||
} | ||
} | ||
|
||
pub(super) struct KeyValIter<'a> { | ||
data: &'a str, | ||
next_index: usize, | ||
} | ||
|
||
impl<'a> KeyValIter<'a> { | ||
fn new(data: &'a str) -> Self { | ||
KeyValIter { | ||
data, | ||
next_index: 0, | ||
} | ||
} | ||
} | ||
|
||
impl<'a> Iterator for KeyValIter<'a> { | ||
type Item = (&'a str, Option<&'a str>); | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
if self.next_index >= self.data.len() { | ||
return None; | ||
} | ||
|
||
let mut pair = &self.data[self.next_index..]; | ||
if let Some(end) = pair.find(',') { | ||
pair = &pair[..end]; | ||
self.next_index += end + 1; | ||
} else { | ||
self.next_index = self.data.len(); | ||
} | ||
let mut split = pair.split('='); | ||
// SAFETY: first split always returns something | ||
let key = split.next().unwrap(); | ||
Some((key, split.next())) | ||
} | ||
} | ||
|
||
pub(crate) trait KeyValFmtAdd { | ||
fn key_val_fmt_add<'a: 'b, 'b>(&'a self, kv: KeyValFmt<'b>) -> KeyValFmt<'b>; | ||
} | ||
|
||
pub(crate) struct KeyValFmt<'a> { | ||
fields: Vec<(&'a str, Box<dyn Encodable + 'a>)>, | ||
} | ||
|
||
impl<'a> KeyValFmt<'a> { | ||
fn new() -> Self { | ||
Self { fields: vec![] } | ||
} | ||
|
||
pub(crate) fn add<T: Encodable + 'a>(mut self, key: &'a str, val: Option<T>) -> Self { | ||
if let Some(val) = val { | ||
self.fields.push((key, Box::new(val))); | ||
} | ||
self | ||
} | ||
} | ||
|
||
impl<'a> fmt::Display for KeyValFmt<'a> { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
let mut first = true; | ||
for (k, v) in self.fields.iter() { | ||
if !first { | ||
write!(f, ",")?; | ||
} | ||
write!(f, "{k}=")?; | ||
v.encode(f)?; | ||
first = false; | ||
} | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
use std::fmt; | ||
|
||
use crate::{Error, Result}; | ||
|
||
use super::Address; | ||
|
||
pub struct AddressList<'a> { | ||
list: Vec<Address<'a>>, | ||
} | ||
|
||
impl<'a> AddressList<'a> { | ||
pub fn list(&self) -> &[Address<'_>] { | ||
self.list.as_ref() | ||
} | ||
} | ||
|
||
impl<'a> TryFrom<&'a str> for AddressList<'a> { | ||
type Error = Error; | ||
|
||
fn try_from(value: &'a str) -> Result<Self> { | ||
let mut list = vec![]; | ||
for addr in value.split(';') { | ||
list.push(Address::try_from(addr)?); | ||
} | ||
Ok(Self { list }) | ||
} | ||
} | ||
|
||
impl fmt::Display for AddressList<'_> { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
let mut first = true; | ||
for a in self.list() { | ||
if !first { | ||
write!(f, ";")?; | ||
} | ||
write!(f, "{}", a)?; | ||
first = false; | ||
} | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
//! D-Bus address handling. | ||
//! | ||
//! Server addresses consist of a transport name followed by a colon, and then an optional, | ||
//! comma-separated list of keys and values in the form key=value. | ||
//! | ||
//! See also: | ||
//! | ||
//! * [Server addresses] in the D-Bus specification. | ||
//! | ||
//! [Server addresses]: https://dbus.freedesktop.org/doc/dbus-specification.html#addresses | ||
|
||
// note: assumes values are utf-8 encoded - this should be clarified in the spec | ||
// otherwise, fail to read them or use lossy representation for display | ||
// | ||
// assumes that empty key=val is accepted, so "transport:,,guid=..." is valid | ||
// | ||
// allows key only, so "transport:foo,bar" is ok | ||
// | ||
// mostly ignores unknown keys and transport | ||
|
||
pub mod transport; | ||
|
||
mod address; | ||
pub use address::Address; | ||
|
||
mod address_list; | ||
pub use address_list::AddressList; | ||
|
||
mod percent; | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use std::{borrow::Cow, ffi::OsStr}; | ||
|
||
use super::{ | ||
transport::{unix::UnixAddrKind, Transport}, | ||
Address, | ||
}; | ||
|
||
#[test] | ||
fn parse_dbus_addresses() { | ||
let addr = | ||
Address::try_from("unix:path=/tmp/dbus-foo,guid=9406e28972c595c590766c9564ce623f") | ||
.unwrap(); | ||
let guid = addr.guid(); | ||
assert!(matches!(guid, Some(Ok(_)))); | ||
let Transport::Unix(u) = addr.transport().unwrap() else { | ||
panic!(); | ||
}; | ||
assert_eq!( | ||
u.kind(), | ||
&UnixAddrKind::Path(Cow::Borrowed(OsStr::new("/tmp/dbus-foo"))) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
use std::{ | ||
borrow::Cow, | ||
ffi::{OsStr, OsString}, | ||
fmt, | ||
str::from_utf8_unchecked, | ||
}; | ||
|
||
use crate::{Error, Result}; | ||
|
||
pub(crate) trait Encodable { | ||
fn encode(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result; | ||
} | ||
|
||
impl<T: ToString> Encodable for T { | ||
fn encode(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { | ||
encode_percents(f, self.to_string().as_bytes()) | ||
} | ||
} | ||
|
||
pub(crate) struct EncData<T: ?Sized>(pub T); | ||
|
||
impl<T: AsRef<[u8]>> Encodable for EncData<T> { | ||
fn encode(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { | ||
encode_percents(f, self.0.as_ref()) | ||
} | ||
} | ||
|
||
pub(crate) struct EncOsStr<T: ?Sized>(pub T); | ||
|
||
impl Encodable for EncOsStr<&Cow<'_, OsStr>> { | ||
fn encode(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { | ||
encode_percents(f, self.0.to_string_lossy().as_bytes()) | ||
} | ||
} | ||
|
||
impl Encodable for EncOsStr<&OsStr> { | ||
fn encode(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { | ||
encode_percents(f, self.0.to_string_lossy().as_bytes()) | ||
} | ||
} | ||
|
||
pub(super) fn encode_percents(f: &mut fmt::Formatter<'_>, mut value: &[u8]) -> std::fmt::Result { | ||
const LOOKUP: &str = "\ | ||
%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f\ | ||
%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f\ | ||
%20%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d%2e%2f\ | ||
%30%31%32%33%34%35%36%37%38%39%3a%3b%3c%3d%3e%3f\ | ||
%40%41%42%43%44%45%46%47%48%49%4a%4b%4c%4d%4e%4f\ | ||
%50%51%52%53%54%55%56%57%58%59%5a%5b%5c%5d%5e%5f\ | ||
%60%61%62%63%64%65%66%67%68%69%6a%6b%6c%6d%6e%6f\ | ||
%70%71%72%73%74%75%76%77%78%79%7a%7b%7c%7d%7e%7f\ | ||
%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f\ | ||
%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f\ | ||
%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af\ | ||
%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf\ | ||
%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf\ | ||
%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df\ | ||
%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef\ | ||
%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff"; | ||
|
||
loop { | ||
let pos = value.iter().position( | ||
|c| !matches!(c, b'-' | b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'_' | b'/' | b'.' | b'\\' | b'*'), | ||
); | ||
|
||
if let Some(pos) = pos { | ||
// SAFETY: The above `position()` call made sure that only ASCII chars are in the string | ||
// up to `pos` | ||
f.write_str(unsafe { from_utf8_unchecked(&value[..pos]) })?; | ||
|
||
let c = value[pos]; | ||
value = &value[pos + 1..]; | ||
|
||
let pos = c as usize * 3; | ||
f.write_str(&LOOKUP[pos..pos + 3])?; | ||
} else { | ||
// SAFETY: The above `position()` call made sure that only ASCII chars are in the rest | ||
// of the string | ||
f.write_str(unsafe { from_utf8_unchecked(value) })?; | ||
return Ok(()); | ||
} | ||
} | ||
} | ||
|
||
fn decode_hex(c: char) -> Result<u8> { | ||
match c { | ||
'0'..='9' => Ok(c as u8 - b'0'), | ||
'a'..='f' => Ok(c as u8 - b'a' + 10), | ||
'A'..='F' => Ok(c as u8 - b'A' + 10), | ||
|
||
_ => Err(Error::Address( | ||
"invalid hexadecimal character in percent-encoded sequence".to_owned(), | ||
)), | ||
} | ||
} | ||
|
||
pub(super) fn decode_percents(value: &str) -> Result<Cow<'_, [u8]>> { | ||
if value.find('%').is_none() { | ||
return Ok(value.as_bytes().into()); | ||
} | ||
|
||
let mut iter = value.chars(); | ||
let mut decoded = Vec::new(); | ||
|
||
while let Some(c) = iter.next() { | ||
if matches!(c, '-' | '0'..='9' | 'A'..='Z' | 'a'..='z' | '_' | '/' | '.' | '\\' | '*') { | ||
decoded.push(c as u8) | ||
} else if c == '%' { | ||
decoded.push( | ||
decode_hex(iter.next().ok_or_else(|| { | ||
Error::Address("incomplete percent-encoded sequence".into()) | ||
})?)? | ||
<< 4 | ||
| decode_hex(iter.next().ok_or_else(|| { | ||
Error::Address("incomplete percent-encoded sequence".into()) | ||
})?)?, | ||
); | ||
} else { | ||
return Err(Error::Address("Invalid character in address".into())); | ||
} | ||
} | ||
|
||
Ok(decoded.into()) | ||
} | ||
|
||
pub(super) fn decode_percents_str(value: &str) -> Result<Cow<'_, str>> { | ||
cow_bytes_to_str(decode_percents(value)?) | ||
} | ||
|
||
fn cow_bytes_to_str<'a>(cow: Cow<'a, [u8]>) -> Result<Cow<'a, str>> { | ||
match cow { | ||
Cow::Borrowed(bytes) => Ok(Cow::Borrowed( | ||
std::str::from_utf8(bytes).map_err(|e| Error::Address(format!("{e}")))?, | ||
)), | ||
Cow::Owned(bytes) => Ok(Cow::Owned( | ||
String::from_utf8(bytes).map_err(|e| Error::Address(format!("{e}")))?, | ||
)), | ||
} | ||
} | ||
|
||
pub(super) fn decode_percents_os_str(value: &str) -> Result<Cow<'_, OsStr>> { | ||
cow_bytes_to_os_str(decode_percents(value)?) | ||
} | ||
|
||
fn cow_bytes_to_os_str<'a>(cow: Cow<'a, [u8]>) -> Result<Cow<'a, OsStr>> { | ||
match cow { | ||
Cow::Borrowed(bytes) => Ok(Cow::Borrowed(OsStr::new( | ||
std::str::from_utf8(bytes).map_err(|e| Error::Address(format!("{e}")))?, | ||
))), | ||
Cow::Owned(bytes) => Ok(Cow::Owned(OsString::from( | ||
String::from_utf8(bytes).map_err(|e| Error::Address(format!("{e}")))?, | ||
))), | ||
} | ||
} |
Oops, something went wrong.