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 .into())
68 }
69 })
70 }
71}
72
73impl AsXmlText for HeaderName {
74 fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
75 Ok(Cow::Borrowed(self.as_str()))
76 }
77}
78
79#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
81#[xml(namespace = ns::HTTP_UPLOAD, name = "header")]
82pub struct Header {
83 #[xml(attribute)]
85 pub name: HeaderName,
86
87 #[xml(text)]
89 pub value: String,
90}
91
92#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
94#[xml(namespace = ns::HTTP_UPLOAD, name = "put")]
95pub struct Put {
96 #[xml(attribute)]
98 pub url: String,
99
100 #[xml(child(n = ..))]
102 pub headers: Vec<Header>,
103}
104
105#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
107#[xml(namespace = ns::HTTP_UPLOAD, name = "get")]
108pub struct Get {
109 #[xml(attribute)]
111 pub url: String,
112}
113
114#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
116#[xml(namespace = ns::HTTP_UPLOAD, name = "slot")]
117pub struct SlotResult {
118 #[xml(child)]
120 pub put: Put,
121
122 #[xml(child)]
124 pub get: Get,
125}
126
127impl IqResultPayload for SlotResult {}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use minidom::Element;
133
134 #[cfg(target_pointer_width = "32")]
135 #[test]
136 fn test_size() {
137 assert_size!(SlotRequest, 32);
138 assert_size!(HeaderName, 1);
139 assert_size!(Header, 16);
140 assert_size!(Put, 24);
141 assert_size!(Get, 12);
142 assert_size!(SlotResult, 36);
143 }
144
145 #[cfg(target_pointer_width = "64")]
146 #[test]
147 fn test_size() {
148 assert_size!(SlotRequest, 56);
149 assert_size!(HeaderName, 1);
150 assert_size!(Header, 32);
151 assert_size!(Put, 48);
152 assert_size!(Get, 24);
153 assert_size!(SlotResult, 72);
154 }
155
156 #[test]
157 fn test_slot_request() {
158 let elem: Element = "<request xmlns='urn:xmpp:http:upload:0'
159 filename='très cool.jpg'
160 size='23456'
161 content-type='image/jpeg' />"
162 .parse()
163 .unwrap();
164 let slot = SlotRequest::try_from(elem).unwrap();
165 assert_eq!(slot.filename, String::from("très cool.jpg"));
166 assert_eq!(slot.size, 23456);
167 assert_eq!(slot.content_type, Some(String::from("image/jpeg")));
168 }
169
170 #[test]
171 fn test_slot_result() {
172 let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
173 <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg'>
174 <header name='Authorization'>Basic Base64String==</header>
175 <header name='Cookie'>foo=bar; user=romeo</header>
176 </put>
177 <get url='https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg' />
178 </slot>"
179 .parse()
180 .unwrap();
181 let slot = SlotResult::try_from(elem).unwrap();
182 assert_eq!(slot.put.url, String::from("https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg"));
183 assert_eq!(
184 slot.put.headers[0],
185 Header {
186 name: HeaderName::Authorization,
187 value: String::from("Basic Base64String==")
188 }
189 );
190 assert_eq!(
191 slot.put.headers[1],
192 Header {
193 name: HeaderName::Cookie,
194 value: String::from("foo=bar; user=romeo")
195 }
196 );
197 assert_eq!(slot.get.url, String::from("https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg"));
198 }
199
200 #[test]
201 fn test_result_no_header() {
202 let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
203 <put url='https://URL' />
204 <get url='https://URL' />
205 </slot>"
206 .parse()
207 .unwrap();
208 let slot = SlotResult::try_from(elem).unwrap();
209 assert_eq!(slot.put.url, String::from("https://URL"));
210 assert_eq!(slot.put.headers.len(), 0);
211 assert_eq!(slot.get.url, String::from("https://URL"));
212 }
213
214 #[test]
215 fn test_result_bad_header() {
216 let elem: Element = "<slot xmlns='urn:xmpp:http:upload:0'>
217 <put url='https://URL'>
218 <header name='EvilHeader'>EvilValue</header>
219 </put>
220 <get url='https://URL' />
221 </slot>"
222 .parse()
223 .unwrap();
224 SlotResult::try_from(elem).unwrap_err();
225 }
226}