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::Element;
 10use xso::error::{Error, FromElementError};
 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 = FromElementError;
 44    fn try_from(elem: Element) -> Result<Header, FromElementError> {
 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::Other(
 57                    "Header name must be either 'Authorization', 'Cookie', or 'Expires'.",
 58                )
 59                .into())
 60            }
 61        })
 62    }
 63}
 64
 65impl From<Header> for Element {
 66    fn from(elem: Header) -> Element {
 67        let (attr, val) = match elem {
 68            Header::Authorization(val) => ("Authorization", val),
 69            Header::Cookie(val) => ("Cookie", val),
 70            Header::Expires(val) => ("Expires", val),
 71        };
 72
 73        Element::builder("header", ns::HTTP_UPLOAD)
 74            .attr("name", attr)
 75            .append(val)
 76            .build()
 77    }
 78}
 79
 80generate_element!(
 81    /// Put URL
 82    Put, "put", HTTP_UPLOAD,
 83    attributes: [
 84        /// URL
 85        url: Required<String> = "url",
 86    ],
 87    children: [
 88        /// Header list
 89        headers: Vec<Header> = ("header", HTTP_UPLOAD) => Header
 90    ]
 91);
 92
 93generate_element!(
 94    /// Get URL
 95    Get, "get", HTTP_UPLOAD,
 96    attributes: [
 97        /// URL
 98        url: Required<String> = "url",
 99    ]
100);
101
102generate_element!(
103    /// Requesting a slot
104    SlotResult, "slot", HTTP_UPLOAD,
105    children: [
106        /// Put URL and headers
107        put: Required<Put> = ("put", HTTP_UPLOAD) => Put,
108        /// Get URL
109        get: Required<Get> = ("get", HTTP_UPLOAD) => Get
110    ]
111);
112
113impl IqResultPayload for SlotResult {}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_slot_request() {
121        let elem: Element = "<request xmlns='urn:xmpp:http:upload:0'
122            filename='très cool.jpg'
123            size='23456'
124            content-type='image/jpeg' />"
125            .parse()
126            .unwrap();
127        let slot = SlotRequest::try_from(elem).unwrap();
128        assert_eq!(slot.filename, String::from("très cool.jpg"));
129        assert_eq!(slot.size, 23456);
130        assert_eq!(slot.content_type, Some(String::from("image/jpeg")));
131    }
132
133    #[test]
134    fn test_slot_result() {
135        let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
136            <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg'>
137              <header name='Authorization'>Basic Base64String==</header>
138              <header name='Cookie'>foo=bar; user=romeo</header>
139            </put>
140            <get url='https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg' />
141          </slot>"
142            .parse()
143            .unwrap();
144        let slot = SlotResult::try_from(elem).unwrap();
145        assert_eq!(slot.put.url, String::from("https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg"));
146        assert_eq!(
147            slot.put.headers[0],
148            Header::Authorization(String::from("Basic Base64String=="))
149        );
150        assert_eq!(
151            slot.put.headers[1],
152            Header::Cookie(String::from("foo=bar; user=romeo"))
153        );
154        assert_eq!(slot.get.url, String::from("https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg"));
155    }
156
157    #[test]
158    fn test_result_no_header() {
159        let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
160            <put url='https://URL' />
161            <get url='https://URL' />
162          </slot>"
163            .parse()
164            .unwrap();
165        let slot = SlotResult::try_from(elem).unwrap();
166        assert_eq!(slot.put.url, String::from("https://URL"));
167        assert_eq!(slot.put.headers.len(), 0);
168        assert_eq!(slot.get.url, String::from("https://URL"));
169    }
170
171    #[test]
172    fn test_result_bad_header() {
173        let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
174            <put url='https://URL'>
175              <header name='EvilHeader'>EvilValue</header>
176            </put>
177            <get url='https://URL' />
178          </slot>"
179            .parse()
180            .unwrap();
181        SlotResult::try_from(elem).unwrap_err();
182    }
183}