Compare commits

...

6 Commits

Author SHA1 Message Date
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
Jonas Schäfer 37481fb8f6 parsers: do not do extensive XEP-0030 validation with disable-validation
Other additional checks are already gated by the absence of this
feature. As the MR to remove these checks altogether is still blocked,
this should serve as at least as an intermediate solution to anyone
affected by buggy remote implementations.
2024-04-28 10:48:35 +02:00
11 changed files with 109 additions and 50 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

@ -151,22 +151,25 @@ impl TryFrom<Element> for DiscoInfoResult {
}
}
if result.identities.is_empty() {
return Err(Error::ParseError(
"There must be at least one identity in disco#info.",
));
}
if result.features.is_empty() {
return Err(Error::ParseError(
"There must be at least one feature in disco#info.",
));
}
if !result.features.contains(&Feature {
var: ns::DISCO_INFO.to_owned(),
}) {
return Err(Error::ParseError(
"disco#info feature not present in disco#info.",
));
#[cfg(not(feature = "disable-validation"))]
{
if result.identities.is_empty() {
return Err(Error::ParseError(
"There must be at least one identity in disco#info.",
));
}
if result.features.is_empty() {
return Err(Error::ParseError(
"There must be at least one feature in disco#info.",
));
}
if !result.features.contains(&Feature {
var: ns::DISCO_INFO.to_owned(),
}) {
return Err(Error::ParseError(
"disco#info feature not present in disco#info.",
));
}
}
Ok(result)

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"