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