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