bob.rs

  1// Copyright (c) 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  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
  7use crate::hashes::{Algo, Hash};
  8use crate::util::error::Error;
  9use crate::util::helpers::Base64;
 10use minidom::IntoAttributeValue;
 11use std::str::FromStr;
 12
 13/// A Content-ID, as defined in RFC2111.
 14///
 15/// The text value SHOULD be of the form algo+hash@bob.xmpp.org, this struct
 16/// enforces that format.
 17#[derive(Clone, Debug, PartialEq)]
 18pub struct ContentId {
 19    hash: Hash,
 20}
 21
 22impl FromStr for ContentId {
 23    type Err = Error;
 24
 25    fn from_str(s: &str) -> Result<Self, Error> {
 26        let temp: Vec<_> = s.splitn(2, '@').collect();
 27        let temp: Vec<_> = match temp[..] {
 28            [lhs, rhs] => {
 29                if rhs != "bob.xmpp.org" {
 30                    return Err(Error::ParseError("Wrong domain for cid URI."));
 31                }
 32                lhs.splitn(2, '+').collect()
 33            }
 34            _ => return Err(Error::ParseError("Missing @ in cid URI.")),
 35        };
 36        let (algo, hex) = match temp[..] {
 37            [lhs, rhs] => {
 38                let algo = match lhs {
 39                    "sha1" => Algo::Sha_1,
 40                    "sha256" => Algo::Sha_256,
 41                    _ => unimplemented!(),
 42                };
 43                (algo, rhs)
 44            }
 45            _ => return Err(Error::ParseError("Missing + in cid URI.")),
 46        };
 47        let hash = Hash::from_hex(algo, hex)?;
 48        Ok(ContentId { hash })
 49    }
 50}
 51
 52impl IntoAttributeValue for ContentId {
 53    fn into_attribute_value(self) -> Option<String> {
 54        let algo = match self.hash.algo {
 55            Algo::Sha_1 => "sha1",
 56            Algo::Sha_256 => "sha256",
 57            _ => unimplemented!(),
 58        };
 59        Some(format!("{}+{}@bob.xmpp.org", algo, self.hash.to_hex()))
 60    }
 61}
 62
 63generate_element!(
 64    /// Request for an uncached cid file.
 65    Data, "data", BOB,
 66    attributes: [
 67        /// The cid in question.
 68        cid: Required<ContentId> = "cid",
 69
 70        /// How long to cache it (in seconds).
 71        max_age: Option<usize> = "max-age",
 72
 73        /// The MIME type of the data being transmitted.
 74        ///
 75        /// See the [IANA MIME Media Types Registry][1] for a list of
 76        /// registered types, but unregistered or yet-to-be-registered are
 77        /// accepted too.
 78        ///
 79        /// [1]: <https://www.iana.org/assignments/media-types/media-types.xhtml>
 80        type_: Option<String> = "type"
 81    ],
 82    text: (
 83        /// The actual data.
 84        data: Base64<Vec<u8>>
 85    )
 86);
 87
 88#[cfg(test)]
 89mod tests {
 90    use super::*;
 91    use crate::Element;
 92    use std::convert::TryFrom;
 93
 94    #[cfg(target_pointer_width = "32")]
 95    #[test]
 96    fn test_size() {
 97        assert_size!(ContentId, 28);
 98        assert_size!(Data, 60);
 99    }
100
101    #[cfg(target_pointer_width = "64")]
102    #[test]
103    fn test_size() {
104        assert_size!(ContentId, 56);
105        assert_size!(Data, 120);
106    }
107
108    #[test]
109    fn test_simple() {
110        let cid: ContentId = "sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org"
111            .parse()
112            .unwrap();
113        assert_eq!(cid.hash.algo, Algo::Sha_1);
114        assert_eq!(
115            cid.hash.hash,
116            b"\x8f\x35\xfe\xf1\x10\xff\xc5\xdf\x08\xd5\x79\xa5\x00\x83\xff\x93\x08\xfb\x62\x42"
117        );
118        assert_eq!(
119            cid.into_attribute_value().unwrap(),
120            "sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org"
121        );
122
123        let elem: Element = "<data xmlns='urn:xmpp:bob' cid='sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org'/>".parse().unwrap();
124        let data = Data::try_from(elem).unwrap();
125        assert_eq!(data.cid.hash.algo, Algo::Sha_1);
126        assert_eq!(
127            data.cid.hash.hash,
128            b"\x8f\x35\xfe\xf1\x10\xff\xc5\xdf\x08\xd5\x79\xa5\x00\x83\xff\x93\x08\xfb\x62\x42"
129        );
130        assert!(data.max_age.is_none());
131        assert!(data.type_.is_none());
132        assert!(data.data.is_empty());
133    }
134
135    #[test]
136    fn invalid_cid() {
137        let error = "Hello world!".parse::<ContentId>().unwrap_err();
138        let message = match error {
139            Error::ParseError(string) => string,
140            _ => panic!(),
141        };
142        assert_eq!(message, "Missing @ in cid URI.");
143
144        let error = "Hello world@bob.xmpp.org".parse::<ContentId>().unwrap_err();
145        let message = match error {
146            Error::ParseError(string) => string,
147            _ => panic!(),
148        };
149        assert_eq!(message, "Missing + in cid URI.");
150
151        let error = "sha1+1234@coucou.linkmauve.fr"
152            .parse::<ContentId>()
153            .unwrap_err();
154        let message = match error {
155            Error::ParseError(string) => string,
156            _ => panic!(),
157        };
158        assert_eq!(message, "Wrong domain for cid URI.");
159
160        let error = "sha1+invalid@bob.xmpp.org"
161            .parse::<ContentId>()
162            .unwrap_err();
163        let message = match error {
164            Error::ParseIntError(error) => error,
165            _ => panic!(),
166        };
167        assert_eq!(message.to_string(), "invalid digit found in string");
168    }
169
170    #[test]
171    fn unknown_child() {
172        let elem: Element = "<data xmlns='urn:xmpp:bob'><coucou/></data>"
173            .parse()
174            .unwrap();
175        let error = Data::try_from(elem).unwrap_err();
176        let message = match error {
177            Error::ParseError(string) => string,
178            _ => panic!(),
179        };
180        assert_eq!(message, "Unknown child in data element.");
181    }
182}