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}