From 80bb42266ec6b61425038ba26fc8958896912d0a Mon Sep 17 00:00:00 2001 From: Glenn Strauss Date: Tue, 26 Apr 2016 21:50:49 -0400 Subject: [PATCH] [mod_webdav] improve PROPFIND,PROPPATCH (#1818, #1953) fix "allprop" propfind request to report all 'live' properties add "supportedlock" 'live' property, if ./configure --with-webdav-locks report collections (directory) paths with trailing slash ('/') on path redirect operations on collections without trailing slash ('/') to URI with trailing slash ('/') fix PROPPATCH to work properly and eliminate PROPPATCH memory leak fix property update after MOVE move CREATE TABLE statements *before* any prepare statements to avoid invalidating the prepare statements when the tables are first created **thx Uranus Zhou for the explanation: https://zohead.com/archives/lighty-sqlite-err/?lang=en x-ref: "Improve DAV support to be able to handle git as a client" https://redmine.lighttpd.net/issues/1953 "add RFC-compliant LOCK support to mod_webdav" (still not compliant) https://redmine.lighttpd.net/issues/1818 Note: this has not been tested whether or not mod_webdav works with git The (highly) recommended method to support git via HTTP is to use git-http-backend via CGI. gitolite and gitosis provide other good alternative ways to access git. This patch does result in more WebDAV 'Litmus' tests passing, even though mod_webdav still pretends to implement "If" conditional locking, granting locks to all requestors and not strictly enforcing locks. --- src/mod_webdav.c | 113 +++++++++++++++++++++++++++++------------------ 1 file changed, 71 insertions(+), 42 deletions(-) diff --git a/src/mod_webdav.c b/src/mod_webdav.c index bac69fbc..038ca874 100644 --- a/src/mod_webdav.c +++ b/src/mod_webdav.c @@ -215,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," @@ -232,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)) { @@ -288,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)); @@ -298,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)) { @@ -901,6 +901,16 @@ static int webdav_get_live_property(server *srv, connection *con, plugin_data *p buffer_append_string_len(b, CONST_STR_LEN("en")); buffer_append_string_len(b, CONST_STR_LEN("")); found = 1; + #ifdef USE_LOCKS + } 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 } } @@ -965,7 +975,9 @@ static webdav_property live_properties[] = { { "DAV:", "resourcetype" }, { "DAV:", "lockdiscovery" }, { "DAV:", "source" }, + #ifdef USE_LOCKS { "DAV:", "supportedlock" }, + #endif { NULL, NULL } }; @@ -980,7 +992,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; @@ -1274,6 +1286,10 @@ SUBREQUEST_FUNC(mod_webdav_subrequest_handler_huge) { 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 ? */ @@ -1459,6 +1475,10 @@ SUBREQUEST_FUNC(mod_webdav_subrequest_handler_huge) { 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_string_is_empty(prop_200)) { @@ -1580,7 +1600,14 @@ SUBREQUEST_FUNC(mod_webdav_subrequest_handler_huge) { 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 */ @@ -1995,6 +2022,11 @@ SUBREQUEST_FUNC(mod_webdav_subrequest_handler_huge) { int r; /* 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; @@ -2075,21 +2107,6 @@ SUBREQUEST_FUNC(mod_webdav_subrequest_handler_huge) { #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, - CONST_BUF_LEN(con->uri.path), - 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) { @@ -2150,6 +2167,11 @@ SUBREQUEST_FUNC(mod_webdav_subrequest_handler_huge) { } } + 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; @@ -2192,6 +2214,7 @@ SUBREQUEST_FUNC(mod_webdav_subrequest_handler_huge) { 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; @@ -2230,9 +2253,13 @@ SUBREQUEST_FUNC(mod_webdav_subrequest_handler_huge) { 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)), + propval ? propval : "", + propval ? strlen(propval) : 0, SQLITE_TRANSIENT); } @@ -2240,6 +2267,8 @@ SUBREQUEST_FUNC(mod_webdav_subrequest_handler_huge) { log_error_write(srv, __FILE__, __LINE__, "ss", "sql-set failed:", sqlite3_errmsg(p->conf.sql)); } + + if (propval) xmlFree(propval); } } if (empty_ns) break;