Compare commits

...

7 Commits

Author SHA1 Message Date
Paul Fariello bf7d794bd2 Merge branch 'feat/oob' into 'main'
Add OOB

See merge request xmpp-rs/xmpp-rs!309
2024-05-06 07:22:16 +00:00
Emmanuel Gil Peyrot f725994fe0 Bump all dependencies but rustls and webpki-roots
The latter have changed their API a bit, while everything else is still
compatible.
2024-05-04 12:42:53 +02:00
Emmanuel Gil Peyrot 48f77acb69 jid: Make the crate no_std
Only std::error::Error is missing in this mode, but that’s only waiting
for its stabilisation, see this issue:
https://github.com/rust-lang/rust/issues/103765
2024-05-04 10:28:25 +00:00
Jonas Schäfer fcadccfbab parsers::vcard: allow linebreaks in binval data
[RFC 2426][1] says:

> The binary data MUST be encoded using the "B" encoding format.
> Long lines of encoded binary data SHOULD BE folded to 75 characters
> using the folding method defined in [MIME-DIR].

That implies that whitespace may occur in binval data and we thus must
be able to parse this correctly.

   [1]: https://datatracker.ietf.org/doc/html/rfc2426#section-2.4.1
2024-05-04 10:02:53 +00:00
Astro 6c3081d656 tokio-xmpp: let happy_eyeballs connect to records in parallel 2024-05-02 22:24:54 +02:00
Astro 598ffdbecf tokio-xmpp: set resolve ip_strategy to Ipv4AndIpv6
The happy_eyeballs implementation should try to connect on both address
families. The default of Ipv4thenIpv6 wouldn't query for AAAA if it got
A.
2024-05-02 22:24:54 +02:00
Paul Fariello ce493e82d2 Add OOB 2024-04-17 15:22:31 +02:00
13 changed files with 163 additions and 34 deletions

View File

@ -32,4 +32,6 @@ serde_test = "1"
jid = { path = ".", features = [ "serde" ] }
[features]
default = ["std"]
std = []
quote = ["dep:quote", "dep:proc-macro2"]

View File

@ -8,8 +8,9 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use core::fmt;
#[cfg(feature = "std")]
use std::error::Error as StdError;
use std::fmt;
/// An error that signifies that a `Jid` cannot be parsed from a string.
#[derive(Debug, PartialEq, Eq)]
@ -49,6 +50,7 @@ pub enum Error {
ResourceInBareJid,
}
#[cfg(feature = "std")]
impl StdError for Error {}
impl fmt::Display for Error {

View File

@ -8,6 +8,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#![no_std]
#![deny(missing_docs)]
//! Represents XMPP addresses, also known as JabberIDs (JIDs) for the [XMPP](https://xmpp.org/)
@ -30,13 +31,22 @@
//! - stringprep error: some characters were invalid according to the stringprep algorithm, such as
//! mixing left-to-write and right-to-left characters
extern crate alloc;
#[cfg(any(feature = "std", test))]
extern crate std;
use alloc::borrow::Cow;
use alloc::format;
use alloc::string::{String, ToString};
use core::borrow::Borrow;
use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::mem;
use core::num::NonZeroU16;
use std::borrow::{Borrow, Cow};
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::str::FromStr;
use core::ops::Deref;
use core::str::FromStr;
use memchr::memchr;
@ -364,13 +374,13 @@ impl Jid {
Ok(unsafe {
// SAFETY: FullJid is #[repr(transparent)] of Jid
// SOUNDNESS: we asserted that self.slash is set above
std::mem::transmute::<&Jid, &FullJid>(self)
mem::transmute::<&Jid, &FullJid>(self)
})
} else {
Err(unsafe {
// SAFETY: BareJid is #[repr(transparent)] of Jid
// SOUNDNESS: we asserted that self.slash is unset above
std::mem::transmute::<&Jid, &BareJid>(self)
mem::transmute::<&Jid, &BareJid>(self)
})
}
}
@ -383,13 +393,13 @@ impl Jid {
Ok(unsafe {
// SAFETY: FullJid is #[repr(transparent)] of Jid
// SOUNDNESS: we asserted that self.slash is set above
std::mem::transmute::<&mut Jid, &mut FullJid>(self)
mem::transmute::<&mut Jid, &mut FullJid>(self)
})
} else {
Err(unsafe {
// SAFETY: BareJid is #[repr(transparent)] of Jid
// SOUNDNESS: we asserted that self.slash is unset above
std::mem::transmute::<&mut Jid, &mut BareJid>(self)
mem::transmute::<&mut Jid, &mut BareJid>(self)
})
}
}
@ -865,10 +875,11 @@ mod tests {
use super::*;
use std::collections::{HashMap, HashSet};
use std::vec::Vec;
macro_rules! assert_size (
($t:ty, $sz:expr) => (
assert_eq!(::std::mem::size_of::<$t>(), $sz);
assert_eq!(::core::mem::size_of::<$t>(), $sz);
);
);

View File

@ -1,7 +1,10 @@
use std::borrow::{Borrow, Cow};
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
use alloc::borrow::{Cow, ToOwned};
use alloc::string::{String, ToString};
use core::borrow::Borrow;
use core::fmt;
use core::mem;
use core::ops::Deref;
use core::str::FromStr;
use stringprep::{nameprep, nodeprep, resourceprep};
@ -162,7 +165,7 @@ macro_rules! def_part_types {
pub(crate) fn from_str_unchecked(s: &str) -> &Self {
// SAFETY: repr(transparent) thing can be transmuted to/from
// its inner.
unsafe { std::mem::transmute(s) }
unsafe { mem::transmute(s) }
}
/// Access the contents as [`str`] slice.

View File

@ -14,7 +14,7 @@ license = "MPL-2.0"
edition = "2021"
[dependencies]
base64 = "0.21"
base64 = "0.22"
digest = "0.10"
sha1 = "0.10"
sha2 = "0.10"

View File

@ -87,6 +87,9 @@ pub mod rsm;
/// XEP-0060: Publish-Subscribe
pub mod pubsub;
/// XEP-0066: OOB
pub mod oob;
/// XEP-0071: XHTML-IM
pub mod xhtml;

View File

@ -63,6 +63,9 @@ pub const PUBSUB_OWNER: &str = "http://jabber.org/protocol/pubsub#owner";
/// XEP-0060: Publish-Subscribe node configuration
pub const PUBSUB_CONFIGURE: &str = "http://jabber.org/protocol/pubsub#node_config";
/// XEP-0066: Out of Band Data
pub const OOB: &str = "jabber:x:oob";
/// XEP-0071: XHTML-IM
pub const XHTML_IM: &str = "http://jabber.org/protocol/xhtml-im";
/// XEP-0071: XHTML-IM

67
parsers/src/oob.rs Normal file
View File

@ -0,0 +1,67 @@
// Copyright (c) 2024 Paul Fariello <xmpp-parsers@fariello.eu>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::message::MessagePayload;
generate_element!(
/// Defines associated out of band url.
Oob, "x", OOB,
children: [
/// The associated URL.
url: Required<String> = ("url", OOB) => String,
/// An optionnal description of the out of band data.
desc: Option<String> = ("desc", OOB) => String,
]
);
impl MessagePayload for Oob {}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::error::Error;
use crate::Element;
#[cfg(target_pointer_width = "32")]
#[test]
fn test_size() {
assert_size!(Oob, 24);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn test_size() {
assert_size!(Oob, 48);
}
#[test]
fn test_simple() {
let elem: Element = "<x xmlns='jabber:x:oob'><url>http://example.org</url></x>"
.parse()
.unwrap();
Oob::try_from(elem).unwrap();
}
#[test]
fn test_with_desc() {
let elem: Element =
"<x xmlns='jabber:x:oob'><url>http://example.org</url><desc>Example website</desc></x>"
.parse()
.unwrap();
Oob::try_from(elem).unwrap();
}
#[test]
fn test_invalid_child() {
let elem: Element = "<x xmlns='jabber:x:oob'></x>".parse().unwrap();
let error = Oob::try_from(elem).unwrap_err();
let message = match error {
Error::ParseError(string) => string,
_ => panic!(),
};
assert_eq!(message, "Missing child url in x element.");
}
}

View File

@ -14,7 +14,7 @@
//! see [`vcard_update`][crate::vcard_update] module.
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
use crate::util::text_node_codecs::{Base64, Codec, Text};
use crate::util::text_node_codecs::{Codec, Text, WhitespaceAwareBase64};
use crate::{ns, Error};
use minidom::Element;
@ -44,7 +44,7 @@ generate_element!(
Binval, "BINVAL", VCARD,
text: (
/// The actual data.
data: Base64
data: WhitespaceAwareBase64
)
);
@ -131,4 +131,34 @@ mod tests {
assert_eq!(photo.type_.data, "image/jpeg".to_string());
assert_eq!(photo.binval.data, bytes);
}
#[test]
fn test_vcard_with_linebreaks() {
// Test xml stolen from https://xmpp.org/extensions/xep-0153.html#example-5
// extended to use a multi-line base64 string as is allowed as per RFC 2426
let test_vcard = r"<vCard xmlns='vcard-temp'>
<BDAY>1476-06-09</BDAY>
<ADR>
<CTRY>Italy</CTRY>
<LOCALITY>Verona</LOCALITY>
<HOME/>
</ADR>
<NICKNAME/>
<N><GIVEN>Juliet</GIVEN><FAMILY>Capulet</FAMILY></N>
<EMAIL>jcapulet@shakespeare.lit</EMAIL>
<PHOTO>
<TYPE>image/jpeg</TYPE>
<BINVAL>Zm9v
Cg==</BINVAL>
</PHOTO>
</vCard>";
let test_vcard = Element::from_str(&test_vcard).expect("Failed to parse XML");
let test_vcard = VCard::try_from(test_vcard).expect("Failed to parse vCard");
let photo = test_vcard.photo.expect("No photo found");
assert_eq!(photo.type_.data, "image/jpeg".to_string());
assert_eq!(photo.binval.data, b"foo\n");
}
}

View File

@ -20,7 +20,7 @@ scram = ["base64", "getrandom", "sha-1", "sha2", "hmac", "pbkdf2"]
anonymous = ["getrandom"]
[dependencies]
base64 = { version = "0.21", optional = true }
base64 = { version = "0.22", optional = true }
getrandom = { version = "0.2", optional = true }
sha-1 = { version = "0.10", optional = true }
sha2 = { version = "0.10", optional = true }

View File

@ -29,13 +29,13 @@ xmpp-parsers = { version = "0.20", path = "../parsers" }
# these are only needed for starttls ServerConnector support
hickory-resolver = { version = "0.24", optional = true}
idna = { version = "0.4", optional = true}
idna = { version = "0.5", optional = true}
native-tls = { version = "0.2", optional = true }
tokio-native-tls = { version = "0.3", optional = true }
tokio-rustls = { version = "0.24", optional = true }
[dev-dependencies]
env_logger = { version = "0.10", default-features = false, features = ["auto-color", "humantime"] }
env_logger = { version = "0.11", default-features = false, features = ["auto-color", "humantime"] }
# this is needed for echo-component example
tokio-xmpp = { path = ".", features = ["insecure-tcp"]}

View File

@ -1,5 +1,8 @@
use super::error::{ConnectorError, Error};
use hickory_resolver::{IntoName, TokioAsyncResolver};
use futures::{future::select_ok, FutureExt};
use hickory_resolver::{
config::LookupIpStrategy, name_server::TokioConnectionProvider, IntoName, TokioAsyncResolver,
};
use log::debug;
use std::net::SocketAddr;
use tokio::net::TcpStream;
@ -13,19 +16,24 @@ pub async fn connect_to_host(domain: &str, port: u16) -> Result<TcpStream, Error
.map_err(|e| Error::from(crate::Error::Io(e)))?);
}
let resolver = TokioAsyncResolver::tokio_from_system_conf().map_err(ConnectorError::Resolve)?;
let (config, mut options) =
hickory_resolver::system_conf::read_system_conf().map_err(ConnectorError::Resolve)?;
options.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
let resolver = TokioAsyncResolver::new(config, options, TokioConnectionProvider::default());
let ips = resolver
.lookup_ip(ascii_domain)
.await
.map_err(ConnectorError::Resolve)?;
for ip in ips.iter() {
match TcpStream::connect(&SocketAddr::new(ip, port)).await {
Ok(stream) => return Ok(stream),
Err(_) => {}
}
}
Err(crate::Error::Disconnected.into())
// Happy Eyeballs: connect to all records in parallel, return the
// first to succeed
select_ok(
ips.into_iter()
.map(|ip| TcpStream::connect(SocketAddr::new(ip, port)).boxed()),
)
.await
.map(|(result, _)| result)
.map_err(|_| crate::Error::Disconnected.into())
}
pub async fn connect_with_srv(

View File

@ -18,13 +18,13 @@ chrono = "0.4"
futures = "0.3"
tokio = { version = "1", features = ["fs"] }
log = "0.4"
reqwest = { version = "0.11.8", features = ["stream"] }
reqwest = { version = "0.12", features = ["stream"] }
tokio-util = { version = "0.7", features = ["codec"] }
# same repository dependencies
tokio-xmpp = { version = "3.4", path = "../tokio-xmpp", default-features = false }
[dev-dependencies]
env_logger = { version = "0.10", default-features = false, features = ["auto-color", "humantime"] }
env_logger = { version = "0.11", default-features = false, features = ["auto-color", "humantime"] }
[[example]]
name = "hello_bot"