vcard.rs

  1// Copyright (c) 2024 xmpp-rs contributors.
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7//! This module implements vCard, for the purpose of vCard-based avatars as defined in
  8//! [XEP-0054](https://xmpp.org/extensions/xep-0054.html).
  9//!
 10//! Only the `<PHOTO>` element is supported as a member of this legacy vCard. For more modern and complete
 11//! user profile management, see [XEP-0292](https://xmpp.org/extensions/xep-0292.html): vCard4 Over XMPP.
 12//!
 13//! For vCard updates defined in [XEP-0153](https://xmpp.org/extensions/xep-0153.html),
 14//! see [`vcard_update`][crate::vcard_update] module.
 15
 16use xso::{
 17    text::{Base64, StripWhitespace, TextCodec},
 18    AsXml, FromXml,
 19};
 20
 21use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
 22use crate::ns;
 23use minidom::Element;
 24
 25/// A photo element.
 26#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 27#[xml(namespace = ns::VCARD, name = "PHOTO")]
 28pub struct Photo {
 29    /// The type of the photo.
 30    #[xml(child)]
 31    pub type_: Type,
 32
 33    /// The binary data of the photo.
 34    #[xml(child)]
 35    pub binval: Binval,
 36}
 37
 38/// The type of the photo.
 39#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 40#[xml(namespace = ns::VCARD, name = "TYPE")]
 41pub struct Type {
 42    /// The type as a plain text string; at least "image/jpeg", "image/gif" and "image/png" SHOULD be supported.
 43    #[xml(text)]
 44    pub data: String,
 45}
 46
 47/// The binary data of the photo.
 48#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 49#[xml(namespace = ns::VCARD, name = "BINVAL")]
 50pub struct Binval {
 51    /// The actual data.
 52    #[xml(text(codec = Base64.filtered(StripWhitespace)))]
 53    pub data: Vec<u8>,
 54}
 55
 56/// A `<vCard>` element; only the `<PHOTO>` element is supported for this legacy vCard ; the rest is ignored.
 57#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 58#[xml(namespace = ns::VCARD, name = "vCard")]
 59pub struct VCard {
 60    /// A photo element.
 61    #[xml(child(default))]
 62    pub photo: Option<Photo>,
 63
 64    /// Every other element in the vCard.
 65    #[xml(element(n = ..))]
 66    pub payloads: Vec<Element>,
 67}
 68
 69impl IqSetPayload for VCard {}
 70impl IqResultPayload for VCard {}
 71
 72/// A `<vCard/>` request element, for use in iq get.
 73#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
 74#[xml(namespace = ns::VCARD, name = "vCard")]
 75pub struct VCardQuery;
 76
 77impl IqGetPayload for VCardQuery {}
 78
 79#[cfg(test)]
 80mod tests {
 81    use super::*;
 82    use core::str::FromStr;
 83
 84    #[cfg(target_pointer_width = "32")]
 85    #[test]
 86    fn test_size() {
 87        assert_size!(Photo, 24);
 88        assert_size!(Type, 12);
 89        assert_size!(Binval, 12);
 90        assert_size!(VCard, 36);
 91        assert_size!(VCardQuery, 0);
 92    }
 93
 94    #[cfg(target_pointer_width = "64")]
 95    #[test]
 96    fn test_size() {
 97        assert_size!(Photo, 48);
 98        assert_size!(Type, 24);
 99        assert_size!(Binval, 24);
100        assert_size!(VCard, 72);
101        assert_size!(VCardQuery, 0);
102    }
103
104    #[test]
105    fn test_vcard() {
106        // Create some bytes:
107        let bytes = [0u8, 1, 2, 129];
108
109        // Test xml stolen from https://xmpp.org/extensions/xep-0153.html#example-5
110        let test_vcard = format!(
111            r"<vCard xmlns='vcard-temp'>
112    <BDAY>1476-06-09</BDAY>
113    <ADR>
114      <CTRY>Italy</CTRY>
115      <LOCALITY>Verona</LOCALITY>
116      <HOME/>
117    </ADR>
118    <NICKNAME/>
119    <N><GIVEN>Juliet</GIVEN><FAMILY>Capulet</FAMILY></N>
120    <EMAIL>jcapulet@shakespeare.lit</EMAIL>
121    <PHOTO>
122      <TYPE>image/jpeg</TYPE>
123      <BINVAL>{}</BINVAL>
124    </PHOTO>
125  </vCard>",
126            base64::Engine::encode(&base64::prelude::BASE64_STANDARD, &bytes)
127        );
128
129        let test_vcard = Element::from_str(&test_vcard).expect("Failed to parse XML");
130        let test_vcard = VCard::try_from(test_vcard).expect("Failed to parse vCard");
131
132        let photo = test_vcard.photo.expect("No photo found");
133
134        assert_eq!(photo.type_.data, "image/jpeg".to_string());
135        assert_eq!(photo.binval.data, bytes);
136    }
137
138    #[test]
139    fn test_vcard_with_linebreaks() {
140        // Test xml stolen from https://xmpp.org/extensions/xep-0153.html#example-5
141        // extended to use a multi-line base64 string as is allowed as per RFC 2426
142        let test_vcard = r"<vCard xmlns='vcard-temp'>
143    <BDAY>1476-06-09</BDAY>
144    <ADR>
145      <CTRY>Italy</CTRY>
146      <LOCALITY>Verona</LOCALITY>
147      <HOME/>
148    </ADR>
149    <NICKNAME/>
150    <N><GIVEN>Juliet</GIVEN><FAMILY>Capulet</FAMILY></N>
151    <EMAIL>jcapulet@shakespeare.lit</EMAIL>
152    <PHOTO>
153      <TYPE>image/jpeg</TYPE>
154      <BINVAL>Zm9v
155Cg==</BINVAL>
156    </PHOTO>
157  </vCard>";
158
159        let test_vcard = Element::from_str(&test_vcard).expect("Failed to parse XML");
160        let test_vcard = VCard::try_from(test_vcard).expect("Failed to parse vCard");
161
162        let photo = test_vcard.photo.expect("No photo found");
163
164        assert_eq!(photo.type_.data, "image/jpeg".to_string());
165        assert_eq!(photo.binval.data, b"foo\n");
166    }
167}