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 use crate::Element;
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}