xmpp_parsers/
http_upload.rs1use xso::{error::Error, AsXml, AsXmlText, FromXml, FromXmlText};
8
9use crate::iq::{IqGetPayload, IqResultPayload};
10use crate::ns;
11use alloc::borrow::Cow;
12
13#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
15#[xml(namespace = ns::HTTP_UPLOAD, name = "request")]
16pub struct SlotRequest {
17 #[xml(attribute)]
19 pub filename: String,
20
21 #[xml(attribute)]
23 pub size: u64,
24
25 #[xml(attribute(name = "content-type"))]
27 pub content_type: Option<String>,
28}
29
30impl IqGetPayload for SlotRequest {}
31
32#[derive(Debug, Clone, PartialEq)]
34pub enum HeaderName {
35 Authorization,
37
38 Cookie,
40
41 Expires,
43}
44
45impl HeaderName {
46 pub fn as_str(&self) -> &'static str {
48 match self {
49 HeaderName::Authorization => "Authorization",
50 HeaderName::Cookie => "Cookie",
51 HeaderName::Expires => "Expires",
52 }
53 }
54}
55
56impl FromXmlText for HeaderName {
57 fn from_xml_text(mut s: String) -> Result<Self, Error> {
58 s.make_ascii_lowercase();
59 Ok(match s.as_str() {
60 "authorization" => HeaderName::Authorization,
61 "cookie" => HeaderName::Cookie,
62 "expires" => HeaderName::Expires,
63 _ => {
64 return Err(Error::Other(
65 "Header name must be either 'Authorization', 'Cookie', or 'Expires'.",
66 ))
67 }
68 })
69 }
70}
71
72impl AsXmlText for HeaderName {
73 fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
74 Ok(Cow::Borrowed(self.as_str()))
75 }
76}
77
78#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
80#[xml(namespace = ns::HTTP_UPLOAD, name = "header")]
81pub struct Header {
82 #[xml(attribute)]
84 pub name: HeaderName,
85
86 #[xml(text)]
88 pub value: String,
89}
90
91#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
93#[xml(namespace = ns::HTTP_UPLOAD, name = "put")]
94pub struct Put {
95 #[xml(attribute)]
97 pub url: String,
98
99 #[xml(child(n = ..))]
101 pub headers: Vec<Header>,
102}
103
104#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
106#[xml(namespace = ns::HTTP_UPLOAD, name = "get")]
107pub struct Get {
108 #[xml(attribute)]
110 pub url: String,
111}
112
113#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
115#[xml(namespace = ns::HTTP_UPLOAD, name = "slot")]
116pub struct SlotResult {
117 #[xml(child)]
119 pub put: Put,
120
121 #[xml(child)]
123 pub get: Get,
124}
125
126impl IqResultPayload for SlotResult {}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use minidom::Element;
132
133 #[cfg(target_pointer_width = "32")]
134 #[test]
135 fn test_size() {
136 assert_size!(SlotRequest, 32);
137 assert_size!(HeaderName, 1);
138 assert_size!(Header, 16);
139 assert_size!(Put, 24);
140 assert_size!(Get, 12);
141 assert_size!(SlotResult, 36);
142 }
143
144 #[cfg(target_pointer_width = "64")]
145 #[test]
146 fn test_size() {
147 assert_size!(SlotRequest, 56);
148 assert_size!(HeaderName, 1);
149 assert_size!(Header, 32);
150 assert_size!(Put, 48);
151 assert_size!(Get, 24);
152 assert_size!(SlotResult, 72);
153 }
154
155 #[test]
156 fn test_slot_request() {
157 let elem: Element = "<request xmlns='urn:xmpp:http:upload:0'
158 filename='très cool.jpg'
159 size='23456'
160 content-type='image/jpeg' />"
161 .parse()
162 .unwrap();
163 let slot = SlotRequest::try_from(elem).unwrap();
164 assert_eq!(slot.filename, String::from("très cool.jpg"));
165 assert_eq!(slot.size, 23456);
166 assert_eq!(slot.content_type, Some(String::from("image/jpeg")));
167 }
168
169 #[test]
170 fn test_slot_result() {
171 let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
172 <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg'>
173 <header name='Authorization'>Basic Base64String==</header>
174 <header name='Cookie'>foo=bar; user=romeo</header>
175 </put>
176 <get url='https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg' />
177 </slot>"
178 .parse()
179 .unwrap();
180 let slot = SlotResult::try_from(elem).unwrap();
181 assert_eq!(slot.put.url, String::from("https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg"));
182 assert_eq!(
183 slot.put.headers[0],
184 Header {
185 name: HeaderName::Authorization,
186 value: String::from("Basic Base64String==")
187 }
188 );
189 assert_eq!(
190 slot.put.headers[1],
191 Header {
192 name: HeaderName::Cookie,
193 value: String::from("foo=bar; user=romeo")
194 }
195 );
196 assert_eq!(slot.get.url, String::from("https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg"));
197 }
198
199 #[test]
200 fn test_result_no_header() {
201 let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
202 <put url='https://URL' />
203 <get url='https://URL' />
204 </slot>"
205 .parse()
206 .unwrap();
207 let slot = SlotResult::try_from(elem).unwrap();
208 assert_eq!(slot.put.url, String::from("https://URL"));
209 assert_eq!(slot.put.headers.len(), 0);
210 assert_eq!(slot.get.url, String::from("https://URL"));
211 }
212
213 #[test]
214 fn test_result_bad_header() {
215 let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
216 <put url='https://URL'>
217 <header name='EvilHeader'>EvilValue</header>
218 </put>
219 <get url='https://URL' />
220 </slot>"
221 .parse()
222 .unwrap();
223 SlotResult::try_from(elem).unwrap_err();
224 }
225}