1 #include <uwsgi.h>
2 #include <libxml/parser.h>
3 #include <libxml/tree.h>
4
5 #if defined(__linux__) || defined(__APPLE__)
6 #include <sys/xattr.h>
7 #endif
8
9 /*
10
11 rfc4918 implementation (WebDAV)
12
13 requires libxml2
14
15 --webdav-mount [mountpoint=]<dir>
16
17 or
18
19 --webdav-use-docroot[=VAR]
20
21 steps to build a path:
22
23 1) get the mountpoint
24
25 2) concat the base with the path_info
26
27 3) realpath() on it
28
29 step 3 could be a non-existent file (for example on MKCOL or PUT). In such a case:
30
31 4) find the last / in the path_info, and try realpath() on it, if success the resource can be created
32
33 ALL MUST BE BOTH THREAD SAFE AND ASYNC SAFE !!!
34
35 Locking requires a cache (local or remote)
36
37 when a lock request is made, an item is added to the cache (directly using cache_set to avoid duplicates). The
38 item key is the full url of the request (host + path_info, in such a way we have virtualhosting for locks). The value is
39 a uuid.
40
41 if a lock_token is passed the url is checked in the cache and uuid compared
42
43 - Resource properties are stored as filesystem xattr (warning, not all operating system support them) -
44
45 */
46
47 extern struct uwsgi_server uwsgi;
48 struct uwsgi_plugin webdav_plugin;
49
50 struct uwsgi_webdav {
51 struct uwsgi_string_list *mountpoints;
52 struct uwsgi_string_list *css;
53 struct uwsgi_string_list *javascript;
54 char *class_directory;
55 char *div;
56
57 char *lock_cache;
58 char *principal_base;
59
60 struct uwsgi_string_list *add_option;
61
62 struct uwsgi_string_list *add_prop;
63 struct uwsgi_string_list *add_collection_prop;
64 struct uwsgi_string_list *add_object_prop;
65
66 struct uwsgi_string_list *add_prop_href;
67 struct uwsgi_string_list *add_collection_prop_href;
68 struct uwsgi_string_list *add_object_prop_href;
69
70 struct uwsgi_string_list *add_prop_comp;
71 struct uwsgi_string_list *add_collection_prop_comp;
72 struct uwsgi_string_list *add_object_prop_comp;
73
74 struct uwsgi_string_list *add_rtype_prop;
75 struct uwsgi_string_list *add_rtype_collection_prop;
76 struct uwsgi_string_list *add_rtype_object_prop;
77
78 struct uwsgi_string_list *skip_prop;
79
80 } udav;
81
82 struct uwsgi_option uwsgi_webdav_options[] = {
83 { "webdav-mount", required_argument, 0, "map a filesystem directory as a webdav store", uwsgi_opt_add_string_list, &udav.mountpoints, UWSGI_OPT_MIME},
84 { "webdav-css", required_argument, 0, "add a css url for automatic webdav directory listing", uwsgi_opt_add_string_list, &udav.css, UWSGI_OPT_MIME},
85 { "webdav-javascript", required_argument, 0, "add a javascript url for automatic webdav directory listing", uwsgi_opt_add_string_list, &udav.javascript, UWSGI_OPT_MIME},
86 { "webdav-js", required_argument, 0, "add a javascript url for automatic webdav directory listing", uwsgi_opt_add_string_list, &udav.javascript, UWSGI_OPT_MIME},
87 { "webdav-class-directory", required_argument, 0, "set the css directory class for automatic webdav directory listing", uwsgi_opt_set_str, &udav.class_directory, UWSGI_OPT_MIME},
88 { "webdav-div", required_argument, 0, "set the div id for automatic webdav directory listing", uwsgi_opt_set_str, &udav.div, UWSGI_OPT_MIME},
89 { "webdav-lock-cache", required_argument, 0, "set the cache to use for webdav locking", uwsgi_opt_set_str, &udav.lock_cache, UWSGI_OPT_MIME},
90 { "webdav-principal-base", required_argument, 0, "enable WebDAV Current Principal Extension using the specified base", uwsgi_opt_set_str, &udav.principal_base, UWSGI_OPT_MIME},
91 { "webdav-add-option", required_argument, 0, "add a WebDAV standard to the OPTIONS response", uwsgi_opt_add_string_list, &udav.add_option, UWSGI_OPT_MIME},
92
93 { "webdav-add-prop", required_argument, 0, "add a WebDAV property to all resources", uwsgi_opt_add_string_list, &udav.add_prop, UWSGI_OPT_MIME},
94 { "webdav-add-collection-prop", required_argument, 0, "add a WebDAV property to all collections", uwsgi_opt_add_string_list, &udav.add_collection_prop, UWSGI_OPT_MIME},
95 { "webdav-add-object-prop", required_argument, 0, "add a WebDAV property to all objects", uwsgi_opt_add_string_list, &udav.add_object_prop, UWSGI_OPT_MIME},
96
97 { "webdav-add-prop-href", required_argument, 0, "add a WebDAV property to all resources (href value)", uwsgi_opt_add_string_list, &udav.add_prop_href, UWSGI_OPT_MIME},
98 { "webdav-add-collection-prop-href", required_argument, 0, "add a WebDAV property to all collections (href value)", uwsgi_opt_add_string_list, &udav.add_collection_prop_href, UWSGI_OPT_MIME},
99 { "webdav-add-object-prop-href", required_argument, 0, "add a WebDAV property to all objects (href value)", uwsgi_opt_add_string_list, &udav.add_object_prop_href, UWSGI_OPT_MIME},
100
101 { "webdav-add-prop-comp", required_argument, 0, "add a WebDAV property to all resources (xml value)", uwsgi_opt_add_string_list, &udav.add_prop_comp, UWSGI_OPT_MIME},
102 { "webdav-add-collection-prop-comp", required_argument, 0, "add a WebDAV property to all collections (xml value)", uwsgi_opt_add_string_list, &udav.add_collection_prop_comp, UWSGI_OPT_MIME},
103 { "webdav-add-object-prop-comp", required_argument, 0, "add a WebDAV property to all objects (xml value)", uwsgi_opt_add_string_list, &udav.add_object_prop_comp, UWSGI_OPT_MIME},
104
105 { "webdav-add-rtype-prop", required_argument, 0, "add a WebDAV resourcetype property to all resources", uwsgi_opt_add_string_list, &udav.add_rtype_prop, UWSGI_OPT_MIME},
106 { "webdav-add-rtype-collection-prop", required_argument, 0, "add a WebDAV resourcetype property to all collections", uwsgi_opt_add_string_list, &udav.add_rtype_collection_prop, UWSGI_OPT_MIME},
107 { "webdav-add-rtype-object-prop", required_argument, 0, "add a WebDAV resourcetype property to all objects", uwsgi_opt_add_string_list, &udav.add_rtype_object_prop, UWSGI_OPT_MIME},
108
109 { "webdav-skip-prop", required_argument, 0, "do not add the specified prop if available in resource xattr", uwsgi_opt_add_string_list, &udav.skip_prop, UWSGI_OPT_MIME},
110
111 { 0, 0, 0, 0, 0, 0, 0 },
112 };
113
uwsgi_webdav_prop_requested(xmlNode * req_prop,char * ns,char * name)114 static int uwsgi_webdav_prop_requested(xmlNode *req_prop, char *ns, char *name) {
115 if (!req_prop) return 1;
116 xmlNode *node;
117 for (node = req_prop->children; node; node = node->next) {
118 if (node->type == XML_ELEMENT_NODE) {
119 if (ns) {
120 if (node->ns && !strcmp((char *) node->ns->href, ns)) {
121 if (!strcmp((char *) node->name, name)) return 1;
122 }
123 }
124 else {
125 if (!strcmp((char *) node->name, name)) return 1;
126 }
127 }
128 }
129 return 0;
130 }
131
uwsgi_webdav_add_a_prop(xmlNode * node,char * opt,xmlNode * req_prop,int type,char * force_name)132 static void uwsgi_webdav_add_a_prop(xmlNode *node, char *opt, xmlNode *req_prop, int type, char *force_name) {
133 char *first_space = strchr(opt, ' ');
134 if (!first_space) return;
135 *first_space = 0;
136 char *second_space = strchr(first_space + 1, ' ');
137 xmlNode *new_node = NULL;
138 char *ns = opt;
139 if (!force_name) force_name = first_space + 1;
140 else {
141 ns = "DAV:";
142 }
143 if (second_space) {
144 *second_space = 0;
145 if (!uwsgi_webdav_prop_requested(req_prop, ns, force_name)) {
146 *first_space = ' ';
147 *second_space = ' ';
148 return;
149 }
150 // href
151 if (type == 1) {
152 new_node = xmlNewChild(node, NULL, BAD_CAST first_space + 1, NULL);
153 xmlNewTextChild(new_node, NULL, BAD_CAST "href", BAD_CAST second_space + 1);
154 }
155 // comp
156 else if (type == 2) {
157 new_node = xmlNewChild(node, NULL, BAD_CAST first_space + 1, NULL);
158 char *comps = uwsgi_str(second_space + 1);
159 char *p, *ctx = NULL;
160 uwsgi_foreach_token(comps, ",", p, ctx) {
161 xmlNode *comp = xmlNewChild(new_node, NULL, BAD_CAST "comp", NULL);
162 xmlNewProp(comp, BAD_CAST "name", BAD_CAST p);
163 }
164 free(comps);
165 }
166 else {
167 if (!uwsgi_webdav_prop_requested(req_prop, ns, first_space + 1)) {
168 *first_space = ' ';
169 *second_space = ' ';
170 return;
171 }
172 new_node = xmlNewTextChild(node, NULL, BAD_CAST first_space + 1, BAD_CAST second_space + 1);
173 }
174 *second_space = ' ';
175 }
176 else {
177 if (!uwsgi_webdav_prop_requested(req_prop, ns, force_name)) {
178 *first_space = ' ';
179 return;
180 }
181 new_node = xmlNewChild(node, NULL, BAD_CAST first_space + 1, NULL);
182 }
183 xmlNsPtr x_ns = xmlNewNs(new_node, BAD_CAST opt, NULL);
184 xmlSetNs(new_node, x_ns);
185 *first_space = ' ';
186 }
187
uwsgi_webdav_foreach_prop(struct uwsgi_string_list * usl,xmlNode * req_prop,xmlNode * node,int type,char * force_name)188 static void uwsgi_webdav_foreach_prop(struct uwsgi_string_list *usl, xmlNode *req_prop, xmlNode *node, int type, char *force_name) {
189 if (!usl) return;
190 while(usl) {
191 uwsgi_webdav_add_a_prop(node, usl->value, req_prop, type, force_name);
192 usl = usl->next;
193 }
194 }
195
196
197 /*
198 OPTIONS: if it is a valid webdav resource add Dav: to the response header
199 */
uwsgi_wevdav_manage_options(struct wsgi_request * wsgi_req)200 static int uwsgi_wevdav_manage_options(struct wsgi_request *wsgi_req) {
201 uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6);
202 if (udav.add_option) {
203 struct uwsgi_buffer *ub = uwsgi_buffer_new(uwsgi.page_size);
204 if (uwsgi_buffer_append(ub, "1, 2, 3", 7)) goto end;
205 struct uwsgi_string_list *usl = udav.add_option;
206 while(usl) {
207 if (uwsgi_buffer_append(ub, ", ", 2)) goto end;
208 if (uwsgi_buffer_append(ub, usl->value, usl->len)) goto end;
209 usl = usl->next;
210 }
211 uwsgi_response_add_header(wsgi_req, "Dav", 3, ub->buf, ub->pos);
212 end:
213 uwsgi_buffer_destroy(ub);
214 }
215 else {
216 uwsgi_response_add_header(wsgi_req, "Dav", 3, "1, 2, 3", 7);
217 }
218 return UWSGI_OK;
219 }
220
uwsgi_webdav_new_date(uint64_t t)221 static char *uwsgi_webdav_new_date(uint64_t t) {
222 // 30+1
223 char d[31];
224 int len = uwsgi_http_date((time_t) t, d);
225 if (!len) {
226 return NULL;
227 }
228 return uwsgi_concat2n(d, len, "", 0);
229 }
230
uwsgi_webdav_add_props(struct wsgi_request * wsgi_req,xmlNode * req_prop,xmlNode * multistatus,xmlNsPtr dav_ns,char * uri,char * filename,int with_values)231 static int uwsgi_webdav_add_props(struct wsgi_request *wsgi_req, xmlNode *req_prop, xmlNode * multistatus, xmlNsPtr dav_ns, char *uri, char *filename, int with_values) {
232 struct stat st;
233 if (stat(filename, &st)) {
234 uwsgi_error("uwsgi_webdav_add_props()/stat()");
235 return -1;
236 }
237
238 int is_collection = 0;
239
240 xmlNode *response = xmlNewChild(multistatus, dav_ns, BAD_CAST "response", NULL);
241 uint16_t uri_len = strlen(uri) ;
242 char *encoded_uri = uwsgi_malloc( (uri_len * 3) + 1);
243 http_url_encode(uri, &uri_len, encoded_uri);
244 encoded_uri[uri_len] = 0;
245 xmlNewChild(response, dav_ns, BAD_CAST "href", BAD_CAST encoded_uri);
246 free(encoded_uri);
247 xmlNode *r_propstat = xmlNewChild(response, dav_ns, BAD_CAST "propstat", NULL);
248 char *r_status = uwsgi_concat2n(wsgi_req->protocol, wsgi_req->protocol_len, " 200 OK", 7);
249 xmlNewChild(r_propstat, dav_ns, BAD_CAST "status", BAD_CAST r_status);
250 free(r_status);
251
252 xmlNode *r_prop = xmlNewChild(r_propstat, dav_ns, BAD_CAST "prop", NULL);
253
254 if (with_values) {
255 if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "displayname")) {
256 char *base_uri = uwsgi_get_last_char(uri, '/');
257 if (base_uri) {
258 xmlNewChild(r_prop, dav_ns, BAD_CAST "displayname", BAD_CAST base_uri+1);
259 }
260 else {
261 xmlNewChild(r_prop, dav_ns, BAD_CAST "displayname", BAD_CAST uri);
262 }
263
264 }
265
266 if (S_ISDIR(st.st_mode)) is_collection = 1;
267
268 xmlNode *r_type = NULL;
269
270 if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "resourcetype")) {
271 r_type = xmlNewChild(r_prop, dav_ns, BAD_CAST "resourcetype", NULL);
272 if (is_collection) {
273 xmlNewChild(r_type, dav_ns, BAD_CAST "collection", NULL);
274 is_collection = 1;
275 }
276 }
277
278
279 if (!is_collection) {
280 if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "getcontentlength")) {
281 char *r_contentlength = uwsgi_num2str(st.st_size);
282 xmlNewChild(r_prop, dav_ns, BAD_CAST "getcontentlength", BAD_CAST r_contentlength);
283 free(r_contentlength);
284 }
285 if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "getcontenttype")) {
286 size_t mime_type_len = 0;
287 char *mime_type = uwsgi_get_mime_type(filename, strlen(filename), &mime_type_len);
288 if (mime_type) {
289 char *r_ctype = uwsgi_concat2n(mime_type, mime_type_len, "", 0);
290 xmlNewTextChild(r_prop, dav_ns, BAD_CAST "getcontenttype", BAD_CAST r_ctype);
291 free(r_ctype);
292 }
293 }
294 }
295
296 if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "creationdate")) {
297 // there is no creation date on UNIX/POSIX, ctime is the nearest thing...
298 char *cdate = uwsgi_webdav_new_date(st.st_ctime);
299 if (cdate) {
300 xmlNewTextChild(r_prop, dav_ns, BAD_CAST "creationdate", BAD_CAST cdate);
301 free(cdate);
302 }
303 }
304
305 if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "getlastmodified")) {
306 char *mdate = uwsgi_webdav_new_date(st.st_mtime);
307 if (mdate) {
308 xmlNewTextChild(r_prop, dav_ns, BAD_CAST "getlastmodified", BAD_CAST mdate);
309 free(mdate);
310 }
311 }
312
313 if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "getetag")) {
314 char *etag = uwsgi_num2str(st.st_mtime);
315 xmlNewTextChild(r_prop, dav_ns, BAD_CAST "getetag", BAD_CAST etag);
316 free(etag);
317 }
318
319 if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "executable")) {
320 xmlNewChild(r_prop, dav_ns, BAD_CAST "executable", NULL);
321 }
322
323 if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "owner")) {
324 xmlNewTextChild(r_prop, dav_ns, BAD_CAST "owner", NULL);
325 }
326
327 if (wsgi_req->remote_user_len > 0) {
328
329 if (udav.principal_base) {
330 if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "current-user-principal")) {
331 char *current_user_principal = uwsgi_concat2n(udav.principal_base, strlen(udav.principal_base), wsgi_req->remote_user, wsgi_req->remote_user_len);
332 xmlNode *cup = xmlNewChild(r_prop, dav_ns, BAD_CAST "current-user-principal", NULL);
333 xmlNewTextChild(cup, dav_ns, BAD_CAST "href", BAD_CAST current_user_principal);
334 if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "resourcetype")) {
335 if (!strcmp(current_user_principal, uri)) {
336 xmlNewChild(r_type, dav_ns, BAD_CAST "principal", NULL);
337 }
338 }
339 free(current_user_principal);
340 }
341 }
342
343 if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "current-user-privilege-set")) {
344 xmlNode *cups = xmlNewChild(r_prop, dav_ns, BAD_CAST "current-user-privilege-set", NULL);
345 xmlNode *privilege = xmlNewChild(cups, dav_ns, BAD_CAST "privilege", NULL);
346 xmlNewChild(privilege, dav_ns, BAD_CAST "all", NULL);
347 xmlNewChild(privilege, dav_ns, BAD_CAST "read", NULL);
348 xmlNewChild(privilege, dav_ns, BAD_CAST "write", NULL);
349 xmlNewChild(privilege, dav_ns, BAD_CAST "write-content", NULL);
350 xmlNewChild(privilege, dav_ns, BAD_CAST "write-properties", NULL);
351 }
352 }
353
354 if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "supported-report-set")) {
355 xmlNode *report_set = xmlNewChild(r_prop, dav_ns, BAD_CAST "supported-report-set", NULL);
356 xmlNode *supported_report = xmlNewChild(report_set, dav_ns, BAD_CAST "supported-report", NULL);
357 xmlNewChild(supported_report, dav_ns, BAD_CAST "report", BAD_CAST "principal-property-search");
358 supported_report = xmlNewChild(report_set, dav_ns, BAD_CAST "supported-report", NULL);
359 xmlNewChild(supported_report, dav_ns, BAD_CAST "report", BAD_CAST "sync-collection");
360 supported_report = xmlNewChild(report_set, dav_ns, BAD_CAST "supported-report", NULL);
361 xmlNewChild(supported_report, dav_ns, BAD_CAST "report", BAD_CAST "expand-property");
362 supported_report = xmlNewChild(report_set, dav_ns, BAD_CAST "supported-report", NULL);
363 xmlNewChild(supported_report, dav_ns, BAD_CAST "report", BAD_CAST "principal-search-property-set");
364 }
365
366 uwsgi_webdav_foreach_prop(udav.add_prop, req_prop, r_prop, 0, NULL );
367 uwsgi_webdav_foreach_prop(udav.add_prop_href, req_prop, r_prop, 1, NULL);
368 uwsgi_webdav_foreach_prop(udav.add_prop_comp,req_prop, r_prop, 2 , NULL);
369
370 uwsgi_webdav_foreach_prop(udav.add_rtype_prop,req_prop, r_type, 0, "resourcetype");
371
372 if (is_collection) {
373 uwsgi_webdav_foreach_prop(udav.add_rtype_collection_prop,req_prop, r_type, 0, "resourcetype");
374 uwsgi_webdav_foreach_prop(udav.add_collection_prop,req_prop, r_prop, 0, NULL);
375 uwsgi_webdav_foreach_prop(udav.add_collection_prop_href,req_prop, r_prop, 1, NULL);
376 uwsgi_webdav_foreach_prop(udav.add_collection_prop_comp,req_prop, r_prop, 2, NULL);
377 }
378 else {
379 uwsgi_webdav_foreach_prop(udav.add_rtype_object_prop,req_prop, r_type, 0, "resourcetype");
380 uwsgi_webdav_foreach_prop(udav.add_object_prop,req_prop, r_prop, 0, NULL);
381 uwsgi_webdav_foreach_prop(udav.add_object_prop_href,req_prop, r_prop, 1, NULL);
382 uwsgi_webdav_foreach_prop(udav.add_object_prop_comp,req_prop, r_prop, 2, NULL);
383 }
384 }
385 else {
386 xmlNewChild(r_prop, dav_ns, BAD_CAST "displayname", NULL);
387 xmlNewChild(r_prop, dav_ns, BAD_CAST "resourcetype", NULL);
388 if (!S_ISDIR(st.st_mode)) {
389 xmlNewChild(r_prop, dav_ns, BAD_CAST "getcontentlength", NULL);
390 xmlNewChild(r_prop, dav_ns, BAD_CAST "getcontenttype", NULL);
391 }
392 xmlNewChild(r_prop, dav_ns, BAD_CAST "creationdate", NULL);
393 xmlNewChild(r_prop, dav_ns, BAD_CAST "getlastmodified", NULL);
394 xmlNewChild(r_prop, dav_ns, BAD_CAST "supported-report-set", NULL);
395 if (wsgi_req->remote_user_len > 0) {
396 xmlNewChild(r_prop, dav_ns, BAD_CAST "current-user-privilege-set", NULL);
397 if (udav.principal_base) {
398 xmlNewChild(r_prop, dav_ns, BAD_CAST "current-user-principal", NULL);
399 }
400 }
401 }
402
403 #if defined(__linux__) || defined(__APPLE__)
404 // get xattr for user.uwsgi.webdav.
405 #if defined(__linux__)
406 ssize_t rlen = listxattr(filename, NULL, 0);
407 #elif defined(__APPLE__)
408 ssize_t rlen = listxattr(filename, NULL, 0, 0);
409 #endif
410 // do not return -1 as the previous xml is valid !!!
411 if (rlen <= 0) return 0;
412 // use calloc to avoid races
413 char *xattrs = uwsgi_calloc(rlen);
414 #if defined(__linux__)
415 if (listxattr(filename, xattrs, rlen) <= 0) {
416 #elif defined(__APPLE__)
417 if (listxattr(filename, xattrs, rlen, 0) <= 0) {
418 #endif
419 free(xattrs);
420 return 0;
421 }
422 // parse the name list
423 ssize_t i;
424 char *key = NULL;
425 for(i=0;i<rlen;i++) {
426 // check for wrong condition
427 if (xattrs[i] == 0 && key == NULL) break;
428 if (key && xattrs[i] == 0) {
429 if (!uwsgi_starts_with(key, strlen(key), "user.uwsgi.webdav.", 18)) {
430 if (uwsgi_string_list_has_item(udav.skip_prop, key + 18, strlen(key + 18))) continue;
431 xmlNsPtr xattr_ns = NULL;
432 // does it has a namespace ?
433 char *separator = strchr(key + 18, '|');
434 char *xattr_key = key + 18;
435 if (separator) {
436 xattr_key = separator + 1;
437 *separator = 0;
438 if (!uwsgi_webdav_prop_requested(req_prop, key + 18, xattr_key)) continue;
439 }
440 else {
441 if (!uwsgi_webdav_prop_requested(req_prop, NULL, xattr_key)) continue;
442 }
443 xmlNode *xattr_item = NULL;
444 if (with_values) {
445 #if defined(__linux__)
446 ssize_t rlen2 = getxattr(filename, key, NULL, 0);
447 #elif defined(__APPLE__)
448 ssize_t rlen2 = getxattr(filename, key, NULL, 0, 0, 0);
449 #endif
450 if (rlen > 0) {
451 // leave space for final 0
452 char *xvalue = uwsgi_calloc(rlen2 + 1);
453 #if defined(__linux__)
454 if (getxattr(filename, key, xvalue, rlen2) > 0) {
455 #elif defined(__APPLE__)
456 if (getxattr(filename, key, xvalue, rlen2, 0 ,0) > 0) {
457 #endif
458 xattr_item = xmlNewTextChild(r_prop, NULL, BAD_CAST xattr_key, BAD_CAST xvalue);
459 }
460 free(xvalue);
461 }
462 else if (rlen == 0) {
463 xattr_item = xmlNewTextChild(r_prop, NULL, BAD_CAST xattr_key, NULL);
464 }
465 }
466 else {
467 xattr_item = xmlNewTextChild(r_prop, NULL, BAD_CAST xattr_key, NULL);
468 }
469 if (separator && xattr_item) {
470 xattr_ns = xmlNewNs(xattr_item, BAD_CAST (key + 18), NULL);
471 *separator = '|';
472 xmlSetNs(xattr_item, xattr_ns);
473 }
474 }
475 key = NULL;
476 }
477 else if (key == NULL) {
478 key = &xattrs[i];
479 }
480 }
481 free(xattrs);
482
483 #endif
484 return 0;
485 }
486
487 static size_t uwsgi_webdav_expand_path(struct wsgi_request *wsgi_req, char *item, uint16_t item_len, char *filename) {
488 struct uwsgi_app *ua = &uwsgi_apps[wsgi_req->app_id];
489 char *docroot = ua->interpreter;
490 size_t docroot_len = strlen(docroot);
491
492 // merge docroot with path_info
493 char *tmp_filename = uwsgi_concat3n(docroot, docroot_len, "/", 1, item, item_len);
494 // try expanding the path
495 if (!realpath(tmp_filename, filename)) {
496 free(tmp_filename);
497 return 0;
498 }
499 free(tmp_filename);
500 return strlen(filename);
501 }
502
503 static size_t uwsgi_webdav_expand_fake_path(struct wsgi_request *wsgi_req, char *item, uint16_t item_len, char *filename) {
504 char *last_slash = uwsgi_get_last_charn(item, item_len, '/');
505 if (!last_slash) return 0;
506 size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, item, last_slash - item, filename);
507 if (!filename_len) return 0;
508 // check for overflow
509 if (filename_len + (item_len - (last_slash - item)) >= PATH_MAX) return 0;
510 memcpy(filename + filename_len, last_slash, (item_len - (last_slash - item)));
511 filename_len += (item_len - (last_slash - item));
512 filename[(int)filename_len] = 0;
513 return filename_len;
514 }
515
516 static xmlDoc *uwsgi_webdav_manage_prop(struct wsgi_request *wsgi_req, xmlNode *req_prop, char *filename, size_t filename_len, int with_values) {
517 // default 1 depth
518 int depth = 1;
519 uint16_t http_depth_len = 0;
520 char *http_depth = uwsgi_get_var(wsgi_req, "HTTP_DEPTH", 10, &http_depth_len);
521 if (http_depth) {
522 depth = uwsgi_str_num(http_depth, http_depth_len);
523 }
524
525 xmlDoc *rdoc = xmlNewDoc(BAD_CAST "1.0");
526 xmlNode *multistatus = xmlNewNode(NULL, BAD_CAST "multistatus");
527 xmlDocSetRootElement(rdoc, multistatus);
528 xmlNsPtr dav_ns = xmlNewNs(multistatus, BAD_CAST "DAV:", BAD_CAST "D");
529 xmlSetNs(multistatus, dav_ns);
530
531 if (depth == 0) {
532 char *uri = uwsgi_concat2n(wsgi_req->path_info, wsgi_req->path_info_len, "", 0);
533 uwsgi_webdav_add_props(wsgi_req, req_prop, multistatus, dav_ns, uri, filename, with_values);
534 free(uri);
535 }
536 else {
537 DIR *collection = opendir(filename);
538 struct dirent de;
539 for (;;) {
540 struct dirent *de_r = NULL;
541 if (readdir_r(collection, &de, &de_r)) {
542 uwsgi_error("uwsgi_wevdav_manage_propfind()/readdir_r()");
543 break;
544 }
545 if (de_r == NULL) {
546 break;
547 }
548 char *uri = NULL;
549 char *direntry = NULL;
550 if (!strcmp(de.d_name, "..")) {
551 // skip ..
552 continue;
553 }
554 else if (!strcmp(de.d_name, ".")) {
555 uri = uwsgi_concat2n(wsgi_req->path_info, wsgi_req->path_info_len, "", 0);
556 direntry = uwsgi_concat2n(filename, filename_len, "", 0);
557 }
558 else if (wsgi_req->path_info[wsgi_req->path_info_len - 1] == '/') {
559 uri = uwsgi_concat2n(wsgi_req->path_info, wsgi_req->path_info_len, de.d_name, strlen(de.d_name));
560 direntry = uwsgi_concat3n(filename, filename_len, "/", 1, de.d_name, strlen(de.d_name));
561 }
562 else {
563 uri = uwsgi_concat3n(wsgi_req->path_info, wsgi_req->path_info_len, "/", 1, de.d_name, strlen(de.d_name));
564 direntry = uwsgi_concat3n(filename, filename_len, "/", 1, de.d_name, strlen(de.d_name));
565 }
566 uwsgi_webdav_add_props(wsgi_req, req_prop, multistatus, dav_ns, uri, direntry, with_values);
567 free(uri);
568 free(direntry);
569 }
570 closedir(collection);
571 }
572
573 return rdoc;
574
575 }
576
577 static int uwsgi_wevdav_manage_propfind(struct wsgi_request *wsgi_req, xmlDoc * doc) {
578 char filename[PATH_MAX];
579 size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
580 if (filename_len == 0) {
581 uwsgi_404(wsgi_req);
582 return UWSGI_OK;
583 }
584 xmlDoc *rdoc = NULL;
585 xmlNode *element = NULL;
586
587 if (doc) {
588 element = xmlDocGetRootElement(doc);
589 if (!element) return -1;
590
591 if (!element || strcmp((char *) element->name, "propfind")) return -1;
592 }
593
594 if (uwsgi_response_prepare_headers(wsgi_req, "207 Multi-Status", 16))
595 return -1;
596 if (uwsgi_response_add_content_type(wsgi_req, "application/xml; charset=\"utf-8\"", 32))
597 return -1;
598
599 if (doc) {
600 // propfind must have a child (scan them until you find a valid one)
601 xmlNode *node;
602 for (node = element->children; node; node = node->next) {
603 if (node->type == XML_ELEMENT_NODE) {
604 if (node->ns && !strcmp((char *) node->ns->href, "DAV:")) {
605 if (!strcmp((char *) node->name, "prop")) {
606 rdoc = uwsgi_webdav_manage_prop(wsgi_req, node, filename, filename_len, 1);
607 break;
608 }
609 if (!strcmp((char *) node->name, "allprop")) {
610 rdoc = uwsgi_webdav_manage_prop(wsgi_req, NULL, filename, filename_len, 1);
611 break;
612 }
613 if (!strcmp((char *) node->name, "propname")) {
614 rdoc = uwsgi_webdav_manage_prop(wsgi_req, node, filename, filename_len, 0);
615 break;
616 }
617 }
618 }
619 }
620 }
621 else {
622 rdoc = uwsgi_webdav_manage_prop(wsgi_req, NULL, filename, filename_len, 1);
623 }
624
625 if (!rdoc) return UWSGI_OK;
626
627 xmlChar *xmlbuf;
628 int xlen = 0;
629 xmlDocDumpFormatMemory(rdoc, &xmlbuf, &xlen, 1);
630 uwsgi_response_add_content_length(wsgi_req, xlen);
631 uwsgi_response_write_body_do(wsgi_req, (char *) xmlbuf, xlen);
632 #ifdef UWSGI_DEBUG
633 uwsgi_log("\n%.*s\n", xlen, xmlbuf);
634 #endif
635 xmlFreeDoc(rdoc);
636 xmlFree(xmlbuf);
637 return UWSGI_OK;
638 }
639
640 static int uwsgi_webdav_prop_set(char *filename, char *attr, char *ns, char *body) {
641 int ret = 0;
642 #if defined(__linux__) || defined(__APPLE__)
643 char *xattr_name = NULL;
644 if (ns) {
645 xattr_name = uwsgi_concat4("user.uwsgi.webdav.", ns, "|", attr);
646 }
647 else {
648 xattr_name = uwsgi_concat2("user.uwsgi.webdav.", attr);
649 }
650 #if defined(__linux__)
651 ret = setxattr(filename, xattr_name, body, strlen(body), 0);
652 #elif defined(__APPLE__)
653 ret = setxattr(filename, xattr_name, body, strlen(body), 0, 0);
654 #endif
655 free(xattr_name);
656 #endif
657 return ret;
658 }
659
660 static int uwsgi_webdav_prop_del(char *filename, char *attr, char *ns) {
661 int ret = 0;
662 #if defined(__linux__) || defined(__APPLE__)
663 char *xattr_name = NULL;
664 if (ns) {
665 xattr_name = uwsgi_concat4("user.uwsgi.webdav.", ns, "|", attr);
666 }
667 else {
668 xattr_name = uwsgi_concat2("user.uwsgi.webdav.", attr);
669 }
670 #if defined(__linux__)
671 ret = removexattr(filename, xattr_name);
672 #elif defined(__APPLE__)
673 ret = removexattr(filename, xattr_name, 0);
674 #endif
675 free(xattr_name);
676 #endif
677 return ret;
678 }
679
680 static void uwsgi_webdav_do_prop_update(struct wsgi_request *wsgi_req, xmlNode *prop, xmlNode *response, char *filename, uint8_t action) {
681 xmlNode *node;
682 // search for "prop"
683 for (node = prop->children; node; node = node->next) {
684 if (node->type == XML_ELEMENT_NODE) {
685 xmlNode *propstat = xmlNewChild(response, NULL, BAD_CAST "propstat", NULL);
686 xmlNode *r_prop = xmlNewChild(propstat, NULL, BAD_CAST "prop" , NULL);
687 xmlNode *new_prop = xmlNewChild(r_prop, NULL, node->name, NULL);
688 if (node->ns) {
689 xmlNsPtr xattr_ns = xmlNewNs(new_prop, node->ns->href, NULL);
690 xmlSetNs(new_prop, xattr_ns);
691 }
692 if (action == 0) {
693 if (uwsgi_webdav_prop_set(filename, (char *) node->name, node->ns ? (char *) node->ns->href : NULL, node->children ? (char *) node->children->content : "")) {
694 char *r_status = uwsgi_concat2n(wsgi_req->protocol, wsgi_req->protocol_len, " 403 Forbidden", 14);
695 xmlNewChild(r_prop, NULL, BAD_CAST "status", BAD_CAST r_status);
696 free(r_status);
697 }
698 else {
699 char *r_status = uwsgi_concat2n(wsgi_req->protocol, wsgi_req->protocol_len, " 200 OK", 7);
700 xmlNewChild(r_prop, NULL, BAD_CAST "status", BAD_CAST r_status);
701 free(r_status);
702 }
703 }
704 else if (action == 1) {
705 if (uwsgi_webdav_prop_del(filename, (char *) node->name, node->ns ? (char *) node->ns->href : NULL)) {
706 char *r_status = uwsgi_concat2n(wsgi_req->protocol, wsgi_req->protocol_len, " 403 Forbidden", 14);
707 xmlNewChild(r_prop, NULL, BAD_CAST "status", BAD_CAST r_status);
708 free(r_status);
709 }
710 else {
711 char *r_status = uwsgi_concat2n(wsgi_req->protocol, wsgi_req->protocol_len, " 200 OK", 7);
712 xmlNewChild(r_prop, NULL, BAD_CAST "status", BAD_CAST r_status);
713 free(r_status);
714 }
715 }
716 }
717 }
718 }
719
720 // action 0 is set, 1 is remove
721 static void uwsgi_webdav_manage_prop_update(struct wsgi_request *wsgi_req, xmlNode *parent, xmlNode *response, char *filename, uint8_t action) {
722 xmlNode *node;
723 // search for "prop"
724 for (node = parent->children; node; node = node->next) {
725 if (node->type == XML_ELEMENT_NODE) {
726 if (node->ns && !strcmp((char *) node->ns->href, "DAV:")) {
727 if (!strcmp((char *) node->name, "prop")) {
728 uwsgi_webdav_do_prop_update(wsgi_req, node, response, filename, action);
729 }
730 }
731 }
732 }
733 }
734
735 static int uwsgi_wevdav_manage_proppatch(struct wsgi_request *wsgi_req, xmlDoc * doc) {
736 char filename[PATH_MAX];
737 size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
738 if (filename_len == 0) {
739 uwsgi_404(wsgi_req);
740 return UWSGI_OK;
741 }
742
743 xmlNode *element = xmlDocGetRootElement(doc);
744 if (!element) return -1;
745
746 if (!element || (strcmp((char *) element->name, "propertyupdate"))) return -1;
747
748 if (uwsgi_response_prepare_headers(wsgi_req, "207 Multi-Status", 16))
749 return -1;
750 if (uwsgi_response_add_content_type(wsgi_req, "application/xml; charset=\"utf-8\"", 32))
751 return -1;
752
753 xmlDoc *rdoc = xmlNewDoc(BAD_CAST "1.0");
754 xmlNode *multistatus = xmlNewNode(NULL, BAD_CAST "multistatus");
755 xmlDocSetRootElement(rdoc, multistatus);
756 xmlNsPtr dav_ns = xmlNewNs(multistatus, BAD_CAST "DAV:", BAD_CAST "D");
757 xmlSetNs(multistatus, dav_ns);
758 xmlNode *response = xmlNewChild(multistatus, dav_ns, BAD_CAST "response", NULL);
759
760 char *uri = uwsgi_concat2n(wsgi_req->path_info, wsgi_req->path_info_len, "", 0);
761 uint16_t uri_len = strlen(uri) ;
762 char *encoded_uri = uwsgi_malloc( (uri_len * 3) + 1);
763 http_url_encode(uri, &uri_len, encoded_uri);
764 encoded_uri[uri_len] = 0;
765 xmlNewChild(response, dav_ns, BAD_CAST "href", BAD_CAST encoded_uri);
766 free(encoded_uri);
767
768 // propfind can be "set" or "remove"
769 xmlNode *node;
770 for (node = element->children; node; node = node->next) {
771 if (node->type == XML_ELEMENT_NODE) {
772 if (node->ns && !strcmp((char *) node->ns->href, "DAV:")) {
773 if (!strcmp((char *) node->name, "set")) {
774 uwsgi_webdav_manage_prop_update(wsgi_req, node, response, filename, 0);
775 }
776 else if (!strcmp((char *) node->name, "remove")) {
777 uwsgi_webdav_manage_prop_update(wsgi_req, node, response, filename, 1);
778 }
779 }
780 }
781 }
782
783 if (!rdoc) return UWSGI_OK;
784
785 xmlChar *xmlbuf;
786 int xlen = 0;
787 xmlDocDumpFormatMemory(rdoc, &xmlbuf, &xlen, 1);
788 uwsgi_response_add_content_length(wsgi_req, xlen);
789 uwsgi_response_write_body_do(wsgi_req, (char *) xmlbuf, xlen);
790 #ifdef UWSGI_DEBUG
791 uwsgi_log("\n%.*s\n", xlen, xmlbuf);
792 #endif
793 xmlFreeDoc(rdoc);
794 xmlFree(xmlbuf);
795 return UWSGI_OK;
796 }
797
798
799 static int uwsgi_wevdav_manage_put(struct wsgi_request *wsgi_req) {
800 char filename[PATH_MAX];
801 size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
802 // the collection does not exist, search for the last /
803 if (!filename_len) {
804 filename_len = uwsgi_webdav_expand_fake_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
805 if (!filename_len) {
806 uwsgi_response_prepare_headers(wsgi_req, "409 Conflict", 12);
807 return UWSGI_OK;
808 }
809 }
810
811 int fd = open(filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
812 if (fd < 0) {
813 uwsgi_403(wsgi_req);
814 return UWSGI_OK;
815 }
816
817 if (uwsgi_response_prepare_headers(wsgi_req, "201 Created", 11)) goto end;
818
819 size_t remains = wsgi_req->post_cl;
820 while(remains > 0) {
821 ssize_t body_len = 0;
822 char *body = uwsgi_request_body_read(wsgi_req, UMIN(remains, 32768) , &body_len);
823 if (!body || body == uwsgi.empty) break;
824 if (write(fd, body, body_len) != body_len) goto end;
825 }
826
827 end:
828 close(fd);
829 return UWSGI_OK;
830 }
831
832 static int uwsgi_webdav_massive_delete(char *dir) {
833 int ret = 0;
834 DIR *d = opendir(dir);
835 for (;;) {
836 struct dirent *de_r = NULL;
837 struct dirent de;
838 if (readdir_r(d, &de, &de_r)) {
839 ret = -1;
840 goto end;
841 }
842 if (de_r == NULL) break;
843 // skip myself and parent
844 if (!strcmp(de.d_name, ".") || !strcmp(de.d_name, "..")) continue;
845 char *item = uwsgi_concat3(dir, "/", de.d_name);
846 if (de.d_type == DT_DIR) {
847 if (uwsgi_webdav_massive_delete(item)) {
848 free(item);
849 ret = -1;
850 goto end;
851 }
852 }
853 else {
854 if (unlink(item)) {
855 free(item);
856 ret = -1;
857 goto end;
858 }
859 }
860 free(item);
861 }
862 if (rmdir(dir)) ret = -1;
863 end:
864 closedir(d);
865 return ret;
866 }
867
868 static int uwsgi_wevdav_manage_delete(struct wsgi_request *wsgi_req) {
869 char filename[PATH_MAX];
870 size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
871 // the collection does not exists
872 if (!filename_len) {
873 uwsgi_404(wsgi_req);
874 return UWSGI_OK;
875 }
876
877 if (uwsgi_is_dir(filename)) {
878 int ret = rmdir(filename);
879 if (ret < 0) {
880 if (errno == ENOTEMPTY) {
881 if (uwsgi_webdav_massive_delete(filename)) {
882 uwsgi_403(wsgi_req);
883 return UWSGI_OK;
884 }
885 }
886 else {
887 uwsgi_403(wsgi_req);
888 return UWSGI_OK;
889 }
890 }
891 }
892 else {
893 if (unlink(filename)) {
894 uwsgi_403(wsgi_req);
895 return UWSGI_OK;
896 }
897 }
898
899 uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6);
900 return UWSGI_OK;
901 }
902
903 static int uwsgi_webdav_dirlist_add_item(struct uwsgi_buffer *ub, char *item, size_t item_len, uint8_t is_dir) {
904 if (is_dir) {
905 if (udav.class_directory) {
906 if (uwsgi_buffer_append(ub, "<li class=\"", 11)) return -1;
907 if (uwsgi_buffer_append(ub, udav.class_directory, strlen(udav.class_directory))) return -1;
908 if (uwsgi_buffer_append(ub, "\"><a href=\"", 11)) return -1;
909 }
910 else {
911 if (uwsgi_buffer_append(ub, "<li class=\"directory\"><a href=\"", 31)) return -1;
912 }
913 }
914 else {
915 if (uwsgi_buffer_append(ub, "<li><a href=\"", 13)) return -1;
916 }
917 if (uwsgi_buffer_append(ub, item, item_len)) return -1;
918 if (is_dir) {
919 if (uwsgi_buffer_append(ub, "/\">", 3)) return -1;
920 if (uwsgi_buffer_append(ub, item, item_len)) return -1;
921 if (uwsgi_buffer_append(ub, "/", 1)) return -1;
922 }
923 else {
924 if (uwsgi_buffer_append(ub, "\">", 2)) return -1;
925 if (uwsgi_buffer_append(ub, item, item_len)) return -1;
926 }
927 if (uwsgi_buffer_append(ub, "</a></li>", 9)) return -1;
928 return 0;
929 }
930
931 static void uwsgi_webdav_dirlist(struct wsgi_request *wsgi_req, char *dir) {
932 struct uwsgi_buffer *ub = uwsgi_buffer_new(uwsgi.page_size);
933 if (uwsgi_buffer_append(ub, "<html><head><title>", 19)) goto end;
934 if (uwsgi_buffer_append(ub, dir, strlen(dir))) goto end;
935 if (uwsgi_buffer_append(ub, "</title>", 8)) goto end;
936
937 struct uwsgi_string_list *usl = udav.css;
938 while(usl) {
939 if (uwsgi_buffer_append(ub, "<link rel=\"stylesheet\" href=\"", 29)) goto end;
940 if (uwsgi_buffer_append(ub, usl->value, usl->len)) goto end;
941 if (uwsgi_buffer_append(ub, "\" type=\"text/css\">", 18)) goto end;
942 usl = usl->next;
943 }
944
945
946 usl = udav.javascript;
947 while(usl) {
948 if (uwsgi_buffer_append(ub, "<script src=\"", 13)) goto end;
949 if (uwsgi_buffer_append(ub, usl->value, usl->len)) goto end;
950 if (uwsgi_buffer_append(ub, "\"></script>", 11)) goto end;
951 usl = usl->next;
952 }
953
954 if (uwsgi_buffer_append(ub, "</head><body>", 13)) goto end;
955
956 if (udav.div) {
957 if (uwsgi_buffer_append(ub, "<div id=\"", 9)) goto end;
958 if (uwsgi_buffer_append(ub, udav.div, strlen(udav.div))) goto end;
959 if (uwsgi_buffer_append(ub, "\">", 2)) goto end;
960 }
961 else {
962 if (uwsgi_buffer_append(ub, "<div>", 5)) goto end;
963 }
964 if (uwsgi_webdav_dirlist_add_item(ub, "..", 2, 1)) goto end;
965
966 #ifdef __linux__
967 struct dirent **tasklist;
968 int n = scandir(dir, &tasklist, 0, versionsort);
969 if (n < 0) goto end;
970 int i;
971 for(i=0;i<n;i++) {
972 if (tasklist[i]->d_name[0] == '.') goto next;
973 if (uwsgi_webdav_dirlist_add_item(ub, tasklist[i]->d_name, strlen(tasklist[i]->d_name), tasklist[i]->d_type == DT_DIR ? 1 : 0)) {
974 free(tasklist[i]);
975 free(tasklist);
976 goto end;
977 }
978 next:
979 free(tasklist[i]);
980 }
981
982 free(tasklist);
983 #else
984 DIR *d = opendir(dir);
985 for (;;) {
986 struct dirent *de_r = NULL;
987 struct dirent de;
988 if (readdir_r(d, &de, &de_r)) goto end;
989 if (de_r == NULL) break;
990 // skip items startign with a dot
991 if (de.d_name[0] == '.') continue;
992 if (uwsgi_webdav_dirlist_add_item(ub, de.d_name, strlen(de.d_name), de.d_type == DT_DIR ? 1 : 0)) goto end;
993 }
994
995 closedir(d);
996 #endif
997
998 if (uwsgi_buffer_append(ub, "</ul></div></body></html>", 25)) goto end;
999
1000 if (uwsgi_response_add_content_type(wsgi_req, "text/html", 9)) goto end;
1001 if (uwsgi_response_add_content_length(wsgi_req, ub->pos)) goto end;
1002
1003 uwsgi_response_write_body_do(wsgi_req, ub->buf, ub->pos);
1004 end:
1005 uwsgi_buffer_destroy(ub);
1006 }
1007
1008 static int uwsgi_wevdav_manage_get(struct wsgi_request *wsgi_req, int send_body) {
1009 char filename[PATH_MAX];
1010 size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
1011 if (!filename_len) {
1012 uwsgi_404(wsgi_req);
1013 return UWSGI_OK;
1014 }
1015
1016 if (uwsgi_is_dir(filename)) {
1017 uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6);
1018 if (send_body) {
1019 uwsgi_webdav_dirlist(wsgi_req, filename);
1020 }
1021 return UWSGI_OK;
1022 }
1023
1024 int fd = open(filename, O_RDONLY);
1025 if (fd < 0) {
1026 uwsgi_403(wsgi_req);
1027 return UWSGI_OK;
1028 }
1029 struct stat st;
1030 if (fstat(fd, &st)) {
1031 close(fd);
1032 uwsgi_403(wsgi_req);
1033 return UWSGI_OK;
1034 }
1035
1036 if (uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6))
1037 goto end;
1038 // add content_length
1039 if (uwsgi_response_add_content_length(wsgi_req, st.st_size))
1040 goto end;
1041 // add last-modified
1042 if (uwsgi_response_add_last_modified(wsgi_req, st.st_mtime))
1043 goto end;
1044 // add mime_type
1045 size_t mime_type_len = 0;
1046 char *mime_type = uwsgi_get_mime_type(filename, filename_len, &mime_type_len);
1047 if (mime_type) {
1048 if (uwsgi_response_add_content_type(wsgi_req, mime_type, mime_type_len))
1049 goto end;
1050 }
1051 // add ETag (based on file mtime, not rock-solid, but good enough)
1052 char *etag = uwsgi_num2str(st.st_mtime);
1053 if (uwsgi_response_add_header(wsgi_req, "ETag", 4, etag, strlen(etag))) {
1054 free(etag);
1055 goto end;
1056 }
1057 free(etag);
1058 // start sending the file (note: we do not use sendfile() api, for being able to use caching and transformations)
1059 if (!send_body)
1060 goto end;
1061 // use a pretty big buffer (for performance reasons)
1062 char buf[32768];
1063 size_t remains = st.st_size;
1064 while (remains > 0) {
1065 ssize_t rlen = read(fd, buf, UMIN(32768, remains));
1066 if (rlen <= 0) {
1067 uwsgi_error("uwsgi_wevdav_manage_get/read()");
1068 break;
1069 }
1070 remains -= rlen;
1071 if (uwsgi_response_write_body_do(wsgi_req, buf, rlen)) {
1072 break;
1073 }
1074 }
1075 end:
1076 close(fd);
1077 return UWSGI_OK;
1078 }
1079
1080 static int uwsgi_wevdav_manage_copy(struct wsgi_request *wsgi_req) {
1081 uint16_t destination_len = 0;
1082 char *destination = uwsgi_get_var(wsgi_req, "HTTP_DESTINATION", 16, &destination_len);
1083 uwsgi_log("Destination: %.*s\n", destination_len, destination);
1084 return -1;
1085 }
1086
1087 static int uwsgi_wevdav_manage_move(struct wsgi_request *wsgi_req) {
1088 char filename[PATH_MAX];
1089 char d_filename[PATH_MAX];
1090 size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
1091 if (filename_len == 0) {
1092 uwsgi_404(wsgi_req);
1093 return UWSGI_OK;
1094 }
1095
1096 uint16_t destination_len = 0;
1097 char *destination = uwsgi_get_var(wsgi_req, "HTTP_DESTINATION", 16, &destination_len);
1098 if (destination_len == 0) {
1099 uwsgi_403(wsgi_req);
1100 return UWSGI_OK;
1101 }
1102
1103 uint16_t overwrite_len = 0;
1104 int can_overwrite = 1;
1105 char *overwrite = uwsgi_get_var(wsgi_req, "HTTP_OVERWRITE", 14, &overwrite_len);
1106 if (overwrite) {
1107 if (overwrite[0] == 'F') {
1108 can_overwrite = 0;
1109 }
1110 }
1111
1112
1113 uint16_t scheme_len = wsgi_req->scheme_len;
1114 if (wsgi_req->scheme_len == 0) {
1115 // http
1116 scheme_len = 4;
1117 }
1118 uint16_t skip = scheme_len + 3 + wsgi_req->host_len;
1119 int already_exists = 0;
1120 size_t d_filename_len = uwsgi_webdav_expand_path(wsgi_req, destination + skip, destination_len - skip, d_filename);
1121 if (d_filename_len > 0) {
1122 already_exists = 1;
1123 if (!can_overwrite) {
1124 uwsgi_response_prepare_headers(wsgi_req, "412 Precondition Failed", 23);
1125 return UWSGI_OK;
1126 }
1127 }
1128 else {
1129 d_filename_len = uwsgi_webdav_expand_fake_path(wsgi_req, destination + skip, destination_len - skip, d_filename);
1130 }
1131
1132 if (d_filename_len == 0) {
1133 uwsgi_response_prepare_headers(wsgi_req, "409 Conflict", 12);
1134 return UWSGI_OK;
1135 }
1136
1137 if (rename(filename, d_filename)) {
1138 uwsgi_403(wsgi_req);
1139 return UWSGI_OK;
1140 }
1141
1142 if (already_exists) {
1143 uwsgi_response_prepare_headers(wsgi_req, "204 No Content", 14);
1144 }
1145 else {
1146 uwsgi_response_prepare_headers(wsgi_req, "201 Created", 11);
1147 }
1148
1149 return UWSGI_OK;
1150 }
1151
1152 static int uwsgi_wevdav_manage_mkcol(struct wsgi_request *wsgi_req) {
1153 if (wsgi_req->post_cl > 0) {
1154 uwsgi_response_prepare_headers(wsgi_req, "415 Unsupported Media Type", 26);
1155 return UWSGI_OK;
1156 }
1157 char filename[PATH_MAX];
1158 size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
1159 // the collection already exists
1160 if (filename_len > 0) {
1161 uwsgi_response_prepare_headers(wsgi_req, "405 Method Not Allowed", 22);
1162 return UWSGI_OK;
1163 }
1164
1165 // remove the last slash (if needed)
1166 if (wsgi_req->path_info_len > 1 && wsgi_req->path_info[wsgi_req->path_info_len-1] == '/') {
1167 wsgi_req->path_info_len--;
1168 }
1169
1170 filename_len = uwsgi_webdav_expand_fake_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
1171 if (!filename_len) {
1172 uwsgi_response_prepare_headers(wsgi_req, "409 Conflict", 12);
1173 return UWSGI_OK;
1174 }
1175 // mkdir, if it fails, return a 409 (Conflict)
1176 if (mkdir(filename, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) {
1177 uwsgi_response_prepare_headers(wsgi_req, "409 Conflict", 12);
1178 }
1179 uwsgi_response_prepare_headers(wsgi_req, "201 Created", 11);
1180 return UWSGI_OK;
1181 }
1182
1183 static int uwsgi_wevdav_manage_mkcalendar(struct wsgi_request *wsgi_req, xmlDoc *doc) {
1184 char filename[PATH_MAX];
1185 size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
1186 // the collection already exists
1187 if (filename_len > 0) {
1188 uwsgi_response_prepare_headers(wsgi_req, "405 Method Not Allowed", 22);
1189 return UWSGI_OK;
1190 }
1191
1192 // remove the last slash (if needed)
1193 if (wsgi_req->path_info_len > 1 && wsgi_req->path_info[wsgi_req->path_info_len-1] == '/') {
1194 wsgi_req->path_info_len--;
1195 }
1196
1197 filename_len = uwsgi_webdav_expand_fake_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
1198 if (!filename_len) {
1199 uwsgi_response_prepare_headers(wsgi_req, "409 Conflict", 12);
1200 return UWSGI_OK;
1201 }
1202 // mkdir, if it fails, return a 409 (Conflict)
1203 if (mkdir(filename, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) {
1204 uwsgi_response_prepare_headers(wsgi_req, "409 Conflict", 12);
1205 return UWSGI_OK;
1206 }
1207
1208 xmlNode *element = xmlDocGetRootElement(doc);
1209 if (!element) return -1;
1210
1211 if (!element || (strcmp((char *) element->name, "mkcalendar"))) return -1;
1212
1213 xmlDoc *rdoc = xmlNewDoc(BAD_CAST "1.0");
1214 xmlNode *foobar = xmlNewNode(NULL, BAD_CAST "foobar");
1215 xmlDocSetRootElement(rdoc, foobar);
1216
1217 // propfind can be "set" or "remove"
1218 xmlNode *node;
1219 for (node = element->children; node; node = node->next) {
1220 if (node->type == XML_ELEMENT_NODE) {
1221 if (node->ns && !strcmp((char *) node->ns->href, "DAV:")) {
1222 if (!strcmp((char *) node->name, "set")) {
1223 uwsgi_webdav_manage_prop_update(wsgi_req, node, foobar, filename, 0);
1224 }
1225 }
1226 }
1227 }
1228
1229 uwsgi_response_prepare_headers(wsgi_req, "201 Created", 11);
1230 xmlFreeDoc(rdoc);
1231 return UWSGI_OK;
1232 }
1233
1234
1235 static int uwsgi_wevdav_manage_lock(struct wsgi_request *wsgi_req) {
1236 uwsgi_response_prepare_headers(wsgi_req, "201 Created", 11);
1237 return UWSGI_OK;
1238 }
1239
1240 static int uwsgi_webdav_request(struct wsgi_request *wsgi_req) {
1241
1242 if (!udav.mountpoints) {
1243 uwsgi_500(wsgi_req);
1244 return -1;
1245 }
1246
1247 if (uwsgi_parse_vars(wsgi_req)) {
1248 return -1;
1249 }
1250
1251 if (wsgi_req->path_info_len == 0) {
1252 uwsgi_403(wsgi_req);
1253 return UWSGI_OK;
1254 }
1255
1256 wsgi_req->app_id = uwsgi_get_app_id(wsgi_req, wsgi_req->appid, wsgi_req->appid_len, webdav_plugin.modifier1);
1257 if (wsgi_req->app_id == -1) {
1258 uwsgi_403(wsgi_req);
1259 return UWSGI_OK;
1260 }
1261
1262 // non lockables methods...
1263
1264 if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "OPTIONS", 7)) {
1265 return uwsgi_wevdav_manage_options(wsgi_req);
1266 }
1267
1268 if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "GET", 3)) {
1269 return uwsgi_wevdav_manage_get(wsgi_req, 1);
1270 }
1271
1272 if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "HEAD", 4)) {
1273 return uwsgi_wevdav_manage_get(wsgi_req, 0);
1274 }
1275
1276 if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "PROPFIND", 8)) {
1277 if (wsgi_req->post_cl > 0) {
1278 ssize_t body_len = 0;
1279 char *body = uwsgi_request_body_read(wsgi_req, wsgi_req->post_cl, &body_len);
1280 #ifdef UWSGI_DEBUG
1281 uwsgi_log("%.*s\n", body_len, body);
1282 #endif
1283 xmlDoc *doc = xmlReadMemory(body, body_len, NULL, NULL, 0);
1284 if (!doc) goto end;
1285 uwsgi_wevdav_manage_propfind(wsgi_req, doc);
1286 xmlFreeDoc(doc);
1287 }
1288 else {
1289 uwsgi_wevdav_manage_propfind(wsgi_req, NULL);
1290 }
1291 }
1292
1293 if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "REPORT", 6)) {
1294 if (wsgi_req->post_cl > 0) {
1295 ssize_t body_len = 0;
1296 char *body = uwsgi_request_body_read(wsgi_req, wsgi_req->post_cl, &body_len);
1297 #ifdef UWSGI_DEBUG
1298 uwsgi_log("%.*s\n", body_len, body);
1299 #endif
1300 xmlDoc *doc = xmlReadMemory(body, body_len, NULL, NULL, 0);
1301 if (!doc) goto end;
1302 xmlFreeDoc(doc);
1303 }
1304 }
1305
1306
1307 // lockable methods ...
1308 // check for locking
1309
1310 if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "PUT", 3)) {
1311 return uwsgi_wevdav_manage_put(wsgi_req);
1312 }
1313
1314 if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "DELETE", 6)) {
1315 return uwsgi_wevdav_manage_delete(wsgi_req);
1316 }
1317
1318 if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "MKCOL", 5)) {
1319 return uwsgi_wevdav_manage_mkcol(wsgi_req);
1320 }
1321
1322 if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "MKCALENDAR", 10)) {
1323 if (wsgi_req->post_cl == 0)
1324 goto end;
1325 ssize_t body_len = 0;
1326 char *body = uwsgi_request_body_read(wsgi_req, wsgi_req->post_cl, &body_len);
1327 #ifdef UWSGI_DEBUG
1328 uwsgi_log("%.*s\n", body_len, body);
1329 #endif
1330 xmlDoc *doc = xmlReadMemory(body, body_len, NULL, NULL, 0);
1331 if (!doc) goto end;
1332 uwsgi_wevdav_manage_mkcalendar(wsgi_req, doc);
1333 xmlFreeDoc(doc);
1334 }
1335
1336 if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "COPY", 4)) {
1337 return uwsgi_wevdav_manage_copy(wsgi_req);
1338 }
1339
1340 if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "MOVE", 4)) {
1341 return uwsgi_wevdav_manage_move(wsgi_req);
1342 }
1343
1344 if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "LOCK", 4)) {
1345 if (wsgi_req->post_cl > 0) {
1346 ssize_t body_len = 0;
1347 char *body = uwsgi_request_body_read(wsgi_req, wsgi_req->post_cl, &body_len);
1348 #ifdef UWSGI_DEBUG
1349 uwsgi_log("%.*s\n", body_len, body);
1350 #endif
1351 xmlDoc *doc = xmlReadMemory(body, body_len, NULL, NULL, 0);
1352 if (!doc) goto end;
1353 xmlFreeDoc(doc);
1354 }
1355 return uwsgi_wevdav_manage_lock(wsgi_req);
1356 }
1357
1358 if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "PROPPATCH", 9)) {
1359 if (wsgi_req->post_cl == 0)
1360 goto end;
1361 ssize_t body_len = 0;
1362 char *body = uwsgi_request_body_read(wsgi_req, wsgi_req->post_cl, &body_len);
1363 #ifdef UWSGI_DEBUG
1364 uwsgi_log("%.*s\n", body_len, body);
1365 #endif
1366 xmlDoc *doc = xmlReadMemory(body, body_len, NULL, NULL, 0);
1367 if (!doc) goto end;
1368 uwsgi_wevdav_manage_proppatch(wsgi_req, doc);
1369 xmlFreeDoc(doc);
1370 }
1371
1372 end:
1373 return UWSGI_OK;
1374 }
1375
1376 static void uwsgi_webdav_mount() {
1377 struct uwsgi_string_list *usl = udav.mountpoints;
1378 while(usl) {
1379 if (uwsgi_apps_cnt >= uwsgi.max_apps) {
1380 uwsgi_log("ERROR: you cannot load more than %d apps in a worker\n", uwsgi.max_apps);
1381 exit(1);
1382 }
1383 int id = uwsgi_apps_cnt;
1384 char *mountpoint = "";
1385 int mountpoint_len = 0;
1386 char *docroot = usl->value;
1387
1388 char *equal = strchr(usl->value, '=');
1389
1390 if (equal) {
1391 *equal = 0;
1392 docroot = equal+1;
1393 mountpoint = usl->value;
1394 mountpoint_len = strlen(mountpoint);
1395 }
1396
1397 char *wd_docroot = uwsgi_calloc(PATH_MAX);
1398 if (!realpath(docroot, wd_docroot)) {
1399 uwsgi_error("uwsgi_webdav_mount()/realpath()");
1400 exit(1);
1401 }
1402 if (equal) {
1403 *equal = '=';
1404 }
1405 struct uwsgi_app *ua = uwsgi_add_app(id, webdav_plugin.modifier1, mountpoint, mountpoint_len, wd_docroot, wd_docroot);
1406 uwsgi_emulate_cow_for_apps(id);
1407 uwsgi_log("WebDAV mountpoint \"%.*s\" (%d) added: docroot=%s\n", ua->mountpoint_len, ua->mountpoint, id, wd_docroot );
1408 usl = usl->next;
1409 }
1410 }
1411
1412 static void uwsgi_webdav_after_request(struct wsgi_request *wsgi_req) {
1413 if (!udav.mountpoints) return;
1414 log_request(wsgi_req);
1415 }
1416
1417 struct uwsgi_plugin webdav_plugin = {
1418 .modifier1 = 35,
1419 .name = "webdav",
1420 .options = uwsgi_webdav_options,
1421 .init_apps = uwsgi_webdav_mount,
1422 .request = uwsgi_webdav_request,
1423 .after_request = uwsgi_webdav_after_request,
1424 };
1425