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}