xmpp_addr/lib.rs
1// Copyright 2017 The Mellium Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! Implements the XMPP Address Format as defined in RFC 7622.
10//!
11//! For historical reasons, XMPP addresses are called "Jabber Identifiers", or JIDs.
12//! JIDs are comprised of three parts: an optional localpart (a username or account), the
13//! domainpart (the server), and an optional resourcepart (a specific client) and look more or less
14//! like an email where the first two parts are demarcated by the '@' character but with the
15//! resourcepart added to the end and demarcated by the '/' character, eg:
16//!
17//! > localpart@domainpart/resourcepart
18//!
19//! Like email, JIDs allow routing across networks based on the domainpart, and local routing based
20//! on the localpart. Unlike emails however, JIDs also allow for last-mile-delivery to *specific*
21//! clients (or "resources") using the resourcepart. Also unlike email, JIDs support
22//! internationalization.
23//!
24//! **Note well** that this package currently isn't fully compliant with [RFC 7622]; it does not
25//! perform the PRECIS ([RFC 8264]) enforcement step.
26//!
27//! [RFC 7622]: https://tools.ietf.org/html/rfc7622
28//! [RFC 8264]: https://tools.ietf.org/html/rfc8264
29//!
30//! # Features
31//!
32//! The following feature flag can be used when compiling the crate:
33//!
34//! - `try_from` — build with experimental [`TryFrom`] impls on nightly
35//!
36//! [`TryFrom`]: https://doc.rust-lang.org/std/convert/trait.TryFrom.html
37//!
38//! No features are enabled by default.
39//!
40//! # Examples
41//!
42//! ## From parts (stable)
43//!
44//! ```rust
45//! # use xmpp_addr::Jid;
46//! # fn main() -> Result<(), xmpp_addr::Error>{
47//! let j = Jid::new("feste", "example.net", None)?;
48//! assert_eq!(j, "feste@example.net");
49//! # Ok(())
50//! # }
51//! ```
52//!
53//! ## From parts (nightly)
54//!
55#![cfg_attr(not(feature = "try_from"), doc = " ```rust,ignore")]
56#![cfg_attr(feature = "try_from", doc = " ```rust")]
57//! #![feature(try_from)]
58//! # use std::convert::{ TryInto, TryFrom };
59//! # use xmpp_addr::Jid;
60//! # fn main() -> Result<(), xmpp_addr::Error> {
61//! let j: Jid = ("feste", "example.net").try_into()?;
62//! assert_eq!(j, "feste@example.net");
63//!
64//! let j = Jid::try_from(("feste", "example.net", "avsgasje"))?;
65//! assert_eq!(j, "feste@example.net/avsgasje");
66//! # Ok(())
67//! # }
68//! ```
69//!
70//! ## Parsing (stable)
71//!
72//! ```rust
73//! # use xmpp_addr::Jid;
74//! # fn main() -> Result<(), xmpp_addr::Error> {
75//! let j = Jid::from_str("juliet@example.net/balcony")?;
76//! assert_eq!(j.localpart(), Some("juliet"));
77//! assert_eq!(j.domainpart(), "example.net");
78//! assert_eq!(j.resourcepart(), Some("balcony"));
79//! # Ok(())
80//! # }
81//! ```
82//!
83//! ## Parsing (nightly)
84//!
85#![cfg_attr(not(feature = "try_from"), doc = " ```rust,ignore")]
86#![cfg_attr(feature = "try_from", doc = " ```rust")]
87//! #![feature(try_from)]
88//! # use std::convert::{ TryInto, TryFrom };
89//! # use xmpp_addr::Jid;
90//! # fn main() -> Result<(), xmpp_addr::Error> {
91//! let j: Jid = "orsino@example.net/ilyria".try_into()?;
92//! assert_eq!(j, "orsino@example.net/ilyria");
93//!
94//! let j = Jid::try_from("juliet@example.net/balcony")?;
95//! assert_eq!(j, "juliet@example.net/balcony");
96//! # Ok(())
97//! # }
98//! ```
99
100#![deny(missing_docs)]
101#![cfg_attr(feature = "try_from", feature(try_from))]
102#![doc(html_root_url = "https://docs.rs/xmpp-addr/0.13.1")]
103
104use unicode_normalization::UnicodeNormalization;
105
106use std::borrow;
107use std::cmp;
108use std::convert;
109use std::fmt;
110use std::net;
111use std::result;
112use std::str;
113use std::str::FromStr;
114
115/// Possible error values that can occur when parsing JIDs.
116#[derive(Debug)]
117pub enum Error {
118 /// Returned if an empty string is being parsed.
119 EmptyJid,
120
121 /// Returned if the localpart is empty (eg. "@example.net").
122 EmptyLocal,
123
124 /// Returned if the localpart is longer than 1023 bytes.
125 LongLocal,
126
127 /// Returned if the domain part is too short to be a valid domain, hostname, or IP address.
128 ShortDomain,
129
130 /// Returned if the domain part is too long to be a valid domain.
131 LongDomain,
132
133 /// Returned if the resourcepart is empty (eg. "example.net/"
134 EmptyResource,
135
136 /// Returned if the resourcepart is longer than 1023 bytes.
137 LongResource,
138
139 /// Returned if a forbidden character was found in any part of the JID.
140 ForbiddenChars,
141
142 /// Returned if an error occured while attempting to parse the domainpart of the JID as an IPv6
143 /// address.
144 Addr(net::AddrParseError),
145
146 /// Returned if an error occured while performing IDNA2008 processing on the domainpart of the
147 /// JID.
148 IDNA(idna::uts46::Errors),
149}
150
151/// A custom result type for JIDs that elides the [error type].
152///
153/// [error type]: ./enum.Error.html
154pub type Result<T> = result::Result<T, Error>;
155
156/// A parsed JID.
157#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
158pub struct Jid<'a> {
159 local: Option<borrow::Cow<'a, str>>,
160 domain: borrow::Cow<'a, str>,
161 resource: Option<borrow::Cow<'a, str>>,
162}
163
164impl<'a> Jid<'a> {
165 /// Splits a JID formatted as a string into its localpart, domainpart, and resourcepart.
166 /// The localpart and resourcepart are optional, but the domainpart is always returned.
167 ///
168 /// # Errors
169 ///
170 /// This function performs a "naive" string split and does not perform any validation of the
171 /// individual parts other than to make sure that required parts exist. For example
172 /// "@example.com" will return an error ([`EmptyLocal`]), but "%@example.com" will not (even
173 /// though "%" is not a valid localpart). The length of localparts and resourceparts is also
174 /// not checked (other than if they're empty). This is because when creating an actual JID it
175 /// is possible for certain Unicode characters to be canonicalized into a shorter length
176 /// encoding, meaning that a part that was previously too long may suddenly fit in the maximum
177 /// length. [`ShortDomain`] may be returned because we know that domains will never become
178 /// longer after performing IDNA2008 operations, but [`LongDomain`] may not be returned for the
179 /// same reasons as mentioned above.
180 ///
181 /// Possible errors include:
182 ///
183 /// - [`EmptyJid`] \(eg. `""`)
184 /// - [`EmptyLocal`] \(`"@example.com"`)
185 /// - [`EmptyResource`] \(`"example.com/"`)
186 /// - [`ShortDomain`] \(`"a"`, `"foo@/bar"`)
187 ///
188 /// [error variant]: ./enum.Error.html
189 /// [`EmptyJid`]: ./enum.Error.html#EmptyJid.v
190 /// [`EmptyLocal`]: ./enum.Error.html#EmptyLocal.v
191 /// [`EmptyResource`]: ./enum.Error.html#EmptyResource.v
192 /// [`ShortDomain`]: ./enum.Error.html#ShortDomain.v
193 /// [`LongDomain`]: ./enum.Error.html#LongDomain.v
194 ///
195 /// # Examples
196 ///
197 /// Basic usage:
198 ///
199 /// ```rust
200 /// # use xmpp_addr::Jid;
201 /// # fn main() -> Result<(), xmpp_addr::Error> {
202 /// let (lp, dp, rp) = Jid::split("feste@example.net")?;
203 /// assert_eq!(lp, Some("feste"));
204 /// assert_eq!(dp, "example.net");
205 /// assert_eq!(rp, None);
206 /// # Ok(())
207 /// # }
208 /// ```
209 pub fn split(s: &'a str) -> Result<(Option<&'a str>, &'a str, Option<&'a str>)> {
210 if s == "" {
211 return Err(Error::EmptyJid);
212 }
213
214 // RFC 7622 §3.1. Fundamentals:
215 //
216 // Implementation Note: When dividing a JID into its component parts,
217 // an implementation needs to match the separator characters '@' and
218 // '/' before applying any transformation algorithms, which might
219 // decompose certain Unicode code points to the separator characters.
220 //
221 // so let's do that now. First we'll parse the domainpart using the rules
222 // defined in §3.2:
223 //
224 // The domainpart of a JID is the portion that remains once the
225 // following parsing steps are taken:
226 //
227 // 1. Remove any portion from the first '/' character to the end of the
228 // string (if there is a '/' character present).
229
230 let mut chars = s.char_indices();
231 let sep = chars.find(|&c| match c {
232 (_, '@') | (_, '/') => true,
233 _ => false,
234 });
235
236 let (lpart, dpart, rpart) = match sep {
237 // If there are no part separators at all, the entire string is a domainpart.
238 None => (None, s, None),
239 // There is a resource part, and we did not find a localpart (the first separator found
240 // was the first '/').
241 Some((i, '/')) => (None, &s[0..i], Some(&s[i + 1..])),
242 // The JID ends with the '@' sign
243 Some((i, '@')) if i + 1 == s.len() => return Err(Error::ShortDomain),
244 // We found a local part, so keep searching to try and find a resource part.
245 Some((i, '@')) => {
246 // Continue looking for a '/'.
247 let slash = chars.find(|&c| match c {
248 (_, '/') => true,
249 _ => false,
250 });
251
252 // RFC 7622 §3.3.1 provides a small table of characters which are still not allowed in
253 // localpart's even though the IdentifierClass base class and the UsernameCaseMapped
254 // profile don't forbid them; disallow them here.
255 if s[0..i].contains(&['"', '&', '\'', '/', ':', '<', '>', '@', '`'][..]) {
256 return Err(Error::ForbiddenChars);
257 }
258 match slash {
259 // This is a bare JID.
260 None => (Some(&s[0..i]), &s[i + 1..], None),
261 // There is a '/', but it's immediately after the '@' (or there is a short
262 // domain part between them).
263 Some((j, _)) if j - i < 3 => return Err(Error::ShortDomain),
264 // This is a full JID.
265 Some((j, _)) => (Some(&s[0..i]), &s[i + 1..j], Some(&s[j + 1..])),
266 }
267 }
268 _ => unreachable!(),
269 };
270
271 // We'll throw out any trailing dots on domainparts, since they're ignored:
272 //
273 // If the domainpart includes a final character considered to be a label
274 // separator (dot) by [RFC1034], this character MUST be stripped from
275 // the domainpart before the JID of which it is a part is used for the
276 // purpose of routing an XML stanza, comparing against another JID, or
277 // constructing an XMPP URI or IRI [RFC5122]. In particular, such a
278 // character MUST be stripped before any other canonicalization steps
279 // are taken.
280 Ok((lpart, dpart.trim_end_matches('.'), rpart))
281 }
282
283 /// Constructs a JID from its constituent parts. The localpart is generally the username of a
284 /// user on a particular server, the domainpart is a domain, hostname, or IP address where the
285 /// user or entity resides, and the resourcepart identifies a specific client. Everything but
286 /// the domain is optional.
287 ///
288 /// # Errors
289 ///
290 /// If the localpart or resourcepart passed to this function is not valid, or the domainpart
291 /// fails IDNA processing or is not a valid IPv6 address, this function returns an [error
292 /// variant].
293 ///
294 /// [error variant]: ./enum.Error.html
295 ///
296 /// # Examples
297 ///
298 /// Basic usage:
299 ///
300 /// ```rust
301 /// # use xmpp_addr::Jid;
302 /// # fn main() -> Result<(), xmpp_addr::Error> {
303 /// let j = Jid::new("feste", "example.net", None)?;
304 /// assert_eq!(j, "feste@example.net");
305 /// # Ok(())
306 /// # }
307 /// ```
308 pub fn new<L, R>(local: L, domain: &'a str, resource: R) -> Result<Jid<'a>>
309 where
310 L: Into<Option<&'a str>>,
311 R: Into<Option<&'a str>>,
312 {
313 Ok(Jid {
314 local: match local.into() {
315 None => None,
316 Some(l) => Some(Jid::process_local(l)?),
317 },
318 domain: match Jid::process_domain(domain) {
319 Err(err) => return Err(err),
320 Ok(d) => d,
321 },
322 resource: match resource.into() {
323 None => None,
324 Some(r) => Some(Jid::process_resource(r)?),
325 },
326 })
327 }
328
329 // TODO: This should all be handled by the PRECIS UsernameCaseMapped profile.
330 fn process_local(local: &'a str) -> Result<borrow::Cow<'a, str>> {
331 let local: borrow::Cow<'a, str> = if local.is_ascii() {
332 // ASCII fast path
333 // TODO: JIDs aren't likely to have long localparts; are multiple scans worth it just
334 // to maybe avoid an allocation?
335 if local.bytes().all(|c| c.is_ascii_lowercase()) {
336 local.into()
337 } else {
338 local.to_ascii_lowercase().into()
339 }
340 } else {
341 // Contains characters outside the ASCII range (needs NFC)
342 local.chars().flat_map(|c| c.to_lowercase()).nfc().collect()
343 };
344 match local.len() {
345 0 => Err(Error::EmptyLocal),
346 l if l > 1023 => Err(Error::LongLocal),
347 _ => Ok(local),
348 }
349 }
350
351 fn process_domain(domain: &'a str) -> Result<borrow::Cow<'a, str>> {
352 let is_v6 = if domain.starts_with('[') && domain.ends_with(']') {
353 // This should be an IPv6 address, validate it.
354 let inner = unsafe { domain.get_unchecked(1..domain.len() - 1) };
355 match net::Ipv6Addr::from_str(inner) {
356 Ok(_) => true,
357 Err(v) => return Err(Error::Addr(v)),
358 }
359 } else {
360 false
361 };
362
363 let dlabel: borrow::Cow<'a, str> = if !is_v6 {
364 let (dlabel, result) = idna::domain_to_unicode(domain);
365 match result {
366 Ok(_) => dlabel.into(),
367 Err(e) => return Err(Error::IDNA(e)),
368 }
369 } else {
370 domain.into()
371 };
372
373 if dlabel.len() > 1023 {
374 return Err(Error::LongDomain);
375 }
376 if dlabel.len() < 1 {
377 return Err(Error::ShortDomain);
378 }
379
380 Ok(dlabel)
381 }
382
383 fn process_resource(res: &'a str) -> Result<borrow::Cow<'a, str>> {
384 let res: borrow::Cow<'a, str> = if res.is_ascii() {
385 res.into()
386 } else {
387 // TODO: This should be done with a separate PRECIS library and the preparation step of
388 // the OpaqueString class should be applied first
389 res.chars()
390 // RFC 7613 §4.2.2:
391 // 2. Additional Mapping Rule: Any instances of non-ASCII space MUST be
392 // mapped to ASCII space (U+0020); a non-ASCII space is any Unicode
393 // code point having a Unicode general category of "Zs" (with the
394 // exception of U+0020).
395 .map(|c| if c.is_whitespace() { '\u{0020}' } else { c })
396 // RFC 7613 §4.2.2:
397 // 4. Normalization Rule: Unicode Normalization Form C (NFC) MUST be
398 // applied to all characters.
399 .nfc()
400 .collect()
401 };
402 match res.len() {
403 0 => Err(Error::EmptyResource),
404 r if r > 1023 => Err(Error::LongResource),
405 _ => Ok(res),
406 }
407 }
408
409 /// Construct a JID containing only a domain part.
410 ///
411 /// # Errors
412 ///
413 /// If domain fails the IDNA "to Unicode" operation, or is enclosed in square brackets ("\[]")
414 /// but is not a valid IPv6 address, this function returns an [error variant].
415 ///
416 /// [error variant]: ./enum.Error.html
417 ///
418 /// # Examples
419 ///
420 /// Basic usage:
421 ///
422 /// ```rust
423 /// # use xmpp_addr::Jid;
424 /// # fn main() -> Result<(), xmpp_addr::Error> {
425 /// let j = Jid::from_domain("example.net")?;
426 /// assert_eq!(j, "example.net");
427 /// # Ok(())
428 /// # }
429 /// ```
430 pub fn from_domain(domain: &'a str) -> Result<Jid<'a>> {
431 Jid::new(None, domain, None)
432 }
433
434 /// Consumes a JID to construct a bare JID (a JID without a resourcepart).
435 ///
436 /// # Examples
437 ///
438 /// Basic usage:
439 ///
440 /// ```rust
441 /// # use xmpp_addr::Jid;
442 /// # fn main() -> Result<(), xmpp_addr::Error> {
443 /// let j = Jid::new("feste", "example.net", "res")?;
444 /// assert_eq!(j.bare(), "feste@example.net");
445 /// # Ok(())
446 /// # }
447 /// ```
448 pub fn bare(self) -> Jid<'a> {
449 Jid {
450 local: self.local,
451 domain: self.domain,
452 resource: None,
453 }
454 }
455
456 /// Consumes a JID to construct a JID with only the domainpart.
457 ///
458 /// # Examples
459 ///
460 /// Basic usage:
461 ///
462 /// ```rust
463 /// # use xmpp_addr::Jid;
464 /// # fn main() -> Result<(), xmpp_addr::Error> {
465 /// let j = Jid::new("feste", "example.net", "res")?;
466 /// assert_eq!(j.domain(), "example.net");
467 /// # Ok(())
468 /// # }
469 /// ```
470 pub fn domain(self) -> Jid<'a> {
471 Jid {
472 local: None,
473 domain: self.domain,
474 resource: None,
475 }
476 }
477
478 /// Consumes a JID to construct a new JID with the given localpart.
479 ///
480 /// # Errors
481 ///
482 /// If the localpart is too long [`Error::LongLocal`] is returned.
483 ///
484 /// [`Error::LongLocal`]: ./enum.Error.html
485 ///
486 /// # Examples
487 ///
488 /// Basic usage:
489 ///
490 /// ```rust
491 /// # use xmpp_addr::Jid;
492 /// # fn main() -> Result<(), xmpp_addr::Error> {
493 /// let j = Jid::from_str("example.net")?;
494 /// assert_eq!(j.with_local("feste")?, "feste@example.net");
495 ///
496 /// let j = Jid::from_str("iago@example.net")?;
497 /// assert_eq!(j.with_local(None)?, "example.net");
498 ///
499 /// let j = Jid::from_str("feste@example.net")?;
500 /// assert!(j.with_local("").is_err());
501 /// # Ok(())
502 /// # }
503 /// ```
504 pub fn with_local<T: Into<Option<&'a str>>>(self, local: T) -> Result<Jid<'a>> {
505 Ok(Jid {
506 local: match local.into() {
507 Some(l) => Some(Jid::process_local(l)?),
508 None => None,
509 },
510 domain: self.domain,
511 resource: self.resource,
512 })
513 }
514
515 /// Consumes a JID to construct a new JID with the given domainpart.
516 ///
517 /// # Errors
518 ///
519 /// If the domain is too long, too short, or fails IDNA processing, an [error variant] is
520 /// returned.
521 ///
522 /// [error variant]: ./enum.Error.html
523 ///
524 /// # Examples
525 ///
526 /// Basic usage:
527 ///
528 /// ```rust
529 /// # use xmpp_addr::Jid;
530 /// # fn main() -> Result<(), xmpp_addr::Error> {
531 /// let j = Jid::from_str("feste@example.net")?;
532 /// assert_eq!(j.with_domain("example.org")?, "feste@example.org");
533 /// # Ok(())
534 /// # }
535 /// ```
536 pub fn with_domain(self, domain: &'a str) -> Result<Jid<'a>> {
537 Ok(Jid {
538 local: self.local,
539 domain: match Jid::process_domain(domain) {
540 Err(err) => return Err(err),
541 Ok(d) => d,
542 },
543 resource: self.resource,
544 })
545 }
546
547 /// Consumes a JID to construct a new JID with the given resourcepart.
548 ///
549 /// # Errors
550 ///
551 /// If the resource is too long [`Error::LongResource`] is returned.
552 ///
553 /// [`Error::LongResource`]: ./enum.Error.html
554 ///
555 /// # Examples
556 ///
557 /// Basic usage:
558 ///
559 /// ```rust
560 /// # use xmpp_addr::Jid;
561 /// # use xmpp_addr::Error;
562 /// # fn main() -> Result<(), xmpp_addr::Error> {
563 /// let j = Jid::from_str("feste@example.net")?;
564 /// assert_eq!(j.with_resource("1234")?, "feste@example.net/1234");
565 ///
566 /// let j = Jid::from_str("feste@example.net/1234")?;
567 /// assert_eq!(j.with_resource(None)?, "feste@example.net");
568 ///
569 /// let j = Jid::from_str("feste@example.net")?;
570 /// assert!(j.with_resource("").is_err());
571 /// # Ok(())
572 /// # }
573 /// ```
574 pub fn with_resource<T: Into<Option<&'a str>>>(self, resource: T) -> Result<Jid<'a>> {
575 Ok(Jid {
576 local: self.local,
577 domain: self.domain,
578 resource: match resource.into() {
579 Some(r) => Some(Jid::process_resource(r)?),
580 None => None,
581 },
582 })
583 }
584
585 /// Parse a string to create a Jid.
586 ///
587 /// This does not implement the `FromStr` trait because the Jid type requires an explicit
588 /// lifetime annotation and the `from_str` method of `FromStr` uses an implicit annotation
589 /// which is not compatible with the Jid type.
590 ///
591 /// # Errors
592 ///
593 /// If the entire string or any part of the JID is empty or not valid, or the domainpart fails
594 /// IDNA processing or is not a valid IPv6 address, this function returns an [error variant].
595 ///
596 /// [error variant]: ./enum.Error.html
597 ///
598 /// # Examples
599 ///
600 /// ```rust
601 /// # use xmpp_addr::Jid;
602 /// # fn main() -> Result<(), xmpp_addr::Error> {
603 /// let j = Jid::from_str("juliet@example.net/balcony")?;
604 /// assert_eq!(j, "juliet@example.net/balcony");
605 /// # Ok(())
606 /// # }
607 /// ```
608 pub fn from_str(s: &'a str) -> Result<Jid<'a>> {
609 let (lpart, dpart, rpart) = Jid::split(s)?;
610 Jid::new(lpart, dpart, rpart)
611 }
612
613 /// Returns the localpart of the JID in canonical form.
614 ///
615 /// # Examples
616 ///
617 /// ```rust
618 /// # use xmpp_addr::Jid;
619 /// # fn main() -> Result<(), xmpp_addr::Error> {
620 /// let j = Jid::from_str("mercutio@example.net/rp")?;
621 /// assert_eq!(j.localpart(), Some("mercutio"));
622 ///
623 /// let j = Jid::from_str("example.net/rp")?;
624 /// assert!(j.localpart().is_none());
625 /// # Ok(())
626 /// # }
627 /// ```
628 pub fn localpart(&self) -> Option<&str> {
629 match self.local {
630 None => None,
631 Some(ref l) => Some(&l[..]),
632 }
633 }
634
635 /// Returns the domainpart of the JID in canonical form.
636 ///
637 /// # Examples
638 ///
639 /// ```rust
640 /// # use xmpp_addr::Jid;
641 /// # fn main() -> Result<(), xmpp_addr::Error> {
642 /// let j = Jid::from_str("mercutio@example.net/rp")?;
643 /// assert_eq!(j.domainpart(), "example.net");
644 /// # Ok(())
645 /// # }
646 /// ```
647 pub fn domainpart(&self) -> &str {
648 &(self.domain)
649 }
650
651 /// Returns the resourcepart of the JID in canonical form.
652 ///
653 /// # Examples
654 ///
655 /// ```rust
656 /// # use xmpp_addr::Jid;
657 /// # fn main() -> Result<(), xmpp_addr::Error> {
658 /// let j = Jid::from_str("example.net/rp")?;
659 /// assert_eq!(j.resourcepart(), Some("rp"));
660 ///
661 /// let j = Jid::from_str("feste@example.net")?;
662 /// assert!(j.resourcepart().is_none());
663 /// # Ok(())
664 /// # }
665 /// ```
666 pub fn resourcepart(&self) -> Option<&str> {
667 match self.resource {
668 None => None,
669 Some(ref r) => Some(&r[..]),
670 }
671 }
672
673 /// Constructs a JID from its constituent parts, bypassing safety checks.
674 /// A `None` value for the localpart or resourcepart indicates that there is no localpart or
675 /// resourcepart. A value of `Some("")` (although note that the `Some()` wrapper may be elided)
676 /// indicates that the localpart or resourcepart is empty, which is invalid, but allowed by
677 /// this unsafe function (eg. `@example.com`).
678 ///
679 /// # Examples
680 ///
681 /// Constructing an invalid JID:
682 ///
683 /// ```rust
684 /// # use xmpp_addr::Jid;
685 /// unsafe {
686 /// let j = Jid::new_unchecked(r#"/o\"#, "[badip]", None);
687 /// assert_eq!(j.localpart(), Some(r#"/o\"#));
688 /// assert_eq!(j.domainpart(), "[badip]");
689 /// assert_eq!(j.resourcepart(), None);
690 ///
691 /// // Note that comparisons you would expect to work may fail when creating unsafe JIDs.
692 /// assert_ne!(j, r#"/o\@[badip]"#);
693 ///
694 /// let j = Jid::new_unchecked("", "example.com", "");
695 /// assert_eq!(j, "@example.com/");
696 /// }
697 /// ```
698 pub unsafe fn new_unchecked<L, R>(local: L, domain: &'a str, resource: R) -> Jid<'a>
699 where
700 L: Into<Option<&'a str>>,
701 R: Into<Option<&'a str>>,
702 {
703 Jid {
704 local: match local.into() {
705 None => None,
706 Some(s) => Some(s.into()),
707 },
708 domain: domain.into(),
709 resource: match resource.into() {
710 None => None,
711 Some(s) => Some(s.into()),
712 },
713 }
714 }
715}
716
717/// Format the JID in its canonical string form.
718///
719/// # Examples
720///
721/// Formatting and printing:
722///
723/// ```rust
724/// # use xmpp_addr::Jid;
725/// # fn main() -> Result<(), xmpp_addr::Error> {
726/// let j = Jid::from_str("viola@example.net")?;
727///
728/// assert_eq!(format!("{}", j), "viola@example.net");
729/// # Ok(())
730/// # }
731/// ```
732impl fmt::Display for Jid<'_> {
733 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
734 match self.local {
735 None => {}
736 Some(ref l) => write!(f, "{}@", l)?,
737 }
738 write!(f, "{}", self.domain)?;
739 match self.resource {
740 None => {}
741 Some(ref r) => write!(f, "/{}", r)?,
742 }
743 Ok(())
744 }
745}
746
747/// Create a bare JID from a 2-tuple.
748///
749/// # Errors
750///
751/// If the first item in the tuple is not a valid localpart or the second item in the tuple fails
752/// IDNA processing or is not a valid IPv6 address, this function returns an [error variant].
753///
754/// [error variant]: ./enum.Error.html
755///
756/// # Examples
757///
758#[cfg_attr(not(feature = "try_from"), doc = " ```rust,ignore")]
759#[cfg_attr(feature = "try_from", doc = " ```rust")]
760/// #![feature(try_from)]
761/// # use std::convert::TryInto;
762/// # use xmpp_addr::{Jid, Result};
763/// # fn main() -> std::result::Result<(), xmpp_addr::Error> {
764/// let j: Jid = ("mercutio", "example.net").try_into()?;
765/// assert_eq!(j, "mercutio@example.net");
766///
767/// let j: Result<Jid> = ("", "example.net").try_into();
768/// assert!(j.is_err());
769/// # Ok(())
770/// # }
771/// ```
772#[cfg(feature = "try_from")]
773impl<'a> convert::TryFrom<(&'a str, &'a str)> for Jid<'a> {
774 type Error = Error;
775
776 fn try_from(parts: (&'a str, &'a str)) -> result::Result<Self, Self::Error> {
777 Jid::new(Some(parts.0), parts.1, None)
778 }
779}
780
781/// Create a bare JID from a 2-tuple where the localpart is optional.
782///
783/// # Errors
784///
785/// If the first item in the tuple is not a valid localpart or the second item in the tuple fails
786/// IDNA processing or is not a valid IPv6 address, this function returns an [error variant].
787///
788/// [error variant]: ./enum.Error.html
789///
790/// # Examples
791///
792#[cfg_attr(not(feature = "try_from"), doc = " ```rust,ignore")]
793#[cfg_attr(feature = "try_from", doc = " ```rust")]
794/// #![feature(try_from)]
795/// # use std::convert::TryInto;
796/// # use xmpp_addr::{Jid, Result};
797/// # fn main() -> std::result::Result<(), xmpp_addr::Error> {
798/// let j: Jid = (Some("mercutio"), "example.net").try_into()?;
799/// assert_eq!(j, "mercutio@example.net");
800///
801/// let j: Jid = (None, "example.net").try_into()?;
802/// assert_eq!(j, "example.net");
803///
804/// let j: Result<Jid> = (Some(""), "example.net").try_into();
805/// assert!(j.is_err());
806/// # Ok(())
807/// # }
808/// ```
809#[cfg(feature = "try_from")]
810impl<'a> convert::TryFrom<(Option<&'a str>, &'a str)> for Jid<'a> {
811 type Error = Error;
812
813 fn try_from(parts: (Option<&'a str>, &'a str)) -> result::Result<Self, Self::Error> {
814 Jid::new(parts.0, parts.1, None)
815 }
816}
817
818/// Create a JID with a domain and resourcepart from a 2-tuple where the resourcepart is optional.
819/// Generally speaking, this is not as useful as the other `TryFrom` implementatoins, but is
820/// included for completenesses sake or for custom clustering implementations.
821///
822/// # Errors
823///
824/// If the first item in the tuplefails IDNA processing or is not a valid IPv6 address or the
825/// second item in the tuple is not a valid resourcepart, this function returns an [error variant].
826///
827/// [error variant]: ./enum.Error.html
828///
829/// # Examples
830///
831#[cfg_attr(not(feature = "try_from"), doc = " ```rust,ignore")]
832#[cfg_attr(feature = "try_from", doc = " ```rust")]
833/// #![feature(try_from)]
834/// # use std::convert::TryInto;
835/// # use xmpp_addr::{Jid, Result};
836/// # fn main() -> std::result::Result<(), xmpp_addr::Error> {
837/// let j: Jid = ("example.net", Some("node1432")).try_into()?;
838/// assert_eq!(j, "example.net/node1432");
839///
840/// let j: Jid = ("example.net", None).try_into()?;
841/// assert_eq!(j, "example.net");
842///
843/// let j: Result<Jid> = ("example.net", Some("")).try_into();
844/// assert!(j.is_err());
845/// # Ok(())
846/// # }
847/// ```
848#[cfg(feature = "try_from")]
849impl<'a> convert::TryFrom<(&'a str, Option<&'a str>)> for Jid<'a> {
850 type Error = Error;
851
852 fn try_from(parts: (&'a str, Option<&'a str>)) -> result::Result<Self, Self::Error> {
853 Jid::new(None, parts.0, parts.1)
854 }
855}
856
857/// Creates a full JID from a 3-tuple.
858///
859/// # Errors
860///
861/// If the first item in the tuple is not a valid localpart, the second item in the tuple fails
862/// IDNA processing or is not a valid IPv6 address, or the third item in the tuple is not a valid
863/// domainpart, this function returns an [error variant].
864///
865/// [error variant]: ./enum.Error.html
866///
867/// # Examples
868///
869#[cfg_attr(not(feature = "try_from"), doc = " ```rust,ignore")]
870#[cfg_attr(feature = "try_from", doc = " ```rust")]
871/// #![feature(try_from)]
872/// # use std::convert::TryInto;
873/// # use xmpp_addr::{Jid, Result};
874/// # fn main() -> std::result::Result<(), xmpp_addr::Error> {
875/// let j: Jid = ("mercutio", "example.net", "nctYeCzm").try_into()?;
876/// assert_eq!(j, "mercutio@example.net/nctYeCzm");
877///
878/// let j: Result<Jid> = ("mercutio", "example.net", "").try_into();
879/// assert!(j.is_err());
880/// # Ok(())
881/// # }
882/// ```
883#[cfg(feature = "try_from")]
884impl<'a> convert::TryFrom<(&'a str, &'a str, &'a str)> for Jid<'a> {
885 type Error = Error;
886
887 fn try_from(parts: (&'a str, &'a str, &'a str)) -> result::Result<Self, Self::Error> {
888 Jid::new(Some(parts.0), parts.1, Some(parts.2))
889 }
890}
891
892/// Creates a full JID from a 3-tuple where the localpart and resourcepart are optional.
893///
894/// # Errors
895///
896/// If the first item in the tuple is not a valid localpart, the second item in the tuple fails
897/// IDNA processing or is not a valid IPv6 address, or the third item in the tuple is not a valid
898/// domainpart, this function returns an [error variant].
899///
900/// [error variant]: ./enum.Error.html
901///
902/// # Examples
903///
904#[cfg_attr(not(feature = "try_from"), doc = " ```rust,ignore")]
905#[cfg_attr(feature = "try_from", doc = " ```rust")]
906/// #![feature(try_from)]
907/// # use std::convert::TryInto;
908/// # use xmpp_addr::{Jid, Result};
909/// # fn main() -> std::result::Result<(), xmpp_addr::Error> {
910/// let j: Jid = (Some("mercutio"), "example.net", Some("nctYeCzm")).try_into()?;
911/// assert_eq!(j, "mercutio@example.net/nctYeCzm");
912///
913/// let j: Jid = (Some("mercutio"), "example.net", None).try_into()?;
914/// assert_eq!(j, "mercutio@example.net");
915///
916/// let j: Result<Jid> = (Some("mercutio"), "example.net", Some("")).try_into();
917/// assert!(j.is_err());
918/// # Ok(())
919/// # }
920/// ```
921#[cfg(feature = "try_from")]
922impl<'a> convert::TryFrom<(Option<&'a str>, &'a str, Option<&'a str>)> for Jid<'a> {
923 type Error = Error;
924
925 fn try_from(
926 parts: (Option<&'a str>, &'a str, Option<&'a str>),
927 ) -> result::Result<Self, Self::Error> {
928 Jid::new(parts.0, parts.1, parts.2)
929 }
930}
931
932/// Parse a string to create a JID.
933///
934/// # Errors
935///
936/// If the entire string or any part of the JID is empty or not valid, or the domainpart fails IDNA
937/// processing or is not a valid IPv6 address, this function returns an [error variant].
938///
939/// [error variant]: ./enum.Error.html
940///
941/// # Examples
942///
943#[cfg_attr(not(feature = "try_from"), doc = " ```rust,ignore")]
944#[cfg_attr(feature = "try_from", doc = " ```rust")]
945/// #![feature(try_from)]
946/// # use std::convert::TryInto;
947/// # use xmpp_addr::Jid;
948/// # fn main() -> Result<(), xmpp_addr::Error> {
949/// let j: Jid = "example.net/rp".try_into()?;
950/// assert_eq!(j, "example.net/rp");
951/// # Ok(())
952/// # }
953/// ```
954#[cfg(feature = "try_from")]
955impl<'a> convert::TryFrom<&'a str> for Jid<'a> {
956 type Error = Error;
957
958 fn try_from(s: &'a str) -> result::Result<Self, Self::Error> {
959 Jid::from_str(s)
960 }
961}
962
963/// Creates a JID from an IPv4 address.
964impl<'a> convert::From<net::Ipv4Addr> for Jid<'a> {
965 fn from(addr: net::Ipv4Addr) -> Jid<'a> {
966 Jid {
967 local: None,
968 domain: format!("{}", addr).into(),
969 resource: None,
970 }
971 }
972}
973
974/// Creates a JID from an IPv6 address.
975impl<'a> convert::From<net::Ipv6Addr> for Jid<'a> {
976 fn from(addr: net::Ipv6Addr) -> Jid<'a> {
977 Jid {
978 local: None,
979 domain: format!("[{}]", addr).into(),
980 resource: None,
981 }
982 }
983}
984
985/// Creates a JID from an IP address.
986impl<'a> convert::From<net::IpAddr> for Jid<'a> {
987 fn from(addr: net::IpAddr) -> Jid<'a> {
988 match addr {
989 net::IpAddr::V6(v6) => v6.into(),
990 net::IpAddr::V4(v4) => v4.into(),
991 }
992 }
993}
994
995/// Allows JIDs to be compared with strings.
996///
997/// **This may be expensive**. The string is first split using [`Jid::split`] and each part is
998/// compared with its corresponding part in the JID. If this comparison is successful, true is
999/// returned and the comparison is cheap. If this does not match, however, the string is then
1000/// canonicalized (by converting it into a JID) and the parts are compared again; this may require
1001/// expensive heap allocations. If constructing a JID from the string fails, the comparison always
1002/// fails (even if the original JID is would match the invalid output). Comparisons involving
1003/// unsafe JIDs constructed with [`Jid::new_unchecked`] should construct an unsafe JID from the
1004/// string and manually compare the parts.
1005///
1006/// [`Jid::split`]: struct.Jid.html#method.split
1007/// [`Jid::new_unchecked`]: struct.Jid.html#method.new_unchecked
1008///
1009/// # Examples
1010///
1011/// ```rust
1012/// # use xmpp_addr::Jid;
1013/// # fn main() -> Result<(), xmpp_addr::Error> {
1014/// let j = Jid::from_str("example.net/rp")?;
1015/// assert!(j == "example.net/rp");
1016/// # Ok(())
1017/// # }
1018/// ```
1019impl cmp::PartialEq<str> for Jid<'_> {
1020 fn eq(&self, other: &str) -> bool {
1021 if match Jid::split(other) {
1022 Err(_) => false,
1023 Ok(p) => {
1024 let local_match = match p.0 {
1025 None => self.local.is_none(),
1026 Some(s) => match self.local {
1027 None => false,
1028 Some(ref l) => s == l,
1029 },
1030 };
1031 let res_match = match p.2 {
1032 None => self.resource.is_none(),
1033 Some(s) => match self.resource {
1034 None => false,
1035 Some(ref r) => s == r,
1036 },
1037 };
1038 local_match && p.1 == self.domain && res_match
1039 }
1040 } {
1041 return true;
1042 }
1043 match Jid::from_str(other) {
1044 Ok(j) => j.eq(self),
1045 Err(_) => false,
1046 }
1047 }
1048}
1049
1050/// Allows JIDs to be compared with strings.
1051///
1052/// **This may be expensive**. The string is first split using [`Jid::split`] and each part is
1053/// compared with its corresponding part in the JID. If this comparison is successful, true is
1054/// returned and the comparison is cheap. If this does not match, however, the string is then
1055/// canonicalized (by converting it into a JID) and the parts are compared again; this may require
1056/// expensive heap allocations. If constructing a JID from the string fails, the comparison always
1057/// fails (even if the original JID is would match the invalid output). Comparisons involving
1058/// unsafe JIDs constructed with [`Jid::new_unchecked`] should construct an unsafe JID from the
1059/// string and manually compare the parts.
1060///
1061/// [`Jid::split`]: struct.Jid.html#method.split
1062/// [`Jid::new_unchecked`]: struct.Jid.html#method.new_unchecked
1063///
1064/// # Examples
1065///
1066/// ```rust
1067/// # use xmpp_addr::Jid;
1068/// # fn main() -> Result<(), xmpp_addr::Error> {
1069/// let j = Jid::from_str("example.net/rp")?;
1070/// assert!("example.net/rp" == j);
1071/// # Ok(())
1072/// # }
1073/// ```
1074impl cmp::PartialEq<Jid<'_>> for str {
1075 fn eq(&self, other: &Jid<'_>) -> bool {
1076 PartialEq::eq(other, self)
1077 }
1078}
1079
1080// Macro from collections::strings
1081macro_rules! impl_eq {
1082 ($lhs:ty, $rhs:ty) => {
1083 /// Allows JIDs to be compared with strings.
1084 ///
1085 /// **This may be expensive**. The string is first split using [`Jid::split`] and each part
1086 /// is compared with its corresponding part in the JID. If this comparison is successful,
1087 /// true is returned and the comparison is cheap. If this does not match, however, the
1088 /// string is then canonicalized (by converting it into a JID) and the parts are compared
1089 /// again; this may require expensive heap allocations. If constructing a JID from the
1090 /// string fails, the comparison always fails (even if the original JID is would match the
1091 /// invalid output). Comparisons involving unsafe JIDs constructed with
1092 /// [`Jid::new_unchecked`] should construct an unsafe JID from the string and manually
1093 /// compare the parts.
1094 ///
1095 /// [`Jid::split`]: struct.Jid.html#method.split
1096 /// [`Jid::new_unchecked`]: struct.Jid.html#method.new_unchecked
1097 impl<'a, 'b> PartialEq<$lhs> for $rhs {
1098 #[inline]
1099 fn eq(&self, other: &$lhs) -> bool {
1100 PartialEq::eq(self, &other[..])
1101 }
1102 }
1103
1104 /// Allows JIDs to be compared with strings.
1105 ///
1106 /// **This may be expensive**. The string is first split using [`Jid::split`] and each part
1107 /// is compared with its corresponding part in the JID. If this comparison is successful,
1108 /// true is returned and the comparison is cheap. If this does not match, however, the
1109 /// string is then canonicalized (by converting it into a JID) and the parts are compared
1110 /// again; this may require expensive heap allocations. If constructing a JID from the
1111 /// string fails, the comparison always fails (even if the original JID is would match the
1112 /// invalid output). Comparisons involving unsafe JIDs constructed with
1113 /// [`Jid::new_unchecked`] should construct an unsafe JID from the string and manually
1114 /// compare the parts.
1115 ///
1116 /// [`Jid::split`]: struct.Jid.html#method.split
1117 /// [`Jid::new_unchecked`]: struct.Jid.html#method.new_unchecked
1118 impl<'a, 'b> PartialEq<$rhs> for $lhs {
1119 #[inline]
1120 fn eq(&self, other: &$rhs) -> bool {
1121 PartialEq::eq(&self[..], other)
1122 }
1123 }
1124 };
1125}
1126
1127impl_eq! { borrow::Cow<'b, str>, Jid<'a> }
1128impl_eq! { &'b str, Jid<'a> }
1129impl_eq! { String, Jid<'a> }