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