Annotation of embedaddon/lighttpd/src/mod_webdav.c, revision 1.1.1.2
1.1 misho 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 */
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 */
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:
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:
1.1.1.2 ! misho 182: p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
1.1 misho 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;
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:
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: }
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:
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:
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:
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:
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:
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:
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:
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:
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
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
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: */
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:
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:
1.1.1.2 ! misho 1261: force_assert(rootnode);
1.1 misho 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))) {
1.1.1.2 ! misho 1693: if (errno != ENOENT ||
1.1 misho 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));
1.1.1.2 ! misho 1720: close(fd);
1.1 misho 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;
1.1.1.2 ! misho 1729: close(fd);
1.1 misho 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:
1.1.1.2 ! misho 2238: force_assert(rootnode);
1.1 misho 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);
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: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>