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