http_upload.rs

  1// Copyright (c) 2021 Maxime “pep” Buquet <pep@bouah.net>
  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::iq::{IqGetPayload, IqResultPayload};
  8use crate::ns;
  9use crate::util::error::Error;
 10use crate::Element;
 11
 12generate_element!(
 13    /// Requesting a slot
 14    SlotRequest, "request", HTTP_UPLOAD,
 15    attributes: [
 16        /// The filename to be uploaded.
 17        filename: Required<String> = "filename",
 18
 19        /// Size of the file to be uploaded.
 20        size: Required<u64> = "size",
 21
 22        /// Content-Type of the file.
 23        content_type: Option<String> = "content-type",
 24    ]
 25);
 26
 27impl IqGetPayload for SlotRequest {}
 28
 29/// Slot header
 30#[derive(Debug, Clone, PartialEq)]
 31pub enum Header {
 32    /// Authorization header
 33    Authorization(String),
 34
 35    /// Cookie header
 36    Cookie(String),
 37
 38    /// Expires header
 39    Expires(String),
 40}
 41
 42impl TryFrom<Element> for Header {
 43    type Error = Error;
 44    fn try_from(elem: Element) -> Result<Header, Error> {
 45        check_self!(elem, "header", HTTP_UPLOAD);
 46        check_no_children!(elem, "header");
 47        check_no_unknown_attributes!(elem, "header", ["name"]);
 48        let name: String = get_attr!(elem, "name", Required);
 49        let text = elem.text();
 50
 51        Ok(match name.to_lowercase().as_str() {
 52            "authorization" => Header::Authorization(text),
 53            "cookie" => Header::Cookie(text),
 54            "expires" => Header::Expires(text),
 55            _ => {
 56                return Err(Error::ParseError(
 57                    "Header name must be either 'Authorization', 'Cookie', or 'Expires'.",
 58                ))
 59            }
 60        })
 61    }
 62}
 63
 64impl From<Header> for Element {
 65    fn from(elem: Header) -> Element {
 66        let (attr, val) = match elem {
 67            Header::Authorization(val) => ("Authorization", val),
 68            Header::Cookie(val) => ("Cookie", val),
 69            Header::Expires(val) => ("Expires", val),
 70        };
 71
 72        Element::builder("header", ns::HTTP_UPLOAD)
 73            .attr("name", attr)
 74            .append(val)
 75            .build()
 76    }
 77}
 78
 79generate_element!(
 80    /// Put URL
 81    Put, "put", HTTP_UPLOAD,
 82    attributes: [
 83        /// URL
 84        url: Required<String> = "url",
 85    ],
 86    children: [
 87        /// Header list
 88        headers: Vec<Header> = ("header", HTTP_UPLOAD) => Header
 89    ]
 90);
 91
 92generate_element!(
 93    /// Get URL
 94    Get, "get", HTTP_UPLOAD,
 95    attributes: [
 96        /// URL
 97        url: Required<String> = "url",
 98    ]
 99);
100
101generate_element!(
102    /// Requesting a slot
103    SlotResult, "slot", HTTP_UPLOAD,
104    children: [
105        /// Put URL and headers
106        put: Required<Put> = ("put", HTTP_UPLOAD) => Put,
107        /// Get URL
108        get: Required<Get> = ("get", HTTP_UPLOAD) => Get
109    ]
110);
111
112impl IqResultPayload for SlotResult {}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_slot_request() {
120        let elem: Element = "<request xmlns='urn:xmpp:http:upload:0'
121            filename='très cool.jpg'
122            size='23456'
123            content-type='image/jpeg' />"
124            .parse()
125            .unwrap();
126        let slot = SlotRequest::try_from(elem).unwrap();
127        assert_eq!(slot.filename, String::from("très cool.jpg"));
128        assert_eq!(slot.size, 23456);
129        assert_eq!(slot.content_type, Some(String::from("image/jpeg")));
130    }
131
132    #[test]
133    fn test_slot_result() {
134        let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
135            <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg'>
136              <header name='Authorization'>Basic Base64String==</header>
137              <header name='Cookie'>foo=bar; user=romeo</header>
138            </put>
139            <get url='https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg' />
140          </slot>"
141            .parse()
142            .unwrap();
143        let slot = SlotResult::try_from(elem).unwrap();
144        assert_eq!(slot.put.url, String::from("https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg"));
145        assert_eq!(
146            slot.put.headers[0],
147            Header::Authorization(String::from("Basic Base64String=="))
148        );
149        assert_eq!(
150            slot.put.headers[1],
151            Header::Cookie(String::from("foo=bar; user=romeo"))
152        );
153        assert_eq!(slot.get.url, String::from("https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg"));
154    }
155
156    #[test]
157    fn test_result_no_header() {
158        let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
159            <put url='https://URL' />
160            <get url='https://URL' />
161          </slot>"
162            .parse()
163            .unwrap();
164        let slot = SlotResult::try_from(elem).unwrap();
165        assert_eq!(slot.put.url, String::from("https://URL"));
166        assert_eq!(slot.put.headers.len(), 0);
167        assert_eq!(slot.get.url, String::from("https://URL"));
168    }
169
170    #[test]
171    fn test_result_bad_header() {
172        let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
173            <put url='https://URL'>
174              <header name='EvilHeader'>EvilValue</header>
175            </put>
176            <get url='https://URL' />
177          </slot>"
178            .parse()
179            .unwrap();
180        SlotResult::try_from(elem).unwrap_err();
181    }
182}