1// Copyright (c) 2017, 2018 lumi <lumi@pew.im>
2// Copyright (c) 2017, 2018, 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
3// Copyright (c) 2017, 2018, 2019 Maxime “pep” Buquet <pep@bouah.net>
4// Copyright (c) 2017, 2018 Astro <astro@spaceboyz.net>
5// Copyright (c) 2017 Bastien Orivel <eijebong@bananium.fr>
6//
7// This Source Code Form is subject to the terms of the Mozilla Public
8// License, v. 2.0. If a copy of the MPL was not distributed with this
9// file, You can obtain one at http://mozilla.org/MPL/2.0/.
10
11#![deny(missing_docs)]
12
13//! Provides a type for Jabber IDs.
14//!
15//! For usage, check the documentation on the `Jid` struct.
16
17use std::convert::Into;
18use std::fmt;
19use std::str::FromStr;
20
21/// An error that signifies that a `Jid` cannot be parsed from a string.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum JidParseError {
24 /// Happens when there is no domain, that is either the string is empty,
25 /// starts with a /, or contains the @/ sequence.
26 NoDomain,
27
28 /// Happens when there is no resource, that is string contains no /.
29 NoResource,
30
31 /// Happens when the node is empty, that is the string starts with a @.
32 EmptyNode,
33
34 /// Happens when the resource is empty, that is the string ends with a /.
35 EmptyResource,
36}
37
38impl fmt::Display for JidParseError {
39 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
40 write!(fmt, "{}", match self {
41 JidParseError::NoDomain => "no domain found in this JID",
42 JidParseError::NoResource => "no resource found in this full JID",
43 JidParseError::EmptyNode => "nodepart empty despite the presence of a @",
44 JidParseError::EmptyResource => "resource empty despite the presence of a /",
45 })
46 }
47}
48
49/// An enum representing a Jabber ID. It can be either a `FullJid` or a `BareJid`.
50#[derive(Debug, Clone, PartialEq)]
51pub enum Jid {
52 /// Bare Jid
53 Bare(BareJid),
54
55 /// Full Jid
56 Full(FullJid),
57}
58
59impl FromStr for Jid {
60 type Err = JidParseError;
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 let (ns, ds, rs): StringJid = _from_str(s)?;
64 Ok(match rs {
65 Some(rs) => Jid::Full(FullJid {
66 node: ns,
67 domain: ds,
68 resource: rs,
69 }),
70 None => Jid::Bare(BareJid {
71 node: ns,
72 domain: ds,
73 }),
74 })
75 }
76}
77
78impl From<Jid> for String {
79 fn from(jid: Jid) -> String {
80 match jid {
81 Jid::Bare(bare) => String::from(bare),
82 Jid::Full(full) => String::from(full),
83 }
84 }
85}
86
87impl From<BareJid> for Jid {
88 fn from(bare_jid: BareJid) -> Jid {
89 Jid::Bare(bare_jid)
90 }
91}
92
93impl From<FullJid> for Jid {
94 fn from(full_jid: FullJid) -> Jid {
95 Jid::Full(full_jid)
96 }
97}
98
99impl Jid {
100 /// The node part of the Jabber ID, if it exists, else None.
101 pub fn node(self) -> Option<String> {
102 match self {
103 Jid::Bare(BareJid { node, .. }) | Jid::Full(FullJid { node, .. }) => node,
104 }
105 }
106
107 /// The domain of the Jabber ID.
108 pub fn domain(self) -> String {
109 match self {
110 Jid::Bare(BareJid { domain, .. }) | Jid::Full(FullJid { domain, .. }) => domain,
111 }
112 }
113}
114
115/// A struct representing a full Jabber ID.
116///
117/// A full Jabber ID is composed of 3 components, of which one is optional:
118///
119/// - A node/name, `node`, which is the optional part before the @.
120/// - A domain, `domain`, which is the mandatory part after the @ but before the /.
121/// - A resource, `resource`, which is the part after the /.
122///
123/// Unlike a `BareJid`, it always contains a resource, and should only be used when you are certain
124/// there is no case where a resource can be missing. Otherwise, use a `Jid` enum.
125#[derive(Clone, PartialEq, Eq, Hash)]
126pub struct FullJid {
127 /// The node part of the Jabber ID, if it exists, else None.
128 pub node: Option<String>,
129 /// The domain of the Jabber ID.
130 pub domain: String,
131 /// The resource of the Jabber ID.
132 pub resource: String,
133}
134
135/// A struct representing a bare Jabber ID.
136///
137/// A bare Jabber ID is composed of 2 components, of which one is optional:
138///
139/// - A node/name, `node`, which is the optional part before the @.
140/// - A domain, `domain`, which is the mandatory part after the @.
141///
142/// Unlike a `FullJid`, it can’t contain a resource, and should only be used when you are certain
143/// there is no case where a resource can be set. Otherwise, use a `Jid` enum.
144#[derive(Clone, PartialEq, Eq, Hash)]
145pub struct BareJid {
146 /// The node part of the Jabber ID, if it exists, else None.
147 pub node: Option<String>,
148 /// The domain of the Jabber ID.
149 pub domain: String,
150}
151
152impl From<FullJid> for String {
153 fn from(jid: FullJid) -> String {
154 let mut string = String::new();
155 if let Some(ref node) = jid.node {
156 string.push_str(node);
157 string.push('@');
158 }
159 string.push_str(&jid.domain);
160 string.push('/');
161 string.push_str(&jid.resource);
162 string
163 }
164}
165
166impl From<BareJid> for String {
167 fn from(jid: BareJid) -> String {
168 let mut string = String::new();
169 if let Some(ref node) = jid.node {
170 string.push_str(node);
171 string.push('@');
172 }
173 string.push_str(&jid.domain);
174 string
175 }
176}
177
178impl Into<BareJid> for FullJid {
179 fn into(self) -> BareJid {
180 BareJid {
181 node: self.node,
182 domain: self.domain,
183 }
184 }
185}
186
187impl fmt::Debug for FullJid {
188 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
189 write!(fmt, "FullJID({})", self)
190 }
191}
192
193impl fmt::Debug for BareJid {
194 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
195 write!(fmt, "BareJID({})", self)
196 }
197}
198
199impl fmt::Display for FullJid {
200 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
201 fmt.write_str(String::from(self.clone()).as_ref())
202 }
203}
204
205impl fmt::Display for BareJid {
206 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
207 fmt.write_str(String::from(self.clone()).as_ref())
208 }
209}
210
211enum ParserState {
212 Node,
213 Domain,
214 Resource,
215}
216
217type StringJid = (Option<String>, String, Option<String>);
218fn _from_str(s: &str) -> Result<StringJid, JidParseError> {
219 // TODO: very naive, may need to do it differently
220 let iter = s.chars();
221 let mut buf = String::with_capacity(s.len());
222 let mut state = ParserState::Node;
223 let mut node = None;
224 let mut domain = None;
225 let mut resource = None;
226 for c in iter {
227 match state {
228 ParserState::Node => {
229 match c {
230 '@' => {
231 if buf == "" {
232 return Err(JidParseError::EmptyNode);
233 }
234 state = ParserState::Domain;
235 node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
236 buf.clear();
237 }
238 '/' => {
239 if buf == "" {
240 return Err(JidParseError::NoDomain);
241 }
242 state = ParserState::Resource;
243 domain = Some(buf.clone()); // TODO: performance tweaks
244 buf.clear();
245 }
246 c => {
247 buf.push(c);
248 }
249 }
250 }
251 ParserState::Domain => {
252 match c {
253 '/' => {
254 if buf == "" {
255 return Err(JidParseError::NoDomain);
256 }
257 state = ParserState::Resource;
258 domain = Some(buf.clone()); // TODO: performance tweaks
259 buf.clear();
260 }
261 c => {
262 buf.push(c);
263 }
264 }
265 }
266 ParserState::Resource => {
267 buf.push(c);
268 }
269 }
270 }
271 if !buf.is_empty() {
272 match state {
273 ParserState::Node => {
274 domain = Some(buf);
275 }
276 ParserState::Domain => {
277 domain = Some(buf);
278 }
279 ParserState::Resource => {
280 resource = Some(buf);
281 }
282 }
283 } else if let ParserState::Resource = state {
284 return Err(JidParseError::EmptyResource);
285 }
286 Ok((node, domain.ok_or(JidParseError::NoDomain)?, resource))
287}
288
289impl FromStr for FullJid {
290 type Err = JidParseError;
291
292 fn from_str(s: &str) -> Result<FullJid, JidParseError> {
293 let (ns, ds, rs): StringJid = _from_str(s)?;
294 Ok(FullJid {
295 node: ns,
296 domain: ds,
297 resource: rs.ok_or(JidParseError::NoResource)?,
298 })
299 }
300}
301
302impl FullJid {
303 /// Constructs a full Jabber ID containing all three components.
304 ///
305 /// This is of the form `node`@`domain`/`resource`.
306 ///
307 /// # Examples
308 ///
309 /// ```
310 /// use jid::FullJid;
311 ///
312 /// let jid = FullJid::new("node", "domain", "resource");
313 ///
314 /// assert_eq!(jid.node, Some("node".to_owned()));
315 /// assert_eq!(jid.domain, "domain".to_owned());
316 /// assert_eq!(jid.resource, "resource".to_owned());
317 /// ```
318 pub fn new<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> FullJid
319 where
320 NS: Into<String>,
321 DS: Into<String>,
322 RS: Into<String>,
323 {
324 FullJid {
325 node: Some(node.into()),
326 domain: domain.into(),
327 resource: resource.into(),
328 }
329 }
330
331 /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
332 ///
333 /// # Examples
334 ///
335 /// ```
336 /// use jid::FullJid;
337 ///
338 /// let jid = FullJid::new("node", "domain", "resource");
339 ///
340 /// assert_eq!(jid.node, Some("node".to_owned()));
341 ///
342 /// let new_jid = jid.with_node("new_node");
343 ///
344 /// assert_eq!(new_jid.node, Some("new_node".to_owned()));
345 /// ```
346 pub fn with_node<NS>(&self, node: NS) -> FullJid
347 where
348 NS: Into<String>,
349 {
350 FullJid {
351 node: Some(node.into()),
352 domain: self.domain.clone(),
353 resource: self.resource.clone(),
354 }
355 }
356
357 /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
358 ///
359 /// # Examples
360 ///
361 /// ```
362 /// use jid::FullJid;
363 ///
364 /// let jid = FullJid::new("node", "domain", "resource");
365 ///
366 /// assert_eq!(jid.domain, "domain".to_owned());
367 ///
368 /// let new_jid = jid.with_domain("new_domain");
369 ///
370 /// assert_eq!(new_jid.domain, "new_domain");
371 /// ```
372 pub fn with_domain<DS>(&self, domain: DS) -> FullJid
373 where
374 DS: Into<String>,
375 {
376 FullJid {
377 node: self.node.clone(),
378 domain: domain.into(),
379 resource: self.resource.clone(),
380 }
381 }
382
383 /// Constructs a full Jabber ID from a bare Jabber ID, specifying a `resource`.
384 ///
385 /// # Examples
386 ///
387 /// ```
388 /// use jid::FullJid;
389 ///
390 /// let jid = FullJid::new("node", "domain", "resource");
391 ///
392 /// assert_eq!(jid.resource, "resource".to_owned());
393 ///
394 /// let new_jid = jid.with_resource("new_resource");
395 ///
396 /// assert_eq!(new_jid.resource, "new_resource");
397 /// ```
398 pub fn with_resource<RS>(&self, resource: RS) -> FullJid
399 where
400 RS: Into<String>,
401 {
402 FullJid {
403 node: self.node.clone(),
404 domain: self.domain.clone(),
405 resource: resource.into(),
406 }
407 }
408}
409
410impl FromStr for BareJid {
411 type Err = JidParseError;
412
413 fn from_str(s: &str) -> Result<BareJid, JidParseError> {
414 let (ns, ds, _rs): StringJid = _from_str(s)?;
415 Ok(BareJid {
416 node: ns,
417 domain: ds,
418 })
419 }
420}
421
422impl BareJid {
423 /// Constructs a bare Jabber ID, containing two components.
424 ///
425 /// This is of the form `node`@`domain`.
426 ///
427 /// # Examples
428 ///
429 /// ```
430 /// use jid::BareJid;
431 ///
432 /// let jid = BareJid::new("node", "domain");
433 ///
434 /// assert_eq!(jid.node, Some("node".to_owned()));
435 /// assert_eq!(jid.domain, "domain".to_owned());
436 /// ```
437 pub fn new<NS, DS>(node: NS, domain: DS) -> BareJid
438 where
439 NS: Into<String>,
440 DS: Into<String>,
441 {
442 BareJid {
443 node: Some(node.into()),
444 domain: domain.into(),
445 }
446 }
447
448 /// Constructs a bare Jabber ID containing only a `domain`.
449 ///
450 /// This is of the form `domain`.
451 ///
452 /// # Examples
453 ///
454 /// ```
455 /// use jid::BareJid;
456 ///
457 /// let jid = BareJid::domain("domain");
458 ///
459 /// assert_eq!(jid.node, None);
460 /// assert_eq!(jid.domain, "domain".to_owned());
461 /// ```
462 pub fn domain<DS>(domain: DS) -> BareJid
463 where
464 DS: Into<String>,
465 {
466 BareJid {
467 node: None,
468 domain: domain.into(),
469 }
470 }
471
472 /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
473 ///
474 /// # Examples
475 ///
476 /// ```
477 /// use jid::BareJid;
478 ///
479 /// let jid = BareJid::domain("domain");
480 ///
481 /// assert_eq!(jid.node, None);
482 ///
483 /// let new_jid = jid.with_node("node");
484 ///
485 /// assert_eq!(new_jid.node, Some("node".to_owned()));
486 /// ```
487 pub fn with_node<NS>(&self, node: NS) -> BareJid
488 where
489 NS: Into<String>,
490 {
491 BareJid {
492 node: Some(node.into()),
493 domain: self.domain.clone(),
494 }
495 }
496
497 /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
498 ///
499 /// # Examples
500 ///
501 /// ```
502 /// use jid::BareJid;
503 ///
504 /// let jid = BareJid::domain("domain");
505 ///
506 /// assert_eq!(jid.domain, "domain");
507 ///
508 /// let new_jid = jid.with_domain("new_domain");
509 ///
510 /// assert_eq!(new_jid.domain, "new_domain");
511 /// ```
512 pub fn with_domain<DS>(&self, domain: DS) -> BareJid
513 where
514 DS: Into<String>,
515 {
516 BareJid {
517 node: self.node.clone(),
518 domain: domain.into(),
519 }
520 }
521
522 /// Constructs a full Jabber ID from a bare Jabber ID, specifying a `resource`.
523 ///
524 /// # Examples
525 ///
526 /// ```
527 /// use jid::BareJid;
528 ///
529 /// let bare = BareJid::new("node", "domain");
530 /// let full = bare.with_resource("resource");
531 ///
532 /// assert_eq!(full.node, Some("node".to_owned()));
533 /// assert_eq!(full.domain, "domain".to_owned());
534 /// assert_eq!(full.resource, "resource".to_owned());
535 /// ```
536 pub fn with_resource<RS>(self, resource: RS) -> FullJid
537 where
538 RS: Into<String>,
539 {
540 FullJid {
541 node: self.node,
542 domain: self.domain,
543 resource: resource.into(),
544 }
545 }
546}
547
548#[cfg(feature = "minidom")]
549use minidom::{IntoAttributeValue, Node};
550
551#[cfg(feature = "minidom")]
552impl IntoAttributeValue for Jid {
553 fn into_attribute_value(self) -> Option<String> {
554 Some(String::from(self))
555 }
556}
557
558#[cfg(feature = "minidom")]
559impl Into<Node> for Jid {
560 fn into(self) -> Node {
561 Node::Text(String::from(self))
562 }
563}
564
565#[cfg(feature = "minidom")]
566impl IntoAttributeValue for FullJid {
567 fn into_attribute_value(self) -> Option<String> {
568 Some(String::from(self))
569 }
570}
571
572#[cfg(feature = "minidom")]
573impl Into<Node> for FullJid {
574 fn into(self) -> Node {
575 Node::Text(String::from(self))
576 }
577}
578
579#[cfg(feature = "minidom")]
580impl IntoAttributeValue for BareJid {
581 fn into_attribute_value(self) -> Option<String> {
582 Some(String::from(self))
583 }
584}
585
586#[cfg(feature = "minidom")]
587impl Into<Node> for BareJid {
588 fn into(self) -> Node {
589 Node::Text(String::from(self))
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596
597 use std::str::FromStr;
598
599 #[test]
600 fn can_parse_full_jids() {
601 assert_eq!(
602 FullJid::from_str("a@b.c/d"),
603 Ok(FullJid::new("a", "b.c", "d"))
604 );
605 assert_eq!(
606 FullJid::from_str("b.c/d"),
607 Ok(FullJid {
608 node: None,
609 domain: "b.c".to_owned(),
610 resource: "d".to_owned(),
611 })
612 );
613
614 assert_eq!(FullJid::from_str("a@b.c"), Err(JidParseError::NoResource));
615 assert_eq!(FullJid::from_str("b.c"), Err(JidParseError::NoResource));
616 }
617
618 #[test]
619 fn can_parse_bare_jids() {
620 assert_eq!(BareJid::from_str("a@b.c/d"), Ok(BareJid::new("a", "b.c")));
621 assert_eq!(
622 BareJid::from_str("b.c/d"),
623 Ok(BareJid {
624 node: None,
625 domain: "b.c".to_owned(),
626 })
627 );
628
629 assert_eq!(BareJid::from_str("a@b.c"), Ok(BareJid::new("a", "b.c")));
630 assert_eq!(
631 BareJid::from_str("b.c"),
632 Ok(BareJid {
633 node: None,
634 domain: "b.c".to_owned(),
635 })
636 );
637 }
638
639 #[test]
640 fn can_parse_jids() {
641 let full = FullJid::from_str("a@b.c/d").unwrap();
642 let bare = BareJid::from_str("e@f.g").unwrap();
643
644 assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::Full(full)));
645 assert_eq!(Jid::from_str("e@f.g"), Ok(Jid::Bare(bare)));
646 }
647
648 #[test]
649 fn full_to_bare_jid() {
650 let bare: BareJid = FullJid::new("a", "b.c", "d").into();
651 assert_eq!(bare, BareJid::new("a", "b.c"));
652 }
653
654 #[test]
655 fn bare_to_full_jid() {
656 assert_eq!(
657 BareJid::new("a", "b.c").with_resource("d"),
658 FullJid::new("a", "b.c", "d")
659 );
660 }
661
662 #[test]
663 fn node_from_jid() {
664 assert_eq!(
665 Jid::Full(FullJid::new("a", "b.c", "d")).node(),
666 Some(String::from("a")),
667 );
668 }
669
670 #[test]
671 fn domain_from_jid() {
672 assert_eq!(
673 Jid::Bare(BareJid::new("a", "b.c")).domain(),
674 String::from("b.c"),
675 );
676 }
677
678 #[test]
679 fn serialise() {
680 assert_eq!(
681 String::from(FullJid::new("a", "b", "c")),
682 String::from("a@b/c")
683 );
684 assert_eq!(String::from(BareJid::new("a", "b")), String::from("a@b"));
685 }
686
687 #[test]
688 fn invalid_jids() {
689 assert_eq!(BareJid::from_str(""), Err(JidParseError::NoDomain));
690 assert_eq!(BareJid::from_str("/c"), Err(JidParseError::NoDomain));
691 assert_eq!(BareJid::from_str("a@/c"), Err(JidParseError::NoDomain));
692 assert_eq!(BareJid::from_str("@b"), Err(JidParseError::EmptyNode));
693 assert_eq!(BareJid::from_str("b/"), Err(JidParseError::EmptyResource));
694
695 assert_eq!(FullJid::from_str(""), Err(JidParseError::NoDomain));
696 assert_eq!(FullJid::from_str("/c"), Err(JidParseError::NoDomain));
697 assert_eq!(FullJid::from_str("a@/c"), Err(JidParseError::NoDomain));
698 assert_eq!(FullJid::from_str("@b"), Err(JidParseError::EmptyNode));
699 assert_eq!(FullJid::from_str("b/"), Err(JidParseError::EmptyResource));
700 assert_eq!(FullJid::from_str("a@b"), Err(JidParseError::NoResource));
701 }
702
703 #[cfg(feature = "minidom")]
704 #[test]
705 fn minidom() {
706 let elem: minidom::Element = "<message from='a@b/c'/>".parse().unwrap();
707 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
708 assert_eq!(to, Jid::Full(FullJid::new("a", "b", "c")));
709
710 let elem: minidom::Element = "<message from='a@b'/>".parse().unwrap();
711 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
712 assert_eq!(to, Jid::Bare(BareJid::new("a", "b")));
713
714 let elem: minidom::Element = "<message from='a@b/c'/>".parse().unwrap();
715 let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
716 assert_eq!(to, FullJid::new("a", "b", "c"));
717
718 let elem: minidom::Element = "<message from='a@b'/>".parse().unwrap();
719 let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
720 assert_eq!(to, BareJid::new("a", "b"));
721 }
722
723 #[cfg(feature = "minidom")]
724 #[test]
725 fn minidom_into_attr() {
726 let full = FullJid::new("a", "b", "c");
727 let elem = minidom::Element::builder("message")
728 .ns("jabber:client")
729 .attr("from", full.clone())
730 .build();
731 assert_eq!(elem.attr("from"), Some(String::from(full).as_ref()));
732
733 let bare = BareJid::new("a", "b");
734 let elem = minidom::Element::builder("message")
735 .ns("jabber:client")
736 .attr("from", bare.clone())
737 .build();
738 assert_eq!(elem.attr("from"), Some(String::from(bare.clone()).as_ref()));
739
740 let jid = Jid::Bare(bare.clone());
741 let _elem = minidom::Element::builder("message")
742 .ns("jabber:client")
743 .attr("from", jid)
744 .build();
745 assert_eq!(elem.attr("from"), Some(String::from(bare).as_ref()));
746 }
747}