|
1 /* |
|
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
|
3 * |
|
4 * Copyright 2019 Olaf Wintermann. All rights reserved. |
|
5 * |
|
6 * Redistribution and use in source and binary forms, with or without |
|
7 * modification, are permitted provided that the following conditions are met: |
|
8 * |
|
9 * 1. Redistributions of source code must retain the above copyright |
|
10 * notice, this list of conditions and the following disclaimer. |
|
11 * |
|
12 * 2. Redistributions in binary form must reproduce the above copyright |
|
13 * notice, this list of conditions and the following disclaimer in the |
|
14 * documentation and/or other materials provided with the distribution. |
|
15 * |
|
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
|
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
26 * POSSIBILITY OF SUCH DAMAGE. |
|
27 */ |
|
28 |
|
29 #include <stdio.h> |
|
30 #include <stdlib.h> |
|
31 |
|
32 #include "../daemon/session.h" |
|
33 |
|
34 #include "multistatus.h" |
|
35 |
|
36 #define MULTISTATUS_BUFFER_LENGTH 2048 |
|
37 |
|
38 Multistatus* multistatus_response(Session *sn, Request *rq) { |
|
39 Multistatus *ms = pool_malloc(sn->pool, sizeof(Multistatus)); |
|
40 if(!ms) { |
|
41 return NULL; |
|
42 } |
|
43 ZERO(ms, sizeof(Multistatus)); |
|
44 ms->response.addresource = multistatus_addresource; |
|
45 ms->sn = sn; |
|
46 ms->rq = rq; |
|
47 ms->namespaces = ucx_map_new_a(session_get_allocator(ms->sn), 8); |
|
48 if(!ms->namespaces) { |
|
49 return NULL; |
|
50 } |
|
51 if(ucx_map_cstr_put(ms->namespaces, "D", "DAV:")) { |
|
52 return NULL; |
|
53 } |
|
54 return ms; |
|
55 } |
|
56 |
|
57 static int send_xml_root(Multistatus *ms, Writer *out) { |
|
58 writer_puts(out, S("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" |
|
59 "<D:multistatus")); |
|
60 |
|
61 // write the namespaces definitions |
|
62 // key is the namespace prefix |
|
63 // the map always contains the "DAV:" namespace with the prefix "D" |
|
64 UcxMapIterator i = ucx_map_iterator(ms->namespaces); |
|
65 char *href; |
|
66 UCX_MAP_FOREACH(key, href, i) { |
|
67 writer_puts(out, S(" xmlns:")); |
|
68 writer_put(out, key.data, key.len); |
|
69 writer_puts(out, S("=\"")); |
|
70 writer_puts(out, sstr(href)); |
|
71 writer_puts(out, S("\"")); |
|
72 } |
|
73 |
|
74 writer_puts(out, S(">\n")); |
|
75 |
|
76 return out->error; |
|
77 } |
|
78 |
|
79 static int send_prop_name( |
|
80 Multistatus *ms, |
|
81 WebdavProperty *p, |
|
82 Writer *out, |
|
83 char *prefixbuf, |
|
84 int *prefixbuflen) |
|
85 { |
|
86 int in_prefix_len = *prefixbuflen; |
|
87 *prefixbuflen = 0; |
|
88 |
|
89 writer_putc(out, '<'); |
|
90 |
|
91 const char *prefix = NULL; |
|
92 const char *href = NULL; |
|
93 |
|
94 if(p->namespace && p->namespace->href) { |
|
95 href = (const char*)p->namespace->href; |
|
96 |
|
97 if(p->namespace->prefix) { |
|
98 // check if there is a namespace with this prefix already defined |
|
99 // and has the same namespace uri |
|
100 prefix = (const char*)p->namespace->prefix; |
|
101 char *nshref = ucx_map_cstr_get( |
|
102 ms->namespaces, |
|
103 (const char*)p->namespace->prefix); |
|
104 if(!strcmp(nshref, href)) { |
|
105 href = NULL; // we don't need a new xmlns def |
|
106 } |
|
107 } else { |
|
108 // generate new prefix |
|
109 for(int i=0;i<1024;i++) { |
|
110 int len = snprintf(prefixbuf, in_prefix_len, "x%d\0", i); |
|
111 char *test = ucx_map_cstr_get(ms->namespaces, prefixbuf); |
|
112 if(!test) { |
|
113 prefix = prefixbuf; |
|
114 *prefixbuflen = len; |
|
115 break; // found an unused prefix |
|
116 } |
|
117 if(!prefix) { |
|
118 // What? Can't find a free prefix? |
|
119 return 1; |
|
120 } |
|
121 } |
|
122 } |
|
123 } |
|
124 |
|
125 if(prefix) { |
|
126 writer_put(out, prefix, strlen(prefix)); |
|
127 writer_put(out, ":", 1); |
|
128 } |
|
129 |
|
130 // write xml element name |
|
131 writer_put(out, (const char*)p->name, strlen((const char*)p->name)); |
|
132 |
|
133 if(href) { |
|
134 writer_puts(out, S(" xmlns:")); |
|
135 writer_put(out, prefix, strlen(prefix)); |
|
136 writer_puts(out, S("=\"")); |
|
137 writer_put(out, href, strlen(href)); |
|
138 writer_putc(out, '\"'); |
|
139 } |
|
140 |
|
141 if(p->lang) { |
|
142 writer_puts(out, S(" lang=\"")); |
|
143 writer_puts(out, sstr(p->lang)); |
|
144 writer_putc(out, '\"'); |
|
145 } |
|
146 |
|
147 writer_putc(out, '>'); |
|
148 |
|
149 return out->error; |
|
150 } |
|
151 |
|
152 |
|
153 |
|
154 #define MAX_XML_TREE_DEPTH 128 |
|
155 static int send_xml(Multistatus *ms, Writer *out, WSXmlNode *node, WSNamespace *rootns, int depth) { |
|
156 int ret = 0; |
|
157 const char *s; |
|
158 while(node) { |
|
159 switch(node->type) { |
|
160 case XML_ELEMENT_NODE: { |
|
161 writer_putc(out, '<'); |
|
162 if(node->ns && node->ns->prefix) { |
|
163 s = (const char*)node->ns->prefix; |
|
164 writer_put(out, s, strlen(s)); |
|
165 writer_putc(out, ':'); |
|
166 } |
|
167 s = (const char*)node->name; |
|
168 writer_put(out, s, strlen(s)); |
|
169 |
|
170 |
|
171 } |
|
172 |
|
173 } |
|
174 node = node->next; |
|
175 } |
|
176 |
|
177 return ret; |
|
178 } |
|
179 |
|
180 static int send_response_tag(Multistatus *ms, MSResponse *rp, Writer *out) { |
|
181 writer_puts(out, S(" <D:response>\n" |
|
182 " <D:href>")); |
|
183 writer_puts(out, sstr(rp->resource.href)); |
|
184 writer_puts(out, S("</href>\n")); |
|
185 |
|
186 if(rp->plist_begin) { |
|
187 writer_puts(out, S(" <D:propstat>" |
|
188 " <D:prop>\n")); |
|
189 WebdavPList *p = rp->plist_begin; |
|
190 char prefix[16]; |
|
191 while(p) { |
|
192 WebdavProperty *prop = p->property; |
|
193 int prefixlen = 16; |
|
194 if(send_prop_name(ms, prop, out, prefix, &prefixlen)) { |
|
195 return 1; |
|
196 } |
|
197 |
|
198 // send content |
|
199 |
|
200 |
|
201 // send end tag |
|
202 writer_put(out, "<", 1); |
|
203 if(prop->namespace && prop->namespace->href) { |
|
204 const char *pre = NULL; |
|
205 if(prop->namespace->prefix) { |
|
206 pre = (const char*)prop->namespace->prefix; |
|
207 } else if(prefixlen > 0) { |
|
208 pre = prefix; |
|
209 } |
|
210 |
|
211 if(pre) { |
|
212 writer_put(out, pre, strlen(pre)); |
|
213 writer_put(out, ":", 1); |
|
214 } |
|
215 } |
|
216 writer_put(out, prop->name, strlen(prop->name)); |
|
217 writer_put(out, ">", 1); |
|
218 |
|
219 if(out->error) { |
|
220 return 1; |
|
221 } |
|
222 |
|
223 p = p->next; |
|
224 } |
|
225 writer_puts(out, S(" </D:prop>\n" |
|
226 " <D:status>HTTP/1.1 200 OK</D:status>" |
|
227 " </D:propstat>\n")); |
|
228 } |
|
229 |
|
230 return out->error; |
|
231 } |
|
232 |
|
233 int multistatus_send(Multistatus *ms, SYS_NETFD net) { |
|
234 char buffer[MULTISTATUS_BUFFER_LENGTH]; |
|
235 Writer writer; |
|
236 Writer *out = &writer; |
|
237 writer_init(out, net, buffer, MULTISTATUS_BUFFER_LENGTH); |
|
238 |
|
239 // send the xml root element with namespace defs |
|
240 if(send_xml_root(ms, out)) { |
|
241 return 1; |
|
242 } |
|
243 |
|
244 // send response tags |
|
245 MSResponse *response = ms->first; |
|
246 while(response) { |
|
247 if(send_response_tag(ms, response, out)) { |
|
248 return 1; |
|
249 } |
|
250 response = response->next; |
|
251 } |
|
252 |
|
253 return 0; |
|
254 } |
|
255 |
|
256 |
|
257 WebdavResource * multistatus_addresource( |
|
258 WebdavResponse *response, |
|
259 const char *path) |
|
260 { |
|
261 Multistatus *ms = (Multistatus*)response; |
|
262 MSResponse *res = pool_malloc(ms->sn->pool, sizeof(MSResponse)); |
|
263 if(!res) { |
|
264 return NULL; |
|
265 } |
|
266 ZERO(res, sizeof(MSResponse)); |
|
267 |
|
268 res->resource.addproperty = msresponse_addproperty; |
|
269 |
|
270 res->multistatus = ms; |
|
271 res->errors = NULL; |
|
272 res->end = 0; |
|
273 |
|
274 if(ms->current) { |
|
275 ms->current->end = 1; |
|
276 ms->current->next = res; |
|
277 } else { |
|
278 ms->first = res; |
|
279 } |
|
280 ms->current = res; |
|
281 |
|
282 return (WebdavResource*)res; |
|
283 } |
|
284 |
|
285 int msresponse_addproperty( |
|
286 WebdavResource *res, |
|
287 WebdavProperty *property, |
|
288 int status) |
|
289 { |
|
290 MSResponse *response = (MSResponse*)res; |
|
291 if(response->end) { |
|
292 log_ereport( |
|
293 LOG_WARN, |
|
294 "%s", |
|
295 "webdav: cannot add property to closed response tag"); |
|
296 return 0; |
|
297 } |
|
298 |
|
299 // add namespace of this property to the namespace map |
|
300 if(property->namespace && property->namespace->prefix) { |
|
301 char *ns = ucx_map_cstr_get( |
|
302 response->multistatus->namespaces, |
|
303 (const char*)property->namespace->prefix); |
|
304 if(!ns) { |
|
305 int err = ucx_map_cstr_put( |
|
306 response->multistatus->namespaces, |
|
307 (const char*)property->namespace->prefix, |
|
308 property->namespace->href); |
|
309 if(err) { |
|
310 return 1; |
|
311 } |
|
312 } |
|
313 } |
|
314 |
|
315 if(status != 200) { |
|
316 return msresponse_addproperror(response, property, status); |
|
317 } |
|
318 |
|
319 // add property to the list |
|
320 WebdavPList *listelm = pool_malloc( |
|
321 response->multistatus->sn->pool, |
|
322 sizeof(WebdavPList)); |
|
323 if(!listelm) { |
|
324 return 1; |
|
325 } |
|
326 |
|
327 listelm->property = property; |
|
328 listelm->next = NULL; |
|
329 |
|
330 if(response->plist_end) { |
|
331 response->plist_end->next = listelm; |
|
332 } else { |
|
333 response->plist_begin = listelm; |
|
334 } |
|
335 response->plist_end = listelm; |
|
336 return 0; |
|
337 } |
|
338 |
|
339 int msresponse_addproperror( |
|
340 MSResponse *response, |
|
341 WebdavProperty *property, |
|
342 int statuscode) |
|
343 { |
|
344 pool_handle_t *pool = response->multistatus->sn->pool; |
|
345 UcxAllocator *a = session_get_allocator(response->multistatus->sn); |
|
346 |
|
347 // MSResponse contains a list of properties for each status code |
|
348 // at first find the list for this status code |
|
349 PropertyErrorList *errlist = NULL; |
|
350 PropertyErrorList *list = response->errors; |
|
351 PropertyErrorList *last = NULL; |
|
352 while(list) { |
|
353 if(list->status == statuscode) { |
|
354 errlist = list; |
|
355 break; |
|
356 } |
|
357 last = list; |
|
358 list = list->next; |
|
359 } |
|
360 |
|
361 if(!errlist) { |
|
362 // no list available for this statuscode |
|
363 PropertyErrorList *newelm = pool_malloc(pool, |
|
364 sizeof(PropertyErrorList)); |
|
365 if(!newelm) { |
|
366 return 1; |
|
367 } |
|
368 newelm->begin = NULL; |
|
369 newelm->end = NULL; |
|
370 newelm->next = NULL; |
|
371 newelm->status = statuscode; |
|
372 |
|
373 if(last) { |
|
374 last->next = newelm; |
|
375 } else { |
|
376 response->errors = newelm; |
|
377 } |
|
378 errlist = newelm; |
|
379 } |
|
380 |
|
381 // we have the list -> add the new element |
|
382 UcxList *newlistelm = ucx_list_append_a(a, errlist->end, property); |
|
383 if(!newlistelm) { |
|
384 return 1; |
|
385 } |
|
386 errlist->end = newlistelm; |
|
387 if(!errlist->begin) { |
|
388 errlist->begin = newlistelm; |
|
389 } |
|
390 return 0; |
|
391 } |