Statistics
| Revision:

root / branches / lighttpd-1.4.x / src / mod_webdav.c @ 1882

History | View | Annotate | Download (63.8 KB)

1
#include <sys/types.h>
2
#include <sys/stat.h>
3
#include <ctype.h>
4
#include <stdlib.h>
5
#include <string.h>
6
#include <errno.h>
7
#include <fcntl.h>
8
#include <stdio.h>
9
#include <assert.h>
10
11
#include <unistd.h>
12
#include <dirent.h>
13
14
#ifdef HAVE_CONFIG_H
15
#include "config.h"
16
#endif
17
18
#if defined(HAVE_LIBXML_H) && defined(HAVE_SQLITE3_H)
19
#define USE_PROPPATCH
20
#include <libxml/tree.h>
21
#include <libxml/parser.h>
22
23
#include <sqlite3.h>
24
#endif
25
26
#if defined(HAVE_LIBXML_H) && defined(HAVE_SQLITE3_H) && defined(HAVE_UUID_UUID_H)
27
#define USE_LOCKS
28
#include <uuid/uuid.h>
29
#endif
30
31
#include "base.h"
32
#include "log.h"
33
#include "buffer.h"
34
#include "response.h"
35
36
#include "plugin.h"
37
38
#include "stream.h"
39
#include "stat_cache.h"
40
41
#include "sys-mmap.h"
42
43
/**
44
 * this is a webdav for a lighttpd plugin
45
 *
46
 * at least a very basic one.
47
 * - for now it is read-only and we only support PROPFIND
48
 *
49
 */
50
51
#define WEBDAV_FILE_MODE S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH
52
#define WEBDAV_DIR_MODE  S_IRWXU | S_IRWXG | S_IRWXO
53
54
/* plugin config for all request/connections */
55
56
typedef struct {
57
        unsigned short enabled;
58
        unsigned short is_readonly;
59
        unsigned short log_xml;
60
61
        buffer *sqlite_db_name;
62
#ifdef USE_PROPPATCH
63
        sqlite3 *sql;
64
        sqlite3_stmt *stmt_update_prop;
65
        sqlite3_stmt *stmt_delete_prop;
66
        sqlite3_stmt *stmt_select_prop;
67
        sqlite3_stmt *stmt_select_propnames;
68
69
        sqlite3_stmt *stmt_delete_uri;
70
        sqlite3_stmt *stmt_move_uri;
71
        sqlite3_stmt *stmt_copy_uri;
72
73
        sqlite3_stmt *stmt_remove_lock;
74
        sqlite3_stmt *stmt_create_lock;
75
        sqlite3_stmt *stmt_read_lock;
76
        sqlite3_stmt *stmt_read_lock_by_uri;
77
        sqlite3_stmt *stmt_refresh_lock;
78
#endif
79
} plugin_config;
80
81
typedef struct {
82
        PLUGIN_DATA;
83
84
        buffer *tmp_buf;
85
        request_uri uri;
86
        physical physical;
87
88
        plugin_config **config_storage;
89
90
        plugin_config conf;
91
} plugin_data;
92
93
/* init the plugin data */
94
INIT_FUNC(mod_webdav_init) {
95
        plugin_data *p;
96
97
        p = calloc(1, sizeof(*p));
98
99
        p->tmp_buf = buffer_init();
100
101
        p->uri.scheme = buffer_init();
102
        p->uri.path_raw = buffer_init();
103
        p->uri.path = buffer_init();
104
        p->uri.authority = buffer_init();
105
106
        p->physical.path = buffer_init();
107
        p->physical.rel_path = buffer_init();
108
        p->physical.doc_root = buffer_init();
109
        p->physical.basedir = buffer_init();
110
111
        return p;
112
}
113
114
/* detroy the plugin data */
115
FREE_FUNC(mod_webdav_free) {
116
        plugin_data *p = p_d;
117
118
        UNUSED(srv);
119
120
        if (!p) return HANDLER_GO_ON;
121
122
        if (p->config_storage) {
123
                size_t i;
124
                for (i = 0; i < srv->config_context->used; i++) {
125
                        plugin_config *s = p->config_storage[i];
126
127
                        if (!s) continue;
128
129
                        buffer_free(s->sqlite_db_name);
130
#ifdef USE_PROPPATCH
131
                        if (s->sql) {
132
                                sqlite3_finalize(s->stmt_delete_prop);
133
                                sqlite3_finalize(s->stmt_delete_uri);
134
                                sqlite3_finalize(s->stmt_copy_uri);
135
                                sqlite3_finalize(s->stmt_move_uri);
136
                                sqlite3_finalize(s->stmt_update_prop);
137
                                sqlite3_finalize(s->stmt_select_prop);
138
                                sqlite3_finalize(s->stmt_select_propnames);
139
140
                                sqlite3_finalize(s->stmt_read_lock);
141
                                sqlite3_finalize(s->stmt_read_lock_by_uri);
142
                                sqlite3_finalize(s->stmt_create_lock);
143
                                sqlite3_finalize(s->stmt_remove_lock);
144
                                sqlite3_finalize(s->stmt_refresh_lock);
145
                                sqlite3_close(s->sql);
146
                        }
147
#endif
148
                        free(s);
149
                }
150
                free(p->config_storage);
151
        }
152
153
        buffer_free(p->uri.scheme);
154
        buffer_free(p->uri.path_raw);
155
        buffer_free(p->uri.path);
156
        buffer_free(p->uri.authority);
157
158
        buffer_free(p->physical.path);
159
        buffer_free(p->physical.rel_path);
160
        buffer_free(p->physical.doc_root);
161
        buffer_free(p->physical.basedir);
162
163
        buffer_free(p->tmp_buf);
164
165
        free(p);
166
167
        return HANDLER_GO_ON;
168
}
169
170
/* handle plugin config and check values */
171
172
SETDEFAULTS_FUNC(mod_webdav_set_defaults) {
173
        plugin_data *p = p_d;
174
        size_t i = 0;
175
176
        config_values_t cv[] = {
177
                { "webdav.activate",            NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },       /* 0 */
178
                { "webdav.is-readonly",         NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },       /* 1 */
179
                { "webdav.sqlite-db-name",      NULL, T_CONFIG_STRING,  T_CONFIG_SCOPE_CONNECTION },       /* 2 */
180
                { "webdav.log-xml",             NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },       /* 3 */
181
                { NULL,                         NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
182
        };
183
184
        if (!p) return HANDLER_ERROR;
185
186
        p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
187
188
        for (i = 0; i < srv->config_context->used; i++) {
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
201
                if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
202
                        return HANDLER_ERROR;
203
                }
204
205
                if (!buffer_is_empty(s->sqlite_db_name)) {
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,
218
                                        "CREATE TABLE properties ("
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
235
                        if (SQLITE_OK != sqlite3_prepare(s->sql,
236
                                CONST_STR_LEN("SELECT value FROM properties WHERE resource = ? AND prop = ? AND ns = ?"),
237
                                &(s->stmt_select_prop), &next_stmt)) {
238
                                /* prepare failed */
239
240
                                log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql));
241
                                return HANDLER_ERROR;
242
                        }
243
244
                        if (SQLITE_OK != sqlite3_prepare(s->sql,
245
                                CONST_STR_LEN("SELECT ns, prop FROM properties WHERE resource = ?"),
246
                                &(s->stmt_select_propnames), &next_stmt)) {
247
                                /* prepare failed */
248
249
                                log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql));
250
                                return HANDLER_ERROR;
251
                        }
252
253
254
                        if (SQLITE_OK != sqlite3_prepare(s->sql,
255
                                CONST_STR_LEN("REPLACE INTO properties (resource, prop, ns, value) VALUES (?, ?, ?, ?)"),
256
                                &(s->stmt_update_prop), &next_stmt)) {
257
                                /* prepare failed */
258
259
                                log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql));
260
                                return HANDLER_ERROR;
261
                        }
262
263
                        if (SQLITE_OK != sqlite3_prepare(s->sql,
264
                                CONST_STR_LEN("DELETE FROM properties WHERE resource = ? AND prop = ? AND ns = ?"),
265
                                &(s->stmt_delete_prop), &next_stmt)) {
266
                                /* prepare failed */
267
                                log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
268
269
                                return HANDLER_ERROR;
270
                        }
271
272
                        if (SQLITE_OK != sqlite3_prepare(s->sql,
273
                                CONST_STR_LEN("DELETE FROM properties WHERE resource = ?"),
274
                                &(s->stmt_delete_uri), &next_stmt)) {
275
                                /* prepare failed */
276
                                log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
277
278
                                return HANDLER_ERROR;
279
                        }
280
281
                        if (SQLITE_OK != sqlite3_prepare(s->sql,
282
                                CONST_STR_LEN("INSERT INTO properties SELECT ?, prop, ns, value FROM properties WHERE resource = ?"),
283
                                &(s->stmt_copy_uri), &next_stmt)) {
284
                                /* prepare failed */
285
                                log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
286
287
                                return HANDLER_ERROR;
288
                        }
289
290
                        if (SQLITE_OK != sqlite3_prepare(s->sql,
291
                                CONST_STR_LEN("UPDATE properties SET resource = ? WHERE resource = ?"),
292
                                &(s->stmt_move_uri), &next_stmt)) {
293
                                /* prepare failed */
294
                                log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql));
295
296
                                return HANDLER_ERROR;
297
                        }
298
299
                        /* LOCKS */
300
301
                        if (SQLITE_OK != sqlite3_exec(s->sql,
302
                                        "CREATE TABLE locks ("
303
                                        "  locktoken TEXT NOT NULL,"
304
                                        "  resource TEXT NOT NULL,"
305
                                        "  lockscope TEXT NOT NULL,"
306
                                        "  locktype TEXT NOT NULL,"
307
                                        "  owner TEXT NOT NULL,"
308
                                        "  depth INT NOT NULL,"
309
                                        "  timeout TIMESTAMP NOT NULL,"
310
                                        "  PRIMARY KEY(locktoken))",
311
                                        NULL, NULL, &err)) {
312
313
                                if (0 != strcmp(err, "table locks already exists")) {
314
                                        log_error_write(srv, __FILE__, __LINE__, "ss", "can't open transaction:", err);
315
                                        sqlite3_free(err);
316
317
                                        return HANDLER_ERROR;
318
                                }
319
                                sqlite3_free(err);
320
                        }
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,
341
                                CONST_STR_LEN("SELECT locktoken, resource, lockscope, locktype, owner, depth, timeout FROM locks WHERE locktoken = ?"),
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,
350
                                CONST_STR_LEN("SELECT locktoken, resource, lockscope, locktype, owner, depth, timeout FROM locks WHERE resource = ?"),
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
453
        if (con->uri.path->used == 0) return HANDLER_GO_ON;
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(b,"<");
489
                buffer_append_string(b, prop_name);
490
                buffer_append_string(b, " xmlns=\"");
491
                buffer_append_string(b, prop_ns);
492
                buffer_append_string(b, "\">");
493
494
                buffer_append_string(b, value);
495
496
                buffer_append_string(b,"</");
497
                buffer_append_string(b, prop_name);
498
                buffer_append_string(b, ">");
499
        } else {
500
                buffer_append_string(b,"<");
501
                buffer_append_string(b, prop_name);
502
                buffer_append_string(b, " xmlns=\"");
503
                buffer_append_string(b, prop_ns);
504
                buffer_append_string(b, "\"/>");
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(b,"<D:response xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\">\n");
515
516
        buffer_append_string(b,"<D:href>\n");
517
        buffer_append_string_buffer(b, dst->rel_path);
518
        buffer_append_string(b,"</D:href>\n");
519
        buffer_append_string(b,"<D:status>\n");
520
521
        if (con->request.http_version == HTTP_VERSION_1_1) {
522
                BUFFER_COPY_STRING_CONST(b, "HTTP/1.1 ");
523
        } else {
524
                BUFFER_COPY_STRING_CONST(b, "HTTP/1.0 ");
525
        }
526
        buffer_append_long(b, status);
527
        BUFFER_APPEND_STRING_CONST(b, " ");
528
        buffer_append_string(b, get_http_status_name(status));
529
530
        buffer_append_string(b,"</D:status>\n");
531
        buffer_append_string(b,"</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,
565
                                          dst->rel_path->ptr,
566
                                          dst->rel_path->used - 1,
567
                                          SQLITE_TRANSIENT);
568
569
                        if (SQLITE_DONE != sqlite3_step(stmt)) {
570
                                /* */
571
                        }
572
                }
573
#endif
574
        }
575
576
        return (status != 0);
577
}
578
579
static int webdav_delete_dir(server *srv, connection *con, plugin_data *p, physical *dst, buffer *b) {
580
        DIR *dir;
581
        int have_multi_status = 0;
582
        physical d;
583
584
        d.path = buffer_init();
585
        d.rel_path = buffer_init();
586
587
        if (NULL != (dir = opendir(dst->path->ptr))) {
588
                struct dirent *de;
589
590
                while(NULL != (de = readdir(dir))) {
591
                        struct stat st;
592
                        int status = 0;
593
594
                        if ((de->d_name[0] == '.' && de->d_name[1] == '\0')  ||
595
                            (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')) {
596
                                continue;
597
                                /* ignore the parent dir */
598
                        }
599
600
                        buffer_copy_string_buffer(d.path, dst->path);
601
                        BUFFER_APPEND_SLASH(d.path);
602
                        buffer_append_string(d.path, de->d_name);
603
604
                        buffer_copy_string_buffer(d.rel_path, dst->rel_path);
605
                        BUFFER_APPEND_SLASH(d.rel_path);
606
                        buffer_append_string(d.rel_path, de->d_name);
607
608
                        /* stat and unlink afterwards */
609
                        if (-1 == stat(d.path->ptr, &st)) {
610
                                /* don't about it yet, rmdir will fail too */
611
                        } else if (S_ISDIR(st.st_mode)) {
612
                                have_multi_status = webdav_delete_dir(srv, con, p, &d, b);
613
614
                                /* try to unlink it */
615
                                if (-1 == rmdir(d.path->ptr)) {
616
                                        switch(errno) {
617
                                        case EACCES:
618
                                        case EPERM:
619
                                                /* 403 */
620
                                                status = 403;
621
                                                break;
622
                                        default:
623
                                                status = 501;
624
                                                break;
625
                                        }
626
                                        have_multi_status = 1;
627
628
                                        webdav_gen_response_status_tag(srv, con, &d, status, b);
629
                                } else {
630
#ifdef USE_PROPPATCH
631
                                        sqlite3_stmt *stmt = p->conf.stmt_delete_uri;
632
633
                                        status = 0;
634
635
                                        if (stmt) {
636
                                                sqlite3_reset(stmt);
637
638
                                                /* bind the values to the insert */
639
640
                                                sqlite3_bind_text(stmt, 1,
641
                                                                  d.rel_path->ptr,
642
                                                                  d.rel_path->used - 1,
643
                                                                  SQLITE_TRANSIENT);
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
664
static int webdav_copy_file(server *srv, connection *con, plugin_data *p, physical *src, physical *dst, int overwrite) {
665
        stream s;
666
        int status = 0, ofd;
667
        UNUSED(srv);
668
        UNUSED(con);
669
670
        if (stream_open(&s, src->path)) {
671
                return 403;
672
        }
673
674
        if (-1 == (ofd = open(dst->path->ptr, O_WRONLY|O_TRUNC|O_CREAT|(overwrite ? 0 : O_EXCL), WEBDAV_FILE_MODE))) {
675
                /* opening the destination failed for some reason */
676
                switch(errno) {
677
                case EEXIST:
678
                        status = 412;
679
                        break;
680
                case EISDIR:
681
                        status = 409;
682
                        break;
683
                case ENOENT:
684
                        /* at least one part in the middle wasn't existing */
685
                        status = 409;
686
                        break;
687
                default:
688
                        status = 403;
689
                        break;
690
                }
691
                stream_close(&s);
692
                return status;
693
        }
694
695
        if (-1 == write(ofd, s.start, s.size)) {
696
                switch(errno) {
697
                case ENOSPC:
698
                        status = 507;
699
                        break;
700
                default:
701
                        status = 403;
702
                        break;
703
                }
704
        }
705
706
        stream_close(&s);
707
        close(ofd);
708
709
#ifdef USE_PROPPATCH
710
        if (0 == status) {
711
                /* copy worked fine, copy connected properties */
712
                sqlite3_stmt *stmt = p->conf.stmt_copy_uri;
713
714
                if (stmt) {
715
                        sqlite3_reset(stmt);
716
717
                        /* bind the values to the insert */
718
                        sqlite3_bind_text(stmt, 1,
719
                                          dst->rel_path->ptr,
720
                                          dst->rel_path->used - 1,
721
                                          SQLITE_TRANSIENT);
722
723
                        sqlite3_bind_text(stmt, 2,
724
                                          src->rel_path->ptr,
725
                                          src->rel_path->used - 1,
726
                                          SQLITE_TRANSIENT);
727
728
                        if (SQLITE_DONE != sqlite3_step(stmt)) {
729
                                /* */
730
                        }
731
                }
732
        }
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(b, "<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(b, "<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(b,"<D:getcontenttype>");
858
                                                buffer_append_string_buffer(b, ds->value);
859
                                                buffer_append_string(b, "</D:getcontenttype>");
860
                                                found = 1;
861
862
                                                break;
863
                                        }
864
                                }
865
                        }
866
                } else if (0 == strcmp(prop_name, "creationdate")) {
867
                        buffer_append_string(b, "<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(b, "</D:creationdate>");
871
                        found = 1;
872
                } else if (0 == strcmp(prop_name, "getlastmodified")) {
873
                        buffer_append_string(b,"<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(b, "</D:getlastmodified>");
877
                        found = 1;
878
                } else if (0 == strcmp(prop_name, "getcontentlength")) {
879
                        buffer_append_string(b,"<D:getcontentlength>");
880
                        buffer_append_off_t(b, sce->st.st_size);
881
                        buffer_append_string(b, "</D:getcontentlength>");
882
                        found = 1;
883
                } else if (0 == strcmp(prop_name, "getcontentlanguage")) {
884
                        buffer_append_string(b,"<D:getcontentlanguage>");
885
                        buffer_append_string(b, "en");
886
                        buffer_append_string(b, "</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
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
1026
                                        return -1;
1027
                                }
1028
1029
                                close(c->file.fd);
1030
                                c->file.fd = -1;
1031
1032
                                c->file.mmap.length = c->file.length;
1033
1034
                                /* chunk_reset() or chunk_free() will cleanup for us */
1035
                        }
1036
1037
                        if (XML_ERR_OK != (err = xmlParseChunk(ctxt, c->file.mmap.start + c->offset, weHave, 0))) {
1038
                                log_error_write(srv, __FILE__, __LINE__, "sodd", "xmlParseChunk failed at:", cq->bytes_out, weHave, err);
1039
                        }
1040
1041
                        c->offset += weHave;
1042
                        cq->bytes_out += weHave;
1043
1044
                        break;
1045
                case MEM_CHUNK:
1046
                        /* append to the buffer */
1047
                        weHave = c->mem->used - 1 - c->offset;
1048
1049
                        if (weHave > weWant) weHave = weWant;
1050
1051
                        if (p->conf.log_xml) {
1052
                                log_error_write(srv, __FILE__, __LINE__, "ss", "XML-request-body:", c->mem->ptr + c->offset);
1053
                        }
1054
1055
                        if (XML_ERR_OK != (err = xmlParseChunk(ctxt, c->mem->ptr + c->offset, weHave, 0))) {
1056
                                log_error_write(srv, __FILE__, __LINE__, "sodd", "xmlParseChunk failed at:", cq->bytes_out, weHave, err);
1057
                        }
1058
1059
                        c->offset += weHave;
1060
                        cq->bytes_out += weHave;
1061
1062
                        break;
1063
                case UNUSED_CHUNK:
1064
                        break;
1065
                }
1066
                chunkqueue_remove_finished_chunks(cq);
1067
        }
1068
1069
1070
        switch ((err = xmlParseChunk(ctxt, 0, 0, 1))) {
1071
        case XML_ERR_DOCUMENT_END:
1072
        case XML_ERR_OK:
1073
                break;
1074
        default:
1075
                log_error_write(srv, __FILE__, __LINE__, "sd", "xmlParseChunk failed at final packet:", err);
1076
                break;
1077
        }
1078
1079
        xml = ctxt->myDoc;
1080
        res = ctxt->wellFormed;
1081
        xmlFreeParserCtxt(ctxt);
1082
1083
        if (res == 0) {
1084
                xmlFreeDoc(xml);
1085
        } else {
1086
                *ret_xml = xml;
1087
        }
1088
1089
        return res;
1090
}
1091
#endif
1092
1093
int webdav_lockdiscovery(server *srv, connection *con,
1094
                buffer *locktoken, const char *lockscope, const char *locktype, int depth) {
1095
1096
        buffer *b;
1097
1098
        response_header_overwrite(srv, con, CONST_STR_LEN("Lock-Token"), CONST_BUF_LEN(locktoken));
1099
1100
        response_header_overwrite(srv, con,
1101
                CONST_STR_LEN("Content-Type"),
1102
                CONST_STR_LEN("text/xml; charset=\"utf-8\""));
1103
1104
        b = chunkqueue_get_append_buffer(con->write_queue);
1105
1106
        buffer_copy_string(b, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
1107
1108
        buffer_append_string(b,"<D:prop xmlns:D=\"DAV:\" xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\">\n");
1109
        buffer_append_string(b,"<D:lockdiscovery>\n");
1110
        buffer_append_string(b,"<D:activelock>\n");
1111
1112
        buffer_append_string(b,"<D:lockscope>");
1113
        buffer_append_string(b,"<D:");
1114
        buffer_append_string(b, lockscope);
1115
        buffer_append_string(b, "/>");
1116
        buffer_append_string(b,"</D:lockscope>\n");
1117
1118
        buffer_append_string(b,"<D:locktype>");
1119
        buffer_append_string(b,"<D:");
1120
        buffer_append_string(b, locktype);
1121
        buffer_append_string(b, "/>");
1122
        buffer_append_string(b,"</D:locktype>\n");
1123
1124
        buffer_append_string(b,"<D:depth>");
1125
        buffer_append_string(b, depth == 0 ? "0" : "infinity");
1126
        buffer_append_string(b,"</D:depth>\n");
1127
1128
        buffer_append_string(b,"<D:timeout>");
1129
        buffer_append_string(b, "Second-600");
1130
        buffer_append_string(b,"</D:timeout>\n");
1131
1132
        buffer_append_string(b,"<D:owner>");
1133
        buffer_append_string(b,"</D:owner>\n");
1134
1135
        buffer_append_string(b,"<D:locktoken>");
1136
        buffer_append_string(b, "<D:href>");
1137
        buffer_append_string_buffer(b, locktoken);
1138
        buffer_append_string(b, "</D:href>");
1139
        buffer_append_string(b,"</D:locktoken>\n");
1140
1141
        buffer_append_string(b,"</D:activelock>\n");
1142
        buffer_append_string(b,"</D:lockdiscovery>\n");
1143
        buffer_append_string(b,"</D:prop>\n");
1144
1145
        return 0;
1146
}
1147
/**
1148
 * check if resource is having the right locks to access to resource
1149
 *
1150
 *
1151
 *
1152
 */
1153
int webdav_has_lock(server *srv, connection *con, plugin_data *p, buffer *uri) {
1154
        int has_lock = 1;
1155
1156
#ifdef USE_LOCKS
1157
        data_string *ds;
1158
1159
        /**
1160
         * This implementation is more fake than real
1161
         * we need a parser for the If: header to really handle the full scope
1162
         *
1163
         * X-Litmus: locks: 11 (owner_modify)
1164
         * If: <http://127.0.0.1:1025/dav/litmus/lockme> (<opaquelocktoken:2165478d-0611-49c4-be92-e790d68a38f1>)
1165
         * - a tagged check:
1166
         *   if http://127.0.0.1:1025/dav/litmus/lockme is locked with
1167
         *   opaquelocktoken:2165478d-0611-49c4-be92-e790d68a38f1, go on
1168
         *
1169
         * X-Litmus: locks: 16 (fail_cond_put)
1170
         * If: (<DAV:no-lock> ["-1622396671"])
1171
         * - untagged:
1172
         *   go on if the resource has the etag [...] and the lock
1173
         */
1174
        if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If"))) {
1175
                /* Ooh, ooh. A if tag, now the fun begins.
1176
                 *
1177
                 * this can only work with a real parser
1178
                 **/
1179
        } else {
1180
                /* we didn't provided a lock-token -> */
1181
                /* if the resource is locked -> 423 */
1182
1183
                sqlite3_stmt *stmt = p->conf.stmt_read_lock_by_uri;
1184
1185
                sqlite3_reset(stmt);
1186
1187
                sqlite3_bind_text(stmt, 1,
1188
                          CONST_BUF_LEN(uri),
1189
                          SQLITE_TRANSIENT);
1190
1191
                while (SQLITE_ROW == sqlite3_step(stmt)) {
1192
                        has_lock = 0;
1193
                }
1194
        }
1195
#endif
1196
1197
        return has_lock;
1198
}
1199
1200
URIHANDLER_FUNC(mod_webdav_subrequest_handler) {
1201
        plugin_data *p = p_d;
1202
        buffer *b;
1203
        DIR *dir;
1204
        data_string *ds;
1205
        int depth = -1;
1206
        struct stat st;
1207
        buffer *prop_200;
1208
        buffer *prop_404;
1209
        webdav_properties *req_props;
1210
        stat_cache_entry *sce = NULL;
1211
1212
        UNUSED(srv);
1213
1214
        if (!p->conf.enabled) return HANDLER_GO_ON;
1215
        /* physical path is setup */
1216
        if (con->physical.path->used == 0) return HANDLER_GO_ON;
1217
1218
        /* PROPFIND need them */
1219
        if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Depth"))) {
1220
                depth = strtol(ds->value->ptr, NULL, 10);
1221
        }
1222
1223
        switch (con->request.http_method) {
1224
        case HTTP_METHOD_PROPFIND:
1225
                /* they want to know the properties of the directory */
1226
                req_props = NULL;
1227
1228
                /* is there a content-body ? */
1229
1230
                switch (stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
1231
                case HANDLER_ERROR:
1232
                        if (errno == ENOENT) {
1233
                                con->http_status = 404;
1234
                                return HANDLER_FINISHED;
1235
                        }
1236
                        break;
1237
                default:
1238
                        break;
1239
                }
1240
1241
1242
#ifdef USE_PROPPATCH
1243
                /* any special requests or just allprop ? */
1244
                if (con->request.content_length) {
1245
                        xmlDocPtr xml;
1246
1247
                        if (1 == webdav_parse_chunkqueue(srv, con, p, con->request_content_queue, &xml)) {
1248
                                xmlNode *rootnode = xmlDocGetRootElement(xml);
1249
1250
                                assert(rootnode);
1251
1252
                                if (0 == xmlStrcmp(rootnode->name, BAD_CAST "propfind")) {
1253
                                        xmlNode *cmd;
1254
1255
                                        req_props = calloc(1, sizeof(*req_props));
1256
1257
                                        for (cmd = rootnode->children; cmd; cmd = cmd->next) {
1258
1259
                                                if (0 == xmlStrcmp(cmd->name, BAD_CAST "prop")) {
1260
                                                        /* get prop by name */
1261
                                                        xmlNode *prop;
1262
1263
                                                        for (prop = cmd->children; prop; prop = prop->next) {
1264
                                                                if (prop->type == XML_TEXT_NODE) continue; /* ignore WS */
1265
1266
                                                                if (prop->ns &&
1267
                                                                    (0 == xmlStrcmp(prop->ns->href, BAD_CAST "")) &&
1268
                                                                    (0 != xmlStrcmp(prop->ns->prefix, BAD_CAST ""))) {
1269
                                                                        size_t i;
1270
                                                                        log_error_write(srv, __FILE__, __LINE__, "ss",
1271
                                                                                        "no name space for:",
1272
                                                                                        prop->name);
1273
1274
                                                                        xmlFreeDoc(xml);
1275
1276
                                                                        for (i = 0; i < req_props->used; i++) {
1277
                                                                                free(req_props->ptr[i]->ns);
1278
                                                                                free(req_props->ptr[i]->prop);
1279
                                                                                free(req_props->ptr[i]);
1280
                                                                        }
1281
                                                                        free(req_props->ptr);
1282
                                                                        free(req_props);
1283
1284
                                                                        con->http_status = 400;
1285
                                                                        return HANDLER_FINISHED;
1286
                                                                }
1287
1288
                                                                /* add property to requested list */
1289
                                                                if (req_props->size == 0) {
1290
                                                                        req_props->size = 16;
1291
                                                                        req_props->ptr = malloc(sizeof(*(req_props->ptr)) * req_props->size);
1292
                                                                } else if (req_props->used == req_props->size) {
1293
                                                                        req_props->size += 16;
1294
                                                                        req_props->ptr = realloc(req_props->ptr, sizeof(*(req_props->ptr)) * req_props->size);
1295
                                                                }
1296
1297
                                                                req_props->ptr[req_props->used] = malloc(sizeof(webdav_property));
1298
                                                                req_props->ptr[req_props->used]->ns = (char *)xmlStrdup(prop->ns ? prop->ns->href : (xmlChar *)"");
1299
                                                                req_props->ptr[req_props->used]->prop = (char *)xmlStrdup(prop->name);
1300
                                                                req_props->used++;
1301
                                                        }
1302
                                                } else if (0 == xmlStrcmp(cmd->name, BAD_CAST "propname")) {
1303
                                                        sqlite3_stmt *stmt = p->conf.stmt_select_propnames;
1304
1305
                                                        if (stmt) {
1306
                                                                /* get all property names (EMPTY) */
1307
                                                                sqlite3_reset(stmt);
1308
                                                                /* bind the values to the insert */
1309
1310
                                                                sqlite3_bind_text(stmt, 1,
1311
                                                                                  con->uri.path->ptr,
1312
                                                                                  con->uri.path->used - 1,
1313
                                                                                  SQLITE_TRANSIENT);
1314
1315
                                                                if (SQLITE_DONE != sqlite3_step(stmt)) {
1316
                                                                }
1317
                                                        }
1318
                                                } else if (0 == xmlStrcmp(cmd->name, BAD_CAST "allprop")) {
1319
                                                        /* get all properties (EMPTY) */
1320
                                                }
1321
                                        }
1322
                                }
1323
1324
                                xmlFreeDoc(xml);
1325
                        } else {
1326
                                con->http_status = 400;
1327
                                return HANDLER_FINISHED;
1328
                        }
1329
                }
1330
#endif
1331
                con->http_status = 207;
1332
1333
                response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml; charset=\"utf-8\""));
1334
1335
                b = chunkqueue_get_append_buffer(con->write_queue);
1336
1337
                buffer_copy_string(b, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
1338
1339
                buffer_append_string(b,"<D:multistatus xmlns:D=\"DAV:\" xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\">\n");
1340
1341
                /* allprop */
1342
1343
                prop_200 = buffer_init();
1344
                prop_404 = buffer_init();
1345
1346
                switch(depth) {
1347
                case 0:
1348
                        /* Depth: 0 */
1349
                        webdav_get_props(srv, con, p, &(con->physical), req_props, prop_200, prop_404);
1350
1351
                        buffer_append_string(b,"<D:response>\n");
1352
                        buffer_append_string(b,"<D:href>");
1353
                        buffer_append_string_buffer(b, con->uri.scheme);
1354
                        buffer_append_string(b,"://");
1355
                        buffer_append_string_buffer(b, con->uri.authority);
1356
                        buffer_append_string_encoded(b, CONST_BUF_LEN(con->uri.path), ENCODING_REL_URI);
1357
                        buffer_append_string(b,"</D:href>\n");
1358
1359
                        if (!buffer_is_empty(prop_200)) {
1360
                                buffer_append_string(b,"<D:propstat>\n");
1361
                                buffer_append_string(b,"<D:prop>\n");
1362
1363
                                buffer_append_string_buffer(b, prop_200);
1364
1365
                                buffer_append_string(b,"</D:prop>\n");
1366
1367
                                buffer_append_string(b,"<D:status>HTTP/1.1 200 OK</D:status>\n");
1368
1369
                                buffer_append_string(b,"</D:propstat>\n");
1370
                        }
1371
                        if (!buffer_is_empty(prop_404)) {
1372
                                buffer_append_string(b,"<D:propstat>\n");
1373
                                buffer_append_string(b,"<D:prop>\n");
1374
1375
                                buffer_append_string_buffer(b, prop_404);
1376
1377
                                buffer_append_string(b,"</D:prop>\n");
1378
1379
                                buffer_append_string(b,"<D:status>HTTP/1.1 404 Not Found</D:status>\n");
1380
1381
                                buffer_append_string(b,"</D:propstat>\n");
1382
                        }
1383
1384
                        buffer_append_string(b,"</D:response>\n");
1385
1386
                        break;
1387
                case 1:
1388
                        if (NULL != (dir = opendir(con->physical.path->ptr))) {
1389
                                struct dirent *de;
1390
                                physical d;
1391
                                physical *dst = &(con->physical);
1392
1393
                                d.path = buffer_init();
1394
                                d.rel_path = buffer_init();
1395
1396
                                while(NULL != (de = readdir(dir))) {
1397
                                        if (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0') {
1398
                                                continue;
1399
                                                /* ignore the parent dir */
1400
                                        }
1401
1402
                                        buffer_copy_string_buffer(d.path, dst->path);
1403
                                        BUFFER_APPEND_SLASH(d.path);
1404
1405
                                        buffer_copy_string_buffer(d.rel_path, dst->rel_path);
1406
                                        BUFFER_APPEND_SLASH(d.rel_path);
1407
1408
                                        if (de->d_name[0] == '.' && de->d_name[1] == '\0') {
1409
                                                /* don't append the . */
1410
                                        } else {
1411
                                                buffer_append_string(d.path, de->d_name);
1412
                                                buffer_append_string(d.rel_path, de->d_name);
1413
                                        }
1414
1415
                                        buffer_reset(prop_200);
1416
                                        buffer_reset(prop_404);
1417
1418
                                        webdav_get_props(srv, con, p, &d, req_props, prop_200, prop_404);
1419
1420
                                        buffer_append_string(b,"<D:response>\n");
1421
                                        buffer_append_string(b,"<D:href>");
1422
                                        buffer_append_string_buffer(b, con->uri.scheme);
1423
                                        buffer_append_string(b,"://");
1424
                                        buffer_append_string_buffer(b, con->uri.authority);
1425
                                        buffer_append_string_encoded(b, CONST_BUF_LEN(d.rel_path), ENCODING_REL_URI);
1426
                                        buffer_append_string(b,"</D:href>\n");
1427
1428
                                        if (!buffer_is_empty(prop_200)) {
1429
                                                buffer_append_string(b,"<D:propstat>\n");
1430
                                                buffer_append_string(b,"<D:prop>\n");
1431
1432
                                                buffer_append_string_buffer(b, prop_200);
1433
1434
                                                buffer_append_string(b,"</D:prop>\n");
1435
1436
                                                buffer_append_string(b,"<D:status>HTTP/1.1 200 OK</D:status>\n");
1437
1438
                                                buffer_append_string(b,"</D:propstat>\n");
1439
                                        }
1440
                                        if (!buffer_is_empty(prop_404)) {
1441
                                                buffer_append_string(b,"<D:propstat>\n");
1442
                                                buffer_append_string(b,"<D:prop>\n");
1443
1444
                                                buffer_append_string_buffer(b, prop_404);
1445
1446
                                                buffer_append_string(b,"</D:prop>\n");
1447
1448
                                                buffer_append_string(b,"<D:status>HTTP/1.1 404 Not Found</D:status>\n");
1449
1450
                                                buffer_append_string(b,"</D:propstat>\n");
1451
                                        }
1452
1453
                                        buffer_append_string(b,"</D:response>\n");
1454
                                }
1455
                                closedir(dir);
1456
                                buffer_free(d.path);
1457
                                buffer_free(d.rel_path);
1458
                        }
1459
                        break;
1460
                }
1461
1462
                if (req_props) {
1463
                        size_t i;
1464
                        for (i = 0; i < req_props->used; i++) {
1465
                                free(req_props->ptr[i]->ns);
1466
                                free(req_props->ptr[i]->prop);
1467
                                free(req_props->ptr[i]);
1468
                        }
1469
                        free(req_props->ptr);
1470
                        free(req_props);
1471
                }
1472
1473
                buffer_free(prop_200);
1474
                buffer_free(prop_404);
1475
1476
                buffer_append_string(b,"</D:multistatus>\n");
1477
1478
                if (p->conf.log_xml) {
1479
                        log_error_write(srv, __FILE__, __LINE__, "sb", "XML-response-body:", b);
1480
                }
1481
                con->file_finished = 1;
1482
1483
                return HANDLER_FINISHED;
1484
        case HTTP_METHOD_MKCOL:
1485
                if (p->conf.is_readonly) {
1486
                        con->http_status = 403;
1487
                        return HANDLER_FINISHED;
1488
                }
1489
1490
                if (con->request.content_length != 0) {
1491
                        /* we don't support MKCOL with a body */
1492
                        con->http_status = 415;
1493
1494
                        return HANDLER_FINISHED;
1495
                }
1496
1497
                /* let's create the directory */
1498
1499
                if (-1 == mkdir(con->physical.path->ptr, WEBDAV_DIR_MODE)) {
1500
                        switch(errno) {
1501
                        case EPERM:
1502
                                con->http_status = 403;
1503
                                break;
1504
                        case ENOENT:
1505
                        case ENOTDIR:
1506
                                con->http_status = 409;
1507
                                break;
1508
                        case EEXIST:
1509
                        default:
1510
                                con->http_status = 405; /* not allowed */
1511
                                break;
1512
                        }
1513
                } else {
1514
                        con->http_status = 201;
1515
                        con->file_finished = 1;
1516
                }
1517
1518
                return HANDLER_FINISHED;
1519
        case HTTP_METHOD_DELETE:
1520
                if (p->conf.is_readonly) {
1521
                        con->http_status = 403;
1522
                        return HANDLER_FINISHED;
1523
                }
1524
1525
                /* does the client have a lock for this connection ? */
1526
                if (!webdav_has_lock(srv, con, p, con->uri.path)) {
1527
                        con->http_status = 423;
1528
                        return HANDLER_FINISHED;
1529
                }
1530
1531
                /* stat and unlink afterwards */
1532
                if (-1 == stat(con->physical.path->ptr, &st)) {
1533
                        /* don't about it yet, unlink will fail too */
1534
                        switch(errno) {
1535
                        case ENOENT:
1536
                                 con->http_status = 404;
1537
                                 break;
1538
                        default:
1539
                                 con->http_status = 403;
1540
                                 break;
1541
                        }
1542
                } else if (S_ISDIR(st.st_mode)) {
1543
                        buffer *multi_status_resp = buffer_init();
1544
1545
                        if (webdav_delete_dir(srv, con, p, &(con->physical), multi_status_resp)) {
1546
                                /* we got an error somewhere in between, build a 207 */
1547
                                response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml; charset=\"utf-8\""));
1548
1549
                                b = chunkqueue_get_append_buffer(con->write_queue);
1550
1551
                                buffer_copy_string(b, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
1552
1553
                                buffer_append_string(b,"<D:multistatus xmlns:D=\"DAV:\">\n");
1554
1555
                                buffer_append_string_buffer(b, multi_status_resp);
1556
1557
                                buffer_append_string(b,"</D:multistatus>\n");
1558
1559
                                if (p->conf.log_xml) {
1560
                                        log_error_write(srv, __FILE__, __LINE__, "sb", "XML-response-body:", b);
1561
                                }
1562
1563
                                con->http_status = 207;
1564
                                con->file_finished = 1;
1565
                        } else {
1566
                                /* everything went fine, remove the directory */
1567
1568
                                if (-1 == rmdir(con->physical.path->ptr)) {
1569
                                        switch(errno) {
1570
                                        case ENOENT:
1571
                                                con->http_status = 404;
1572
                                                break;
1573
                                        default:
1574
                                                con->http_status = 501;
1575
                                                break;
1576
                                        }
1577
                                } else {
1578
                                        con->http_status = 204;
1579
                                }
1580
                        }
1581
1582
                        buffer_free(multi_status_resp);
1583
                } else if (-1 == unlink(con->physical.path->ptr)) {
1584
                        switch(errno) {
1585
                        case EPERM:
1586
                                con->http_status = 403;
1587
                                break;
1588
                        case ENOENT:
1589
                                con->http_status = 404;
1590
                                break;
1591
                        default:
1592
                                con->http_status = 501;
1593
                                break;
1594
                        }
1595
                } else {
1596
                        con->http_status = 204;
1597
                }
1598
                return HANDLER_FINISHED;
1599
        case HTTP_METHOD_PUT: {
1600
                int fd;
1601
                chunkqueue *cq = con->request_content_queue;
1602
                chunk *c;
1603
                data_string *ds_range;
1604
1605
                if (p->conf.is_readonly) {
1606
                        con->http_status = 403;
1607
                        return HANDLER_FINISHED;
1608
                }
1609
1610
                /* is a exclusive lock set on the source */
1611
                if (!webdav_has_lock(srv, con, p, con->uri.path)) {
1612
                        con->http_status = 423;
1613
                        return HANDLER_FINISHED;
1614
                }
1615
1616
1617
                assert(chunkqueue_length(cq) == (off_t)con->request.content_length);
1618
1619
                /* RFC2616 Section 9.6 PUT requires us to send 501 on all Content-* we don't support
1620
                 * - most important Content-Range
1621
                 *
1622
                 *
1623
                 * Example: Content-Range: bytes 100-1037/1038 */
1624
1625
                if (NULL != (ds_range = (data_string *)array_get_element(con->request.headers, "Content-Range"))) {
1626
                        const char *num = ds_range->value->ptr;
1627
                        off_t offset;
1628
                        char *err = NULL;
1629
1630
                        if (0 != strncmp(num, "bytes ", 6)) {
1631
                                con->http_status = 501; /* not implemented */
1632
1633
                                return HANDLER_FINISHED;
1634
                        }
1635
1636
                        /* we only support <num>- ... */
1637
1638
                        num += 6;
1639
1640
                        /* skip WS */
1641
                        while (*num == ' ' || *num == '\t') num++;
1642
1643
                        if (*num == '\0') {
1644
                                con->http_status = 501; /* not implemented */
1645
1646
                                return HANDLER_FINISHED;
1647
                        }
1648
1649
                        offset = strtoll(num, &err, 10);
1650
1651
                        if (*err != '-' || offset < 0) {
1652
                                con->http_status = 501; /* not implemented */
1653
1654
                                return HANDLER_FINISHED;
1655
                        }
1656
1657
                        if (-1 == (fd = open(con->physical.path->ptr, O_WRONLY, WEBDAV_FILE_MODE))) {
1658
                                switch (errno) {
1659
                                case ENOENT:
1660
                                        con->http_status = 404; /* not found */
1661
                                        break;
1662
                                default:
1663
                                        con->http_status = 403; /* not found */
1664
                                        break;
1665
                                }
1666
                                return HANDLER_FINISHED;
1667
                        }
1668
1669
                        if (-1 == lseek(fd, offset, SEEK_SET)) {
1670
                                con->http_status = 501; /* not implemented */
1671
1672
                                close(fd);
1673
1674
                                return HANDLER_FINISHED;
1675
                        }
1676
                        con->http_status = 200; /* modified */
1677
                } else {
1678
                        /* take what we have in the request-body and write it to a file */
1679
1680
                        /* if the file doesn't exist, create it */
1681
                        if (-1 == (fd = open(con->physical.path->ptr, O_WRONLY|O_TRUNC, WEBDAV_FILE_MODE))) {
1682
                                if (errno == ENOENT &&
1683
                                    -1 == (fd = open(con->physical.path->ptr, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, WEBDAV_FILE_MODE))) {
1684
                                        /* we can't open the file */
1685
                                        con->http_status = 403;
1686
1687
                                        return HANDLER_FINISHED;
1688
                                } else {
1689
                                        con->http_status = 201; /* created */
1690
                                }
1691
                        } else {
1692
                                con->http_status = 200; /* modified */
1693
                        }
1694
                }
1695
1696
                con->file_finished = 1;
1697
1698
                for (c = cq->first; c; c = cq->first) {
1699
                        int r = 0;
1700
1701
                        /* copy all chunks */
1702
                        switch(c->type) {
1703
                        case FILE_CHUNK:
1704
1705
                                if (c->file.mmap.start == MAP_FAILED) {
1706
                                        if (-1 == c->file.fd &&  /* open the file if not already open */
1707
                                            -1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) {
1708
                                                log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
1709
1710
                                                return HANDLER_ERROR;
1711
                                        }
1712
1713
                                        if (MAP_FAILED == (c->file.mmap.start = mmap(0, c->file.length, PROT_READ, MAP_SHARED, c->file.fd, 0))) {
1714
                                                log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ",
1715
                                                                strerror(errno), c->file.name,  c->file.fd);
1716
1717
                                                return HANDLER_ERROR;
1718
                                        }
1719
1720
                                        c->file.mmap.length = c->file.length;
1721
1722
                                        close(c->file.fd);
1723
                                        c->file.fd = -1;
1724
1725
                                        /* chunk_reset() or chunk_free() will cleanup for us */
1726
                                }
1727
1728
                                if ((r = write(fd, c->file.mmap.start + c->offset, c->file.length - c->offset)) < 0) {
1729
                                        switch(errno) {
1730
                                        case ENOSPC:
1731
                                                con->http_status = 507;
1732
1733
                                                break;
1734
                                        default:
1735
                                                con->http_status = 403;
1736
                                                break;
1737
                                        }
1738
                                }
1739
                                break;
1740
                        case MEM_CHUNK:
1741
                                if ((r = write(fd, c->mem->ptr + c->offset, c->mem->used - c->offset - 1)) < 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 UNUSED_CHUNK:
1754
                                break;
1755
                        }
1756
1757
                        if (r > 0) {
1758
                                c->offset += r;
1759
                                cq->bytes_out += r;
1760
                        } else {
1761
                                break;
1762
                        }
1763
                        chunkqueue_remove_finished_chunks(cq);
1764
                }
1765
                close(fd);
1766
1767
                return HANDLER_FINISHED;
1768
        }
1769
        case HTTP_METHOD_MOVE:
1770
        case HTTP_METHOD_COPY: {
1771
                buffer *destination = NULL;
1772
                char *sep, *start;
1773
                int overwrite = 1;
1774
1775
                if (p->conf.is_readonly) {
1776
                        con->http_status = 403;
1777
                        return HANDLER_FINISHED;
1778
                }
1779
1780
                /* is a exclusive lock set on the source */
1781
                if (con->request.http_method == HTTP_METHOD_MOVE) {
1782
                        if (!webdav_has_lock(srv, con, p, con->uri.path)) {
1783
                                con->http_status = 423;
1784
                                return HANDLER_FINISHED;
1785
                        }
1786
                }
1787
1788
                if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Destination"))) {
1789
                        destination = ds->value;
1790
                } else {
1791
                        con->http_status = 400;
1792
                        return HANDLER_FINISHED;
1793
                }
1794
1795
                if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Overwrite"))) {
1796
                        if (ds->value->used != 2 ||
1797
                            (ds->value->ptr[0] != 'F' &&
1798
                             ds->value->ptr[0] != 'T') )  {
1799
                                con->http_status = 400;
1800
                                return HANDLER_FINISHED;
1801
                        }
1802
                        overwrite = (ds->value->ptr[0] == 'F' ? 0 : 1);
1803
                }
1804
                /* let's parse the Destination
1805
                 *
1806
                 * http://127.0.0.1:1025/dav/litmus/copydest
1807
                 *
1808
                 * - host has to be the same as the Host: header we got
1809
                 * - we have to stay inside the document root
1810
                 * - the query string is thrown away
1811
                 *  */
1812
1813
                buffer_reset(p->uri.scheme);
1814
                buffer_reset(p->uri.path_raw);
1815
                buffer_reset(p->uri.authority);
1816
1817
                start = destination->ptr;
1818
1819
                if (NULL == (sep = strstr(start, "://"))) {
1820
                        con->http_status = 400;
1821
                        return HANDLER_FINISHED;
1822
                }
1823
                buffer_copy_string_len(p->uri.scheme, start, sep - start);
1824
1825
                start = sep + 3;
1826
1827
                if (NULL == (sep = strchr(start, '/'))) {
1828
                        con->http_status = 400;
1829
                        return HANDLER_FINISHED;
1830
                }
1831
                buffer_copy_string_len(p->uri.authority, start, sep - start);
1832
1833
                start = sep + 1;
1834
1835
                if (NULL == (sep = strchr(start, '?'))) {
1836
                        /* no query string, good */
1837
                        buffer_copy_string(p->uri.path_raw, start);
1838
                } else {
1839
                        buffer_copy_string_len(p->uri.path_raw, start, sep - start);
1840
                }
1841
1842
                if (!buffer_is_equal(p->uri.authority, con->uri.authority)) {
1843
                        /* not the same host */
1844
                        con->http_status = 502;
1845
                        return HANDLER_FINISHED;
1846
                }
1847
1848
                buffer_copy_string_buffer(p->tmp_buf, p->uri.path_raw);
1849
                buffer_urldecode_path(p->tmp_buf);
1850
                buffer_path_simplify(p->uri.path, p->tmp_buf);
1851
1852
                /* we now have a URI which is clean. transform it into a physical path */
1853
                buffer_copy_string_buffer(p->physical.doc_root, con->physical.doc_root);
1854
                buffer_copy_string_buffer(p->physical.rel_path, p->uri.path);
1855
1856
                if (con->conf.force_lowercase_filenames) {
1857
                        buffer_to_lower(p->physical.rel_path);
1858
                }
1859
1860
                buffer_copy_string_buffer(p->physical.path, p->physical.doc_root);
1861
                BUFFER_APPEND_SLASH(p->physical.path);
1862
                buffer_copy_string_buffer(p->physical.basedir, p->physical.path);
1863
1864
                /* don't add a second / */
1865
                if (p->physical.rel_path->ptr[0] == '/') {
1866
                        buffer_append_string_len(p->physical.path, p->physical.rel_path->ptr + 1, p->physical.rel_path->used - 2);
1867
                } else {
1868
                        buffer_append_string_buffer(p->physical.path, p->physical.rel_path);
1869
                }
1870
1871
                /* let's see if the source is a directory
1872
                 * if yes, we fail with 501 */
1873
1874
                if (-1 == stat(con->physical.path->ptr, &st)) {
1875
                        /* don't about it yet, unlink will fail too */
1876
                        switch(errno) {
1877
                        case ENOENT:
1878
                                 con->http_status = 404;
1879
                                 break;
1880
                        default:
1881
                                 con->http_status = 403;
1882
                                 break;
1883
                        }
1884
                } else if (S_ISDIR(st.st_mode)) {
1885
                        int r;
1886
                        /* src is a directory */
1887
1888
                        if (-1 == stat(p->physical.path->ptr, &st)) {
1889
                                if (-1 == mkdir(p->physical.path->ptr, WEBDAV_DIR_MODE)) {
1890
                                        con->http_status = 403;
1891
                                        return HANDLER_FINISHED;
1892
                                }
1893
                        } else if (!S_ISDIR(st.st_mode)) {
1894
                                if (overwrite == 0) {
1895
                                        /* copying into a non-dir ? */
1896
                                        con->http_status = 409;
1897
                                        return HANDLER_FINISHED;
1898
                                } else {
1899
                                        unlink(p->physical.path->ptr);
1900
                                        if (-1 == mkdir(p->physical.path->ptr, WEBDAV_DIR_MODE)) {
1901
                                                con->http_status = 403;
1902
                                                return HANDLER_FINISHED;
1903
                                        }
1904
                                }
1905
                        }
1906
1907
                        /* copy the content of src to dest */
1908
                        if (0 != (r = webdav_copy_dir(srv, con, p, &(con->physical), &(p->physical), overwrite))) {
1909
                                con->http_status = r;
1910
                                return HANDLER_FINISHED;
1911
                        }
1912
                        if (con->request.http_method == HTTP_METHOD_MOVE) {
1913
                                b = buffer_init();
1914
                                webdav_delete_dir(srv, con, p, &(con->physical), b); /* content */
1915
                                buffer_free(b);
1916
1917
                                rmdir(con->physical.path->ptr);
1918
                        }
1919
                        con->http_status = 201;
1920
                        con->file_finished = 1;
1921
                } else {
1922
                        /* it is just a file, good */
1923
                        int r;
1924
1925
                        /* does the client have a lock for this connection ? */
1926
                        if (!webdav_has_lock(srv, con, p, p->uri.path)) {
1927
                                con->http_status = 423;
1928
                                return HANDLER_FINISHED;
1929
                        }
1930
1931
                        /* destination exists */
1932
                        if (0 == (r = stat(p->physical.path->ptr, &st))) {
1933
                                if (S_ISDIR(st.st_mode)) {
1934
                                        /* file to dir/
1935
                                         * append basename to physical path */
1936
1937
                                        if (NULL != (sep = strrchr(con->physical.path->ptr, '/'))) {
1938
                                                buffer_append_string(p->physical.path, sep);
1939
                                                r = stat(p->physical.path->ptr, &st);
1940
                                        }
1941
                                }
1942
                        }
1943
1944
                        if (-1 == r) {
1945
                                con->http_status = 201; /* we will create a new one */
1946
                                con->file_finished = 1;
1947
1948
                                switch(errno) {
1949
                                case ENOTDIR:
1950
                                        con->http_status = 409;
1951
                                        return HANDLER_FINISHED;
1952
                                }
1953
                        } else if (overwrite == 0) {
1954
                                /* destination exists, but overwrite is not set */
1955
                                con->http_status = 412;
1956
                                return HANDLER_FINISHED;
1957
                        } else {
1958
                                con->http_status = 204; /* resource already existed */
1959
                        }
1960
1961
                        if (con->request.http_method == HTTP_METHOD_MOVE) {
1962
                                /* try a rename */
1963
1964
                                if (0 == rename(con->physical.path->ptr, p->physical.path->ptr)) {
1965
#ifdef USE_PROPPATCH
1966
                                        sqlite3_stmt *stmt = p->conf.stmt_move_uri;
1967
1968
                                        if (stmt) {
1969
1970
                                                sqlite3_reset(stmt);
1971
1972
                                                /* bind the values to the insert */
1973
                                                sqlite3_bind_text(stmt, 1,
1974
                                                                  p->uri.path->ptr,
1975
                                                                  p->uri.path->used - 1,
1976
                                                                  SQLITE_TRANSIENT);
1977
1978
                                                sqlite3_bind_text(stmt, 2,
1979
                                                                  con->uri.path->ptr,
1980
                                                                  con->uri.path->used - 1,
1981
                                                                  SQLITE_TRANSIENT);
1982
1983
                                                if (SQLITE_DONE != sqlite3_step(stmt)) {
1984
                                                        log_error_write(srv, __FILE__, __LINE__, "ss", "sql-move failed:", sqlite3_errmsg(p->conf.sql));
1985
                                                }
1986
                                        }
1987
#endif
1988
                                        return HANDLER_FINISHED;
1989
                                }
1990
1991
                                /* rename failed, fall back to COPY + DELETE */
1992
                        }
1993
1994
                        if (0 != (r = webdav_copy_file(srv, con, p, &(con->physical), &(p->physical), overwrite))) {
1995
                                con->http_status = r;
1996
1997
                                return HANDLER_FINISHED;
1998
                        }
1999
2000
                        if (con->request.http_method == HTTP_METHOD_MOVE) {
2001
                                b = buffer_init();
2002
                                webdav_delete_file(srv, con, p, &(con->physical), b);
2003
                                buffer_free(b);
2004
                        }
2005
                }
2006
2007
                return HANDLER_FINISHED;
2008
        }
2009
        case HTTP_METHOD_PROPPATCH:
2010
                if (p->conf.is_readonly) {
2011
                        con->http_status = 403;
2012
                        return HANDLER_FINISHED;
2013
                }
2014
2015
                if (!webdav_has_lock(srv, con, p, con->uri.path)) {
2016
                        con->http_status = 423;
2017
                        return HANDLER_FINISHED;
2018
                }
2019
2020
                /* check if destination exists */
2021
                if (-1 == stat(con->physical.path->ptr, &st)) {
2022
                        switch(errno) {
2023
                        case ENOENT:
2024
                                con->http_status = 404;
2025
                                break;
2026
                        }
2027
                }
2028
2029
#ifdef USE_PROPPATCH
2030
                if (con->request.content_length) {
2031
                        xmlDocPtr xml;
2032
2033
                        if (1 == webdav_parse_chunkqueue(srv, con, p, con->request_content_queue, &xml)) {
2034
                                xmlNode *rootnode = xmlDocGetRootElement(xml);
2035
2036
                                if (0 == xmlStrcmp(rootnode->name, BAD_CAST "propertyupdate")) {
2037
                                        xmlNode *cmd;
2038
                                        char *err = NULL;
2039
                                        int empty_ns = 0; /* send 400 on a empty namespace attribute */
2040
2041
                                        /* start response */
2042
2043
                                        if (SQLITE_OK != sqlite3_exec(p->conf.sql, "BEGIN TRANSACTION", NULL, NULL, &err)) {
2044
                                                log_error_write(srv, __FILE__, __LINE__, "ss", "can't open transaction:", err);
2045
                                                sqlite3_free(err);
2046
2047
                                                goto propmatch_cleanup;
2048
                                        }
2049
2050
                                        /* a UPDATE request, we know 'set' and 'remove' */
2051
                                        for (cmd = rootnode->children; cmd; cmd = cmd->next) {
2052
                                                xmlNode *props;
2053
                                                /* either set or remove */
2054
2055
                                                if ((0 == xmlStrcmp(cmd->name, BAD_CAST "set")) ||
2056
                                                    (0 == xmlStrcmp(cmd->name, BAD_CAST "remove"))) {
2057
2058
                                                        sqlite3_stmt *stmt;
2059
2060
                                                        stmt = (0 == xmlStrcmp(cmd->name, BAD_CAST "remove")) ?
2061
                                                                p->conf.stmt_delete_prop : p->conf.stmt_update_prop;
2062
2063
                                                        for (props = cmd->children; props; props = props->next) {
2064
                                                                if (0 == xmlStrcmp(props->name, BAD_CAST "prop")) {
2065
                                                                        xmlNode *prop;
2066
                                                                        int r;
2067
2068
                                                                        prop = props->children;
2069
2070
                                                                        if (prop->ns &&
2071
                                                                            (0 == xmlStrcmp(prop->ns->href, BAD_CAST "")) &&
2072
                                                                            (0 != xmlStrcmp(prop->ns->prefix, BAD_CAST ""))) {
2073
                                                                                log_error_write(srv, __FILE__, __LINE__, "ss",
2074
                                                                                                "no name space for:",
2075
                                                                                                prop->name);
2076
2077
                                                                                empty_ns = 1;
2078
                                                                                break;
2079
                                                                        }
2080
2081
                                                                        sqlite3_reset(stmt);
2082
2083
                                                                        /* bind the values to the insert */
2084
2085
                                                                        sqlite3_bind_text(stmt, 1,
2086
                                                                                          con->uri.path->ptr,
2087
                                                                                          con->uri.path->used - 1,
2088
                                                                                          SQLITE_TRANSIENT);
2089
                                                                        sqlite3_bind_text(stmt, 2,
2090
                                                                                          (char *)prop->name,
2091
                                                                                          strlen((char *)prop->name),
2092
                                                                                          SQLITE_TRANSIENT);
2093
                                                                        if (prop->ns) {
2094
                                                                                sqlite3_bind_text(stmt, 3,
2095
                                                                                                  (char *)prop->ns->href,
2096
                                                                                                  strlen((char *)prop->ns->href),
2097
                                                                                                  SQLITE_TRANSIENT);
2098
                                                                        } else {
2099
                                                                                sqlite3_bind_text(stmt, 3,
2100
                                                                                                  "",
2101
                                                                                                  0,
2102
                                                                                                  SQLITE_TRANSIENT);
2103
                                                                        }
2104
                                                                        if (stmt == p->conf.stmt_update_prop) {
2105
                                                                                sqlite3_bind_text(stmt, 4,
2106
                                                                                          (char *)xmlNodeGetContent(prop),
2107
                                                                                          strlen((char *)xmlNodeGetContent(prop)),
2108
                                                                                          SQLITE_TRANSIENT);
2109
                                                                        }
2110
2111
                                                                        if (SQLITE_DONE != (r = sqlite3_step(stmt))) {
2112
                                                                                log_error_write(srv, __FILE__, __LINE__, "ss",
2113
                                                                                                "sql-set failed:", sqlite3_errmsg(p->conf.sql));
2114
                                                                        }
2115
                                                                }
2116
                                                        }
2117
                                                        if (empty_ns) break;
2118
                                                }
2119
                                        }
2120
2121
                                        if (empty_ns) {
2122
                                                if (SQLITE_OK != sqlite3_exec(p->conf.sql, "ROLLBACK", NULL, NULL, &err)) {
2123
                                                        log_error_write(srv, __FILE__, __LINE__, "ss", "can't rollback transaction:", err);
2124
                                                        sqlite3_free(err);
2125
2126
                                                        goto propmatch_cleanup;
2127
                                                }
2128
2129
                                                con->http_status = 400;
2130
                                        } else {
2131
                                                if (SQLITE_OK != sqlite3_exec(p->conf.sql, "COMMIT", NULL, NULL, &err)) {
2132
                                                        log_error_write(srv, __FILE__, __LINE__, "ss", "can't commit transaction:", err);
2133
                                                        sqlite3_free(err);
2134
2135
                                                        goto propmatch_cleanup;
2136
                                                }
2137
                                                con->http_status = 200;
2138
                                        }
2139
                                        con->file_finished = 1;
2140
2141
                                        return HANDLER_FINISHED;
2142
                                }
2143
2144
propmatch_cleanup:
2145
2146
                                xmlFreeDoc(xml);
2147
                        } else {
2148
                                con->http_status = 400;
2149
                                return HANDLER_FINISHED;
2150
                        }
2151
                }
2152
#endif
2153
                con->http_status = 501;
2154
                return HANDLER_FINISHED;
2155
        case HTTP_METHOD_LOCK:
2156
                /**
2157
                 * a mac wants to write
2158
                 *
2159
                 * LOCK /dav/expire.txt HTTP/1.1\r\n
2160
                 * User-Agent: WebDAVFS/1.3 (01308000) Darwin/8.1.0 (Power Macintosh)\r\n
2161
                 * Accept: * / *\r\n
2162
                 * Depth: 0\r\n
2163
                 * Timeout: Second-600\r\n
2164
                 * Content-Type: text/xml; charset=\"utf-8\"\r\n
2165
                 * Content-Length: 229\r\n
2166
                 * Connection: keep-alive\r\n
2167
                 * Host: 192.168.178.23:1025\r\n
2168
                 * \r\n
2169
                 * <?xml version=\"1.0\" encoding=\"utf-8\"?>\n
2170
                 * <D:lockinfo xmlns:D=\"DAV:\">\n
2171
                 *  <D:lockscope><D:exclusive/></D:lockscope>\n
2172
                 *  <D:locktype><D:write/></D:locktype>\n
2173
                 *  <D:owner>\n
2174
                 *   <D:href>http://www.apple.com/webdav_fs/</D:href>\n
2175
                 *  </D:owner>\n
2176
                 * </D:lockinfo>\n
2177
                 */
2178
2179
                if (depth != 0 && depth != -1) {
2180
                        con->http_status = 400;
2181
2182
                        return HANDLER_FINISHED;
2183
                }
2184
2185
#ifdef USE_LOCKS
2186
                if (con->request.content_length) {
2187
                        xmlDocPtr xml;
2188
                        buffer *hdr_if = NULL;
2189
2190
                        if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If"))) {
2191
                                hdr_if = ds->value;
2192
                        }
2193
2194
                        /* we don't support Depth: Infinity on locks */
2195
                        if (hdr_if == NULL && depth == -1) {
2196
                                con->http_status = 409; /* Conflict */
2197
2198
                                return HANDLER_FINISHED;
2199
                        }
2200
2201
                        if (1 == webdav_parse_chunkqueue(srv, con, p, con->request_content_queue, &xml)) {
2202
                                xmlNode *rootnode = xmlDocGetRootElement(xml);
2203
2204
                                assert(rootnode);
2205
2206
                                if (0 == xmlStrcmp(rootnode->name, BAD_CAST "lockinfo")) {
2207
                                        xmlNode *lockinfo;
2208
                                        const xmlChar *lockscope = NULL, *locktype = NULL; /* TODO: compiler says unused: *owner = NULL; */
2209
2210
                                        for (lockinfo = rootnode->children; lockinfo; lockinfo = lockinfo->next) {
2211
                                                if (0 == xmlStrcmp(lockinfo->name, BAD_CAST "lockscope")) {
2212
                                                        xmlNode *value;
2213
                                                        for (value = lockinfo->children; value; value = value->next) {
2214
                                                                if ((0 == xmlStrcmp(value->name, BAD_CAST "exclusive")) ||
2215
                                                                    (0 == xmlStrcmp(value->name, BAD_CAST "shared"))) {
2216
                                                                        lockscope = value->name;
2217
                                                                } else {
2218
                                                                        con->http_status = 400;
2219
2220
                                                                        xmlFreeDoc(xml);
2221
                                                                        return HANDLER_FINISHED;
2222
                                                                }
2223
                                                        }
2224
                                                } else if (0 == xmlStrcmp(lockinfo->name, BAD_CAST "locktype")) {
2225
                                                        xmlNode *value;
2226
                                                        for (value = lockinfo->children; value; value = value->next) {
2227
                                                                if ((0 == xmlStrcmp(value->name, BAD_CAST "write"))) {
2228
                                                                        locktype = value->name;
2229
                                                                } else {
2230
                                                                        con->http_status = 400;
2231
2232
                                                                        xmlFreeDoc(xml);
2233
                                                                        return HANDLER_FINISHED;
2234
                                                                }
2235
                                                        }
2236
2237
                                                } else if (0 == xmlStrcmp(lockinfo->name, BAD_CAST "owner")) {
2238
                                                }
2239
                                        }
2240
2241
                                        if (lockscope && locktype) {
2242
                                                sqlite3_stmt *stmt = p->conf.stmt_read_lock_by_uri;
2243
2244
                                                /* is this resourse already locked ? */
2245
2246
                                                /* SELECT locktoken, resource, lockscope, locktype, owner, depth, timeout
2247
                                                 *   FROM locks
2248
                                                 *  WHERE resource = ? */
2249
2250
                                                if (stmt) {
2251
2252
                                                        sqlite3_reset(stmt);
2253
2254
                                                        sqlite3_bind_text(stmt, 1,
2255
                                                                          p->uri.path->ptr,
2256
                                                                          p->uri.path->used - 1,
2257
                                                                          SQLITE_TRANSIENT);
2258
2259
                                                        /* it is the PK */
2260
                                                        while (SQLITE_ROW == sqlite3_step(stmt)) {
2261
                                                                /* we found a lock
2262
                                                                 * 1. is it compatible ?
2263
                                                                 * 2. is it ours */
2264
                                                                char *sql_lockscope = (char *)sqlite3_column_text(stmt, 2);
2265
2266
                                                                if (strcmp(sql_lockscope, "exclusive")) {
2267
                                                                        con->http_status = 423;
2268
                                                                } else if (0 == xmlStrcmp(lockscope, BAD_CAST "exclusive")) {
2269
                                                                        /* resourse is locked with a shared lock
2270
                                                                         * client wants exclusive */
2271
                                                                        con->http_status = 423;
2272
                                                                }
2273
                                                        }
2274
                                                        if (con->http_status == 423) {
2275
                                                                xmlFreeDoc(xml);
2276
                                                                return HANDLER_FINISHED;
2277
                                                        }
2278
                                                }
2279
2280
                                                stmt = p->conf.stmt_create_lock;
2281
                                                if (stmt) {
2282
                                                        /* create a lock-token */
2283
                                                        uuid_t id;
2284
                                                        char uuid[37] /* 36 + \0 */;
2285
2286
                                                        uuid_generate(id);
2287
                                                        uuid_unparse(id, uuid);
2288
2289
                                                        buffer_copy_string(p->tmp_buf, "opaquelocktoken:");
2290
                                                        buffer_append_string(p->tmp_buf, uuid);
2291
2292
                                                        /* "CREATE TABLE locks ("
2293
                                                         * "  locktoken TEXT NOT NULL,"
2294
                                                         * "  resource TEXT NOT NULL,"
2295
                                                         * "  lockscope TEXT NOT NULL,"
2296
                                                         * "  locktype TEXT NOT NULL,"
2297
                                                         * "  owner TEXT NOT NULL,"
2298
                                                         * "  depth INT NOT NULL,"
2299
                                                         */
2300
2301
                                                        sqlite3_reset(stmt);
2302
2303
                                                        sqlite3_bind_text(stmt, 1,
2304
                                                                          CONST_BUF_LEN(p->tmp_buf),
2305
                                                                          SQLITE_TRANSIENT);
2306
2307
                                                        sqlite3_bind_text(stmt, 2,
2308
                                                                          CONST_BUF_LEN(con->uri.path),
2309
                                                                          SQLITE_TRANSIENT);
2310
2311
                                                        sqlite3_bind_text(stmt, 3,
2312
                                                                          (const char *)lockscope,
2313
                                                                          xmlStrlen(lockscope),
2314
                                                                          SQLITE_TRANSIENT);
2315
2316
                                                        sqlite3_bind_text(stmt, 4,
2317
                                                                          (const char *)locktype,
2318
                                                                          xmlStrlen(locktype),
2319
                                                                          SQLITE_TRANSIENT);
2320
2321
                                                        /* owner */
2322
                                                        sqlite3_bind_text(stmt, 5,
2323
                                                                          "",
2324
                                                                          0,
2325
                                                                          SQLITE_TRANSIENT);
2326
2327
                                                        /* depth */
2328
                                                        sqlite3_bind_int(stmt, 6,
2329
                                                                         depth);
2330
2331
2332
                                                        if (SQLITE_DONE != sqlite3_step(stmt)) {
2333
                                                                log_error_write(srv, __FILE__, __LINE__, "ss",
2334
                                                                                "create lock:", sqlite3_errmsg(p->conf.sql));
2335
                                                        }
2336
2337
                                                        /* looks like we survived */
2338
                                                        webdav_lockdiscovery(srv, con, p->tmp_buf, (const char *)lockscope, (const char *)locktype, depth);
2339
2340
                                                        con->http_status = 201;
2341
                                                        con->file_finished = 1;
2342
                                                }
2343
                                        }
2344
                                }
2345
2346
                                xmlFreeDoc(xml);
2347
                                return HANDLER_FINISHED;
2348
                        } else {
2349
                                con->http_status = 400;
2350
                                return HANDLER_FINISHED;
2351
                        }
2352
                } else {
2353
2354
                        if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If"))) {
2355
                                buffer *locktoken = ds->value;
2356
                                sqlite3_stmt *stmt = p->conf.stmt_refresh_lock;
2357
2358
                                /* remove the < > around the token */
2359
                                if (locktoken->used < 6) {
2360
                                        con->http_status = 400;
2361
2362
                                        return HANDLER_FINISHED;
2363
                                }
2364
2365
                                buffer_copy_string_len(p->tmp_buf, locktoken->ptr + 2, locktoken->used - 5);
2366
2367
                                sqlite3_reset(stmt);
2368
2369
                                sqlite3_bind_text(stmt, 1,
2370
                                          CONST_BUF_LEN(p->tmp_buf),
2371
                                          SQLITE_TRANSIENT);
2372
2373
                                if (SQLITE_DONE != sqlite3_step(stmt)) {
2374
                                        log_error_write(srv, __FILE__, __LINE__, "ss",
2375
                                                "refresh lock:", sqlite3_errmsg(p->conf.sql));
2376
                                }
2377
2378
                                webdav_lockdiscovery(srv, con, p->tmp_buf, "exclusive", "write", 0);
2379
2380
                                con->http_status = 200;
2381
                                con->file_finished = 1;
2382
                                return HANDLER_FINISHED;
2383
                        } else {
2384
                                /* we need a lock-token to refresh */
2385
                                con->http_status = 400;
2386
2387
                                return HANDLER_FINISHED;
2388
                        }
2389
                }
2390
                break;
2391
#else
2392
                con->http_status = 501;
2393
                return HANDLER_FINISHED;
2394
#endif
2395
        case HTTP_METHOD_UNLOCK:
2396
#ifdef USE_LOCKS
2397
                if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Lock-Token"))) {
2398
                        buffer *locktoken = ds->value;
2399
                        sqlite3_stmt *stmt = p->conf.stmt_remove_lock;
2400
2401
                        /* remove the < > around the token */
2402
                        if (locktoken->used < 4) {
2403
                                con->http_status = 400;
2404
2405
                                return HANDLER_FINISHED;
2406
                        }
2407
2408
                        /**
2409
                         * FIXME:
2410
                         *
2411
                         * if the resourse is locked:
2412
                         * - by us: unlock
2413
                         * - by someone else: 401
2414
                         * if the resource is not locked:
2415
                         * - 412
2416
                         *  */
2417
2418
                        buffer_copy_string_len(p->tmp_buf, locktoken->ptr + 1, locktoken->used - 3);
2419
2420
                        sqlite3_reset(stmt);
2421
2422
                        sqlite3_bind_text(stmt, 1,
2423
                                  CONST_BUF_LEN(p->tmp_buf),
2424
                                  SQLITE_TRANSIENT);
2425
2426
                        sqlite3_bind_text(stmt, 2,
2427
                                  CONST_BUF_LEN(con->uri.path),
2428
                                  SQLITE_TRANSIENT);
2429
2430
                        if (SQLITE_DONE != sqlite3_step(stmt)) {
2431
                                log_error_write(srv, __FILE__, __LINE__, "ss",
2432
                                        "remove lock:", sqlite3_errmsg(p->conf.sql));
2433
                        }
2434
2435
                        if (0 == sqlite3_changes(p->conf.sql)) {
2436
                                con->http_status = 401;
2437
                        } else {
2438
                                con->http_status = 204;
2439
                        }
2440
                        return HANDLER_FINISHED;
2441
                } else {
2442
                        /* we need a lock-token to unlock */
2443
                        con->http_status = 400;
2444
2445
                        return HANDLER_FINISHED;
2446
                }
2447
                break;
2448
#else
2449
                con->http_status = 501;
2450
                return HANDLER_FINISHED;
2451
#endif
2452
        default:
2453
                break;
2454
        }
2455
2456
        /* not found */
2457
        return HANDLER_GO_ON;
2458
}
2459
2460
2461
/* this function is called at dlopen() time and inits the callbacks */
2462
2463
int mod_webdav_plugin_init(plugin *p) {
2464
        p->version     = LIGHTTPD_VERSION_ID;
2465
        p->name        = buffer_init_string("webdav");
2466
2467
        p->init        = mod_webdav_init;
2468
        p->handle_uri_clean  = mod_webdav_uri_handler;
2469
        p->handle_physical   = mod_webdav_subrequest_handler;
2470
        p->set_defaults  = mod_webdav_set_defaults;
2471
        p->cleanup     = mod_webdav_free;
2472
2473
        p->data        = NULL;
2474
2475
        return 0;
2476
}