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
118 #[test]
119 fn test_slot_request() {
120 let elem: Element = "<request xmlns='urn:xmpp:http:upload:0'
121 filename='très cool.jpg'
122 size='23456'
123 content-type='image/jpeg' />"
124 .parse()
125 .unwrap();
126 let slot = SlotRequest::try_from(elem).unwrap();
127 assert_eq!(slot.filename, String::from("très cool.jpg"));
128 assert_eq!(slot.size, 23456);
129 assert_eq!(slot.content_type, Some(String::from("image/jpeg")));
130 }
131
132 #[test]
133 fn test_slot_result() {
134 let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
135 <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg'>
136 <header name='Authorization'>Basic Base64String==</header>
137 <header name='Cookie'>foo=bar; user=romeo</header>
138 </put>
139 <get url='https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg' />
140 </slot>"
141 .parse()
142 .unwrap();
143 let slot = SlotResult::try_from(elem).unwrap();
144 assert_eq!(slot.put.url, String::from("https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg"));
145 assert_eq!(
146 slot.put.headers[0],
147 Header::Authorization(String::from("Basic Base64String=="))
148 );
149 assert_eq!(
150 slot.put.headers[1],
151 Header::Cookie(String::from("foo=bar; user=romeo"))
152 );
153 assert_eq!(slot.get.url, String::from("https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg"));
154 }
155
156 #[test]
157 fn test_result_no_header() {
158 let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
159 <put url='https://URL' />
160 <get url='https://URL' />
161 </slot>"
162 .parse()
163 .unwrap();
164 let slot = SlotResult::try_from(elem).unwrap();
165 assert_eq!(slot.put.url, String::from("https://URL"));
166 assert_eq!(slot.put.headers.len(), 0);
167 assert_eq!(slot.get.url, String::from("https://URL"));
168 }
169
170 #[test]
171 fn test_result_bad_header() {
172 let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
173 <put url='https://URL'>
174 <header name='EvilHeader'>EvilValue</header>
175 </put>
176 <get url='https://URL' />
177 </slot>"
178 .parse()
179 .unwrap();
180 SlotResult::try_from(elem).unwrap_err();
181 }
182}