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