1 #include "base.h"
2 #include "log.h"
3 #include "buffer.h"
4 #include "response.h"
5 
6 #include "plugin.h"
7 
8 #include "stream.h"
9 #include "stat_cache.h"
10 
11 #include "sys-mmap.h"
12 
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <ctype.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <stdio.h>
21 #include <assert.h>
22 
23 #include <unistd.h>
24 #include <dirent.h>
25 
26 #if defined(HAVE_LIBXML_H) && defined(HAVE_SQLITE3_H)
27 #define USE_PROPPATCH
28 #include <libxml/tree.h>
29 #include <libxml/parser.h>
30 
31 #include <sqlite3.h>
32 #endif
33 
34 #if defined(HAVE_LIBXML_H) && defined(HAVE_SQLITE3_H) && defined(HAVE_UUID_UUID_H)
35 #define USE_LOCKS
36 #include <uuid/uuid.h>
37 #endif
38 
39 /**
40  * this is a webdav for a lighttpd plugin
41  *
42  * at least a very basic one.
43  * - for now it is read-only and we only support PROPFIND
44  *
45  */
46 
47 #define WEBDAV_FILE_MODE S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH
48 #define WEBDAV_DIR_MODE  S_IRWXU | S_IRWXG | S_IRWXO
49 
50 /* plugin config for all request/connections */
51 
52 typedef struct {
53 	unsigned short enabled;
54 	unsigned short is_readonly;
55 	unsigned short log_xml;
56 
57 	buffer *sqlite_db_name;
58 #ifdef USE_PROPPATCH
59 	sqlite3 *sql;
60 	sqlite3_stmt *stmt_update_prop;
61 	sqlite3_stmt *stmt_delete_prop;
62 	sqlite3_stmt *stmt_select_prop;
63 	sqlite3_stmt *stmt_select_propnames;
64 
65 	sqlite3_stmt *stmt_delete_uri;
66 	sqlite3_stmt *stmt_move_uri;
67 	sqlite3_stmt *stmt_copy_uri;
68 
69 	sqlite3_stmt *stmt_remove_lock;
70 	sqlite3_stmt *stmt_create_lock;
71 	sqlite3_stmt *stmt_read_lock;
72 	sqlite3_stmt *stmt_read_lock_by_uri;
73 	sqlite3_stmt *stmt_refresh_lock;
74 #endif
75 } plugin_config;
76 
77 typedef struct {
78 	PLUGIN_DATA;
79 
80 	buffer *tmp_buf;
81 	request_uri uri;
82 	physical physical;
83 
84 	plugin_config **config_storage;
85 
86 	plugin_config conf;
87 } plugin_data;
88 
89 /* init the plugin data */
INIT_FUNC(mod_webdav_init)90 INIT_FUNC(mod_webdav_init) {
91 	plugin_data *p;
92 
93 	p = calloc(1, sizeof(*p));
94 
95 	p->tmp_buf = buffer_init();
96 
97 	p->uri.scheme = buffer_init();
98 	p->uri.path_raw = buffer_init();
99 	p->uri.path = buffer_init();
100 	p->uri.authority = buffer_init();
101 
102 	p->physical.path = buffer_init();
103 	p->physical.rel_path = buffer_init();
104 	p->physical.doc_root = buffer_init();
105 	p->physical.basedir = buffer_init();
106 
107 	return p;
108 }
109 
110 /* detroy the plugin data */
FREE_FUNC(mod_webdav_free)111 FREE_FUNC(mod_webdav_free) {
112 	plugin_data *p = p_d;
113 
114 	UNUSED(srv);
115 
116 	if (!p) return HANDLER_GO_ON;
117 
118 	if (p->config_storage) {
119 		size_t i;
120 		for (i = 0; i < srv->config_context->used; i++) {
121 			plugin_config *s = p->config_storage[i];
122 
123 			if (!s) continue;
124 
125 			buffer_free(s->sqlite_db_name);
126 #ifdef USE_PROPPATCH
127 			if (s->sql) {
128 				sqlite3_finalize(s->stmt_delete_prop);
129 				sqlite3_finalize(s->stmt_delete_uri);
130 				sqlite3_finalize(s->stmt_copy_uri);
131 				sqlite3_finalize(s->stmt_move_uri);
132 				sqlite3_finalize(s->stmt_update_prop);
133 				sqlite3_finalize(s->stmt_select_prop);
134 				sqlite3_finalize(s->stmt_select_propnames);
135 
136 				sqlite3_finalize(s->stmt_read_lock);
137 				sqlite3_finalize(s->stmt_read_lock_by_uri);
138 				sqlite3_finalize(s->stmt_create_lock);
139 				sqlite3_finalize(s->stmt_remove_lock);
140 				sqlite3_finalize(s->stmt_refresh_lock);
141 				sqlite3_close(s->sql);
142 			}
143 #endif
144 			free(s);
145 		}
146 		free(p->config_storage);
147 	}
148 
149 	buffer_free(p->uri.scheme);
150 	buffer_free(p->uri.path_raw);
151 	buffer_free(p->uri.path);
152 	buffer_free(p->uri.authority);
153 
154 	buffer_free(p->physical.path);
155 	buffer_free(p->physical.rel_path);
156 	buffer_free(p->physical.doc_root);
157 	buffer_free(p->physical.basedir);
158 
159 	buffer_free(p->tmp_buf);
160 
161 	free(p);
162 
163 	return HANDLER_GO_ON;
164 }
165 
166 /* handle plugin config and check values */
167 
SETDEFAULTS_FUNC(mod_webdav_set_defaults)168 SETDEFAULTS_FUNC(mod_webdav_set_defaults) {
169 	plugin_data *p = p_d;
170 	size_t i = 0;
171 
172 	config_values_t cv[] = {
173 		{ "webdav.activate",            NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },       /* 0 */
174 		{ "webdav.is-readonly",         NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },       /* 1 */
175 		{ "webdav.sqlite-db-name",      NULL, T_CONFIG_STRING,  T_CONFIG_SCOPE_CONNECTION },       /* 2 */
176 		{ "webdav.log-xml",             NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },       /* 3 */
177 		{ NULL,                         NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
178 	};
179 
180 	if (!p) return HANDLER_ERROR;
181 
182 	p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
183 
184 	for (i = 0; i < srv->config_context->used; i++) {
185 		plugin_config *s;
186 
187 		s = calloc(1, sizeof(plugin_config));
188 		s->sqlite_db_name = buffer_init();
189 
190 		cv[0].destination = &(s->enabled);
191 		cv[1].destination = &(s->is_readonly);
192 		cv[2].destination = s->sqlite_db_name;
193 		cv[3].destination = &(s->log_xml);
194 
195 		p->config_storage[i] = s;
196 
197 		if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
198 			return HANDLER_ERROR;
199 		}
200 
201 		if (!buffer_is_empty(s->sqlite_db_name)) {
202 #ifdef USE_PROPPATCH
203 			const char *next_stmt;
204 			char *err;
205 
206 			if (SQLITE_OK != sqlite3_open(s->sqlite_db_name->ptr, &(s->sql))) {
207 				log_error_write(srv, __FILE__, __LINE__, "sbs", "sqlite3_open failed for",
208 						s->sqlite_db_name,
209 						sqlite3_errmsg(s->sql));
210 				return HANDLER_ERROR;
211 			}
212 
213 			if (SQLITE_OK != sqlite3_exec(s->sql,
214 					"CREATE TABLE properties ("
215 					"  resource TEXT NOT NULL,"
216 					"  prop TEXT NOT NULL,"
217 					"  ns TEXT NOT NULL,"
218 					"  value TEXT NOT NULL,"
219 					"  PRIMARY KEY(resource, prop, ns))",
220 					NULL, NULL, &err)) {
221 
222 				if (0 != strcmp(err, "table properties already exists")) {
223 					log_error_write(srv, __FILE__, __LINE__, "ss", "can't open transaction:", err);
224 					sqlite3_free(err);
225 
226 					return HANDLER_ERROR;
227 				}
228 				sqlite3_free(err);
229 			}
230 
231 			if (SQLITE_OK != sqlite3_prepare(s->sql,
232 				CONST_STR_LEN("SELECT value FROM properties WHERE resource = ? AND prop = ? AND ns = ?"),
233 				&(s->stmt_select_prop), &next_stmt)) {
234 				/* prepare failed */
235 
236 				log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql));
237 				return HANDLER_ERROR;
238 			}
239 
240 			if (SQLITE_OK != sqlite3_prepare(s->sql,
241 				CONST_STR_LEN("SELECT ns, prop FROM properties WHERE resource = ?"),
242 				&(s->stmt_select_propnames), &next_stmt)) {
243 				/* prepare failed */
244 
245 				log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql));
246 				return HANDLER_ERROR;
247 			}
248 
249 
250 			if (SQLITE_OK != sqlite3_prepare(s->sql,
251 				CONST_STR_LEN("REPLACE INTO properties (resource, prop, ns, value) VALUES (?, ?, ?, ?)"),
252 				&(s->stmt_update_prop), &next_stmt)) {
253 				/* prepare failed */
254 
255 				log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql));
256 				return HANDLER_ERROR;
257 			}
258 
259 			if (SQLITE_OK != sqlite3_prepare(s->sql,
260 				CONST_STR_LEN("DELETE FROM properties WHERE resource = ? AND prop = ? AND ns = ?"),
261 				&(s->stmt_delete_prop), &next_stmt)) {
262 				/* prepare failed */
263 				log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
264 
265 				return HANDLER_ERROR;
266 			}
267 
268 			if (SQLITE_OK != sqlite3_prepare(s->sql,
269 				CONST_STR_LEN("DELETE FROM properties WHERE resource = ?"),
270 				&(s->stmt_delete_uri), &next_stmt)) {
271 				/* prepare failed */
272 				log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
273 
274 				return HANDLER_ERROR;
275 			}
276 
277 			if (SQLITE_OK != sqlite3_prepare(s->sql,
278 				CONST_STR_LEN("INSERT INTO properties SELECT ?, prop, ns, value FROM properties WHERE resource = ?"),
279 				&(s->stmt_copy_uri), &next_stmt)) {
280 				/* prepare failed */
281 				log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
282 
283 				return HANDLER_ERROR;
284 			}
285 
286 			if (SQLITE_OK != sqlite3_prepare(s->sql,
287 				CONST_STR_LEN("UPDATE properties SET resource = ? WHERE resource = ?"),
288 				&(s->stmt_move_uri), &next_stmt)) {
289 				/* prepare failed */
290 				log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
291 
292 				return HANDLER_ERROR;
293 			}
294 
295 			/* LOCKS */
296 
297 			if (SQLITE_OK != sqlite3_exec(s->sql,
298 					"CREATE TABLE locks ("
299 					"  locktoken TEXT NOT NULL,"
300 					"  resource TEXT NOT NULL,"
301 					"  lockscope TEXT NOT NULL,"
302 					"  locktype TEXT NOT NULL,"
303 					"  owner TEXT NOT NULL,"
304 					"  depth INT NOT NULL,"
305 					"  timeout TIMESTAMP NOT NULL,"
306 					"  PRIMARY KEY(locktoken))",
307 					NULL, NULL, &err)) {
308 
309 				if (0 != strcmp(err, "table locks already exists")) {
310 					log_error_write(srv, __FILE__, __LINE__, "ss", "can't open transaction:", err);
311 					sqlite3_free(err);
312 
313 					return HANDLER_ERROR;
314 				}
315 				sqlite3_free(err);
316 			}
317 
318 			if (SQLITE_OK != sqlite3_prepare(s->sql,
319 				CONST_STR_LEN("INSERT INTO locks (locktoken, resource, lockscope, locktype, owner, depth, timeout) VALUES (?,?,?,?,?,?, CURRENT_TIME + 600)"),
320 				&(s->stmt_create_lock), &next_stmt)) {
321 				/* prepare failed */
322 				log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
323 
324 				return HANDLER_ERROR;
325 			}
326 
327 			if (SQLITE_OK != sqlite3_prepare(s->sql,
328 				CONST_STR_LEN("DELETE FROM locks WHERE locktoken = ?"),
329 				&(s->stmt_remove_lock), &next_stmt)) {
330 				/* prepare failed */
331 				log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
332 
333 				return HANDLER_ERROR;
334 			}
335 
336 			if (SQLITE_OK != sqlite3_prepare(s->sql,
337 				CONST_STR_LEN("SELECT locktoken, resource, lockscope, locktype, owner, depth, timeout FROM locks WHERE locktoken = ?"),
338 				&(s->stmt_read_lock), &next_stmt)) {
339 				/* prepare failed */
340 				log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
341 
342 				return HANDLER_ERROR;
343 			}
344 
345 			if (SQLITE_OK != sqlite3_prepare(s->sql,
346 				CONST_STR_LEN("SELECT locktoken, resource, lockscope, locktype, owner, depth, timeout FROM locks WHERE resource = ?"),
347 				&(s->stmt_read_lock_by_uri), &next_stmt)) {
348 				/* prepare failed */
349 				log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
350 
351 				return HANDLER_ERROR;
352 			}
353 
354 			if (SQLITE_OK != sqlite3_prepare(s->sql,
355 				CONST_STR_LEN("UPDATE locks SET timeout = CURRENT_TIME + 600 WHERE locktoken = ?"),
356 				&(s->stmt_refresh_lock), &next_stmt)) {
357 				/* prepare failed */
358 				log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
359 
360 				return HANDLER_ERROR;
361 			}
362 
363 
364 #else
365 			log_error_write(srv, __FILE__, __LINE__, "s", "Sorry, no sqlite3 and libxml2 support include, compile with --with-webdav-props");
366 			return HANDLER_ERROR;
367 #endif
368 		}
369 	}
370 
371 	return HANDLER_GO_ON;
372 }
373 
374 #define PATCH_OPTION(x) \
375 	p->conf.x = s->x;
mod_webdav_patch_connection(server * srv,connection * con,plugin_data * p)376 static int mod_webdav_patch_connection(server *srv, connection *con, plugin_data *p) {
377 	size_t i, j;
378 	plugin_config *s = p->config_storage[0];
379 
380 	PATCH_OPTION(enabled);
381 	PATCH_OPTION(is_readonly);
382 	PATCH_OPTION(log_xml);
383 
384 #ifdef USE_PROPPATCH
385 	PATCH_OPTION(sql);
386 	PATCH_OPTION(stmt_update_prop);
387 	PATCH_OPTION(stmt_delete_prop);
388 	PATCH_OPTION(stmt_select_prop);
389 	PATCH_OPTION(stmt_select_propnames);
390 
391 	PATCH_OPTION(stmt_delete_uri);
392 	PATCH_OPTION(stmt_move_uri);
393 	PATCH_OPTION(stmt_copy_uri);
394 
395 	PATCH_OPTION(stmt_remove_lock);
396 	PATCH_OPTION(stmt_refresh_lock);
397 	PATCH_OPTION(stmt_create_lock);
398 	PATCH_OPTION(stmt_read_lock);
399 	PATCH_OPTION(stmt_read_lock_by_uri);
400 #endif
401 	/* skip the first, the global context */
402 	for (i = 1; i < srv->config_context->used; i++) {
403 		data_config *dc = (data_config *)srv->config_context->data[i];
404 		s = p->config_storage[i];
405 
406 		/* condition didn't match */
407 		if (!config_check_cond(srv, con, dc)) continue;
408 
409 		/* merge config */
410 		for (j = 0; j < dc->value->used; j++) {
411 			data_unset *du = dc->value->data[j];
412 
413 			if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.activate"))) {
414 				PATCH_OPTION(enabled);
415 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.is-readonly"))) {
416 				PATCH_OPTION(is_readonly);
417 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.log-xml"))) {
418 				PATCH_OPTION(log_xml);
419 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.sqlite-db-name"))) {
420 #ifdef USE_PROPPATCH
421 				PATCH_OPTION(sql);
422 				PATCH_OPTION(stmt_update_prop);
423 				PATCH_OPTION(stmt_delete_prop);
424 				PATCH_OPTION(stmt_select_prop);
425 				PATCH_OPTION(stmt_select_propnames);
426 
427 				PATCH_OPTION(stmt_delete_uri);
428 				PATCH_OPTION(stmt_move_uri);
429 				PATCH_OPTION(stmt_copy_uri);
430 
431 				PATCH_OPTION(stmt_remove_lock);
432 				PATCH_OPTION(stmt_refresh_lock);
433 				PATCH_OPTION(stmt_create_lock);
434 				PATCH_OPTION(stmt_read_lock);
435 				PATCH_OPTION(stmt_read_lock_by_uri);
436 #endif
437 			}
438 		}
439 	}
440 
441 	return 0;
442 }
443 
URIHANDLER_FUNC(mod_webdav_uri_handler)444 URIHANDLER_FUNC(mod_webdav_uri_handler) {
445 	plugin_data *p = p_d;
446 
447 	UNUSED(srv);
448 
449 	if (con->uri.path->used == 0) return HANDLER_GO_ON;
450 
451 	mod_webdav_patch_connection(srv, con, p);
452 
453 	if (!p->conf.enabled) return HANDLER_GO_ON;
454 
455 	switch (con->request.http_method) {
456 	case HTTP_METHOD_OPTIONS:
457 		/* we fake a little bit but it makes MS W2k happy and it let's us mount the volume */
458 		response_header_overwrite(srv, con, CONST_STR_LEN("DAV"), CONST_STR_LEN("1,2"));
459 		response_header_overwrite(srv, con, CONST_STR_LEN("MS-Author-Via"), CONST_STR_LEN("DAV"));
460 
461 		if (p->conf.is_readonly) {
462 			response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("PROPFIND"));
463 		} else {
464 			response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("PROPFIND, DELETE, MKCOL, PUT, MOVE, COPY, PROPPATCH, LOCK, UNLOCK"));
465 		}
466 		break;
467 	default:
468 		break;
469 	}
470 
471 	/* not found */
472 	return HANDLER_GO_ON;
473 }
webdav_gen_prop_tag(server * srv,connection * con,char * prop_name,char * prop_ns,char * value,buffer * b)474 static int webdav_gen_prop_tag(server *srv, connection *con,
475 		char *prop_name,
476 		char *prop_ns,
477 		char *value,
478 		buffer *b) {
479 
480 	UNUSED(srv);
481 	UNUSED(con);
482 
483 	if (value) {
484 		buffer_append_string_len(b,CONST_STR_LEN("<"));
485 		buffer_append_string(b, prop_name);
486 		buffer_append_string_len(b, CONST_STR_LEN(" xmlns=\""));
487 		buffer_append_string(b, prop_ns);
488 		buffer_append_string_len(b, CONST_STR_LEN("\">"));
489 
490 		buffer_append_string(b, value);
491 
492 		buffer_append_string_len(b,CONST_STR_LEN("</"));
493 		buffer_append_string(b, prop_name);
494 		buffer_append_string_len(b, CONST_STR_LEN(">"));
495 	} else {
496 		buffer_append_string_len(b,CONST_STR_LEN("<"));
497 		buffer_append_string(b, prop_name);
498 		buffer_append_string_len(b, CONST_STR_LEN(" xmlns=\""));
499 		buffer_append_string(b, prop_ns);
500 		buffer_append_string_len(b, CONST_STR_LEN("\"/>"));
501 	}
502 
503 	return 0;
504 }
505 
506 
webdav_gen_response_status_tag(server * srv,connection * con,physical * dst,int status,buffer * b)507 static int webdav_gen_response_status_tag(server *srv, connection *con, physical *dst, int status, buffer *b) {
508 	UNUSED(srv);
509 
510 	buffer_append_string_len(b,CONST_STR_LEN("<D:response xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\">\n"));
511 
512 	buffer_append_string_len(b,CONST_STR_LEN("<D:href>\n"));
513 	buffer_append_string_buffer(b, dst->rel_path);
514 	buffer_append_string_len(b,CONST_STR_LEN("</D:href>\n"));
515 	buffer_append_string_len(b,CONST_STR_LEN("<D:status>\n"));
516 
517 	if (con->request.http_version == HTTP_VERSION_1_1) {
518 		buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.1 "));
519 	} else {
520 		buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.0 "));
521 	}
522 	buffer_append_long(b, status);
523 	buffer_append_string_len(b, CONST_STR_LEN(" "));
524 	buffer_append_string(b, get_http_status_name(status));
525 
526 	buffer_append_string_len(b,CONST_STR_LEN("</D:status>\n"));
527 	buffer_append_string_len(b,CONST_STR_LEN("</D:response>\n"));
528 
529 	return 0;
530 }
531 
webdav_delete_file(server * srv,connection * con,plugin_data * p,physical * dst,buffer * b)532 static int webdav_delete_file(server *srv, connection *con, plugin_data *p, physical *dst, buffer *b) {
533 	int status = 0;
534 
535 	/* try to unlink it */
536 	if (-1 == unlink(dst->path->ptr)) {
537 		switch(errno) {
538 		case EACCES:
539 		case EPERM:
540 			/* 403 */
541 			status = 403;
542 			break;
543 		default:
544 			status = 501;
545 			break;
546 		}
547 		webdav_gen_response_status_tag(srv, con, dst, status, b);
548 	} else {
549 #ifdef USE_PROPPATCH
550 		sqlite3_stmt *stmt = p->conf.stmt_delete_uri;
551 
552 		if (!stmt) {
553 			status = 403;
554 			webdav_gen_response_status_tag(srv, con, dst, status, b);
555 		} else {
556 			sqlite3_reset(stmt);
557 
558 			/* bind the values to the insert */
559 
560 			sqlite3_bind_text(stmt, 1,
561 					  dst->rel_path->ptr,
562 					  dst->rel_path->used - 1,
563 					  SQLITE_TRANSIENT);
564 
565 			if (SQLITE_DONE != sqlite3_step(stmt)) {
566 				/* */
567 			}
568 		}
569 #else
570 		UNUSED(p);
571 #endif
572 	}
573 
574 	return (status != 0);
575 }
576 
webdav_delete_dir(server * srv,connection * con,plugin_data * p,physical * dst,buffer * b)577 static int webdav_delete_dir(server *srv, connection *con, plugin_data *p, physical *dst, buffer *b) {
578 	DIR *dir;
579 	int have_multi_status = 0;
580 	physical d;
581 
582 	d.path = buffer_init();
583 	d.rel_path = buffer_init();
584 
585 	if (NULL != (dir = opendir(dst->path->ptr))) {
586 		struct dirent *de;
587 
588 		while(NULL != (de = readdir(dir))) {
589 			struct stat st;
590 			int status = 0;
591 
592 			if ((de->d_name[0] == '.' && de->d_name[1] == '\0')  ||
593 			    (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')) {
594 				continue;
595 				/* ignore the parent dir */
596 			}
597 
598 			buffer_copy_string_buffer(d.path, dst->path);
599 			BUFFER_APPEND_SLASH(d.path);
600 			buffer_append_string(d.path, de->d_name);
601 
602 			buffer_copy_string_buffer(d.rel_path, dst->rel_path);
603 			BUFFER_APPEND_SLASH(d.rel_path);
604 			buffer_append_string(d.rel_path, de->d_name);
605 
606 			/* stat and unlink afterwards */
607 			if (-1 == stat(d.path->ptr, &st)) {
608 				/* don't about it yet, rmdir will fail too */
609 			} else if (S_ISDIR(st.st_mode)) {
610 				have_multi_status = webdav_delete_dir(srv, con, p, &d, b);
611 
612 				/* try to unlink it */
613 				if (-1 == rmdir(d.path->ptr)) {
614 					switch(errno) {
615 					case EACCES:
616 					case EPERM:
617 						/* 403 */
618 						status = 403;
619 						break;
620 					default:
621 						status = 501;
622 						break;
623 					}
624 					have_multi_status = 1;
625 
626 					webdav_gen_response_status_tag(srv, con, &d, status, b);
627 				} else {
628 #ifdef USE_PROPPATCH
629 					sqlite3_stmt *stmt = p->conf.stmt_delete_uri;
630 
631 					status = 0;
632 
633 					if (stmt) {
634 						sqlite3_reset(stmt);
635 
636 						/* bind the values to the insert */
637 
638 						sqlite3_bind_text(stmt, 1,
639 								  d.rel_path->ptr,
640 								  d.rel_path->used - 1,
641 								  SQLITE_TRANSIENT);
642 
643 						if (SQLITE_DONE != sqlite3_step(stmt)) {
644 							/* */
645 						}
646 					}
647 #endif
648 				}
649 			} else {
650 				have_multi_status = webdav_delete_file(srv, con, p, &d, b);
651 			}
652 		}
653 		closedir(dir);
654 
655 		buffer_free(d.path);
656 		buffer_free(d.rel_path);
657 	}
658 
659 	return have_multi_status;
660 }
661 
webdav_copy_file(server * srv,connection * con,plugin_data * p,physical * src,physical * dst,int overwrite)662 static int webdav_copy_file(server *srv, connection *con, plugin_data *p, physical *src, physical *dst, int overwrite) {
663 	stream s;
664 	int status = 0, ofd;
665 	UNUSED(srv);
666 	UNUSED(con);
667 
668 	if (stream_open(&s, src->path)) {
669 		return 403;
670 	}
671 
672 	if (-1 == (ofd = open(dst->path->ptr, O_WRONLY|O_TRUNC|O_CREAT|(overwrite ? 0 : O_EXCL), WEBDAV_FILE_MODE))) {
673 		/* opening the destination failed for some reason */
674 		switch(errno) {
675 		case EEXIST:
676 			status = 412;
677 			break;
678 		case EISDIR:
679 			status = 409;
680 			break;
681 		case ENOENT:
682 			/* at least one part in the middle wasn't existing */
683 			status = 409;
684 			break;
685 		default:
686 			status = 403;
687 			break;
688 		}
689 		stream_close(&s);
690 		return status;
691 	}
692 
693 	if (-1 == write(ofd, s.start, s.size)) {
694 		switch(errno) {
695 		case ENOSPC:
696 			status = 507;
697 			break;
698 		default:
699 			status = 403;
700 			break;
701 		}
702 	}
703 
704 	stream_close(&s);
705 	close(ofd);
706 
707 #ifdef USE_PROPPATCH
708 	if (0 == status) {
709 		/* copy worked fine, copy connected properties */
710 		sqlite3_stmt *stmt = p->conf.stmt_copy_uri;
711 
712 		if (stmt) {
713 			sqlite3_reset(stmt);
714 
715 			/* bind the values to the insert */
716 			sqlite3_bind_text(stmt, 1,
717 					  dst->rel_path->ptr,
718 					  dst->rel_path->used - 1,
719 					  SQLITE_TRANSIENT);
720 
721 			sqlite3_bind_text(stmt, 2,
722 					  src->rel_path->ptr,
723 					  src->rel_path->used - 1,
724 					  SQLITE_TRANSIENT);
725 
726 			if (SQLITE_DONE != sqlite3_step(stmt)) {
727 				/* */
728 			}
729 		}
730 	}
731 #else
732 	UNUSED(p);
733 #endif
734 	return status;
735 }
736 
webdav_copy_dir(server * srv,connection * con,plugin_data * p,physical * src,physical * dst,int overwrite)737 static int webdav_copy_dir(server *srv, connection *con, plugin_data *p, physical *src, physical *dst, int overwrite) {
738 	DIR *srcdir;
739 	int status = 0;
740 
741 	if (NULL != (srcdir = opendir(src->path->ptr))) {
742 		struct dirent *de;
743 		physical s, d;
744 
745 		s.path = buffer_init();
746 		s.rel_path = buffer_init();
747 
748 		d.path = buffer_init();
749 		d.rel_path = buffer_init();
750 
751 		while (NULL != (de = readdir(srcdir))) {
752 			struct stat st;
753 
754 			if ((de->d_name[0] == '.' && de->d_name[1] == '\0') ||
755 		            (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')) {
756 				continue;
757 			}
758 
759 			buffer_copy_string_buffer(s.path, src->path);
760 			BUFFER_APPEND_SLASH(s.path);
761 			buffer_append_string(s.path, de->d_name);
762 
763 			buffer_copy_string_buffer(d.path, dst->path);
764 			BUFFER_APPEND_SLASH(d.path);
765 			buffer_append_string(d.path, de->d_name);
766 
767 			buffer_copy_string_buffer(s.rel_path, src->rel_path);
768 			BUFFER_APPEND_SLASH(s.rel_path);
769 			buffer_append_string(s.rel_path, de->d_name);
770 
771 			buffer_copy_string_buffer(d.rel_path, dst->rel_path);
772 			BUFFER_APPEND_SLASH(d.rel_path);
773 			buffer_append_string(d.rel_path, de->d_name);
774 
775 			if (-1 == stat(s.path->ptr, &st)) {
776 				/* why ? */
777 			} else if (S_ISDIR(st.st_mode)) {
778 				/* a directory */
779 				if (-1 == mkdir(d.path->ptr, WEBDAV_DIR_MODE) &&
780 				    errno != EEXIST) {
781 					/* WTH ? */
782 				} else {
783 #ifdef USE_PROPPATCH
784 					sqlite3_stmt *stmt = p->conf.stmt_copy_uri;
785 
786 					if (0 != (status = webdav_copy_dir(srv, con, p, &s, &d, overwrite))) {
787 						break;
788 					}
789 					/* directory is copied, copy the properties too */
790 
791 					if (stmt) {
792 						sqlite3_reset(stmt);
793 
794 						/* bind the values to the insert */
795 						sqlite3_bind_text(stmt, 1,
796 							  dst->rel_path->ptr,
797 							  dst->rel_path->used - 1,
798 							  SQLITE_TRANSIENT);
799 
800 						sqlite3_bind_text(stmt, 2,
801 							  src->rel_path->ptr,
802 							  src->rel_path->used - 1,
803 							  SQLITE_TRANSIENT);
804 
805 						if (SQLITE_DONE != sqlite3_step(stmt)) {
806 							/* */
807 						}
808 					}
809 #endif
810 				}
811 			} else if (S_ISREG(st.st_mode)) {
812 				/* a plain file */
813 				if (0 != (status = webdav_copy_file(srv, con, p, &s, &d, overwrite))) {
814 					break;
815 				}
816 			}
817 		}
818 
819 		buffer_free(s.path);
820 		buffer_free(s.rel_path);
821 		buffer_free(d.path);
822 		buffer_free(d.rel_path);
823 
824 		closedir(srcdir);
825 	}
826 
827 	return status;
828 }
829 
webdav_get_live_property(server * srv,connection * con,plugin_data * p,physical * dst,char * prop_name,buffer * b)830 static int webdav_get_live_property(server *srv, connection *con, plugin_data *p, physical *dst, char *prop_name, buffer *b) {
831 	stat_cache_entry *sce = NULL;
832 	int found = 0;
833 
834 	UNUSED(p);
835 
836 	if (HANDLER_ERROR != (stat_cache_get_entry(srv, con, dst->path, &sce))) {
837 		char ctime_buf[] = "2005-08-18T07:27:16Z";
838 		char mtime_buf[] = "Thu, 18 Aug 2005 07:27:16 GMT";
839 		size_t k;
840 
841 		if (0 == strcmp(prop_name, "resourcetype")) {
842 			if (S_ISDIR(sce->st.st_mode)) {
843 				buffer_append_string_len(b, CONST_STR_LEN("<D:resourcetype><D:collection/></D:resourcetype>"));
844 				found = 1;
845 			}
846 		} else if (0 == strcmp(prop_name, "getcontenttype")) {
847 			if (S_ISDIR(sce->st.st_mode)) {
848 				buffer_append_string_len(b, CONST_STR_LEN("<D:getcontenttype>httpd/unix-directory</D:getcontenttype>"));
849 				found = 1;
850 			} else if(S_ISREG(sce->st.st_mode)) {
851 				for (k = 0; k < con->conf.mimetypes->used; k++) {
852 					data_string *ds = (data_string *)con->conf.mimetypes->data[k];
853 
854 					if (ds->key->used == 0) continue;
855 
856 					if (buffer_is_equal_right_len(dst->path, ds->key, ds->key->used - 1)) {
857 						buffer_append_string_len(b,CONST_STR_LEN("<D:getcontenttype>"));
858 						buffer_append_string_buffer(b, ds->value);
859 						buffer_append_string_len(b, CONST_STR_LEN("</D:getcontenttype>"));
860 						found = 1;
861 
862 						break;
863 					}
864 				}
865 			}
866 		} else if (0 == strcmp(prop_name, "creationdate")) {
867 			buffer_append_string_len(b, CONST_STR_LEN("<D:creationdate ns0:dt=\"dateTime.tz\">"));
868 			strftime(ctime_buf, sizeof(ctime_buf), "%Y-%m-%dT%H:%M:%SZ", gmtime(&(sce->st.st_ctime)));
869 			buffer_append_string(b, ctime_buf);
870 			buffer_append_string_len(b, CONST_STR_LEN("</D:creationdate>"));
871 			found = 1;
872 		} else if (0 == strcmp(prop_name, "getlastmodified")) {
873 			buffer_append_string_len(b,CONST_STR_LEN("<D:getlastmodified ns0:dt=\"dateTime.rfc1123\">"));
874 			strftime(mtime_buf, sizeof(mtime_buf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(sce->st.st_mtime)));
875 			buffer_append_string(b, mtime_buf);
876 			buffer_append_string_len(b, CONST_STR_LEN("</D:getlastmodified>"));
877 			found = 1;
878 		} else if (0 == strcmp(prop_name, "getcontentlength")) {
879 			buffer_append_string_len(b,CONST_STR_LEN("<D:getcontentlength>"));
880 			buffer_append_off_t(b, sce->st.st_size);
881 			buffer_append_string_len(b, CONST_STR_LEN("</D:getcontentlength>"));
882 			found = 1;
883 		} else if (0 == strcmp(prop_name, "getcontentlanguage")) {
884 			buffer_append_string_len(b,CONST_STR_LEN("<D:getcontentlanguage>"));
885 			buffer_append_string_len(b, CONST_STR_LEN("en"));
886 			buffer_append_string_len(b, CONST_STR_LEN("</D:getcontentlanguage>"));
887 			found = 1;
888 		}
889 	}
890 
891 	return found ? 0 : -1;
892 }
893 
webdav_get_property(server * srv,connection * con,plugin_data * p,physical * dst,char * prop_name,char * prop_ns,buffer * b)894 static int webdav_get_property(server *srv, connection *con, plugin_data *p, physical *dst, char *prop_name, char *prop_ns, buffer *b) {
895 	if (0 == strcmp(prop_ns, "DAV:")) {
896 		/* a local 'live' property */
897 		return webdav_get_live_property(srv, con, p, dst, prop_name, b);
898 	} else {
899 		int found = 0;
900 #ifdef USE_PROPPATCH
901 		sqlite3_stmt *stmt = p->conf.stmt_select_prop;
902 
903 		if (stmt) {
904 			/* perhaps it is in sqlite3 */
905 			sqlite3_reset(stmt);
906 
907 			/* bind the values to the insert */
908 
909 			sqlite3_bind_text(stmt, 1,
910 					  dst->rel_path->ptr,
911 					  dst->rel_path->used - 1,
912 					  SQLITE_TRANSIENT);
913 			sqlite3_bind_text(stmt, 2,
914 					  prop_name,
915 					  strlen(prop_name),
916 					  SQLITE_TRANSIENT);
917 			sqlite3_bind_text(stmt, 3,
918 					  prop_ns,
919 					  strlen(prop_ns),
920 					  SQLITE_TRANSIENT);
921 
922 			/* it is the PK */
923 			while (SQLITE_ROW == sqlite3_step(stmt)) {
924 				/* there is a row for us, we only expect a single col 'value' */
925 				webdav_gen_prop_tag(srv, con, prop_name, prop_ns, (char *)sqlite3_column_text(stmt, 0), b);
926 				found = 1;
927 			}
928 		}
929 #endif
930 		return found ? 0 : -1;
931 	}
932 
933 	/* not found */
934 	return -1;
935 }
936 
937 typedef struct {
938 	char *ns;
939 	char *prop;
940 } webdav_property;
941 
942 static webdav_property live_properties[] = {
943 	{ "DAV:", "creationdate" },
944 	{ "DAV:", "displayname" },
945 	{ "DAV:", "getcontentlanguage" },
946 	{ "DAV:", "getcontentlength" },
947 	{ "DAV:", "getcontenttype" },
948 	{ "DAV:", "getetag" },
949 	{ "DAV:", "getlastmodified" },
950 	{ "DAV:", "resourcetype" },
951 	{ "DAV:", "lockdiscovery" },
952 	{ "DAV:", "source" },
953 	{ "DAV:", "supportedlock" },
954 
955 	{ NULL, NULL }
956 };
957 
958 typedef struct {
959 	webdav_property **ptr;
960 
961 	size_t used;
962 	size_t size;
963 } webdav_properties;
964 
webdav_get_props(server * srv,connection * con,plugin_data * p,physical * dst,webdav_properties * props,buffer * b_200,buffer * b_404)965 static int webdav_get_props(server *srv, connection *con, plugin_data *p, physical *dst, webdav_properties *props, buffer *b_200, buffer *b_404) {
966 	size_t i;
967 
968 	if (props) {
969 		for (i = 0; i < props->used; i++) {
970 			webdav_property *prop;
971 
972 			prop = props->ptr[i];
973 
974 			if (0 != webdav_get_property(srv, con, p,
975 				dst, prop->prop, prop->ns, b_200)) {
976 				webdav_gen_prop_tag(srv, con, prop->prop, prop->ns, NULL, b_404);
977 			}
978 		}
979 	} else {
980 		for (i = 0; live_properties[i].prop; i++) {
981 			/* a local 'live' property */
982 			webdav_get_live_property(srv, con, p, dst, live_properties[i].prop, b_200);
983 		}
984 	}
985 
986 	return 0;
987 }
988 
989 #ifdef USE_PROPPATCH
webdav_parse_chunkqueue(server * srv,connection * con,plugin_data * p,chunkqueue * cq,xmlDoc ** ret_xml)990 static int webdav_parse_chunkqueue(server *srv, connection *con, plugin_data *p, chunkqueue *cq, xmlDoc **ret_xml) {
991 	xmlParserCtxtPtr ctxt;
992 	xmlDoc *xml;
993 	int res;
994 	int err;
995 
996 	chunk *c;
997 
998 	UNUSED(con);
999 
1000 	/* read the chunks in to the XML document */
1001 	ctxt = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL);
1002 
1003 	for (c = cq->first; cq->bytes_out != cq->bytes_in; c = cq->first) {
1004 		size_t weWant = cq->bytes_out - cq->bytes_in;
1005 		size_t weHave;
1006 
1007 		switch(c->type) {
1008 		case FILE_CHUNK:
1009 			weHave = c->file.length - c->offset;
1010 
1011 			if (weHave > weWant) weHave = weWant;
1012 
1013 			/* xml chunks are always memory, mmap() is our friend */
1014 			if (c->file.mmap.start == MAP_FAILED) {
1015 				if (-1 == c->file.fd &&  /* open the file if not already open */
1016 				    -1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) {
1017 					log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
1018 
1019 					return -1;
1020 				}
1021 
1022 				if (MAP_FAILED == (c->file.mmap.start = mmap(0, c->file.length, PROT_READ, MAP_SHARED, c->file.fd, 0))) {
1023 					log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ",
1024 							strerror(errno), c->file.name,  c->file.fd);
1025 					close(c->file.fd);
1026 					c->file.fd = -1;
1027 
1028 					return -1;
1029 				}
1030 
1031 				close(c->file.fd);
1032 				c->file.fd = -1;
1033 
1034 				c->file.mmap.length = c->file.length;
1035 
1036 				/* chunk_reset() or chunk_free() will cleanup for us */
1037 			}
1038 
1039 			if (XML_ERR_OK != (err = xmlParseChunk(ctxt, c->file.mmap.start + c->offset, weHave, 0))) {
1040 				log_error_write(srv, __FILE__, __LINE__, "sodd", "xmlParseChunk failed at:", cq->bytes_out, weHave, err);
1041 			}
1042 
1043 			c->offset += weHave;
1044 			cq->bytes_out += weHave;
1045 
1046 			break;
1047 		case MEM_CHUNK:
1048 			/* append to the buffer */
1049 			weHave = c->mem->used - 1 - c->offset;
1050 
1051 			if (weHave > weWant) weHave = weWant;
1052 
1053 			if (p->conf.log_xml) {
1054 				log_error_write(srv, __FILE__, __LINE__, "ss", "XML-request-body:", c->mem->ptr + c->offset);
1055 			}
1056 
1057 			if (XML_ERR_OK != (err = xmlParseChunk(ctxt, c->mem->ptr + c->offset, weHave, 0))) {
1058 				log_error_write(srv, __FILE__, __LINE__, "sodd", "xmlParseChunk failed at:", cq->bytes_out, weHave, err);
1059 			}
1060 
1061 			c->offset += weHave;
1062 			cq->bytes_out += weHave;
1063 
1064 			break;
1065 		case UNUSED_CHUNK:
1066 			break;
1067 		}
1068 		chunkqueue_remove_finished_chunks(cq);
1069 	}
1070 
1071 
1072 	switch ((err = xmlParseChunk(ctxt, 0, 0, 1))) {
1073 	case XML_ERR_DOCUMENT_END:
1074 	case XML_ERR_OK:
1075 		break;
1076 	default:
1077 		log_error_write(srv, __FILE__, __LINE__, "sd", "xmlParseChunk failed at final packet:", err);
1078 		break;
1079 	}
1080 
1081 	xml = ctxt->myDoc;
1082 	res = ctxt->wellFormed;
1083 	xmlFreeParserCtxt(ctxt);
1084 
1085 	if (res == 0) {
1086 		xmlFreeDoc(xml);
1087 	} else {
1088 		*ret_xml = xml;
1089 	}
1090 
1091 	return res;
1092 }
1093 #endif
1094 
1095 #ifdef USE_LOCKS
webdav_lockdiscovery(server * srv,connection * con,buffer * locktoken,const char * lockscope,const char * locktype,int depth)1096 static int webdav_lockdiscovery(server *srv, connection *con,
1097 		buffer *locktoken, const char *lockscope, const char *locktype, int depth) {
1098 
1099 	buffer *b;
1100 
1101 	response_header_overwrite(srv, con, CONST_STR_LEN("Lock-Token"), CONST_BUF_LEN(locktoken));
1102 
1103 	response_header_overwrite(srv, con,
1104 		CONST_STR_LEN("Content-Type"),
1105 		CONST_STR_LEN("text/xml; charset=\"utf-8\""));
1106 
1107 	b = chunkqueue_get_append_buffer(con->write_queue);
1108 
1109 	buffer_copy_string_len(b, CONST_STR_LEN("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"));
1110 
1111 	buffer_append_string_len(b,CONST_STR_LEN("<D:prop xmlns:D=\"DAV:\" xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\">\n"));
1112 	buffer_append_string_len(b,CONST_STR_LEN("<D:lockdiscovery>\n"));
1113 	buffer_append_string_len(b,CONST_STR_LEN("<D:activelock>\n"));
1114 
1115 	buffer_append_string_len(b,CONST_STR_LEN("<D:lockscope>"));
1116 	buffer_append_string_len(b,CONST_STR_LEN("<D:"));
1117 	buffer_append_string(b, lockscope);
1118 	buffer_append_string_len(b, CONST_STR_LEN("/>"));
1119 	buffer_append_string_len(b,CONST_STR_LEN("</D:lockscope>\n"));
1120 
1121 	buffer_append_string_len(b,CONST_STR_LEN("<D:locktype>"));
1122 	buffer_append_string_len(b,CONST_STR_LEN("<D:"));
1123 	buffer_append_string(b, locktype);
1124 	buffer_append_string_len(b, CONST_STR_LEN("/>"));
1125 	buffer_append_string_len(b,CONST_STR_LEN("</D:locktype>\n"));
1126 
1127 	buffer_append_string_len(b,CONST_STR_LEN("<D:depth>"));
1128 	buffer_append_string(b, depth == 0 ? "0" : "infinity");
1129 	buffer_append_string_len(b,CONST_STR_LEN("</D:depth>\n"));
1130 
1131 	buffer_append_string_len(b,CONST_STR_LEN("<D:timeout>"));
1132 	buffer_append_string_len(b, CONST_STR_LEN("Second-600"));
1133 	buffer_append_string_len(b,CONST_STR_LEN("</D:timeout>\n"));
1134 
1135 	buffer_append_string_len(b,CONST_STR_LEN("<D:owner>"));
1136 	buffer_append_string_len(b,CONST_STR_LEN("</D:owner>\n"));
1137 
1138 	buffer_append_string_len(b,CONST_STR_LEN("<D:locktoken>"));
1139 	buffer_append_string_len(b, CONST_STR_LEN("<D:href>"));
1140 	buffer_append_string_buffer(b, locktoken);
1141 	buffer_append_string_len(b, CONST_STR_LEN("</D:href>"));
1142 	buffer_append_string_len(b,CONST_STR_LEN("</D:locktoken>\n"));
1143 
1144 	buffer_append_string_len(b,CONST_STR_LEN("</D:activelock>\n"));
1145 	buffer_append_string_len(b,CONST_STR_LEN("</D:lockdiscovery>\n"));
1146 	buffer_append_string_len(b,CONST_STR_LEN("</D:prop>\n"));
1147 
1148 	return 0;
1149 }
1150 #endif
1151 
1152 /**
1153  * check if resource is having the right locks to access to resource
1154  *
1155  *
1156  *
1157  */
webdav_has_lock(server * srv,connection * con,plugin_data * p,buffer * uri)1158 static int webdav_has_lock(server *srv, connection *con, plugin_data *p, buffer *uri) {
1159 	int has_lock = 1;
1160 
1161 #ifdef USE_LOCKS
1162 	data_string *ds;
1163 	UNUSED(srv);
1164 
1165 	/**
1166 	 * This implementation is more fake than real
1167 	 * we need a parser for the If: header to really handle the full scope
1168 	 *
1169 	 * X-Litmus: locks: 11 (owner_modify)
1170 	 * If: <http://127.0.0.1:1025/dav/litmus/lockme> (<opaquelocktoken:2165478d-0611-49c4-be92-e790d68a38f1>)
1171 	 * - a tagged check:
1172 	 *   if http://127.0.0.1:1025/dav/litmus/lockme is locked with
1173 	 *   opaquelocktoken:2165478d-0611-49c4-be92-e790d68a38f1, go on
1174 	 *
1175 	 * X-Litmus: locks: 16 (fail_cond_put)
1176 	 * If: (<DAV:no-lock> ["-1622396671"])
1177 	 * - untagged:
1178 	 *   go on if the resource has the etag [...] and the lock
1179 	 */
1180 	if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If"))) {
1181 		/* Ooh, ooh. A if tag, now the fun begins.
1182 		 *
1183 		 * this can only work with a real parser
1184 		 **/
1185 	} else {
1186 		/* we didn't provided a lock-token -> */
1187 		/* if the resource is locked -> 423 */
1188 
1189 		sqlite3_stmt *stmt = p->conf.stmt_read_lock_by_uri;
1190 
1191 		sqlite3_reset(stmt);
1192 
1193 		sqlite3_bind_text(stmt, 1,
1194 			  CONST_BUF_LEN(uri),
1195 			  SQLITE_TRANSIENT);
1196 
1197 		while (SQLITE_ROW == sqlite3_step(stmt)) {
1198 			has_lock = 0;
1199 		}
1200 	}
1201 #else
1202 	UNUSED(srv);
1203 	UNUSED(con);
1204 	UNUSED(p);
1205 	UNUSED(uri);
1206 #endif
1207 
1208 	return has_lock;
1209 }
1210 
URIHANDLER_FUNC(mod_webdav_subrequest_handler)1211 URIHANDLER_FUNC(mod_webdav_subrequest_handler) {
1212 	plugin_data *p = p_d;
1213 	buffer *b;
1214 	DIR *dir;
1215 	data_string *ds;
1216 	int depth = -1;
1217 	struct stat st;
1218 	buffer *prop_200;
1219 	buffer *prop_404;
1220 	webdav_properties *req_props;
1221 	stat_cache_entry *sce = NULL;
1222 
1223 	UNUSED(srv);
1224 
1225 	if (!p->conf.enabled) return HANDLER_GO_ON;
1226 	/* physical path is setup */
1227 	if (con->physical.path->used == 0) return HANDLER_GO_ON;
1228 
1229 	/* PROPFIND need them */
1230 	if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Depth"))) {
1231 		depth = strtol(ds->value->ptr, NULL, 10);
1232 	}
1233 
1234 	switch (con->request.http_method) {
1235 	case HTTP_METHOD_PROPFIND:
1236 		/* they want to know the properties of the directory */
1237 		req_props = NULL;
1238 
1239 		/* is there a content-body ? */
1240 
1241 		switch (stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
1242 		case HANDLER_ERROR:
1243 			if (errno == ENOENT) {
1244 				con->http_status = 404;
1245 				return HANDLER_FINISHED;
1246 			}
1247 			break;
1248 		default:
1249 			break;
1250 		}
1251 
1252 
1253 #ifdef USE_PROPPATCH
1254 		/* any special requests or just allprop ? */
1255 		if (con->request.content_length) {
1256 			xmlDocPtr xml;
1257 
1258 			if (1 == webdav_parse_chunkqueue(srv, con, p, con->request_content_queue, &xml)) {
1259 				xmlNode *rootnode = xmlDocGetRootElement(xml);
1260 
1261 				assert(rootnode);
1262 
1263 				if (0 == xmlStrcmp(rootnode->name, BAD_CAST "propfind")) {
1264 					xmlNode *cmd;
1265 
1266 					req_props = calloc(1, sizeof(*req_props));
1267 
1268 					for (cmd = rootnode->children; cmd; cmd = cmd->next) {
1269 
1270 						if (0 == xmlStrcmp(cmd->name, BAD_CAST "prop")) {
1271 							/* get prop by name */
1272 							xmlNode *prop;
1273 
1274 							for (prop = cmd->children; prop; prop = prop->next) {
1275 								if (prop->type == XML_TEXT_NODE) continue; /* ignore WS */
1276 
1277 								if (prop->ns &&
1278 								    (0 == xmlStrcmp(prop->ns->href, BAD_CAST "")) &&
1279 								    (0 != xmlStrcmp(prop->ns->prefix, BAD_CAST ""))) {
1280 									size_t i;
1281 									log_error_write(srv, __FILE__, __LINE__, "ss",
1282 											"no name space for:",
1283 											prop->name);
1284 
1285 									xmlFreeDoc(xml);
1286 
1287 									for (i = 0; i < req_props->used; i++) {
1288 										free(req_props->ptr[i]->ns);
1289 										free(req_props->ptr[i]->prop);
1290 										free(req_props->ptr[i]);
1291 									}
1292 									free(req_props->ptr);
1293 									free(req_props);
1294 
1295 									con->http_status = 400;
1296 									return HANDLER_FINISHED;
1297 								}
1298 
1299 								/* add property to requested list */
1300 								if (req_props->size == 0) {
1301 									req_props->size = 16;
1302 									req_props->ptr = malloc(sizeof(*(req_props->ptr)) * req_props->size);
1303 								} else if (req_props->used == req_props->size) {
1304 									req_props->size += 16;
1305 									req_props->ptr = realloc(req_props->ptr, sizeof(*(req_props->ptr)) * req_props->size);
1306 								}
1307 
1308 								req_props->ptr[req_props->used] = malloc(sizeof(webdav_property));
1309 								req_props->ptr[req_props->used]->ns = (char *)xmlStrdup(prop->ns ? prop->ns->href : (xmlChar *)"");
1310 								req_props->ptr[req_props->used]->prop = (char *)xmlStrdup(prop->name);
1311 								req_props->used++;
1312 							}
1313 						} else if (0 == xmlStrcmp(cmd->name, BAD_CAST "propname")) {
1314 							sqlite3_stmt *stmt = p->conf.stmt_select_propnames;
1315 
1316 							if (stmt) {
1317 								/* get all property names (EMPTY) */
1318 								sqlite3_reset(stmt);
1319 								/* bind the values to the insert */
1320 
1321 								sqlite3_bind_text(stmt, 1,
1322 										  con->uri.path->ptr,
1323 										  con->uri.path->used - 1,
1324 										  SQLITE_TRANSIENT);
1325 
1326 								if (SQLITE_DONE != sqlite3_step(stmt)) {
1327 								}
1328 							}
1329 						} else if (0 == xmlStrcmp(cmd->name, BAD_CAST "allprop")) {
1330 							/* get all properties (EMPTY) */
1331 						}
1332 					}
1333 				}
1334 
1335 				xmlFreeDoc(xml);
1336 			} else {
1337 				con->http_status = 400;
1338 				return HANDLER_FINISHED;
1339 			}
1340 		}
1341 #endif
1342 		con->http_status = 207;
1343 
1344 		response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml; charset=\"utf-8\""));
1345 
1346 		b = chunkqueue_get_append_buffer(con->write_queue);
1347 
1348 		buffer_copy_string_len(b, CONST_STR_LEN("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"));
1349 
1350 		buffer_append_string_len(b,CONST_STR_LEN("<D:multistatus xmlns:D=\"DAV:\" xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\">\n"));
1351 
1352 		/* allprop */
1353 
1354 		prop_200 = buffer_init();
1355 		prop_404 = buffer_init();
1356 
1357 		switch(depth) {
1358 		case 0:
1359 			/* Depth: 0 */
1360 			webdav_get_props(srv, con, p, &(con->physical), req_props, prop_200, prop_404);
1361 
1362 			buffer_append_string_len(b,CONST_STR_LEN("<D:response>\n"));
1363 			buffer_append_string_len(b,CONST_STR_LEN("<D:href>"));
1364 			buffer_append_string_buffer(b, con->uri.scheme);
1365 			buffer_append_string_len(b,CONST_STR_LEN("://"));
1366 			buffer_append_string_buffer(b, con->uri.authority);
1367 			buffer_append_string_encoded(b, CONST_BUF_LEN(con->uri.path), ENCODING_REL_URI);
1368 			buffer_append_string_len(b,CONST_STR_LEN("</D:href>\n"));
1369 
1370 			if (!buffer_is_empty(prop_200)) {
1371 				buffer_append_string_len(b,CONST_STR_LEN("<D:propstat>\n"));
1372 				buffer_append_string_len(b,CONST_STR_LEN("<D:prop>\n"));
1373 
1374 				buffer_append_string_buffer(b, prop_200);
1375 
1376 				buffer_append_string_len(b,CONST_STR_LEN("</D:prop>\n"));
1377 
1378 				buffer_append_string_len(b,CONST_STR_LEN("<D:status>HTTP/1.1 200 OK</D:status>\n"));
1379 
1380 				buffer_append_string_len(b,CONST_STR_LEN("</D:propstat>\n"));
1381 			}
1382 			if (!buffer_is_empty(prop_404)) {
1383 				buffer_append_string_len(b,CONST_STR_LEN("<D:propstat>\n"));
1384 				buffer_append_string_len(b,CONST_STR_LEN("<D:prop>\n"));
1385 
1386 				buffer_append_string_buffer(b, prop_404);
1387 
1388 				buffer_append_string_len(b,CONST_STR_LEN("</D:prop>\n"));
1389 
1390 				buffer_append_string_len(b,CONST_STR_LEN("<D:status>HTTP/1.1 404 Not Found</D:status>\n"));
1391 
1392 				buffer_append_string_len(b,CONST_STR_LEN("</D:propstat>\n"));
1393 			}
1394 
1395 			buffer_append_string_len(b,CONST_STR_LEN("</D:response>\n"));
1396 
1397 			break;
1398 		case 1:
1399 			if (NULL != (dir = opendir(con->physical.path->ptr))) {
1400 				struct dirent *de;
1401 				physical d;
1402 				physical *dst = &(con->physical);
1403 
1404 				d.path = buffer_init();
1405 				d.rel_path = buffer_init();
1406 
1407 				while(NULL != (de = readdir(dir))) {
1408 					if (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0') {
1409 						continue;
1410 						/* ignore the parent dir */
1411 					}
1412 
1413 					buffer_copy_string_buffer(d.path, dst->path);
1414 					BUFFER_APPEND_SLASH(d.path);
1415 
1416 					buffer_copy_string_buffer(d.rel_path, dst->rel_path);
1417 					BUFFER_APPEND_SLASH(d.rel_path);
1418 
1419 					if (de->d_name[0] == '.' && de->d_name[1] == '\0') {
1420 						/* don't append the . */
1421 					} else {
1422 						buffer_append_string(d.path, de->d_name);
1423 						buffer_append_string(d.rel_path, de->d_name);
1424 					}
1425 
1426 					buffer_reset(prop_200);
1427 					buffer_reset(prop_404);
1428 
1429 					webdav_get_props(srv, con, p, &d, req_props, prop_200, prop_404);
1430 
1431 					buffer_append_string_len(b,CONST_STR_LEN("<D:response>\n"));
1432 					buffer_append_string_len(b,CONST_STR_LEN("<D:href>"));
1433 					buffer_append_string_buffer(b, con->uri.scheme);
1434 					buffer_append_string_len(b,CONST_STR_LEN("://"));
1435 					buffer_append_string_buffer(b, con->uri.authority);
1436 					buffer_append_string_encoded(b, CONST_BUF_LEN(d.rel_path), ENCODING_REL_URI);
1437 					buffer_append_string_len(b,CONST_STR_LEN("</D:href>\n"));
1438 
1439 					if (!buffer_is_empty(prop_200)) {
1440 						buffer_append_string_len(b,CONST_STR_LEN("<D:propstat>\n"));
1441 						buffer_append_string_len(b,CONST_STR_LEN("<D:prop>\n"));
1442 
1443 						buffer_append_string_buffer(b, prop_200);
1444 
1445 						buffer_append_string_len(b,CONST_STR_LEN("</D:prop>\n"));
1446 
1447 						buffer_append_string_len(b,CONST_STR_LEN("<D:status>HTTP/1.1 200 OK</D:status>\n"));
1448 
1449 						buffer_append_string_len(b,CONST_STR_LEN("</D:propstat>\n"));
1450 					}
1451 					if (!buffer_is_empty(prop_404)) {
1452 						buffer_append_string_len(b,CONST_STR_LEN("<D:propstat>\n"));
1453 						buffer_append_string_len(b,CONST_STR_LEN("<D:prop>\n"));
1454 
1455 						buffer_append_string_buffer(b, prop_404);
1456 
1457 						buffer_append_string_len(b,CONST_STR_LEN("</D:prop>\n"));
1458 
1459 						buffer_append_string_len(b,CONST_STR_LEN("<D:status>HTTP/1.1 404 Not Found</D:status>\n"));
1460 
1461 						buffer_append_string_len(b,CONST_STR_LEN("</D:propstat>\n"));
1462 					}
1463 
1464 					buffer_append_string_len(b,CONST_STR_LEN("</D:response>\n"));
1465 				}
1466 				closedir(dir);
1467 				buffer_free(d.path);
1468 				buffer_free(d.rel_path);
1469 			}
1470 			break;
1471 		}
1472 
1473 		if (req_props) {
1474 			size_t i;
1475 			for (i = 0; i < req_props->used; i++) {
1476 				free(req_props->ptr[i]->ns);
1477 				free(req_props->ptr[i]->prop);
1478 				free(req_props->ptr[i]);
1479 			}
1480 			free(req_props->ptr);
1481 			free(req_props);
1482 		}
1483 
1484 		buffer_free(prop_200);
1485 		buffer_free(prop_404);
1486 
1487 		buffer_append_string_len(b,CONST_STR_LEN("</D:multistatus>\n"));
1488 
1489 		if (p->conf.log_xml) {
1490 			log_error_write(srv, __FILE__, __LINE__, "sb", "XML-response-body:", b);
1491 		}
1492 		con->file_finished = 1;
1493 
1494 		return HANDLER_FINISHED;
1495 	case HTTP_METHOD_MKCOL:
1496 		if (p->conf.is_readonly) {
1497 			con->http_status = 403;
1498 			return HANDLER_FINISHED;
1499 		}
1500 
1501 		if (con->request.content_length != 0) {
1502 			/* we don't support MKCOL with a body */
1503 			con->http_status = 415;
1504 
1505 			return HANDLER_FINISHED;
1506 		}
1507 
1508 		/* let's create the directory */
1509 
1510 		if (-1 == mkdir(con->physical.path->ptr, WEBDAV_DIR_MODE)) {
1511 			switch(errno) {
1512 			case EPERM:
1513 				con->http_status = 403;
1514 				break;
1515 			case ENOENT:
1516 			case ENOTDIR:
1517 				con->http_status = 409;
1518 				break;
1519 			case EEXIST:
1520 			default:
1521 				con->http_status = 405; /* not allowed */
1522 				break;
1523 			}
1524 		} else {
1525 			con->http_status = 201;
1526 			con->file_finished = 1;
1527 		}
1528 
1529 		return HANDLER_FINISHED;
1530 	case HTTP_METHOD_DELETE:
1531 		if (p->conf.is_readonly) {
1532 			con->http_status = 403;
1533 			return HANDLER_FINISHED;
1534 		}
1535 
1536 		/* does the client have a lock for this connection ? */
1537 		if (!webdav_has_lock(srv, con, p, con->uri.path)) {
1538 			con->http_status = 423;
1539 			return HANDLER_FINISHED;
1540 		}
1541 
1542 		/* stat and unlink afterwards */
1543 		if (-1 == stat(con->physical.path->ptr, &st)) {
1544 			/* don't about it yet, unlink will fail too */
1545 			switch(errno) {
1546 			case ENOENT:
1547 				 con->http_status = 404;
1548 				 break;
1549 			default:
1550 				 con->http_status = 403;
1551 				 break;
1552 			}
1553 		} else if (S_ISDIR(st.st_mode)) {
1554 			buffer *multi_status_resp = buffer_init();
1555 
1556 			if (webdav_delete_dir(srv, con, p, &(con->physical), multi_status_resp)) {
1557 				/* we got an error somewhere in between, build a 207 */
1558 				response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml; charset=\"utf-8\""));
1559 
1560 				b = chunkqueue_get_append_buffer(con->write_queue);
1561 
1562 				buffer_copy_string_len(b, CONST_STR_LEN("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"));
1563 
1564 				buffer_append_string_len(b,CONST_STR_LEN("<D:multistatus xmlns:D=\"DAV:\">\n"));
1565 
1566 				buffer_append_string_buffer(b, multi_status_resp);
1567 
1568 				buffer_append_string_len(b,CONST_STR_LEN("</D:multistatus>\n"));
1569 
1570 				if (p->conf.log_xml) {
1571 					log_error_write(srv, __FILE__, __LINE__, "sb", "XML-response-body:", b);
1572 				}
1573 
1574 				con->http_status = 207;
1575 				con->file_finished = 1;
1576 			} else {
1577 				/* everything went fine, remove the directory */
1578 
1579 				if (-1 == rmdir(con->physical.path->ptr)) {
1580 					switch(errno) {
1581 					case ENOENT:
1582 						con->http_status = 404;
1583 						break;
1584 					default:
1585 						con->http_status = 501;
1586 						break;
1587 					}
1588 				} else {
1589 					con->http_status = 204;
1590 				}
1591 			}
1592 
1593 			buffer_free(multi_status_resp);
1594 		} else if (-1 == unlink(con->physical.path->ptr)) {
1595 			switch(errno) {
1596 			case EPERM:
1597 				con->http_status = 403;
1598 				break;
1599 			case ENOENT:
1600 				con->http_status = 404;
1601 				break;
1602 			default:
1603 				con->http_status = 501;
1604 				break;
1605 			}
1606 		} else {
1607 			con->http_status = 204;
1608 		}
1609 		return HANDLER_FINISHED;
1610 	case HTTP_METHOD_PUT: {
1611 		int fd;
1612 		chunkqueue *cq = con->request_content_queue;
1613 		chunk *c;
1614 		data_string *ds_range;
1615 
1616 		if (p->conf.is_readonly) {
1617 			con->http_status = 403;
1618 			return HANDLER_FINISHED;
1619 		}
1620 
1621 		/* is a exclusive lock set on the source */
1622 		if (!webdav_has_lock(srv, con, p, con->uri.path)) {
1623 			con->http_status = 423;
1624 			return HANDLER_FINISHED;
1625 		}
1626 
1627 
1628 		assert(chunkqueue_length(cq) == (off_t)con->request.content_length);
1629 
1630 		/* RFC2616 Section 9.6 PUT requires us to send 501 on all Content-* we don't support
1631 		 * - most important Content-Range
1632 		 *
1633 		 *
1634 		 * Example: Content-Range: bytes 100-1037/1038 */
1635 
1636 		if (NULL != (ds_range = (data_string *)array_get_element(con->request.headers, "Content-Range"))) {
1637 			const char *num = ds_range->value->ptr;
1638 			off_t offset;
1639 			char *err = NULL;
1640 
1641 			if (0 != strncmp(num, "bytes ", 6)) {
1642 				con->http_status = 501; /* not implemented */
1643 
1644 				return HANDLER_FINISHED;
1645 			}
1646 
1647 			/* we only support <num>- ... */
1648 
1649 			num += 6;
1650 
1651 			/* skip WS */
1652 			while (*num == ' ' || *num == '\t') num++;
1653 
1654 			if (*num == '\0') {
1655 				con->http_status = 501; /* not implemented */
1656 
1657 				return HANDLER_FINISHED;
1658 			}
1659 
1660 			offset = strtoll(num, &err, 10);
1661 
1662 			if (*err != '-' || offset < 0) {
1663 				con->http_status = 501; /* not implemented */
1664 
1665 				return HANDLER_FINISHED;
1666 			}
1667 
1668 			if (-1 == (fd = open(con->physical.path->ptr, O_WRONLY, WEBDAV_FILE_MODE))) {
1669 				switch (errno) {
1670 				case ENOENT:
1671 					con->http_status = 404; /* not found */
1672 					break;
1673 				default:
1674 					con->http_status = 403; /* not found */
1675 					break;
1676 				}
1677 				return HANDLER_FINISHED;
1678 			}
1679 
1680 			if (-1 == lseek(fd, offset, SEEK_SET)) {
1681 				con->http_status = 501; /* not implemented */
1682 
1683 				close(fd);
1684 
1685 				return HANDLER_FINISHED;
1686 			}
1687 			con->http_status = 200; /* modified */
1688 		} else {
1689 			/* take what we have in the request-body and write it to a file */
1690 
1691 			/* if the file doesn't exist, create it */
1692 			if (-1 == (fd = open(con->physical.path->ptr, O_WRONLY|O_TRUNC, WEBDAV_FILE_MODE))) {
1693 				if (errno == ENOENT &&
1694 				    -1 == (fd = open(con->physical.path->ptr, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, WEBDAV_FILE_MODE))) {
1695 					/* we can't open the file */
1696 					con->http_status = 403;
1697 
1698 					return HANDLER_FINISHED;
1699 				} else {
1700 					con->http_status = 201; /* created */
1701 				}
1702 			} else {
1703 				con->http_status = 200; /* modified */
1704 			}
1705 		}
1706 
1707 		con->file_finished = 1;
1708 
1709 		for (c = cq->first; c; c = cq->first) {
1710 			int r = 0;
1711 
1712 			/* copy all chunks */
1713 			switch(c->type) {
1714 			case FILE_CHUNK:
1715 
1716 				if (c->file.mmap.start == MAP_FAILED) {
1717 					if (-1 == c->file.fd &&  /* open the file if not already open */
1718 					    -1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) {
1719 						log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
1720 
1721 						return HANDLER_ERROR;
1722 					}
1723 
1724 					if (MAP_FAILED == (c->file.mmap.start = mmap(NULL, c->file.length, PROT_READ, MAP_SHARED, c->file.fd, 0))) {
1725 						log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ",
1726 								strerror(errno), c->file.name,  c->file.fd);
1727 						close(c->file.fd);
1728 						c->file.fd = -1;
1729 
1730 						return HANDLER_ERROR;
1731 					}
1732 
1733 					c->file.mmap.length = c->file.length;
1734 
1735 					close(c->file.fd);
1736 					c->file.fd = -1;
1737 
1738 					/* chunk_reset() or chunk_free() will cleanup for us */
1739 				}
1740 
1741 				if ((r = write(fd, c->file.mmap.start + c->offset, c->file.length - c->offset)) < 0) {
1742 					switch(errno) {
1743 					case ENOSPC:
1744 						con->http_status = 507;
1745 
1746 						break;
1747 					default:
1748 						con->http_status = 403;
1749 						break;
1750 					}
1751 				}
1752 				break;
1753 			case MEM_CHUNK:
1754 				if ((r = write(fd, c->mem->ptr + c->offset, c->mem->used - c->offset - 1)) < 0) {
1755 					switch(errno) {
1756 					case ENOSPC:
1757 						con->http_status = 507;
1758 
1759 						break;
1760 					default:
1761 						con->http_status = 403;
1762 						break;
1763 					}
1764 				}
1765 				break;
1766 			case UNUSED_CHUNK:
1767 				break;
1768 			}
1769 
1770 			if (r > 0) {
1771 				c->offset += r;
1772 				cq->bytes_out += r;
1773 			} else {
1774 				break;
1775 			}
1776 			chunkqueue_remove_finished_chunks(cq);
1777 		}
1778 		close(fd);
1779 
1780 		return HANDLER_FINISHED;
1781 	}
1782 	case HTTP_METHOD_MOVE:
1783 	case HTTP_METHOD_COPY: {
1784 		buffer *destination = NULL;
1785 		char *sep, *sep2, *start;
1786 		int overwrite = 1;
1787 
1788 		if (p->conf.is_readonly) {
1789 			con->http_status = 403;
1790 			return HANDLER_FINISHED;
1791 		}
1792 
1793 		/* is a exclusive lock set on the source */
1794 		if (con->request.http_method == HTTP_METHOD_MOVE) {
1795 			if (!webdav_has_lock(srv, con, p, con->uri.path)) {
1796 				con->http_status = 423;
1797 				return HANDLER_FINISHED;
1798 			}
1799 		}
1800 
1801 		if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Destination"))) {
1802 			destination = ds->value;
1803 		} else {
1804 			con->http_status = 400;
1805 			return HANDLER_FINISHED;
1806 		}
1807 
1808 		if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Overwrite"))) {
1809 			if (ds->value->used != 2 ||
1810 			    (ds->value->ptr[0] != 'F' &&
1811 			     ds->value->ptr[0] != 'T') )  {
1812 				con->http_status = 400;
1813 				return HANDLER_FINISHED;
1814 			}
1815 			overwrite = (ds->value->ptr[0] == 'F' ? 0 : 1);
1816 		}
1817 		/* let's parse the Destination
1818 		 *
1819 		 * http://127.0.0.1:1025/dav/litmus/copydest
1820 		 *
1821 		 * - host has to be the same as the Host: header we got
1822 		 * - we have to stay inside the document root
1823 		 * - the query string is thrown away
1824 		 *  */
1825 
1826 		buffer_reset(p->uri.scheme);
1827 		buffer_reset(p->uri.path_raw);
1828 		buffer_reset(p->uri.authority);
1829 
1830 		start = destination->ptr;
1831 
1832 		if (NULL == (sep = strstr(start, "://"))) {
1833 			con->http_status = 400;
1834 			return HANDLER_FINISHED;
1835 		}
1836 		buffer_copy_string_len(p->uri.scheme, start, sep - start);
1837 
1838 		start = sep + 3;
1839 
1840 		if (NULL == (sep = strchr(start, '/'))) {
1841 			con->http_status = 400;
1842 			return HANDLER_FINISHED;
1843 		}
1844 		if (NULL != (sep2 = memchr(start, '@', sep - start))) {
1845 			/* skip login information */
1846 			start = sep2 + 1;
1847 		}
1848 		buffer_copy_string_len(p->uri.authority, start, sep - start);
1849 
1850 		start = sep + 1;
1851 
1852 		if (NULL == (sep = strchr(start, '?'))) {
1853 			/* no query string, good */
1854 			buffer_copy_string(p->uri.path_raw, start);
1855 		} else {
1856 			buffer_copy_string_len(p->uri.path_raw, start, sep - start);
1857 		}
1858 
1859 		if (!buffer_is_equal(p->uri.authority, con->uri.authority)) {
1860 			/* not the same host */
1861 			con->http_status = 502;
1862 			return HANDLER_FINISHED;
1863 		}
1864 
1865 		buffer_copy_string_buffer(p->tmp_buf, p->uri.path_raw);
1866 		buffer_urldecode_path(p->tmp_buf);
1867 		buffer_path_simplify(p->uri.path, p->tmp_buf);
1868 
1869 		/* we now have a URI which is clean. transform it into a physical path */
1870 		buffer_copy_string_buffer(p->physical.doc_root, con->physical.doc_root);
1871 		buffer_copy_string_buffer(p->physical.rel_path, p->uri.path);
1872 
1873 		if (con->conf.force_lowercase_filenames) {
1874 			buffer_to_lower(p->physical.rel_path);
1875 		}
1876 
1877 		buffer_copy_string_buffer(p->physical.path, p->physical.doc_root);
1878 		BUFFER_APPEND_SLASH(p->physical.path);
1879 		buffer_copy_string_buffer(p->physical.basedir, p->physical.path);
1880 
1881 		/* don't add a second / */
1882 		if (p->physical.rel_path->ptr[0] == '/') {
1883 			buffer_append_string_len(p->physical.path, p->physical.rel_path->ptr + 1, p->physical.rel_path->used - 2);
1884 		} else {
1885 			buffer_append_string_buffer(p->physical.path, p->physical.rel_path);
1886 		}
1887 
1888 		/* let's see if the source is a directory
1889 		 * if yes, we fail with 501 */
1890 
1891 		if (-1 == stat(con->physical.path->ptr, &st)) {
1892 			/* don't about it yet, unlink will fail too */
1893 			switch(errno) {
1894 			case ENOENT:
1895 				 con->http_status = 404;
1896 				 break;
1897 			default:
1898 				 con->http_status = 403;
1899 				 break;
1900 			}
1901 		} else if (S_ISDIR(st.st_mode)) {
1902 			int r;
1903 			/* src is a directory */
1904 
1905 			if (-1 == stat(p->physical.path->ptr, &st)) {
1906 				if (-1 == mkdir(p->physical.path->ptr, WEBDAV_DIR_MODE)) {
1907 					con->http_status = 403;
1908 					return HANDLER_FINISHED;
1909 				}
1910 			} else if (!S_ISDIR(st.st_mode)) {
1911 				if (overwrite == 0) {
1912 					/* copying into a non-dir ? */
1913 					con->http_status = 409;
1914 					return HANDLER_FINISHED;
1915 				} else {
1916 					unlink(p->physical.path->ptr);
1917 					if (-1 == mkdir(p->physical.path->ptr, WEBDAV_DIR_MODE)) {
1918 						con->http_status = 403;
1919 						return HANDLER_FINISHED;
1920 					}
1921 				}
1922 			}
1923 
1924 			/* copy the content of src to dest */
1925 			if (0 != (r = webdav_copy_dir(srv, con, p, &(con->physical), &(p->physical), overwrite))) {
1926 				con->http_status = r;
1927 				return HANDLER_FINISHED;
1928 			}
1929 			if (con->request.http_method == HTTP_METHOD_MOVE) {
1930 				b = buffer_init();
1931 				webdav_delete_dir(srv, con, p, &(con->physical), b); /* content */
1932 				buffer_free(b);
1933 
1934 				rmdir(con->physical.path->ptr);
1935 			}
1936 			con->http_status = 201;
1937 			con->file_finished = 1;
1938 		} else {
1939 			/* it is just a file, good */
1940 			int r;
1941 
1942 			/* does the client have a lock for this connection ? */
1943 			if (!webdav_has_lock(srv, con, p, p->uri.path)) {
1944 				con->http_status = 423;
1945 				return HANDLER_FINISHED;
1946 			}
1947 
1948 			/* destination exists */
1949 			if (0 == (r = stat(p->physical.path->ptr, &st))) {
1950 				if (S_ISDIR(st.st_mode)) {
1951 					/* file to dir/
1952 					 * append basename to physical path */
1953 
1954 					if (NULL != (sep = strrchr(con->physical.path->ptr, '/'))) {
1955 						buffer_append_string(p->physical.path, sep);
1956 						r = stat(p->physical.path->ptr, &st);
1957 					}
1958 				}
1959 			}
1960 
1961 			if (-1 == r) {
1962 				con->http_status = 201; /* we will create a new one */
1963 				con->file_finished = 1;
1964 
1965 				switch(errno) {
1966 				case ENOTDIR:
1967 					con->http_status = 409;
1968 					return HANDLER_FINISHED;
1969 				}
1970 			} else if (overwrite == 0) {
1971 				/* destination exists, but overwrite is not set */
1972 				con->http_status = 412;
1973 				return HANDLER_FINISHED;
1974 			} else {
1975 				con->http_status = 204; /* resource already existed */
1976 			}
1977 
1978 			if (con->request.http_method == HTTP_METHOD_MOVE) {
1979 				/* try a rename */
1980 
1981 				if (0 == rename(con->physical.path->ptr, p->physical.path->ptr)) {
1982 #ifdef USE_PROPPATCH
1983 					sqlite3_stmt *stmt;
1984 
1985 					stmt = p->conf.stmt_delete_uri;
1986 					if (stmt) {
1987 
1988 						sqlite3_reset(stmt);
1989 
1990 						/* bind the values to the insert */
1991 						sqlite3_bind_text(stmt, 1,
1992 								  con->uri.path->ptr,
1993 								  con->uri.path->used - 1,
1994 								  SQLITE_TRANSIENT);
1995 
1996 						if (SQLITE_DONE != sqlite3_step(stmt)) {
1997 							log_error_write(srv, __FILE__, __LINE__, "ss", "sql-move(delete old) failed:", sqlite3_errmsg(p->conf.sql));
1998 						}
1999 					}
2000 
2001 					stmt = p->conf.stmt_move_uri;
2002 					if (stmt) {
2003 
2004 						sqlite3_reset(stmt);
2005 
2006 						/* bind the values to the insert */
2007 						sqlite3_bind_text(stmt, 1,
2008 								  p->uri.path->ptr,
2009 								  p->uri.path->used - 1,
2010 								  SQLITE_TRANSIENT);
2011 
2012 						sqlite3_bind_text(stmt, 2,
2013 								  con->uri.path->ptr,
2014 								  con->uri.path->used - 1,
2015 								  SQLITE_TRANSIENT);
2016 
2017 						if (SQLITE_DONE != sqlite3_step(stmt)) {
2018 							log_error_write(srv, __FILE__, __LINE__, "ss", "sql-move failed:", sqlite3_errmsg(p->conf.sql));
2019 						}
2020 					}
2021 #endif
2022 					return HANDLER_FINISHED;
2023 				}
2024 
2025 				/* rename failed, fall back to COPY + DELETE */
2026 			}
2027 
2028 			if (0 != (r = webdav_copy_file(srv, con, p, &(con->physical), &(p->physical), overwrite))) {
2029 				con->http_status = r;
2030 
2031 				return HANDLER_FINISHED;
2032 			}
2033 
2034 			if (con->request.http_method == HTTP_METHOD_MOVE) {
2035 				b = buffer_init();
2036 				webdav_delete_file(srv, con, p, &(con->physical), b);
2037 				buffer_free(b);
2038 			}
2039 		}
2040 
2041 		return HANDLER_FINISHED;
2042 	}
2043 	case HTTP_METHOD_PROPPATCH:
2044 		if (p->conf.is_readonly) {
2045 			con->http_status = 403;
2046 			return HANDLER_FINISHED;
2047 		}
2048 
2049 		if (!webdav_has_lock(srv, con, p, con->uri.path)) {
2050 			con->http_status = 423;
2051 			return HANDLER_FINISHED;
2052 		}
2053 
2054 		/* check if destination exists */
2055 		if (-1 == stat(con->physical.path->ptr, &st)) {
2056 			switch(errno) {
2057 			case ENOENT:
2058 				con->http_status = 404;
2059 				break;
2060 			}
2061 		}
2062 
2063 #ifdef USE_PROPPATCH
2064 		if (con->request.content_length) {
2065 			xmlDocPtr xml;
2066 
2067 			if (1 == webdav_parse_chunkqueue(srv, con, p, con->request_content_queue, &xml)) {
2068 				xmlNode *rootnode = xmlDocGetRootElement(xml);
2069 
2070 				if (0 == xmlStrcmp(rootnode->name, BAD_CAST "propertyupdate")) {
2071 					xmlNode *cmd;
2072 					char *err = NULL;
2073 					int empty_ns = 0; /* send 400 on a empty namespace attribute */
2074 
2075 					/* start response */
2076 
2077 					if (SQLITE_OK != sqlite3_exec(p->conf.sql, "BEGIN TRANSACTION", NULL, NULL, &err)) {
2078 						log_error_write(srv, __FILE__, __LINE__, "ss", "can't open transaction:", err);
2079 						sqlite3_free(err);
2080 
2081 						goto propmatch_cleanup;
2082 					}
2083 
2084 					/* a UPDATE request, we know 'set' and 'remove' */
2085 					for (cmd = rootnode->children; cmd; cmd = cmd->next) {
2086 						xmlNode *props;
2087 						/* either set or remove */
2088 
2089 						if ((0 == xmlStrcmp(cmd->name, BAD_CAST "set")) ||
2090 						    (0 == xmlStrcmp(cmd->name, BAD_CAST "remove"))) {
2091 
2092 							sqlite3_stmt *stmt;
2093 
2094 							stmt = (0 == xmlStrcmp(cmd->name, BAD_CAST "remove")) ?
2095 								p->conf.stmt_delete_prop : p->conf.stmt_update_prop;
2096 
2097 							for (props = cmd->children; props; props = props->next) {
2098 								if (0 == xmlStrcmp(props->name, BAD_CAST "prop")) {
2099 									xmlNode *prop;
2100 									int r;
2101 
2102 									prop = props->children;
2103 
2104 									if (prop->ns &&
2105 									    (0 == xmlStrcmp(prop->ns->href, BAD_CAST "")) &&
2106 									    (0 != xmlStrcmp(prop->ns->prefix, BAD_CAST ""))) {
2107 										log_error_write(srv, __FILE__, __LINE__, "ss",
2108 												"no name space for:",
2109 												prop->name);
2110 
2111 										empty_ns = 1;
2112 										break;
2113 									}
2114 
2115 									sqlite3_reset(stmt);
2116 
2117 									/* bind the values to the insert */
2118 
2119 									sqlite3_bind_text(stmt, 1,
2120 											  con->uri.path->ptr,
2121 											  con->uri.path->used - 1,
2122 											  SQLITE_TRANSIENT);
2123 									sqlite3_bind_text(stmt, 2,
2124 											  (char *)prop->name,
2125 											  strlen((char *)prop->name),
2126 											  SQLITE_TRANSIENT);
2127 									if (prop->ns) {
2128 										sqlite3_bind_text(stmt, 3,
2129 												  (char *)prop->ns->href,
2130 												  strlen((char *)prop->ns->href),
2131 												  SQLITE_TRANSIENT);
2132 									} else {
2133 										sqlite3_bind_text(stmt, 3,
2134 												  "",
2135 												  0,
2136 												  SQLITE_TRANSIENT);
2137 									}
2138 									if (stmt == p->conf.stmt_update_prop) {
2139 										sqlite3_bind_text(stmt, 4,
2140 											  (char *)xmlNodeGetContent(prop),
2141 											  strlen((char *)xmlNodeGetContent(prop)),
2142 											  SQLITE_TRANSIENT);
2143 									}
2144 
2145 									if (SQLITE_DONE != (r = sqlite3_step(stmt))) {
2146 										log_error_write(srv, __FILE__, __LINE__, "ss",
2147 												"sql-set failed:", sqlite3_errmsg(p->conf.sql));
2148 									}
2149 								}
2150 							}
2151 							if (empty_ns) break;
2152 						}
2153 					}
2154 
2155 					if (empty_ns) {
2156 						if (SQLITE_OK != sqlite3_exec(p->conf.sql, "ROLLBACK", NULL, NULL, &err)) {
2157 							log_error_write(srv, __FILE__, __LINE__, "ss", "can't rollback transaction:", err);
2158 							sqlite3_free(err);
2159 
2160 							goto propmatch_cleanup;
2161 						}
2162 
2163 						con->http_status = 400;
2164 					} else {
2165 						if (SQLITE_OK != sqlite3_exec(p->conf.sql, "COMMIT", NULL, NULL, &err)) {
2166 							log_error_write(srv, __FILE__, __LINE__, "ss", "can't commit transaction:", err);
2167 							sqlite3_free(err);
2168 
2169 							goto propmatch_cleanup;
2170 						}
2171 						con->http_status = 200;
2172 					}
2173 					con->file_finished = 1;
2174 
2175 					return HANDLER_FINISHED;
2176 				}
2177 
2178 propmatch_cleanup:
2179 
2180 				xmlFreeDoc(xml);
2181 			} else {
2182 				con->http_status = 400;
2183 				return HANDLER_FINISHED;
2184 			}
2185 		}
2186 #endif
2187 		con->http_status = 501;
2188 		return HANDLER_FINISHED;
2189 	case HTTP_METHOD_LOCK:
2190 		/**
2191 		 * a mac wants to write
2192 		 *
2193 		 * LOCK /dav/expire.txt HTTP/1.1\r\n
2194 		 * User-Agent: WebDAVFS/1.3 (01308000) Darwin/8.1.0 (Power Macintosh)\r\n
2195 		 * Accept: * / *\r\n
2196 		 * Depth: 0\r\n
2197 		 * Timeout: Second-600\r\n
2198 		 * Content-Type: text/xml; charset=\"utf-8\"\r\n
2199 		 * Content-Length: 229\r\n
2200 		 * Connection: keep-alive\r\n
2201 		 * Host: 192.168.178.23:1025\r\n
2202 		 * \r\n
2203 		 * <?xml version=\"1.0\" encoding=\"utf-8\"?>\n
2204 		 * <D:lockinfo xmlns:D=\"DAV:\">\n
2205 		 *  <D:lockscope><D:exclusive/></D:lockscope>\n
2206 		 *  <D:locktype><D:write/></D:locktype>\n
2207 		 *  <D:owner>\n
2208 		 *   <D:href>http://www.apple.com/webdav_fs/</D:href>\n
2209 		 *  </D:owner>\n
2210 		 * </D:lockinfo>\n
2211 		 */
2212 
2213 		if (depth != 0 && depth != -1) {
2214 			con->http_status = 400;
2215 
2216 			return HANDLER_FINISHED;
2217 		}
2218 
2219 #ifdef USE_LOCKS
2220 		if (con->request.content_length) {
2221 			xmlDocPtr xml;
2222 			buffer *hdr_if = NULL;
2223 
2224 			if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If"))) {
2225 				hdr_if = ds->value;
2226 			}
2227 
2228 			/* we don't support Depth: Infinity on locks */
2229 			if (hdr_if == NULL && depth == -1) {
2230 				con->http_status = 409; /* Conflict */
2231 
2232 				return HANDLER_FINISHED;
2233 			}
2234 
2235 			if (1 == webdav_parse_chunkqueue(srv, con, p, con->request_content_queue, &xml)) {
2236 				xmlNode *rootnode = xmlDocGetRootElement(xml);
2237 
2238 				assert(rootnode);
2239 
2240 				if (0 == xmlStrcmp(rootnode->name, BAD_CAST "lockinfo")) {
2241 					xmlNode *lockinfo;
2242 					const xmlChar *lockscope = NULL, *locktype = NULL; /* TODO: compiler says unused: *owner = NULL; */
2243 
2244 					for (lockinfo = rootnode->children; lockinfo; lockinfo = lockinfo->next) {
2245 						if (0 == xmlStrcmp(lockinfo->name, BAD_CAST "lockscope")) {
2246 							xmlNode *value;
2247 							for (value = lockinfo->children; value; value = value->next) {
2248 								if ((0 == xmlStrcmp(value->name, BAD_CAST "exclusive")) ||
2249 								    (0 == xmlStrcmp(value->name, BAD_CAST "shared"))) {
2250 									lockscope = value->name;
2251 								} else {
2252 									con->http_status = 400;
2253 
2254 									xmlFreeDoc(xml);
2255 									return HANDLER_FINISHED;
2256 								}
2257 							}
2258 						} else if (0 == xmlStrcmp(lockinfo->name, BAD_CAST "locktype")) {
2259 							xmlNode *value;
2260 							for (value = lockinfo->children; value; value = value->next) {
2261 								if ((0 == xmlStrcmp(value->name, BAD_CAST "write"))) {
2262 									locktype = value->name;
2263 								} else {
2264 									con->http_status = 400;
2265 
2266 									xmlFreeDoc(xml);
2267 									return HANDLER_FINISHED;
2268 								}
2269 							}
2270 
2271 						} else if (0 == xmlStrcmp(lockinfo->name, BAD_CAST "owner")) {
2272 						}
2273 					}
2274 
2275 					if (lockscope && locktype) {
2276 						sqlite3_stmt *stmt = p->conf.stmt_read_lock_by_uri;
2277 
2278 						/* is this resourse already locked ? */
2279 
2280 						/* SELECT locktoken, resource, lockscope, locktype, owner, depth, timeout
2281 						 *   FROM locks
2282 						 *  WHERE resource = ? */
2283 
2284 						if (stmt) {
2285 
2286 							sqlite3_reset(stmt);
2287 
2288 							sqlite3_bind_text(stmt, 1,
2289 									  p->uri.path->ptr,
2290 									  p->uri.path->used - 1,
2291 									  SQLITE_TRANSIENT);
2292 
2293 							/* it is the PK */
2294 							while (SQLITE_ROW == sqlite3_step(stmt)) {
2295 								/* we found a lock
2296 								 * 1. is it compatible ?
2297 								 * 2. is it ours */
2298 								char *sql_lockscope = (char *)sqlite3_column_text(stmt, 2);
2299 
2300 								if (strcmp(sql_lockscope, "exclusive")) {
2301 									con->http_status = 423;
2302 								} else if (0 == xmlStrcmp(lockscope, BAD_CAST "exclusive")) {
2303 									/* resourse is locked with a shared lock
2304 									 * client wants exclusive */
2305 									con->http_status = 423;
2306 								}
2307 							}
2308 							if (con->http_status == 423) {
2309 								xmlFreeDoc(xml);
2310 								return HANDLER_FINISHED;
2311 							}
2312 						}
2313 
2314 						stmt = p->conf.stmt_create_lock;
2315 						if (stmt) {
2316 							/* create a lock-token */
2317 							uuid_t id;
2318 							char uuid[37] /* 36 + \0 */;
2319 
2320 							uuid_generate(id);
2321 							uuid_unparse(id, uuid);
2322 
2323 							buffer_copy_string_len(p->tmp_buf, CONST_STR_LEN("opaquelocktoken:"));
2324 							buffer_append_string(p->tmp_buf, uuid);
2325 
2326 							/* "CREATE TABLE locks ("
2327 							 * "  locktoken TEXT NOT NULL,"
2328 							 * "  resource TEXT NOT NULL,"
2329 							 * "  lockscope TEXT NOT NULL,"
2330 							 * "  locktype TEXT NOT NULL,"
2331 							 * "  owner TEXT NOT NULL,"
2332 							 * "  depth INT NOT NULL,"
2333 							 */
2334 
2335 							sqlite3_reset(stmt);
2336 
2337 							sqlite3_bind_text(stmt, 1,
2338 									  CONST_BUF_LEN(p->tmp_buf),
2339 									  SQLITE_TRANSIENT);
2340 
2341 							sqlite3_bind_text(stmt, 2,
2342 									  CONST_BUF_LEN(con->uri.path),
2343 									  SQLITE_TRANSIENT);
2344 
2345 							sqlite3_bind_text(stmt, 3,
2346 									  (const char *)lockscope,
2347 									  xmlStrlen(lockscope),
2348 									  SQLITE_TRANSIENT);
2349 
2350 							sqlite3_bind_text(stmt, 4,
2351 									  (const char *)locktype,
2352 									  xmlStrlen(locktype),
2353 									  SQLITE_TRANSIENT);
2354 
2355 							/* owner */
2356 							sqlite3_bind_text(stmt, 5,
2357 									  "",
2358 									  0,
2359 									  SQLITE_TRANSIENT);
2360 
2361 							/* depth */
2362 							sqlite3_bind_int(stmt, 6,
2363 									 depth);
2364 
2365 
2366 							if (SQLITE_DONE != sqlite3_step(stmt)) {
2367 								log_error_write(srv, __FILE__, __LINE__, "ss",
2368 										"create lock:", sqlite3_errmsg(p->conf.sql));
2369 							}
2370 
2371 							/* looks like we survived */
2372 							webdav_lockdiscovery(srv, con, p->tmp_buf, (const char *)lockscope, (const char *)locktype, depth);
2373 
2374 							con->http_status = 201;
2375 							con->file_finished = 1;
2376 						}
2377 					}
2378 				}
2379 
2380 				xmlFreeDoc(xml);
2381 				return HANDLER_FINISHED;
2382 			} else {
2383 				con->http_status = 400;
2384 				return HANDLER_FINISHED;
2385 			}
2386 		} else {
2387 
2388 			if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If"))) {
2389 				buffer *locktoken = ds->value;
2390 				sqlite3_stmt *stmt = p->conf.stmt_refresh_lock;
2391 
2392 				/* remove the < > around the token */
2393 				if (locktoken->used < 6) {
2394 					con->http_status = 400;
2395 
2396 					return HANDLER_FINISHED;
2397 				}
2398 
2399 				buffer_copy_string_len(p->tmp_buf, locktoken->ptr + 2, locktoken->used - 5);
2400 
2401 				sqlite3_reset(stmt);
2402 
2403 				sqlite3_bind_text(stmt, 1,
2404 					  CONST_BUF_LEN(p->tmp_buf),
2405 					  SQLITE_TRANSIENT);
2406 
2407 				if (SQLITE_DONE != sqlite3_step(stmt)) {
2408 					log_error_write(srv, __FILE__, __LINE__, "ss",
2409 						"refresh lock:", sqlite3_errmsg(p->conf.sql));
2410 				}
2411 
2412 				webdav_lockdiscovery(srv, con, p->tmp_buf, "exclusive", "write", 0);
2413 
2414 				con->http_status = 200;
2415 				con->file_finished = 1;
2416 				return HANDLER_FINISHED;
2417 			} else {
2418 				/* we need a lock-token to refresh */
2419 				con->http_status = 400;
2420 
2421 				return HANDLER_FINISHED;
2422 			}
2423 		}
2424 		break;
2425 #else
2426 		con->http_status = 501;
2427 		return HANDLER_FINISHED;
2428 #endif
2429 	case HTTP_METHOD_UNLOCK:
2430 #ifdef USE_LOCKS
2431 		if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Lock-Token"))) {
2432 			buffer *locktoken = ds->value;
2433 			sqlite3_stmt *stmt = p->conf.stmt_remove_lock;
2434 
2435 			/* remove the < > around the token */
2436 			if (locktoken->used < 4) {
2437 				con->http_status = 400;
2438 
2439 				return HANDLER_FINISHED;
2440 			}
2441 
2442 			/**
2443 			 * FIXME:
2444 			 *
2445 			 * if the resourse is locked:
2446 			 * - by us: unlock
2447 			 * - by someone else: 401
2448 			 * if the resource is not locked:
2449 			 * - 412
2450 			 *  */
2451 
2452 			buffer_copy_string_len(p->tmp_buf, locktoken->ptr + 1, locktoken->used - 3);
2453 
2454 			sqlite3_reset(stmt);
2455 
2456 			sqlite3_bind_text(stmt, 1,
2457 				  CONST_BUF_LEN(p->tmp_buf),
2458 				  SQLITE_TRANSIENT);
2459 
2460 			sqlite3_bind_text(stmt, 2,
2461 				  CONST_BUF_LEN(con->uri.path),
2462 				  SQLITE_TRANSIENT);
2463 
2464 			if (SQLITE_DONE != sqlite3_step(stmt)) {
2465 				log_error_write(srv, __FILE__, __LINE__, "ss",
2466 					"remove lock:", sqlite3_errmsg(p->conf.sql));
2467 			}
2468 
2469 			if (0 == sqlite3_changes(p->conf.sql)) {
2470 				con->http_status = 401;
2471 			} else {
2472 				con->http_status = 204;
2473 			}
2474 			return HANDLER_FINISHED;
2475 		} else {
2476 			/* we need a lock-token to unlock */
2477 			con->http_status = 400;
2478 
2479 			return HANDLER_FINISHED;
2480 		}
2481 		break;
2482 #else
2483 		con->http_status = 501;
2484 		return HANDLER_FINISHED;
2485 #endif
2486 	default:
2487 		break;
2488 	}
2489 
2490 	/* not found */
2491 	return HANDLER_GO_ON;
2492 }
2493 
2494 
2495 /* this function is called at dlopen() time and inits the callbacks */
2496 
2497 int mod_webdav_plugin_init(plugin *p);
mod_webdav_plugin_init(plugin * p)2498 int mod_webdav_plugin_init(plugin *p) {
2499 	p->version     = LIGHTTPD_VERSION_ID;
2500 	p->name        = buffer_init_string("webdav");
2501 
2502 	p->init        = mod_webdav_init;
2503 	p->handle_uri_clean  = mod_webdav_uri_handler;
2504 	p->handle_physical   = mod_webdav_subrequest_handler;
2505 	p->set_defaults  = mod_webdav_set_defaults;
2506 	p->cleanup     = mod_webdav_free;
2507 
2508 	p->data        = NULL;
2509 
2510 	return 0;
2511 }
2512