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