1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// Copyright (c) 2024 xmpp-rs contributors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

//! This module implements vCard, for the purpose of vCard-based avatars as defined in
//! [XEP-0054](https://xmpp.org/extensions/xep-0054.html).
//!
//! Only the `<PHOTO>` element is supported as a member of this legacy vCard. For more modern and complete
//! user profile management, see [XEP-0292](https://xmpp.org/extensions/xep-0292.html): vCard4 Over XMPP.
//!
//! For vCard updates defined in [XEP-0153](https://xmpp.org/extensions/xep-0153.html),
//! see [`vcard_update`][crate::vcard_update] module.
use crate::core::{Base64, FromXml, IntoXml, StripWhitespace};
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
use crate::ns::VCARD;

/// A photo element.
#[derive(FromXml, IntoXml, Debug, PartialEq, Clone)]
#[xml(namespace = VCARD, name = "PHOTO")]
pub struct Photo {
    /// The type of the photo.
    #[xml(child(namespace = VCARD, name = "TYPE", extract(text)))]
    pub type_: String,

    /// The binary data of the photo.
    #[xml(child(namespace = VCARD, name = "BINVAL", extract(text(codec = Base64<StripWhitespace>))))]
    pub binval: Vec<u8>,
}

/// A `<vCard>` element; only the `<PHOTO>` element is supported for this legacy vCard ; the rest is ignored.
#[derive(FromXml, IntoXml, Debug, PartialEq, Clone)]
#[xml(namespace = VCARD, name = "vCard", on_unknown_child = Ignore)]
pub struct VCard {
    /// A photo element.
    #[xml(child(default))]
    pub photo: Option<Photo>,
}

impl IqGetPayload for VCard {}
impl IqSetPayload for VCard {}
impl IqResultPayload for VCard {}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Element;
    use base64::Engine;
    use std::str::FromStr;

    #[test]
    fn test_vcard() {
        // Create some bytes:
        let bytes = [0u8, 1, 2, 129];

        // Test xml stolen from https://xmpp.org/extensions/xep-0153.html#example-5
        let test_vcard = format!(
            r"<vCard xmlns='vcard-temp'>
    <BDAY>1476-06-09</BDAY>
    <ADR>
      <CTRY>Italy</CTRY>
      <LOCALITY>Verona</LOCALITY>
      <HOME/>
    </ADR>
    <NICKNAME/>
    <N><GIVEN>Juliet</GIVEN><FAMILY>Capulet</FAMILY></N>
    <EMAIL>jcapulet@shakespeare.lit</EMAIL>
    <PHOTO>
      <TYPE>image/jpeg</TYPE>
      <BINVAL>{}</BINVAL>
    </PHOTO>
  </vCard>",
            base64::prelude::BASE64_STANDARD.encode(&bytes)
        );

        let test_vcard = Element::from_str(&test_vcard).expect("Failed to parse XML");
        let test_vcard = VCard::try_from(test_vcard).expect("Failed to parse vCard");

        let photo = test_vcard.photo.expect("No photo found");

        assert_eq!(photo.type_, "image/jpeg".to_string());
        assert_eq!(photo.binval, bytes);
    }

    #[test]
    fn test_vcard_with_linebreaks() {
        // Test xml stolen from https://xmpp.org/extensions/xep-0153.html#example-5
        // extended to use a multi-line base64 string as is allowed as per RFC 2426
        let test_vcard = r"<vCard xmlns='vcard-temp'>
    <BDAY>1476-06-09</BDAY>
    <ADR>
      <CTRY>Italy</CTRY>
      <LOCALITY>Verona</LOCALITY>
      <HOME/>
    </ADR>
    <NICKNAME/>
    <N><GIVEN>Juliet</GIVEN><FAMILY>Capulet</FAMILY></N>
    <EMAIL>jcapulet@shakespeare.lit</EMAIL>
    <PHOTO>
      <TYPE>image/jpeg</TYPE>
      <BINVAL>Zm9v
Cg==</BINVAL>
    </PHOTO>
  </vCard>";

        let test_vcard = Element::from_str(&test_vcard).expect("Failed to parse XML");
        let test_vcard = VCard::try_from(test_vcard).expect("Failed to parse vCard");

        let photo = test_vcard.photo.expect("No photo found");

        assert_eq!(photo.type_, "image/jpeg".to_string());
        assert_eq!(photo.binval, b"foo\n");
    }
}