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> }