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