--- embedaddon/lighttpd/src/mod_webdav.c 2014/06/15 20:20:06 1.1.1.2 +++ embedaddon/lighttpd/src/mod_webdav.c 2016/11/02 10:35:00 1.1.1.3 @@ -1,7 +1,10 @@ +#include "first.h" + #include "base.h" #include "log.h" #include "buffer.h" #include "response.h" +#include "connections.h" #include "plugin.h" @@ -120,7 +123,7 @@ FREE_FUNC(mod_webdav_free) { for (i = 0; i < srv->config_context->used; i++) { plugin_config *s = p->config_storage[i]; - if (!s) continue; + if (NULL == s) continue; buffer_free(s->sqlite_db_name); #ifdef USE_PROPPATCH @@ -182,6 +185,7 @@ SETDEFAULTS_FUNC(mod_webdav_set_defaults) { p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *)); for (i = 0; i < srv->config_context->used; i++) { + data_config const* config = (data_config const*)srv->config_context->data[i]; plugin_config *s; s = calloc(1, sizeof(plugin_config)); @@ -194,11 +198,11 @@ SETDEFAULTS_FUNC(mod_webdav_set_defaults) { p->config_storage[i] = s; - if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { return HANDLER_ERROR; } - if (!buffer_is_empty(s->sqlite_db_name)) { + if (!buffer_string_is_empty(s->sqlite_db_name)) { #ifdef USE_PROPPATCH const char *next_stmt; char *err; @@ -211,7 +215,7 @@ SETDEFAULTS_FUNC(mod_webdav_set_defaults) { } if (SQLITE_OK != sqlite3_exec(s->sql, - "CREATE TABLE properties (" + "CREATE TABLE IF NOT EXISTS properties (" " resource TEXT NOT NULL," " prop TEXT NOT NULL," " ns TEXT NOT NULL," @@ -228,6 +232,27 @@ SETDEFAULTS_FUNC(mod_webdav_set_defaults) { sqlite3_free(err); } + if (SQLITE_OK != sqlite3_exec(s->sql, + "CREATE TABLE IF NOT EXISTS locks (" + " locktoken TEXT NOT NULL," + " resource TEXT NOT NULL," + " lockscope TEXT NOT NULL," + " locktype TEXT NOT NULL," + " owner TEXT NOT NULL," + " depth INT NOT NULL," + " timeout TIMESTAMP NOT NULL," + " PRIMARY KEY(locktoken))", + NULL, NULL, &err)) { + + if (0 != strcmp(err, "table locks already exists")) { + log_error_write(srv, __FILE__, __LINE__, "ss", "can't open transaction:", err); + sqlite3_free(err); + + return HANDLER_ERROR; + } + sqlite3_free(err); + } + if (SQLITE_OK != sqlite3_prepare(s->sql, CONST_STR_LEN("SELECT value FROM properties WHERE resource = ? AND prop = ? AND ns = ?"), &(s->stmt_select_prop), &next_stmt)) { @@ -284,7 +309,7 @@ SETDEFAULTS_FUNC(mod_webdav_set_defaults) { } if (SQLITE_OK != sqlite3_prepare(s->sql, - CONST_STR_LEN("UPDATE properties SET resource = ? WHERE resource = ?"), + CONST_STR_LEN("UPDATE OR REPLACE properties SET resource = ? WHERE resource = ?"), &(s->stmt_move_uri), &next_stmt)) { /* prepare failed */ log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql)); @@ -294,27 +319,6 @@ SETDEFAULTS_FUNC(mod_webdav_set_defaults) { /* LOCKS */ - if (SQLITE_OK != sqlite3_exec(s->sql, - "CREATE TABLE locks (" - " locktoken TEXT NOT NULL," - " resource TEXT NOT NULL," - " lockscope TEXT NOT NULL," - " locktype TEXT NOT NULL," - " owner TEXT NOT NULL," - " depth INT NOT NULL," - " timeout TIMESTAMP NOT NULL," - " PRIMARY KEY(locktoken))", - NULL, NULL, &err)) { - - if (0 != strcmp(err, "table locks already exists")) { - log_error_write(srv, __FILE__, __LINE__, "ss", "can't open transaction:", err); - sqlite3_free(err); - - return HANDLER_ERROR; - } - sqlite3_free(err); - } - if (SQLITE_OK != sqlite3_prepare(s->sql, CONST_STR_LEN("INSERT INTO locks (locktoken, resource, lockscope, locktype, owner, depth, timeout) VALUES (?,?,?,?,?,?, CURRENT_TIME + 600)"), &(s->stmt_create_lock), &next_stmt)) { @@ -334,7 +338,7 @@ SETDEFAULTS_FUNC(mod_webdav_set_defaults) { } if (SQLITE_OK != sqlite3_prepare(s->sql, - CONST_STR_LEN("SELECT locktoken, resource, lockscope, locktype, owner, depth, timeout FROM locks WHERE locktoken = ?"), + CONST_STR_LEN("SELECT locktoken, resource, lockscope, locktype, owner, depth, timeout-CURRENT_TIME FROM locks WHERE locktoken = ?"), &(s->stmt_read_lock), &next_stmt)) { /* prepare failed */ log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql)); @@ -343,7 +347,7 @@ SETDEFAULTS_FUNC(mod_webdav_set_defaults) { } if (SQLITE_OK != sqlite3_prepare(s->sql, - CONST_STR_LEN("SELECT locktoken, resource, lockscope, locktype, owner, depth, timeout FROM locks WHERE resource = ?"), + CONST_STR_LEN("SELECT locktoken, resource, lockscope, locktype, owner, depth, timeout-CURRENT_TIME FROM locks WHERE resource = ?"), &(s->stmt_read_lock_by_uri), &next_stmt)) { /* prepare failed */ log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql)); @@ -446,7 +450,7 @@ URIHANDLER_FUNC(mod_webdav_uri_handler) { UNUSED(srv); - if (con->uri.path->used == 0) return HANDLER_GO_ON; + if (buffer_is_empty(con->uri.path)) return HANDLER_GO_ON; mod_webdav_patch_connection(srv, con, p); @@ -519,7 +523,7 @@ static int webdav_gen_response_status_tag(server *srv, } else { buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.0 ")); } - buffer_append_long(b, status); + buffer_append_int(b, status); buffer_append_string_len(b, CONST_STR_LEN(" ")); buffer_append_string(b, get_http_status_name(status)); @@ -558,9 +562,8 @@ static int webdav_delete_file(server *srv, connection /* bind the values to the insert */ sqlite3_bind_text(stmt, 1, - dst->rel_path->ptr, - dst->rel_path->used - 1, - SQLITE_TRANSIENT); + CONST_BUF_LEN(dst->rel_path), + SQLITE_TRANSIENT); if (SQLITE_DONE != sqlite3_step(stmt)) { /* */ @@ -590,17 +593,17 @@ static int webdav_delete_dir(server *srv, connection * int status = 0; if ((de->d_name[0] == '.' && de->d_name[1] == '\0') || - (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')) { + (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')) { continue; /* ignore the parent dir */ } - buffer_copy_string_buffer(d.path, dst->path); - BUFFER_APPEND_SLASH(d.path); + buffer_copy_buffer(d.path, dst->path); + buffer_append_slash(d.path); buffer_append_string(d.path, de->d_name); - buffer_copy_string_buffer(d.rel_path, dst->rel_path); - BUFFER_APPEND_SLASH(d.rel_path); + buffer_copy_buffer(d.rel_path, dst->rel_path); + buffer_append_slash(d.rel_path); buffer_append_string(d.rel_path, de->d_name); /* stat and unlink afterwards */ @@ -636,9 +639,8 @@ static int webdav_delete_dir(server *srv, connection * /* bind the values to the insert */ sqlite3_bind_text(stmt, 1, - d.rel_path->ptr, - d.rel_path->used - 1, - SQLITE_TRANSIENT); + CONST_BUF_LEN(d.rel_path), + SQLITE_TRANSIENT); if (SQLITE_DONE != sqlite3_step(stmt)) { /* */ @@ -659,13 +661,25 @@ static int webdav_delete_dir(server *srv, connection * return have_multi_status; } +/* don't want to block when open()ing a fifo */ +#if defined(O_NONBLOCK) +# define FIFO_NONBLOCK O_NONBLOCK +#else +# define FIFO_NONBLOCK 0 +#endif + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + static int webdav_copy_file(server *srv, connection *con, plugin_data *p, physical *src, physical *dst, int overwrite) { - stream s; - int status = 0, ofd; + char *data; + ssize_t rd, wr, offset; + int status = 0, ifd, ofd; UNUSED(srv); UNUSED(con); - if (stream_open(&s, src->path)) { + if (-1 == (ifd = open(src->path->ptr, O_RDONLY | O_BINARY | FIFO_NONBLOCK))) { return 403; } @@ -686,23 +700,33 @@ static int webdav_copy_file(server *srv, connection *c status = 403; break; } - stream_close(&s); + close(ifd); return status; } - if (-1 == write(ofd, s.start, s.size)) { - switch(errno) { - case ENOSPC: - status = 507; + data = malloc(131072); + force_assert(data); + + while (0 < (rd = read(ifd, data, 131072))) { + offset = 0; + do { + wr = write(ofd, data+offset, (size_t)(rd-offset)); + } while (wr >= 0 ? (offset += wr) != rd : (errno == EINTR)); + if (-1 == wr) { + status = (errno == ENOSPC) ? 507 : 403; break; - default: - status = 403; - break; } + } + if (0 != rd && 0 == status) status = 403; - stream_close(&s); - close(ofd); + free(data); + close(ifd); + if (0 != close(ofd)) { + if (0 == status) status = (errno == ENOSPC) ? 507 : 403; + log_error_write(srv, __FILE__, __LINE__, "sbss", + "close ", dst->path, "failed: ", strerror(errno)); + } #ifdef USE_PROPPATCH if (0 == status) { @@ -714,14 +738,12 @@ static int webdav_copy_file(server *srv, connection *c /* bind the values to the insert */ sqlite3_bind_text(stmt, 1, - dst->rel_path->ptr, - dst->rel_path->used - 1, - SQLITE_TRANSIENT); + CONST_BUF_LEN(dst->rel_path), + SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 2, - src->rel_path->ptr, - src->rel_path->used - 1, - SQLITE_TRANSIENT); + CONST_BUF_LEN(src->rel_path), + SQLITE_TRANSIENT); if (SQLITE_DONE != sqlite3_step(stmt)) { /* */ @@ -751,25 +773,25 @@ static int webdav_copy_dir(server *srv, connection *co while (NULL != (de = readdir(srcdir))) { struct stat st; - if ((de->d_name[0] == '.' && de->d_name[1] == '\0') || - (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')) { + if ((de->d_name[0] == '.' && de->d_name[1] == '\0') + || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')) { continue; } - buffer_copy_string_buffer(s.path, src->path); - BUFFER_APPEND_SLASH(s.path); + buffer_copy_buffer(s.path, src->path); + buffer_append_slash(s.path); buffer_append_string(s.path, de->d_name); - buffer_copy_string_buffer(d.path, dst->path); - BUFFER_APPEND_SLASH(d.path); + buffer_copy_buffer(d.path, dst->path); + buffer_append_slash(d.path); buffer_append_string(d.path, de->d_name); - buffer_copy_string_buffer(s.rel_path, src->rel_path); - BUFFER_APPEND_SLASH(s.rel_path); + buffer_copy_buffer(s.rel_path, src->rel_path); + buffer_append_slash(s.rel_path); buffer_append_string(s.rel_path, de->d_name); - buffer_copy_string_buffer(d.rel_path, dst->rel_path); - BUFFER_APPEND_SLASH(d.rel_path); + buffer_copy_buffer(d.rel_path, dst->rel_path); + buffer_append_slash(d.rel_path); buffer_append_string(d.rel_path, de->d_name); if (-1 == stat(s.path->ptr, &st)) { @@ -793,14 +815,12 @@ static int webdav_copy_dir(server *srv, connection *co /* bind the values to the insert */ sqlite3_bind_text(stmt, 1, - dst->rel_path->ptr, - dst->rel_path->used - 1, - SQLITE_TRANSIENT); + CONST_BUF_LEN(dst->rel_path), + SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 2, - src->rel_path->ptr, - src->rel_path->used - 1, - SQLITE_TRANSIENT); + CONST_BUF_LEN(src->rel_path), + SQLITE_TRANSIENT); if (SQLITE_DONE != sqlite3_step(stmt)) { /* */ @@ -827,6 +847,84 @@ static int webdav_copy_dir(server *srv, connection *co return status; } +#ifdef USE_LOCKS +static void webdav_activelock(buffer *b, + const buffer *locktoken, const char *lockscope, const char *locktype, int depth, int timeout) { + buffer_append_string_len(b, CONST_STR_LEN("\n")); + + buffer_append_string_len(b, CONST_STR_LEN("")); + buffer_append_string_len(b, CONST_STR_LEN("")); + buffer_append_string_len(b, CONST_STR_LEN("\n")); + + buffer_append_string_len(b, CONST_STR_LEN("")); + buffer_append_string_len(b, CONST_STR_LEN("")); + buffer_append_string_len(b, CONST_STR_LEN("\n")); + + buffer_append_string_len(b, CONST_STR_LEN("")); + buffer_append_string(b, depth == 0 ? "0" : "infinity"); + buffer_append_string_len(b, CONST_STR_LEN("\n")); + + buffer_append_string_len(b, CONST_STR_LEN("")); + buffer_append_string_len(b, CONST_STR_LEN("Second-")); + buffer_append_int(b, timeout); + buffer_append_string_len(b, CONST_STR_LEN("\n")); + + buffer_append_string_len(b, CONST_STR_LEN("")); + buffer_append_string_len(b, CONST_STR_LEN("\n")); + + buffer_append_string_len(b, CONST_STR_LEN("")); + buffer_append_string_len(b, CONST_STR_LEN("")); + buffer_append_string_buffer(b, locktoken); + buffer_append_string_len(b, CONST_STR_LEN("")); + buffer_append_string_len(b, CONST_STR_LEN("\n")); + + buffer_append_string_len(b, CONST_STR_LEN("\n")); +} + +static void webdav_get_live_property_lockdiscovery(server *srv, connection *con, plugin_data *p, physical *dst, buffer *b) { + + sqlite3_stmt *stmt = p->conf.stmt_read_lock_by_uri; + if (!stmt) { /*(should not happen)*/ + buffer_append_string_len(b, CONST_STR_LEN("\n\n")); + return; + } + UNUSED(srv); + UNUSED(con); + + /* SELECT locktoken, resource, lockscope, locktype, owner, depth, timeout + * FROM locks + * WHERE resource = ? */ + + sqlite3_reset(stmt); + + sqlite3_bind_text(stmt, 1, + CONST_BUF_LEN(dst->rel_path), + SQLITE_TRANSIENT); + + buffer_append_string_len(b, CONST_STR_LEN("\n")); + while (SQLITE_ROW == sqlite3_step(stmt)) { + const char *lockscope = (const char *)sqlite3_column_text(stmt, 2); + const char *locktype = (const char *)sqlite3_column_text(stmt, 3); + const int depth = sqlite3_column_int(stmt, 5); + const int timeout = sqlite3_column_int(stmt, 6); + buffer locktoken = { NULL, 0, 0 }; + locktoken.ptr = (char *)sqlite3_column_text(stmt, 0); + locktoken.used = sqlite3_column_bytes(stmt, 0); + if (locktoken.used) ++locktoken.used; + locktoken.size = locktoken.used; + + if (timeout > 0) { + webdav_activelock(b, &locktoken, lockscope, locktype, depth, timeout); + } + } + buffer_append_string_len(b, CONST_STR_LEN("\n")); +} +#endif + static int webdav_get_live_property(server *srv, connection *con, plugin_data *p, physical *dst, char *prop_name, buffer *b) { stat_cache_entry *sce = NULL; int found = 0; @@ -841,8 +939,10 @@ static int webdav_get_live_property(server *srv, conne if (0 == strcmp(prop_name, "resourcetype")) { if (S_ISDIR(sce->st.st_mode)) { buffer_append_string_len(b, CONST_STR_LEN("")); - found = 1; + } else { + buffer_append_string_len(b, CONST_STR_LEN("")); } + found = 1; } else if (0 == strcmp(prop_name, "getcontenttype")) { if (S_ISDIR(sce->st.st_mode)) { buffer_append_string_len(b, CONST_STR_LEN("httpd/unix-directory")); @@ -851,9 +951,9 @@ static int webdav_get_live_property(server *srv, conne for (k = 0; k < con->conf.mimetypes->used; k++) { data_string *ds = (data_string *)con->conf.mimetypes->data[k]; - if (ds->key->used == 0) continue; + if (buffer_is_empty(ds->key)) continue; - if (buffer_is_equal_right_len(dst->path, ds->key, ds->key->used - 1)) { + if (buffer_is_equal_right_len(dst->path, ds->key, buffer_string_length(ds->key))) { buffer_append_string_len(b,CONST_STR_LEN("")); buffer_append_string_buffer(b, ds->value); buffer_append_string_len(b, CONST_STR_LEN("")); @@ -877,7 +977,7 @@ static int webdav_get_live_property(server *srv, conne found = 1; } else if (0 == strcmp(prop_name, "getcontentlength")) { buffer_append_string_len(b,CONST_STR_LEN("")); - buffer_append_off_t(b, sce->st.st_size); + buffer_append_int(b, sce->st.st_size); buffer_append_string_len(b, CONST_STR_LEN("")); found = 1; } else if (0 == strcmp(prop_name, "getcontentlanguage")) { @@ -885,6 +985,26 @@ static int webdav_get_live_property(server *srv, conne buffer_append_string_len(b, CONST_STR_LEN("en")); buffer_append_string_len(b, CONST_STR_LEN("")); found = 1; + } else if (0 == strcmp(prop_name, "getetag")) { + etag_create(con->physical.etag, &sce->st, con->etag_flags); + buffer_append_string_len(b, CONST_STR_LEN("")); + buffer_append_string_buffer(b, con->physical.etag); + buffer_append_string_len(b, CONST_STR_LEN("")); + buffer_reset(con->physical.etag); + found = 1; + #ifdef USE_LOCKS + } else if (0 == strcmp(prop_name, "lockdiscovery")) { + webdav_get_live_property_lockdiscovery(srv, con, p, dst, b); + found = 1; + } else if (0 == strcmp(prop_name, "supportedlock")) { + buffer_append_string_len(b,CONST_STR_LEN("")); + buffer_append_string_len(b,CONST_STR_LEN("")); + buffer_append_string_len(b,CONST_STR_LEN("")); + buffer_append_string_len(b,CONST_STR_LEN("")); + buffer_append_string_len(b,CONST_STR_LEN("")); + buffer_append_string_len(b, CONST_STR_LEN("")); + found = 1; + #endif } } @@ -907,17 +1027,16 @@ static int webdav_get_property(server *srv, connection /* bind the values to the insert */ sqlite3_bind_text(stmt, 1, - dst->rel_path->ptr, - dst->rel_path->used - 1, - SQLITE_TRANSIENT); + CONST_BUF_LEN(dst->rel_path), + SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 2, - prop_name, - strlen(prop_name), - SQLITE_TRANSIENT); + prop_name, + strlen(prop_name), + SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 3, - prop_ns, - strlen(prop_ns), - SQLITE_TRANSIENT); + prop_ns, + strlen(prop_ns), + SQLITE_TRANSIENT); /* it is the PK */ while (SQLITE_ROW == sqlite3_step(stmt)) { @@ -941,16 +1060,18 @@ typedef struct { static webdav_property live_properties[] = { { "DAV:", "creationdate" }, - { "DAV:", "displayname" }, + /*{ "DAV:", "displayname" },*//*(not implemented)*/ { "DAV:", "getcontentlanguage" }, { "DAV:", "getcontentlength" }, { "DAV:", "getcontenttype" }, { "DAV:", "getetag" }, { "DAV:", "getlastmodified" }, { "DAV:", "resourcetype" }, + /*{ "DAV:", "source" },*//*(not implemented)*/ + #ifdef USE_LOCKS { "DAV:", "lockdiscovery" }, - { "DAV:", "source" }, { "DAV:", "supportedlock" }, + #endif { NULL, NULL } }; @@ -965,7 +1086,7 @@ typedef struct { static int webdav_get_props(server *srv, connection *con, plugin_data *p, physical *dst, webdav_properties *props, buffer *b_200, buffer *b_404) { size_t i; - if (props) { + if (props && props->used) { for (i = 0; i < props->used; i++) { webdav_property *prop; @@ -1003,6 +1124,8 @@ static int webdav_parse_chunkqueue(server *srv, connec for (c = cq->first; cq->bytes_out != cq->bytes_in; c = cq->first) { size_t weWant = cq->bytes_out - cq->bytes_in; size_t weHave; + int mapped; + void *data; switch(c->type) { case FILE_CHUNK: @@ -1011,7 +1134,10 @@ static int webdav_parse_chunkqueue(server *srv, connec if (weHave > weWant) weHave = weWant; /* xml chunks are always memory, mmap() is our friend */ - if (c->file.mmap.start == MAP_FAILED) { + mapped = (c->file.mmap.start != MAP_FAILED); + if (mapped) { + data = c->file.mmap.start + c->offset; + } else { if (-1 == c->file.fd && /* open the file if not already open */ -1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) { log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno)); @@ -1019,34 +1145,38 @@ static int webdav_parse_chunkqueue(server *srv, connec return -1; } - if (MAP_FAILED == (c->file.mmap.start = mmap(0, c->file.length, PROT_READ, MAP_SHARED, c->file.fd, 0))) { - log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ", - strerror(errno), c->file.name, c->file.fd); - close(c->file.fd); - c->file.fd = -1; - - return -1; + if (MAP_FAILED != (c->file.mmap.start = mmap(0, c->file.length, PROT_READ, MAP_PRIVATE, c->file.fd, 0))) { + /* chunk_reset() or chunk_free() will cleanup for us */ + c->file.mmap.length = c->file.length; + data = c->file.mmap.start + c->offset; + mapped = 1; + } else { + ssize_t rd; + if (weHave > 65536) weHave = 65536; + data = malloc(weHave); + force_assert(data); + if (-1 == lseek(c->file.fd, c->file.start + c->offset, SEEK_SET) + || 0 > (rd = read(c->file.fd, data, weHave))) { + log_error_write(srv, __FILE__, __LINE__, "ssbd", "lseek/read failed: ", + strerror(errno), c->file.name, c->file.fd); + free(data); + return -1; + } + weHave = (size_t)rd; } - - close(c->file.fd); - c->file.fd = -1; - - c->file.mmap.length = c->file.length; - - /* chunk_reset() or chunk_free() will cleanup for us */ } - if (XML_ERR_OK != (err = xmlParseChunk(ctxt, c->file.mmap.start + c->offset, weHave, 0))) { + if (XML_ERR_OK != (err = xmlParseChunk(ctxt, data, weHave, 0))) { log_error_write(srv, __FILE__, __LINE__, "sodd", "xmlParseChunk failed at:", cq->bytes_out, weHave, err); } - c->offset += weHave; - cq->bytes_out += weHave; + chunkqueue_mark_written(cq, weHave); + if (!mapped) free(data); break; case MEM_CHUNK: /* append to the buffer */ - weHave = c->mem->used - 1 - c->offset; + weHave = buffer_string_length(c->mem) - c->offset; if (weHave > weWant) weHave = weWant; @@ -1058,17 +1188,12 @@ static int webdav_parse_chunkqueue(server *srv, connec log_error_write(srv, __FILE__, __LINE__, "sodd", "xmlParseChunk failed at:", cq->bytes_out, weHave, err); } - c->offset += weHave; - cq->bytes_out += weHave; + chunkqueue_mark_written(cq, weHave); break; - case UNUSED_CHUNK: - break; } - chunkqueue_remove_finished_chunks(cq); } - switch ((err = xmlParseChunk(ctxt, 0, 0, 1))) { case XML_ERR_DOCUMENT_END: case XML_ERR_OK: @@ -1096,7 +1221,7 @@ static int webdav_parse_chunkqueue(server *srv, connec static int webdav_lockdiscovery(server *srv, connection *con, buffer *locktoken, const char *lockscope, const char *locktype, int depth) { - buffer *b; + buffer *b = buffer_init(); response_header_overwrite(srv, con, CONST_STR_LEN("Lock-Token"), CONST_BUF_LEN(locktoken)); @@ -1104,47 +1229,17 @@ static int webdav_lockdiscovery(server *srv, connectio CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml; charset=\"utf-8\"")); - b = chunkqueue_get_append_buffer(con->write_queue); - buffer_copy_string_len(b, CONST_STR_LEN("\n")); buffer_append_string_len(b,CONST_STR_LEN("\n")); buffer_append_string_len(b,CONST_STR_LEN("\n")); - buffer_append_string_len(b,CONST_STR_LEN("\n")); - - buffer_append_string_len(b,CONST_STR_LEN("")); - buffer_append_string_len(b,CONST_STR_LEN("")); - buffer_append_string_len(b,CONST_STR_LEN("\n")); - - buffer_append_string_len(b,CONST_STR_LEN("")); - buffer_append_string_len(b,CONST_STR_LEN("")); - buffer_append_string_len(b,CONST_STR_LEN("\n")); - - buffer_append_string_len(b,CONST_STR_LEN("")); - buffer_append_string(b, depth == 0 ? "0" : "infinity"); - buffer_append_string_len(b,CONST_STR_LEN("\n")); - - buffer_append_string_len(b,CONST_STR_LEN("")); - buffer_append_string_len(b, CONST_STR_LEN("Second-600")); - buffer_append_string_len(b,CONST_STR_LEN("\n")); - - buffer_append_string_len(b,CONST_STR_LEN("")); - buffer_append_string_len(b,CONST_STR_LEN("\n")); - - buffer_append_string_len(b,CONST_STR_LEN("")); - buffer_append_string_len(b, CONST_STR_LEN("")); - buffer_append_string_buffer(b, locktoken); - buffer_append_string_len(b, CONST_STR_LEN("")); - buffer_append_string_len(b,CONST_STR_LEN("\n")); - - buffer_append_string_len(b,CONST_STR_LEN("\n")); + webdav_activelock(b, locktoken, lockscope, locktype, depth, 600); buffer_append_string_len(b,CONST_STR_LEN("\n")); buffer_append_string_len(b,CONST_STR_LEN("\n")); + chunkqueue_append_buffer(con->write_queue, b); + buffer_free(b); + return 0; } #endif @@ -1191,8 +1286,8 @@ static int webdav_has_lock(server *srv, connection *co sqlite3_reset(stmt); sqlite3_bind_text(stmt, 1, - CONST_BUF_LEN(uri), - SQLITE_TRANSIENT); + CONST_BUF_LEN(uri), + SQLITE_TRANSIENT); while (SQLITE_ROW == sqlite3_step(stmt)) { has_lock = 0; @@ -1208,12 +1303,13 @@ static int webdav_has_lock(server *srv, connection *co return has_lock; } -URIHANDLER_FUNC(mod_webdav_subrequest_handler) { + +SUBREQUEST_FUNC(mod_webdav_subrequest_handler_huge) { plugin_data *p = p_d; buffer *b; DIR *dir; data_string *ds; - int depth = -1; + int depth = -1; /* (Depth: infinity) */ struct stat st; buffer *prop_200; buffer *prop_404; @@ -1224,12 +1320,16 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { if (!p->conf.enabled) return HANDLER_GO_ON; /* physical path is setup */ - if (con->physical.path->used == 0) return HANDLER_GO_ON; + if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON; /* PROPFIND need them */ - if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Depth"))) { - depth = strtol(ds->value->ptr, NULL, 10); - } + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Depth")) && 1 == buffer_string_length(ds->value)) { + if ('0' == *ds->value->ptr) { + depth = 0; + } else if ('1' == *ds->value->ptr) { + depth = 1; + } + } /* else treat as Depth: infinity */ switch (con->request.http_method) { case HTTP_METHOD_PROPFIND: @@ -1249,12 +1349,21 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { break; } + if (S_ISDIR(sce->st.st_mode) && con->physical.path->ptr[buffer_string_length(con->physical.path)-1] != '/') { + http_response_redirect_to_directory(srv, con); + return HANDLER_FINISHED; + } #ifdef USE_PROPPATCH /* any special requests or just allprop ? */ if (con->request.content_length) { xmlDocPtr xml; + if (con->state == CON_STATE_READ_POST) { + handler_t r = connection_handle_read_post_state(srv, con); + if (r != HANDLER_GO_ON) return r; + } + if (1 == webdav_parse_chunkqueue(srv, con, p, con->request_content_queue, &xml)) { xmlNode *rootnode = xmlDocGetRootElement(xml); @@ -1275,7 +1384,7 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { if (prop->type == XML_TEXT_NODE) continue; /* ignore WS */ if (prop->ns && - (0 == xmlStrcmp(prop->ns->href, BAD_CAST "")) && + (0 == xmlStrcmp(prop->ns->href, BAD_CAST "")) && (0 != xmlStrcmp(prop->ns->prefix, BAD_CAST ""))) { size_t i; log_error_write(srv, __FILE__, __LINE__, "ss", @@ -1319,9 +1428,8 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { /* bind the values to the insert */ sqlite3_bind_text(stmt, 1, - con->uri.path->ptr, - con->uri.path->used - 1, - SQLITE_TRANSIENT); + CONST_BUF_LEN(con->uri.path), + SQLITE_TRANSIENT); if (SQLITE_DONE != sqlite3_step(stmt)) { } @@ -1343,7 +1451,7 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml; charset=\"utf-8\"")); - b = chunkqueue_get_append_buffer(con->write_queue); + b = buffer_init(); buffer_copy_string_len(b, CONST_STR_LEN("\n")); @@ -1354,9 +1462,8 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { prop_200 = buffer_init(); prop_404 = buffer_init(); - switch(depth) { - case 0: - /* Depth: 0 */ + { + /* Depth: 0 or Depth: 1 */ webdav_get_props(srv, con, p, &(con->physical), req_props, prop_200, prop_404); buffer_append_string_len(b,CONST_STR_LEN("\n")); @@ -1367,7 +1474,7 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { buffer_append_string_encoded(b, CONST_BUF_LEN(con->uri.path), ENCODING_REL_URI); buffer_append_string_len(b,CONST_STR_LEN("\n")); - if (!buffer_is_empty(prop_200)) { + if (!buffer_string_is_empty(prop_200)) { buffer_append_string_len(b,CONST_STR_LEN("\n")); buffer_append_string_len(b,CONST_STR_LEN("\n")); @@ -1379,7 +1486,7 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { buffer_append_string_len(b,CONST_STR_LEN("\n")); } - if (!buffer_is_empty(prop_404)) { + if (!buffer_string_is_empty(prop_404)) { buffer_append_string_len(b,CONST_STR_LEN("\n")); buffer_append_string_len(b,CONST_STR_LEN("\n")); @@ -1393,9 +1500,10 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { } buffer_append_string_len(b,CONST_STR_LEN("\n")); + } - break; - case 1: + if (depth == 1) { + if (NULL != (dir = opendir(con->physical.path->ptr))) { struct dirent *de; physical d; @@ -1405,23 +1513,19 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { d.rel_path = buffer_init(); while(NULL != (de = readdir(dir))) { - if (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0') { + if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) { continue; - /* ignore the parent dir */ + /* ignore the parent and target dir */ } - buffer_copy_string_buffer(d.path, dst->path); - BUFFER_APPEND_SLASH(d.path); + buffer_copy_buffer(d.path, dst->path); + buffer_append_slash(d.path); - buffer_copy_string_buffer(d.rel_path, dst->rel_path); - BUFFER_APPEND_SLASH(d.rel_path); + buffer_copy_buffer(d.rel_path, dst->rel_path); + buffer_append_slash(d.rel_path); - if (de->d_name[0] == '.' && de->d_name[1] == '\0') { - /* don't append the . */ - } else { - buffer_append_string(d.path, de->d_name); - buffer_append_string(d.rel_path, de->d_name); - } + buffer_append_string(d.path, de->d_name); + buffer_append_string(d.rel_path, de->d_name); buffer_reset(prop_200); buffer_reset(prop_404); @@ -1434,9 +1538,13 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { buffer_append_string_len(b,CONST_STR_LEN("://")); buffer_append_string_buffer(b, con->uri.authority); buffer_append_string_encoded(b, CONST_BUF_LEN(d.rel_path), ENCODING_REL_URI); + if (0 == stat(d.path->ptr, &st) && S_ISDIR(st.st_mode)) { + /* Append a '/' on subdirectories */ + buffer_append_string_len(b,CONST_STR_LEN("/")); + } buffer_append_string_len(b,CONST_STR_LEN("\n")); - if (!buffer_is_empty(prop_200)) { + if (!buffer_string_is_empty(prop_200)) { buffer_append_string_len(b,CONST_STR_LEN("\n")); buffer_append_string_len(b,CONST_STR_LEN("\n")); @@ -1448,7 +1556,7 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { buffer_append_string_len(b,CONST_STR_LEN("\n")); } - if (!buffer_is_empty(prop_404)) { + if (!buffer_string_is_empty(prop_404)) { buffer_append_string_len(b,CONST_STR_LEN("\n")); buffer_append_string_len(b,CONST_STR_LEN("\n")); @@ -1467,7 +1575,7 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { buffer_free(d.path); buffer_free(d.rel_path); } - break; + } if (req_props) { @@ -1489,6 +1597,10 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { if (p->conf.log_xml) { log_error_write(srv, __FILE__, __LINE__, "sb", "XML-response-body:", b); } + + chunkqueue_append_buffer(con->write_queue, b); + buffer_free(b); + con->file_finished = 1; return HANDLER_FINISHED; @@ -1551,13 +1663,20 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { break; } } else if (S_ISDIR(st.st_mode)) { - buffer *multi_status_resp = buffer_init(); + buffer *multi_status_resp; + if (con->physical.path->ptr[buffer_string_length(con->physical.path)-1] != '/') { + http_response_redirect_to_directory(srv, con); + return HANDLER_FINISHED; + } + + multi_status_resp = buffer_init(); + if (webdav_delete_dir(srv, con, p, &(con->physical), multi_status_resp)) { /* we got an error somewhere in between, build a 207 */ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml; charset=\"utf-8\"")); - b = chunkqueue_get_append_buffer(con->write_queue); + b = buffer_init(); buffer_copy_string_len(b, CONST_STR_LEN("\n")); @@ -1571,6 +1690,9 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { log_error_write(srv, __FILE__, __LINE__, "sb", "XML-response-body:", b); } + chunkqueue_append_buffer(con->write_queue, b); + buffer_free(b); + con->http_status = 207; con->file_finished = 1; } else { @@ -1578,6 +1700,9 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { if (-1 == rmdir(con->physical.path->ptr)) { switch(errno) { + case EPERM: + con->http_status = 403; + break; case ENOENT: con->http_status = 404; break; @@ -1619,14 +1744,17 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { } /* is a exclusive lock set on the source */ - if (!webdav_has_lock(srv, con, p, con->uri.path)) { + /* (check for lock once before potentially reading large input) */ + if (0 == cq->bytes_in && !webdav_has_lock(srv, con, p, con->uri.path)) { con->http_status = 423; return HANDLER_FINISHED; } + if (con->state == CON_STATE_READ_POST) { + handler_t r = connection_handle_read_post_state(srv, con); + if (r != HANDLER_GO_ON) return r; + } - assert(chunkqueue_length(cq) == (off_t)con->request.content_length); - /* RFC2616 Section 9.6 PUT requires us to send 501 on all Content-* we don't support * - most important Content-Range * @@ -1708,12 +1836,19 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { for (c = cq->first; c; c = cq->first) { int r = 0; + int mapped; + void *data; + size_t dlen; /* copy all chunks */ switch(c->type) { case FILE_CHUNK: - if (c->file.mmap.start == MAP_FAILED) { + mapped = (c->file.mmap.start != MAP_FAILED); + dlen = c->file.length - c->offset; + if (mapped) { + data = c->file.mmap.start + c->offset; + } else { if (-1 == c->file.fd && /* open the file if not already open */ -1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) { log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno)); @@ -1721,24 +1856,30 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { return HANDLER_ERROR; } - if (MAP_FAILED == (c->file.mmap.start = mmap(NULL, c->file.length, PROT_READ, MAP_SHARED, c->file.fd, 0))) { - log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ", - strerror(errno), c->file.name, c->file.fd); - close(c->file.fd); - c->file.fd = -1; - close(fd); - return HANDLER_ERROR; + if (MAP_FAILED != (c->file.mmap.start = mmap(NULL, c->file.length, PROT_READ, MAP_PRIVATE, c->file.fd, 0))) { + /* chunk_reset() or chunk_free() will cleanup for us */ + c->file.mmap.length = c->file.length; + data = c->file.mmap.start + c->offset; + mapped = 1; + } else { + ssize_t rd; + if (dlen > 65536) dlen = 65536; + data = malloc(dlen); + force_assert(data); + if (-1 == lseek(c->file.fd, c->file.start + c->offset, SEEK_SET) + || 0 > (rd = read(c->file.fd, data, dlen))) { + log_error_write(srv, __FILE__, __LINE__, "ssbd", "lseek/read failed: ", + strerror(errno), c->file.name, c->file.fd); + free(data); + close(fd); + return HANDLER_ERROR; + } + dlen = (size_t)rd; } - c->file.mmap.length = c->file.length; - - close(c->file.fd); - c->file.fd = -1; - - /* chunk_reset() or chunk_free() will cleanup for us */ } - if ((r = write(fd, c->file.mmap.start + c->offset, c->file.length - c->offset)) < 0) { + if ((r = write(fd, data, dlen)) < 0) { switch(errno) { case ENOSPC: con->http_status = 507; @@ -1749,9 +1890,11 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { break; } } + + if (!mapped) free(data); break; case MEM_CHUNK: - if ((r = write(fd, c->mem->ptr + c->offset, c->mem->used - c->offset - 1)) < 0) { + if ((r = write(fd, c->mem->ptr + c->offset, buffer_string_length(c->mem) - c->offset)) < 0) { switch(errno) { case ENOSPC: con->http_status = 507; @@ -1763,19 +1906,19 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { } } break; - case UNUSED_CHUNK: - break; } if (r > 0) { - c->offset += r; - cq->bytes_out += r; + chunkqueue_mark_written(cq, r); } else { break; } - chunkqueue_remove_finished_chunks(cq); } - close(fd); + if (0 != close(fd)) { + log_error_write(srv, __FILE__, __LINE__, "sbss", + "close ", con->physical.path, "failed: ", strerror(errno)); + return HANDLER_ERROR; + } return HANDLER_FINISHED; } @@ -1806,7 +1949,7 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { } if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Overwrite"))) { - if (ds->value->used != 2 || + if (buffer_string_length(ds->value) != 1 || (ds->value->ptr[0] != 'F' && ds->value->ptr[0] != 'T') ) { con->http_status = 400; @@ -1862,27 +2005,74 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { return HANDLER_FINISHED; } - buffer_copy_string_buffer(p->tmp_buf, p->uri.path_raw); + buffer_copy_buffer(p->tmp_buf, p->uri.path_raw); buffer_urldecode_path(p->tmp_buf); buffer_path_simplify(p->uri.path, p->tmp_buf); /* we now have a URI which is clean. transform it into a physical path */ - buffer_copy_string_buffer(p->physical.doc_root, con->physical.doc_root); - buffer_copy_string_buffer(p->physical.rel_path, p->uri.path); + buffer_copy_buffer(p->physical.doc_root, con->physical.doc_root); + buffer_copy_buffer(p->physical.rel_path, p->uri.path); if (con->conf.force_lowercase_filenames) { buffer_to_lower(p->physical.rel_path); } - buffer_copy_string_buffer(p->physical.path, p->physical.doc_root); - BUFFER_APPEND_SLASH(p->physical.path); - buffer_copy_string_buffer(p->physical.basedir, p->physical.path); + /* Destination physical path + * src con->physical.path might have been remapped with mod_alias. + * (but mod_alias does not modify con->physical.rel_path) + * Find matching prefix to support use of mod_alias to remap webdav root. + * Aliasing of paths underneath the webdav root might not work. + * Likewise, mod_rewrite URL rewriting might thwart this comparison. + * Use mod_redirect instead of mod_alias to remap paths *under* webdav root. + * Use mod_redirect instead of mod_rewrite on *any* parts of path to webdav. + * (Related, use mod_auth to protect webdav root, but avoid attempting to + * use mod_auth on paths underneath webdav root, as Destination is not + * validated with mod_auth) + * + * tl;dr: webdav paths and webdav properties are managed by mod_webdav, + * so do not modify paths externally or else undefined behavior + * or corruption may occur + */ + { + /* find matching URI prefix + * check if remaining con->physical.rel_path matches suffix + * of con->physical.basedir so that we can use it to + * remap Destination physical path */ + size_t i, remain; + sep = con->uri.path->ptr; + sep2 = p->uri.path->ptr; + for (i = 0; sep[i] && sep[i] == sep2[i]; ++i) ; + if (sep[i] == '\0' && (sep2[i] == '\0' || sep2[i] == '/' || (i > 0 && sep[i-1] == '/'))) { + /* src and dst URI match or dst is nested inside src; invalid COPY or MOVE */ + con->http_status = 403; + return HANDLER_FINISHED; + } + while (i != 0 && sep[--i] != '/') ; /* find matching directory path */ + remain = buffer_string_length(con->uri.path) - i; + if (!con->conf.force_lowercase_filenames + ? buffer_is_equal_right_len(con->physical.path, con->physical.rel_path, remain) + :(buffer_string_length(con->physical.path) >= remain + && 0 == strncasecmp(con->physical.path->ptr+buffer_string_length(con->physical.path)-remain, con->physical.rel_path->ptr+i, remain))) { + /* (at this point, p->physical.rel_path is identical to (or lowercased version of) p->uri.path) */ + buffer_copy_string_len(p->physical.path, con->physical.path->ptr, buffer_string_length(con->physical.path)-remain); + buffer_append_string_len(p->physical.path, p->physical.rel_path->ptr+i, buffer_string_length(p->physical.rel_path)-i); - /* don't add a second / */ - if (p->physical.rel_path->ptr[0] == '/') { - buffer_append_string_len(p->physical.path, p->physical.rel_path->ptr + 1, p->physical.rel_path->used - 2); - } else { - buffer_append_string_buffer(p->physical.path, p->physical.rel_path); + buffer_copy_buffer(p->physical.basedir, con->physical.basedir); + buffer_append_slash(p->physical.basedir); + } else { + /* unable to perform physical path remap here; + * assume doc_root/rel_path and no remapping */ + buffer_copy_buffer(p->physical.path, p->physical.doc_root); + buffer_append_slash(p->physical.path); + buffer_copy_buffer(p->physical.basedir, p->physical.path); + + /* don't add a second / */ + if (p->physical.rel_path->ptr[0] == '/') { + buffer_append_string_len(p->physical.path, p->physical.rel_path->ptr + 1, buffer_string_length(p->physical.rel_path) - 1); + } else { + buffer_append_string_buffer(p->physical.path, p->physical.rel_path); + } + } } /* let's see if the source is a directory @@ -1900,13 +2090,20 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { } } else if (S_ISDIR(st.st_mode)) { int r; + int created = 0; /* src is a directory */ + if (con->physical.path->ptr[buffer_string_length(con->physical.path)-1] != '/') { + http_response_redirect_to_directory(srv, con); + return HANDLER_FINISHED; + } + if (-1 == stat(p->physical.path->ptr, &st)) { if (-1 == mkdir(p->physical.path->ptr, WEBDAV_DIR_MODE)) { con->http_status = 403; return HANDLER_FINISHED; } + created = 1; } else if (!S_ISDIR(st.st_mode)) { if (overwrite == 0) { /* copying into a non-dir ? */ @@ -1918,6 +2115,7 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { con->http_status = 403; return HANDLER_FINISHED; } + created = 1; } } @@ -1933,11 +2131,12 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { rmdir(con->physical.path->ptr); } - con->http_status = 201; + con->http_status = created ? 201 : 204; con->file_finished = 1; } else { /* it is just a file, good */ int r; + int destdir = 0; /* does the client have a lock for this connection ? */ if (!webdav_has_lock(srv, con, p, p->uri.path)) { @@ -1950,6 +2149,7 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { if (S_ISDIR(st.st_mode)) { /* file to dir/ * append basename to physical path */ + destdir = 1; if (NULL != (sep = strrchr(con->physical.path->ptr, '/'))) { buffer_append_string(p->physical.path, sep); @@ -1959,7 +2159,7 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { } if (-1 == r) { - con->http_status = 201; /* we will create a new one */ + con->http_status = destdir ? 204 : 201; /* we will create a new one */ con->file_finished = 1; switch(errno) { @@ -1982,22 +2182,6 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { #ifdef USE_PROPPATCH sqlite3_stmt *stmt; - stmt = p->conf.stmt_delete_uri; - if (stmt) { - - sqlite3_reset(stmt); - - /* bind the values to the insert */ - sqlite3_bind_text(stmt, 1, - con->uri.path->ptr, - con->uri.path->used - 1, - SQLITE_TRANSIENT); - - if (SQLITE_DONE != sqlite3_step(stmt)) { - log_error_write(srv, __FILE__, __LINE__, "ss", "sql-move(delete old) failed:", sqlite3_errmsg(p->conf.sql)); - } - } - stmt = p->conf.stmt_move_uri; if (stmt) { @@ -2005,14 +2189,12 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { /* bind the values to the insert */ sqlite3_bind_text(stmt, 1, - p->uri.path->ptr, - p->uri.path->used - 1, - SQLITE_TRANSIENT); + CONST_BUF_LEN(p->uri.path), + SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 2, - con->uri.path->ptr, - con->uri.path->used - 1, - SQLITE_TRANSIENT); + CONST_BUF_LEN(con->uri.path), + SQLITE_TRANSIENT); if (SQLITE_DONE != sqlite3_step(stmt)) { log_error_write(srv, __FILE__, __LINE__, "ss", "sql-move failed:", sqlite3_errmsg(p->conf.sql)); @@ -2060,10 +2242,20 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { } } + if (S_ISDIR(st.st_mode) && con->physical.path->ptr[buffer_string_length(con->physical.path)-1] != '/') { + http_response_redirect_to_directory(srv, con); + return HANDLER_FINISHED; + } + #ifdef USE_PROPPATCH if (con->request.content_length) { xmlDocPtr xml; + if (con->state == CON_STATE_READ_POST) { + handler_t r = connection_handle_read_post_state(srv, con); + if (r != HANDLER_GO_ON) return r; + } + if (1 == webdav_parse_chunkqueue(srv, con, p, con->request_content_queue, &xml)) { xmlNode *rootnode = xmlDocGetRootElement(xml); @@ -2097,6 +2289,7 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { for (props = cmd->children; props; props = props->next) { if (0 == xmlStrcmp(props->name, BAD_CAST "prop")) { xmlNode *prop; + char *propval = NULL; int r; prop = props->children; @@ -2117,35 +2310,40 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { /* bind the values to the insert */ sqlite3_bind_text(stmt, 1, - con->uri.path->ptr, - con->uri.path->used - 1, - SQLITE_TRANSIENT); + CONST_BUF_LEN(con->uri.path), + SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 2, - (char *)prop->name, - strlen((char *)prop->name), - SQLITE_TRANSIENT); + (char *)prop->name, + strlen((char *)prop->name), + SQLITE_TRANSIENT); if (prop->ns) { sqlite3_bind_text(stmt, 3, - (char *)prop->ns->href, - strlen((char *)prop->ns->href), - SQLITE_TRANSIENT); + (char *)prop->ns->href, + strlen((char *)prop->ns->href), + SQLITE_TRANSIENT); } else { sqlite3_bind_text(stmt, 3, - "", - 0, - SQLITE_TRANSIENT); + "", + 0, + SQLITE_TRANSIENT); } if (stmt == p->conf.stmt_update_prop) { + propval = prop->children + ? (char *)xmlNodeListGetString(xml, prop->children, 0) + : NULL; + sqlite3_bind_text(stmt, 4, - (char *)xmlNodeGetContent(prop), - strlen((char *)xmlNodeGetContent(prop)), - SQLITE_TRANSIENT); + propval ? propval : "", + propval ? strlen(propval) : 0, + SQLITE_TRANSIENT); } if (SQLITE_DONE != (r = sqlite3_step(stmt))) { log_error_write(srv, __FILE__, __LINE__, "ss", "sql-set failed:", sqlite3_errmsg(p->conf.sql)); } + + if (propval) xmlFree(propval); } } if (empty_ns) break; @@ -2172,6 +2370,7 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { } con->file_finished = 1; + xmlFreeDoc(xml); return HANDLER_FINISHED; } @@ -2220,16 +2419,38 @@ propmatch_cleanup: if (con->request.content_length) { xmlDocPtr xml; buffer *hdr_if = NULL; + int created = 0; + if (con->state == CON_STATE_READ_POST) { + handler_t r = connection_handle_read_post_state(srv, con); + if (r != HANDLER_GO_ON) return r; + } + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If"))) { hdr_if = ds->value; } - /* we don't support Depth: Infinity on locks */ - if (hdr_if == NULL && depth == -1) { - con->http_status = 409; /* Conflict */ + if (0 != stat(con->physical.path->ptr, &st)) { + if (errno == ENOENT) { + int fd = open(con->physical.path->ptr, O_WRONLY|O_CREAT|O_APPEND|O_BINARY|FIFO_NONBLOCK, WEBDAV_FILE_MODE); + if (fd >= 0) { + close(fd); + created = 1; + } else { + log_error_write(srv, __FILE__, __LINE__, "sBss", + "create file", con->physical.path, ":", strerror(errno)); + con->http_status = 403; /* Forbidden */ - return HANDLER_FINISHED; + return HANDLER_FINISHED; + } + } + } else if (hdr_if == NULL && depth == -1) { + /* we don't support Depth: Infinity on directories */ + if (S_ISDIR(st.st_mode)) { + con->http_status = 409; /* Conflict */ + + return HANDLER_FINISHED; + } } if (1 == webdav_parse_chunkqueue(srv, con, p, con->request_content_queue, &xml)) { @@ -2286,9 +2507,8 @@ propmatch_cleanup: sqlite3_reset(stmt); sqlite3_bind_text(stmt, 1, - p->uri.path->ptr, - p->uri.path->used - 1, - SQLITE_TRANSIENT); + CONST_BUF_LEN(p->uri.path), + SQLITE_TRANSIENT); /* it is the PK */ while (SQLITE_ROW == sqlite3_step(stmt)) { @@ -2335,32 +2555,32 @@ propmatch_cleanup: sqlite3_reset(stmt); sqlite3_bind_text(stmt, 1, - CONST_BUF_LEN(p->tmp_buf), - SQLITE_TRANSIENT); + CONST_BUF_LEN(p->tmp_buf), + SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 2, - CONST_BUF_LEN(con->uri.path), - SQLITE_TRANSIENT); + CONST_BUF_LEN(con->uri.path), + SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 3, - (const char *)lockscope, - xmlStrlen(lockscope), - SQLITE_TRANSIENT); + (const char *)lockscope, + xmlStrlen(lockscope), + SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 4, - (const char *)locktype, - xmlStrlen(locktype), - SQLITE_TRANSIENT); + (const char *)locktype, + xmlStrlen(locktype), + SQLITE_TRANSIENT); /* owner */ sqlite3_bind_text(stmt, 5, - "", - 0, - SQLITE_TRANSIENT); + "", + 0, + SQLITE_TRANSIENT); /* depth */ sqlite3_bind_int(stmt, 6, - depth); + depth); if (SQLITE_DONE != sqlite3_step(stmt)) { @@ -2371,7 +2591,7 @@ propmatch_cleanup: /* looks like we survived */ webdav_lockdiscovery(srv, con, p->tmp_buf, (const char *)lockscope, (const char *)locktype, depth); - con->http_status = 201; + con->http_status = created ? 201 : 200; con->file_finished = 1; } } @@ -2390,19 +2610,19 @@ propmatch_cleanup: sqlite3_stmt *stmt = p->conf.stmt_refresh_lock; /* remove the < > around the token */ - if (locktoken->used < 6) { + if (buffer_string_length(locktoken) < 5) { con->http_status = 400; return HANDLER_FINISHED; } - buffer_copy_string_len(p->tmp_buf, locktoken->ptr + 2, locktoken->used - 5); + buffer_copy_string_len(p->tmp_buf, locktoken->ptr + 2, buffer_string_length(locktoken) - 4); sqlite3_reset(stmt); sqlite3_bind_text(stmt, 1, - CONST_BUF_LEN(p->tmp_buf), - SQLITE_TRANSIENT); + CONST_BUF_LEN(p->tmp_buf), + SQLITE_TRANSIENT); if (SQLITE_DONE != sqlite3_step(stmt)) { log_error_write(srv, __FILE__, __LINE__, "ss", @@ -2433,7 +2653,7 @@ propmatch_cleanup: sqlite3_stmt *stmt = p->conf.stmt_remove_lock; /* remove the < > around the token */ - if (locktoken->used < 4) { + if (buffer_string_length(locktoken) < 3) { con->http_status = 400; return HANDLER_FINISHED; @@ -2449,18 +2669,14 @@ propmatch_cleanup: * - 412 * */ - buffer_copy_string_len(p->tmp_buf, locktoken->ptr + 1, locktoken->used - 3); + buffer_copy_string_len(p->tmp_buf, locktoken->ptr + 1, buffer_string_length(locktoken) - 2); sqlite3_reset(stmt); sqlite3_bind_text(stmt, 1, - CONST_BUF_LEN(p->tmp_buf), - SQLITE_TRANSIENT); + CONST_BUF_LEN(p->tmp_buf), + SQLITE_TRANSIENT); - sqlite3_bind_text(stmt, 2, - CONST_BUF_LEN(con->uri.path), - SQLITE_TRANSIENT); - if (SQLITE_DONE != sqlite3_step(stmt)) { log_error_write(srv, __FILE__, __LINE__, "ss", "remove lock:", sqlite3_errmsg(p->conf.sql)); @@ -2492,6 +2708,47 @@ propmatch_cleanup: } +SUBREQUEST_FUNC(mod_webdav_subrequest_handler) { + handler_t r; + plugin_data *p = p_d; + if (con->mode != p->id) return HANDLER_GO_ON; + + r = mod_webdav_subrequest_handler_huge(srv, con, p_d); + if (con->http_status >= 400) con->mode = DIRECT; + return r; +} + + +PHYSICALPATH_FUNC(mod_webdav_physical_handler) { + plugin_data *p = p_d; + if (!p->conf.enabled) return HANDLER_GO_ON; + + /* physical path is setup */ + if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON; + + UNUSED(srv); + + switch (con->request.http_method) { + case HTTP_METHOD_PROPFIND: + case HTTP_METHOD_PROPPATCH: + case HTTP_METHOD_PUT: + case HTTP_METHOD_COPY: + case HTTP_METHOD_MOVE: + case HTTP_METHOD_MKCOL: + case HTTP_METHOD_DELETE: + case HTTP_METHOD_LOCK: + case HTTP_METHOD_UNLOCK: + con->conf.stream_request_body = 0; + con->mode = p->id; + break; + default: + break; + } + + return HANDLER_GO_ON; +} + + /* this function is called at dlopen() time and inits the callbacks */ int mod_webdav_plugin_init(plugin *p); @@ -2501,7 +2758,8 @@ int mod_webdav_plugin_init(plugin *p) { p->init = mod_webdav_init; p->handle_uri_clean = mod_webdav_uri_handler; - p->handle_physical = mod_webdav_subrequest_handler; + p->handle_physical = mod_webdav_physical_handler; + p->handle_subrequest = mod_webdav_subrequest_handler; p->set_defaults = mod_webdav_set_defaults; p->cleanup = mod_webdav_free;