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