diff options
author | Jan Kneschke <jan@kneschke.de> | 2005-08-20 19:10:44 +0000 |
---|---|---|
committer | Jan Kneschke <jan@kneschke.de> | 2005-08-20 19:10:44 +0000 |
commit | 163c25a2a9d4e58e5ae17e09baf8fff234c8696b (patch) | |
tree | c421df8c3f45c8fdb5b492c6468689fb350e487b | |
parent | 30b1973266a42988a36a09f5ec95239c84193aee (diff) | |
download | lighttpd-git-163c25a2a9d4e58e5ae17e09baf8fff234c8696b.tar.gz |
added MOVE, COPY, PROPPATCH and nearly complete PROPFIND (Level 1-3 of litmus passed)
git-svn-id: svn://svn.lighttpd.net/lighttpd/branches/lighttpd-merge-1.4.x@593 152afb58-edef-0310-8abb-c4023f1b3aa9
-rw-r--r-- | configure.in | 39 | ||||
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/connections.c | 4 | ||||
-rw-r--r-- | src/keyvalue.c | 3 | ||||
-rw-r--r-- | src/keyvalue.h | 2 | ||||
-rw-r--r-- | src/mod_webdav.c | 1298 | ||||
-rw-r--r-- | src/request.c | 4 |
7 files changed, 1189 insertions, 165 deletions
diff --git a/configure.in b/configure.in index 5509e883..7b26812e 100644 --- a/configure.in +++ b/configure.in @@ -268,9 +268,20 @@ AC_ARG_WITH(gamin, AC_HELP_STRING([--with-gamin],[gamin for reducing number of s AC_DEFINE([HAVE_FAM_H], [1], [fam.h]) ]) ],[AC_MSG_RESULT(no)]) -AC_SUBST(FAM_LIB) +AC_MSG_CHECKING(for properties in mod_webdav) +AC_ARG_WITH(webdav-props, AC_HELP_STRING([--with-webdav-props],[properties in mod_webdav]), +[AC_MSG_RESULT(yes) + PKG_CHECK_MODULES(XML, libxml-2.0, [ + AC_DEFINE([HAVE_LIBXML2], [1], [libxml2]) + AC_DEFINE([HAVE_LIBXML_H], [1], [libxml.h]) + ]) + PKG_CHECK_MODULES(SQLITE, sqlite3, [ + AC_DEFINE([HAVE_SQLITE3], [1], [libsqlite3]) + AC_DEFINE([HAVE_SQLITE3_H], [1], [sqlite3.h]) + ]) +],[AC_MSG_RESULT(no)]) AC_MSG_CHECKING(for gdbm) AC_ARG_WITH(gdbm, AC_HELP_STRING([--with-gdbm],[gdbm storage for mod_trigger_b4_dl]), @@ -292,7 +303,7 @@ AC_ARG_WITH(memcache, AC_HELP_STRING([--with-memcache],[memcached storage for mo AC_CHECK_HEADERS([memcache.h],[ MEMCACHE_LIB=-lmemcache AC_DEFINE([HAVE_MEMCACHE], [1], [libmemcache]) - AC_DEFINE([HAVE_MEMCACHE_H], [1]) + AC_DEFINE([HAVE_MEMCACHE_H], [1], [memcache.h]) ]) ]) ],[AC_MSG_RESULT(no)]) @@ -301,14 +312,22 @@ AC_SUBST(MEMCACHE_LIB) AC_MSG_CHECKING(for lua) AC_ARG_WITH(lua, AC_HELP_STRING([--with-lua],[lua engine for mod_cml]), [AC_MSG_RESULT(yes) - AC_CHECK_LIB(lua, lua_open, [ + AC_PATH_PROG(LUACONFIG, lua-config) + if test x"$LUACONFIG" != x; then + LUA_CFLAGS=`$LUACONFIG --include` + LUA_LIB=`$LUACONFIG --libs --extralibs` + else + AC_CHECK_LIB(lua, lua_open, [ AC_CHECK_HEADERS([lua.h],[ - LUA_LIB="-llua -llualib -lm" + LUA_LIB=-llua AC_DEFINE([HAVE_LUA], [1], [liblua]) - AC_DEFINE([HAVE_LUA_H], [1]) + AC_DEFINE([HAVE_LUA_H], [1], [lua.h]) ]) - ]) + ]) + fi + ],[AC_MSG_RESULT(no)]) +AC_SUBST(LUA_CFLAGS) AC_SUBST(LUA_LIB) @@ -548,12 +567,18 @@ else fi features="stat-cache-fam" -if test ! "x$FAM_LIB" = x; then +if test ! "x$FAM_LIBS" = x; then enable_feature="$enable_feature $features" else disable_feature="$disable_feature $features" fi +features="webdav-properties" +if test "x$XML_LIBS" \!= x -a "x$SQLITE_LIBS" \!= x; then + enable_feature="$enable_feature $features" +else + disable_feature="$disable_feature $features" +fi ## post processing do_build=`echo $do_build | sed 's/ /\n/g' | sort` diff --git a/src/Makefile.am b/src/Makefile.am index 96254c7a..8328ff00 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -70,11 +70,13 @@ endif lib_LTLIBRARIES += mod_webdav.la mod_webdav_la_SOURCES = mod_webdav.c +mod_webdav_la_CFLAGS = $(XML_CFLAGS) mod_webdav_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined -mod_webdav_la_LIBADD = $(common_libadd) +mod_webdav_la_LIBADD = $(common_libadd) $(XML_LIBS) $(SQLITE_LIBS) lib_LTLIBRARIES += mod_cml.la mod_cml_la_SOURCES = mod_cml.c mod_cml_lua.c mod_cml_funcs.c +mod_cml_la_CFLAGS = $(AM_CFLAGS) $(LUA_CFLAGS) mod_cml_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined mod_cml_la_LIBADD = $(MEMCACHE_LIB) $(common_libadd) $(LUA_LIB) diff --git a/src/connections.c b/src/connections.c index 1c1722f9..20c24e4a 100644 --- a/src/connections.c +++ b/src/connections.c @@ -306,6 +306,10 @@ static int connection_handle_write_prepare(server *srv, connection *con) { case HTTP_METHOD_PUT: case HTTP_METHOD_MKCOL: case HTTP_METHOD_DELETE: + case HTTP_METHOD_COPY: + case HTTP_METHOD_MOVE: + case HTTP_METHOD_PROPFIND: + case HTTP_METHOD_PROPPATCH: break; case HTTP_METHOD_OPTIONS: if (con->uri.path->ptr[0] != '*') { diff --git a/src/keyvalue.c b/src/keyvalue.c index 194c6967..873153f3 100644 --- a/src/keyvalue.c +++ b/src/keyvalue.c @@ -16,10 +16,13 @@ static keyvalue http_methods[] = { { HTTP_METHOD_POST, "POST" }, { HTTP_METHOD_HEAD, "HEAD" }, { HTTP_METHOD_PROPFIND, "PROPFIND" }, + { HTTP_METHOD_PROPPATCH, "PROPPATCH" }, { HTTP_METHOD_OPTIONS, "OPTIONS" }, { HTTP_METHOD_MKCOL, "MKCOL" }, { HTTP_METHOD_PUT, "PUT" }, { HTTP_METHOD_DELETE, "DELETE" }, + { HTTP_METHOD_COPY, "COPY" }, + { HTTP_METHOD_MOVE, "MOVE" }, { HTTP_METHOD_UNSET, NULL } }; diff --git a/src/keyvalue.h b/src/keyvalue.h index 611973c8..e3a9aa10 100644 --- a/src/keyvalue.h +++ b/src/keyvalue.h @@ -7,7 +7,7 @@ # include <pcre.h> #endif -typedef enum { HTTP_METHOD_UNSET = -1, HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD, HTTP_METHOD_PROPFIND, HTTP_METHOD_OPTIONS, HTTP_METHOD_MKCOL, HTTP_METHOD_PUT, HTTP_METHOD_DELETE } http_method_t; +typedef enum { HTTP_METHOD_UNSET = -1, HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD, HTTP_METHOD_PROPFIND, HTTP_METHOD_OPTIONS, HTTP_METHOD_MKCOL, HTTP_METHOD_PUT, HTTP_METHOD_DELETE, HTTP_METHOD_COPY, HTTP_METHOD_MOVE, HTTP_METHOD_PROPPATCH } http_method_t; typedef enum { HTTP_VERSION_UNSET = -1, HTTP_VERSION_1_0, HTTP_VERSION_1_1 } http_version_t; typedef struct { diff --git a/src/mod_webdav.c b/src/mod_webdav.c index 1ed02f2b..ecee69f7 100644 --- a/src/mod_webdav.c +++ b/src/mod_webdav.c @@ -7,6 +7,12 @@ #include <errno.h> #include <unistd.h> #include <fcntl.h> +#include <stdio.h> + +#include <libxml/tree.h> +#include <libxml/parser.h> + +#include <sqlite3.h> #include "base.h" #include "log.h" @@ -16,6 +22,8 @@ #include "plugin.h" #include "config.h" +#include "stream.h" +#include "stat_cache.h" /** * this is a webdav for a lighttpd plugin @@ -32,13 +40,27 @@ typedef struct { unsigned short enabled; unsigned short is_readonly; + + buffer *sqlite_db_name; + + sqlite3 *sql; + sqlite3_stmt *stmt_update_prop; + sqlite3_stmt *stmt_delete_prop; + sqlite3_stmt *stmt_select_prop; + sqlite3_stmt *stmt_select_propnames; + + sqlite3_stmt *stmt_delete_uri; + sqlite3_stmt *stmt_move_uri; + sqlite3_stmt *stmt_copy_uri; } plugin_config; typedef struct { PLUGIN_DATA; buffer *tmp_buf; - + request_uri uri; + physical physical; + plugin_config **config_storage; plugin_config conf; @@ -51,6 +73,16 @@ INIT_FUNC(mod_webdav_init) { p = calloc(1, sizeof(*p)); p->tmp_buf = buffer_init(); + + p->uri.scheme = buffer_init(); + p->uri.path_raw = buffer_init(); + p->uri.path = buffer_init(); + p->uri.authority = buffer_init(); + + p->physical.path = buffer_init(); + p->physical.rel_path = buffer_init(); + p->physical.doc_root = buffer_init(); + p->physical.basedir = buffer_init(); return p; } @@ -69,7 +101,16 @@ FREE_FUNC(mod_webdav_free) { plugin_config *s = p->config_storage[i]; if (!s) continue; - + + if (s->sql) { + sqlite3_finalize(s->stmt_delete_prop); + sqlite3_finalize(s->stmt_delete_uri); + sqlite3_finalize(s->stmt_update_prop); + sqlite3_finalize(s->stmt_select_prop); + sqlite3_finalize(s->stmt_select_propnames); + sqlite3_close(s->sql); + } + free(s); } free(p->config_storage); @@ -91,6 +132,7 @@ SETDEFAULTS_FUNC(mod_webdav_set_defaults) { config_values_t cv[] = { { "webdav.activate", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ { "webdav.is-readonly", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { "webdav.sqlite-db-name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } }; @@ -102,15 +144,107 @@ SETDEFAULTS_FUNC(mod_webdav_set_defaults) { plugin_config *s; s = calloc(1, sizeof(plugin_config)); + s->sqlite_db_name = buffer_init(); cv[0].destination = &(s->enabled); cv[1].destination = &(s->is_readonly); + cv[2].destination = s->sqlite_db_name; p->config_storage[i] = s; if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { return HANDLER_ERROR; } + + if (!buffer_is_empty(s->sqlite_db_name)) { + const char *next_stmt; + char *err; + + if (SQLITE_OK != sqlite3_open(s->sqlite_db_name->ptr, &(s->sql))) { + log_error_write(srv, __FILE__, __LINE__, "s", "sqlite3_open failed"); + return HANDLER_ERROR; + } + + 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)) { + /* prepare failed */ + + log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql)); + return HANDLER_ERROR; + } + + if (SQLITE_OK != sqlite3_prepare(s->sql, + CONST_STR_LEN("SELECT ns, prop FROM properties WHERE resource = ?"), + &(s->stmt_select_propnames), &next_stmt)) { + /* prepare failed */ + + log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql)); + return HANDLER_ERROR; + } + + if (SQLITE_OK != sqlite3_exec(s->sql, + "CREATE TABLE properties (" + " resource TEXT NOT NULL," + " prop TEXT NOT NULL," + " ns TEXT NOT NULL," + " value TEXT NOT NULL," + " PRIMARY KEY(resource, prop, ns))", + NULL, NULL, &err)) { + + if (0 != strcmp(err, "table properties already exists")) { + log_error_write(srv, __FILE__, __LINE__, "ss", "can't open transaction:", err); + sqlite3_free(err); + + return HANDLER_ERROR; + } + } + + if (SQLITE_OK != sqlite3_prepare(s->sql, + CONST_STR_LEN("REPLACE INTO properties (resource, prop, ns, value) VALUES (?, ?, ?, ?)"), + &(s->stmt_update_prop), &next_stmt)) { + /* prepare failed */ + + log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql)); + return HANDLER_ERROR; + } + + if (SQLITE_OK != sqlite3_prepare(s->sql, + CONST_STR_LEN("DELETE FROM properties WHERE resource = ? AND prop = ? AND ns = ?"), + &(s->stmt_delete_prop), &next_stmt)) { + /* prepare failed */ + log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql)); + + return HANDLER_ERROR; + } + + if (SQLITE_OK != sqlite3_prepare(s->sql, + CONST_STR_LEN("DELETE FROM properties WHERE resource = ?"), + &(s->stmt_delete_uri), &next_stmt)) { + /* prepare failed */ + log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql)); + + return HANDLER_ERROR; + } + + if (SQLITE_OK != sqlite3_prepare(s->sql, + CONST_STR_LEN("INSERT INTO properties SELECT ?, prop, ns, value FROM properties WHERE resource = ?"), + &(s->stmt_copy_uri), &next_stmt)) { + /* prepare failed */ + log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql)); + + return HANDLER_ERROR; + } + + if (SQLITE_OK != sqlite3_prepare(s->sql, + CONST_STR_LEN("UPDATE 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)); + + return HANDLER_ERROR; + } + } } return HANDLER_GO_ON; @@ -125,6 +259,16 @@ static int mod_webdav_patch_connection(server *srv, connection *con, plugin_data PATCH(enabled); PATCH(is_readonly); + PATCH(sql); + PATCH(stmt_update_prop); + PATCH(stmt_delete_prop); + PATCH(stmt_select_prop); + PATCH(stmt_select_propnames); + + PATCH(stmt_delete_uri); + PATCH(stmt_move_uri); + PATCH(stmt_copy_uri); + /* skip the first, the global context */ for (i = 1; i < srv->config_context->used; i++) { data_config *dc = (data_config *)srv->config_context->data[i]; @@ -141,6 +285,17 @@ static int mod_webdav_patch_connection(server *srv, connection *con, plugin_data PATCH(enabled); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.is-readonly"))) { PATCH(is_readonly); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.sqlite-db-name"))) { + PATCH(sql); + PATCH(stmt_update_prop); + PATCH(stmt_delete_prop); + PATCH(stmt_select_prop); + PATCH(stmt_select_propnames); + + PATCH(stmt_delete_uri); + PATCH(stmt_move_uri); + PATCH(stmt_copy_uri); + } } } @@ -169,7 +324,7 @@ URIHANDLER_FUNC(mod_webdav_uri_handler) { if (p->conf.is_readonly) { response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("PROPFIND")); } else { - response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("PROPFIND, DELETE, MKCOL")); + response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("PROPFIND, DELETE, MKCOL, PUT, MOVE, COPY")); } break; default: @@ -179,192 +334,488 @@ URIHANDLER_FUNC(mod_webdav_uri_handler) { /* not found */ return HANDLER_GO_ON; } +static int webdav_gen_prop_tag(server *srv, connection *con, + xmlChar *prop_name, + xmlChar *prop_ns, + const char *value, + buffer *b) { + + if (value) { + buffer_append_string(b,"<"); + buffer_append_string(b, prop_name); + buffer_append_string(b, " xmlns=\""); + buffer_append_string(b, prop_ns); + buffer_append_string(b, "\">"); + + buffer_append_string(b, value); + + buffer_append_string(b,"</"); + buffer_append_string(b, prop_name); + buffer_append_string(b, ">"); + } else { + buffer_append_string(b,"<"); + buffer_append_string(b, prop_name); + buffer_append_string(b, " xmlns=\""); + buffer_append_string(b, prop_ns); + buffer_append_string(b, "\"/>"); + } -static int get_response_entry(server *srv, connection *con, plugin_data *p, buffer *b, const char *filename) { - struct stat st; + return 0; +} - UNUSED(srv); +static int webdav_gen_response_status_tag(server *srv, connection *con, physical *dst, int status, buffer *b) { buffer_append_string(b,"<D:response xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\">\n"); buffer_append_string(b,"<D:href>\n"); - buffer_append_string_buffer(b, con->uri.path); - BUFFER_APPEND_SLASH(b); - buffer_append_string(b, filename); + buffer_append_string_buffer(b, dst->rel_path); buffer_append_string(b,"</D:href>\n"); - buffer_append_string(b,"<D:propstat>\n"); - /* we have to stat now */ - - buffer_copy_string_buffer(p->tmp_buf, con->physical.path); - BUFFER_APPEND_SLASH(p->tmp_buf); - buffer_append_string(p->tmp_buf, filename); + buffer_append_string(b,"<D:status>\n"); + + if (con->request.http_version == HTTP_VERSION_1_1) { + BUFFER_COPY_STRING_CONST(b, "HTTP/1.1 "); + } else { + BUFFER_COPY_STRING_CONST(b, "HTTP/1.0 "); + } + buffer_append_long(b, status); + BUFFER_APPEND_STRING_CONST(b, " "); + buffer_append_string(b, get_http_status_name(status)); - if (0 == stat(p->tmp_buf->ptr, &st)) { - char ctime_buf[] = "2005-08-18T07:27:16Z"; - char mtime_buf[] = "Thu, 18 Aug 2005 07:27:16 GMT"; - size_t k; + buffer_append_string(b,"</D:status>\n"); + buffer_append_string(b,"</D:response>\n"); - buffer_append_string(b,"<D:prop>\n"); - if (S_ISDIR(st.st_mode)) { - buffer_append_string(b, "<D:resourcetype><D:collection/></D:resourcetype>" - "<D:getcontenttype>httpd/unix-directory</D:getcontenttype>\n"); - } + return 0; +} - buffer_append_string(b, "<D:creationdate ns0:dt=\"dateTime.tz\">"); - strftime(ctime_buf, sizeof(ctime_buf), "%Y-%m-%dT%H:%M:%SZ", gmtime(&st.st_ctime)); - buffer_append_string(b, ctime_buf); - buffer_append_string(b, "</D:creationdate>"); - - buffer_append_string(b,"<D:getlastmodified ns0:dt=\"dateTime.rfc1123\">"); - strftime(mtime_buf, sizeof(mtime_buf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&st.st_mtime)); - buffer_append_string(b, mtime_buf); - buffer_append_string(b, "</D:getlastmodified>"); +static int webdav_delete_file(server *srv, connection *con, plugin_data *p, physical *dst, buffer *b) { + int status = 0; - buffer_append_string(b,"<D:getcontentlength>"); - buffer_append_off_t(b, st.st_size); - buffer_append_string(b, "</D:getcontentlength>"); + /* try to unlink it */ + if (-1 == unlink(dst->path->ptr)) { + switch(errno) { + case EACCES: + case EPERM: + /* 403 */ + status = 403; + break; + default: + status = 501; + break; + } + webdav_gen_response_status_tag(srv, con, dst, status, b); + } else { + sqlite3_stmt *stmt = p->conf.stmt_delete_uri; - buffer_append_string(b,"<D:getcontentlanguage>"); - buffer_append_string(b, "en"); - buffer_append_string(b, "</D:getcontentlanguage>"); + sqlite3_reset(stmt); - 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_equal_right_len(p->tmp_buf, ds->key, ds->key->used - 1)) { - buffer_append_string(b,"<D:contenttype>"); - buffer_append_string_buffer(b, ds->value); - buffer_append_string(b, "</D:contenttype>"); + /* bind the values to the insert */ - break; - } + sqlite3_bind_text(stmt, 1, + dst->rel_path->ptr, + dst->rel_path->used - 1, + SQLITE_TRANSIENT); + + if (SQLITE_DONE != sqlite3_step(stmt)) { + /* */ + WP(); } - - buffer_append_string(b,"</D:prop>\n"); - - buffer_append_string(b,"<D:status>HTTP/1.1 200 OK</D:status>\n"); - } else { - buffer_append_string(b,"<D:status>HTTP/1.1 404 Not Found</D:status>\n"); } - buffer_append_string(b,"</D:propstat>\n"); - - buffer_append_string(b,"</D:response>\n"); - return 0; + return (status != 0); } -static int webdav_unlink_dir(server *srv, connection *con, buffer *subdir, buffer *b) { +static int webdav_delete_dir(server *srv, connection *con, plugin_data *p, physical *dst, buffer *b) { DIR *dir; - buffer *dirname; int have_multi_status = 0; + physical d; - dirname = buffer_init(); - - buffer_copy_string_buffer(dirname, con->physical.path); - BUFFER_APPEND_SLASH(dirname); - if (subdir) buffer_append_string_buffer(dirname, subdir); + d.path = buffer_init(); + d.rel_path = buffer_init(); - if (NULL != (dir = opendir(dirname->ptr))) { + if (NULL != (dir = opendir(dst->path->ptr))) { struct dirent *de; while(NULL != (de = readdir(dir))) { - if (de->d_name[0] == '.' && de->d_name[1] == '\0') { - /* ignore the current dir */ - } else if (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0') { + struct stat st; + 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')) { + continue; /* ignore the parent dir */ - } else { - struct stat st; - buffer *next_subdir = buffer_init(); - int status = 0; + } - if (subdir) { - buffer_copy_string_buffer(next_subdir, subdir); - } else { - buffer_copy_string(next_subdir, "/"); - } - BUFFER_APPEND_SLASH(next_subdir); - buffer_append_string(next_subdir, de->d_name); - - /* physical name of the resource */ - buffer_copy_string_buffer(dirname, con->physical.path); - BUFFER_APPEND_SLASH(dirname); - buffer_append_string_buffer(dirname, next_subdir); - - /* stat and unlink afterwards */ - if (-1 == stat(dirname->ptr, &st)) { - /* don't about it yet, unlink will fail too */ - } else if (S_ISDIR(st.st_mode)) { - have_multi_status = webdav_unlink_dir(srv, con, next_subdir, b); + buffer_copy_string_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_append_string(d.rel_path, de->d_name); + + /* stat and unlink afterwards */ + if (-1 == stat(d.path->ptr, &st)) { + /* don't about it yet, rmdir will fail too */ + } else if (S_ISDIR(st.st_mode)) { + have_multi_status = webdav_delete_dir(srv, con, p, &d, b); - /* try to unlink it */ - if (-1 == rmdir(dirname->ptr)) { - switch(errno) { - case EACCES: - case EPERM: - /* 403 */ - status = 403; - break; - default: - status = 501; - break; - } - } else { - status = 0; + /* try to unlink it */ + if (-1 == rmdir(d.path->ptr)) { + switch(errno) { + case EACCES: + case EPERM: + /* 403 */ + status = 403; + break; + default: + status = 501; + break; } + have_multi_status = 1; + + webdav_gen_response_status_tag(srv, con, &d, status, b); } else { - /* try to unlink it */ - if (-1 == unlink(dirname->ptr)) { - switch(errno) { - case EACCES: - case EPERM: - /* 403 */ - status = 403; - break; - default: - status = 501; - break; - } - } else { - status = 0; + sqlite3_stmt *stmt = p->conf.stmt_delete_uri; + + status = 0; + + sqlite3_reset(stmt); + + /* bind the values to the insert */ + + sqlite3_bind_text(stmt, 1, + d.rel_path->ptr, + d.rel_path->used - 1, + SQLITE_TRANSIENT); + + if (SQLITE_DONE != sqlite3_step(stmt)) { + /* */ + WP(); } } + } else { + have_multi_status = webdav_delete_file(srv, con, p, &d, b); + } + } + closedir(dir); - if (status) { - have_multi_status = 1; + buffer_free(d.path); + buffer_free(d.rel_path); + } - buffer_append_string(b,"<D:response xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\">\n"); + return have_multi_status; +} - buffer_append_string(b,"<D:href>\n"); - buffer_append_string_buffer(b, con->uri.path); - BUFFER_APPEND_SLASH(b); - buffer_append_string_buffer(b, next_subdir); - buffer_append_string(b,"</D:href>\n"); - buffer_append_string(b,"<D:status>\n"); +static int webdav_copy_file(server *srv, connection *con, plugin_data *p, physical *src, physical *dst, int overwrite) { + stream s; + int status = 0, ofd; + + if (stream_open(&s, src->path)) { + return 403; + } + + if (-1 == (ofd = open(dst->path->ptr, O_WRONLY|O_TRUNC|O_CREAT|(overwrite ? 0 : O_EXCL), 0600))) { + /* opening the destination failed for some reason */ + switch(errno) { + case EEXIST: + status = 412; + break; + case EISDIR: + status = 409; + break; + case ENOENT: + /* at least one part in the middle wasn't existing */ + status = 409; + break; + default: + status = 403; + break; + } + stream_close(&s); + return status; + } + + if (-1 == write(ofd, s.start, s.size)) { + switch(errno) { + case ENOSPC: + status = 507; + break; + default: + status = 403; + break; + } + } - if (con->request.http_version == HTTP_VERSION_1_1) { - BUFFER_COPY_STRING_CONST(b, "HTTP/1.1 "); - } else { - BUFFER_COPY_STRING_CONST(b, "HTTP/1.0 "); + stream_close(&s); + close(ofd); + + if (0 == status) { + /* copy worked fine, copy connected properties */ + sqlite3_stmt *stmt = p->conf.stmt_copy_uri; + + sqlite3_reset(stmt); + + /* bind the values to the insert */ + sqlite3_bind_text(stmt, 1, + dst->rel_path->ptr, + dst->rel_path->used - 1, + SQLITE_TRANSIENT); + + sqlite3_bind_text(stmt, 2, + src->rel_path->ptr, + src->rel_path->used - 1, + SQLITE_TRANSIENT); + + if (SQLITE_DONE != sqlite3_step(stmt)) { + /* */ + WP(); + } + } + + return status; +} + +static int webdav_copy_dir(server *srv, connection *con, plugin_data *p, physical *src, physical *dst, int overwrite) { + DIR *srcdir; + int status = 0; + + if (NULL != (srcdir = opendir(src->path->ptr))) { + struct dirent *de; + physical s, d; + + s.path = buffer_init(); + s.rel_path = buffer_init(); + + d.path = buffer_init(); + d.rel_path = buffer_init(); + + 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')) { + continue; + } + + buffer_copy_string_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_append_string(d.path, de->d_name); + + buffer_copy_string_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_append_string(d.rel_path, de->d_name); + + if (-1 == stat(s.path->ptr, &st)) { + /* why ? */ + } else if (S_ISDIR(st.st_mode)) { + /* a directory */ + if (-1 == mkdir(d.path->ptr, 0700) && + errno != EEXIST) { + /* WTH ? */ + } else { + sqlite3_stmt *stmt = p->conf.stmt_copy_uri; + + if (0 != (status = webdav_copy_dir(srv, con, p, &s, &d, overwrite))) { + break; + } + /* directory is copied, copy the properties too */ + + sqlite3_reset(stmt); + + /* bind the values to the insert */ + sqlite3_bind_text(stmt, 1, + dst->rel_path->ptr, + dst->rel_path->used - 1, + SQLITE_TRANSIENT); + + sqlite3_bind_text(stmt, 2, + src->rel_path->ptr, + src->rel_path->used - 1, + SQLITE_TRANSIENT); + + if (SQLITE_DONE != sqlite3_step(stmt)) { + /* */ + WP(); } - buffer_append_long(b, status); - BUFFER_APPEND_STRING_CONST(b, " "); - buffer_append_string(b, get_http_status_name(status)); + } + } else if (S_ISREG(st.st_mode)) { + /* a plain file */ + if (0 != (status = webdav_copy_file(srv, con, p, &s, &d, overwrite))) { + break; + } + } + } - buffer_append_string(b,"</D:status>\n"); - buffer_append_string(b,"</D:response>\n"); + buffer_free(s.path); + buffer_free(s.rel_path); + buffer_free(d.path); + buffer_free(d.rel_path); + + closedir(srcdir); + } + + return status; +} + +static int webdav_get_live_property(server *srv, connection *con, plugin_data *p, physical *dst, xmlChar *prop_name, buffer *b) { + stat_cache_entry *sce = NULL; + int found = 0; + + if (HANDLER_ERROR != (stat_cache_get_entry(srv, con, dst->path, &sce))) { + char ctime_buf[] = "2005-08-18T07:27:16Z"; + char mtime_buf[] = "Thu, 18 Aug 2005 07:27:16 GMT"; + size_t k; + + if (0 == xmlStrcmp(prop_name, BAD_CAST "resourcetype")) { + if (S_ISDIR(sce->st.st_mode)) { + buffer_append_string(b, "<D:resourcetype><D:collection/></D:resourcetype>"); + found = 1; + } + } else if (0 == xmlStrcmp(prop_name, BAD_CAST "getcontenttype")) { + if (S_ISDIR(sce->st.st_mode)) { + buffer_append_string(b, "<D:getcontenttype>httpd/unix-directory</D:getcontenttype>"); + found = 1; + } else if(S_ISREG(sce->st.st_mode)) { + 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_equal_right_len(p->tmp_buf, ds->key, ds->key->used - 1)) { + buffer_append_string(b,"<D:getcontenttype>"); + buffer_append_string_buffer(b, ds->value); + buffer_append_string(b, "</D:getcontenttype>"); + found = 1; + + break; + } } - buffer_free(next_subdir); } + } else if (0 == xmlStrcmp(prop_name, BAD_CAST "creationdate")) { + buffer_append_string(b, "<D:creationdate ns0:dt=\"dateTime.tz\">"); + strftime(ctime_buf, sizeof(ctime_buf), "%Y-%m-%dT%H:%M:%SZ", gmtime(&(sce->st.st_ctime))); + buffer_append_string(b, ctime_buf); + buffer_append_string(b, "</D:creationdate>"); + found = 1; + } else if (0 == xmlStrcmp(prop_name, BAD_CAST "getlastmodified")) { + buffer_append_string(b,"<D:getlastmodified ns0:dt=\"dateTime.rfc1123\">"); + strftime(mtime_buf, sizeof(mtime_buf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(sce->st.st_mtime))); + buffer_append_string(b, mtime_buf); + buffer_append_string(b, "</D:getlastmodified>"); + found = 1; + } else if (0 == xmlStrcmp(prop_name, BAD_CAST "getcontentlength")) { + buffer_append_string(b,"<D:getcontentlength>"); + buffer_append_off_t(b, sce->st.st_size); + buffer_append_string(b, "</D:getcontentlength>"); + found = 1; + } else if (0 == xmlStrcmp(prop_name, BAD_CAST "getcontentlanguage")) { + buffer_append_string(b,"<D:getcontentlanguage>"); + buffer_append_string(b, "en"); + buffer_append_string(b, "</D:getcontentlanguage>"); + found = 1; } - closedir(dir); } - buffer_free(dirname); + return found ? 0 : -1; +} + +static int webdav_get_property(server *srv, connection *con, plugin_data *p, physical *dst, xmlChar *prop_name, xmlChar *prop_ns, buffer *b) { + if (0 == xmlStrcmp(prop_ns, BAD_CAST "DAV:")) { + /* a local 'live' property */ + return webdav_get_live_property(srv, con, p, dst, prop_name, b); + } else { + int found = 0; + /* perhaps it is in sqlite3 */ + sqlite3_reset(p->conf.stmt_select_prop); + + /* bind the values to the insert */ + + sqlite3_bind_text(p->conf.stmt_select_prop, 1, + dst->rel_path->ptr, + dst->rel_path->used - 1, + SQLITE_TRANSIENT); + sqlite3_bind_text(p->conf.stmt_select_prop, 2, + prop_name, + strlen(prop_name), + SQLITE_TRANSIENT); + sqlite3_bind_text(p->conf.stmt_select_prop, 3, + prop_ns, + strlen(prop_ns), + SQLITE_TRANSIENT); + + /* it is the PK */ + while (SQLITE_ROW == sqlite3_step(p->conf.stmt_select_prop)) { + /* there is a row for us, we only expect a single col 'value' */ + webdav_gen_prop_tag(srv, con, prop_name, prop_ns, sqlite3_column_text(p->conf.stmt_select_prop, 0), b); + found = 1; + } + return found ? 0 : -1; + } + + /* not found */ + return -1; +} - return have_multi_status; +typedef struct { + char *ns; + char *prop; +} webdav_property; + +webdav_property live_properties[] = { + { "DAV:", "creationdate" }, + { "DAV:", "displayname" }, + { "DAV:", "getcontentlanguage" }, + { "DAV:", "getcontentlength" }, + { "DAV:", "getcontenttype" }, + { "DAV:", "getetag" }, + { "DAV:", "getlastmodified" }, + { "DAV:", "resourcetype" }, + { "DAV:", "lockdiscovery" }, + { "DAV:", "source" }, + { "DAV:", "supportedlock" }, + + { NULL, NULL } +}; + +typedef struct { + webdav_property **ptr; + + size_t used; + size_t size; +} webdav_properties; + +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) { + for (i = 0; i < props->used; i++) { + webdav_property *prop; + + prop = props->ptr[i]; + + if (0 != webdav_get_property(srv, con, p, + &(con->physical), prop->prop, prop->ns, b_200)) { + webdav_gen_prop_tag(srv, con, prop->prop, prop->ns, NULL, b_404); + } + } + } else { + for (i = 0; live_properties[i].prop; i++) { + /* a local 'live' property */ + webdav_get_live_property(srv, con, p, dst, live_properties[i].prop, b_200); + } + } + + return 0; } + URIHANDLER_FUNC(mod_webdav_subrequest_handler) { plugin_data *p = p_d; buffer *b; @@ -372,6 +823,9 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { data_string *ds; int depth = -1; struct stat st; + buffer *prop_200; + buffer *prop_404; + webdav_properties *req_props; UNUSED(srv); @@ -379,7 +833,7 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { /* physical path is setup */ if (con->physical.path->used == 0) return HANDLER_GO_ON; - /* PROPFIND and DELETE need them */ + /* PROPFIND need them */ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Depth"))) { depth = strtol(ds->value->ptr, NULL, 10); } @@ -387,7 +841,95 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { switch (con->request.http_method) { case HTTP_METHOD_PROPFIND: /* they want to know the properties of the directory */ - + req_props = NULL; + + /* is there a content-body ? */ + + /* any special requests or just allprop ? */ + if (con->request.content_length) { + xmlDocPtr xml; + buffer *xmldoc = con->request.content; + + if (NULL != (xml = xmlReadMemory(xmldoc->ptr, xmldoc->used - 1, "DAV", NULL, 0))) { + xmlNode *rootnode = xmlDocGetRootElement(xml); + + if (0 == xmlStrcmp(rootnode->name, BAD_CAST "propfind")) { + xmlNode *cmd; + + req_props = calloc(1, sizeof(*req_props)); + + for (cmd = rootnode->children; cmd; cmd = cmd->next) { + + if (0 == xmlStrcmp(cmd->name, BAD_CAST "prop")) { + /* get prop by name */ + xmlNode *prop; + + for (prop = cmd->children; prop; prop = prop->next) { + if (prop->type == XML_TEXT_NODE) continue; /* ignore WS */ + + if (prop->ns && + (0 == xmlStrcmp(prop->ns->href, BAD_CAST "")) && + (0 != xmlStrcmp(prop->ns->prefix, BAD_CAST ""))) { + size_t i; + log_error_write(srv, __FILE__, __LINE__, "ss", + "no name space for:", + prop->name); + + xmlFreeDoc(xml); + + for (i = 0; i < req_props->used; i++) { + free(req_props->ptr[i]->ns); + free(req_props->ptr[i]->prop); + free(req_props->ptr[i]); + } + free(req_props->ptr); + free(req_props); + + con->http_status = 400; + return HANDLER_FINISHED; + } + + /* add property to requested list */ + if (req_props->size == 0) { + req_props->size = 16; + req_props->ptr = malloc(sizeof(*(req_props->ptr)) * req_props->size); + } else if (req_props->used == req_props->size) { + req_props->size += 16; + req_props->ptr = realloc(req_props->ptr, sizeof(*(req_props->ptr)) * req_props->size); + } + + req_props->ptr[req_props->used] = malloc(sizeof(webdav_property)); + req_props->ptr[req_props->used]->ns = xmlStrdup(prop->ns ? prop->ns->href : (xmlChar *)""); + req_props->ptr[req_props->used]->prop = xmlStrdup(prop->name); + req_props->used++; + } + } else if (0 == xmlStrcmp(cmd->name, BAD_CAST "propname")) { + /* get all property names (EMPTY) */ + sqlite3_reset(p->conf.stmt_select_propnames); + /* bind the values to the insert */ + + sqlite3_bind_text(p->conf.stmt_select_propnames, 1, + con->uri.path->ptr, + con->uri.path->used - 1, + SQLITE_TRANSIENT); + + if (SQLITE_DONE != sqlite3_step(p->conf.stmt_select_propnames)) { + WP(); + } + + } else if (0 == xmlStrcmp(cmd->name, BAD_CAST "allprop")) { + /* get all properties (EMPTY) */ + } + } + } + + xmlFreeDoc(xml); + } else { + con->http_status = 400; + return HANDLER_FINISHED; + } + } + con->http_status = 207; response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml; charset=\"utf-8\"")); @@ -396,31 +938,133 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { buffer_copy_string(b, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); - buffer_append_string(b,"<D:multistatus xmlns:D=\"DAV:\">\n"); + buffer_append_string(b,"<D:multistatus xmlns:D=\"DAV:\" xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\">\n"); + + /* allprop */ + + prop_200 = buffer_init(); + prop_404 = buffer_init(); switch(depth) { case 0: /* Depth: 0 */ - get_response_entry(srv, con, p, b, ""); + webdav_get_props(srv, con, p, &(con->physical), req_props, prop_200, prop_404); + + buffer_append_string(b,"<D:response>\n"); + buffer_append_string(b,"<D:href>"); + buffer_append_string_buffer(b, con->uri.scheme); + buffer_append_string(b,"://"); + buffer_append_string_buffer(b, con->uri.authority); + buffer_append_string_buffer(b, con->uri.path); + buffer_append_string(b,"</D:href>\n"); + + if (!buffer_is_empty(prop_200)) { + buffer_append_string(b,"<D:propstat>\n"); + buffer_append_string(b,"<D:prop>\n"); + + buffer_append_string_buffer(b, prop_200); + + buffer_append_string(b,"</D:prop>\n"); + + buffer_append_string(b,"<D:status>HTTP/1.1 200 OK</D:status>\n"); + + buffer_append_string(b,"</D:propstat>\n"); + } + if (!buffer_is_empty(prop_404)) { + buffer_append_string(b,"<D:propstat>\n"); + buffer_append_string(b,"<D:prop>\n"); + + buffer_append_string_buffer(b, prop_404); + + buffer_append_string(b,"</D:prop>\n"); + + buffer_append_string(b,"<D:status>HTTP/1.1 404 Not Found</D:status>\n"); + + buffer_append_string(b,"</D:propstat>\n"); + } + + buffer_append_string(b,"</D:response>\n"); + break; - case 1: + case 1: if (NULL != (dir = opendir(con->physical.path->ptr))) { struct dirent *de; + physical d; + physical *dst = &(con->physical); + + d.path = buffer_init(); + d.rel_path = buffer_init(); while(NULL != (de = readdir(dir))) { - if (de->d_name[0] == '.' && de->d_name[1] == '\0') { - /* ignore the current dir */ - } else 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[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')) { + continue; /* ignore the parent dir */ - } else { - get_response_entry(srv, con, p, b, de->d_name); + } + + buffer_copy_string_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_append_string(d.rel_path, de->d_name); + + webdav_get_props(srv, con, p, &d, req_props, prop_200, prop_404); + + buffer_append_string(b,"<D:response>\n"); + buffer_append_string(b,"<D:href>"); + buffer_append_string_buffer(b, con->uri.scheme); + buffer_append_string(b,"://"); + buffer_append_string_buffer(b, con->uri.authority); + buffer_append_string_buffer(b, d.rel_path); + buffer_append_string(b,"</D:href>\n"); + + if (!buffer_is_empty(prop_200)) { + buffer_append_string(b,"<D:propstat>\n"); + buffer_append_string(b,"<D:prop>\n"); + + buffer_append_string_buffer(b, prop_200); + + buffer_append_string(b,"</D:prop>\n"); + + buffer_append_string(b,"<D:status>HTTP/1.1 200 OK</D:status>\n"); + + buffer_append_string(b,"</D:propstat>\n"); + } + if (!buffer_is_empty(prop_404)) { + buffer_append_string(b,"<D:propstat>\n"); + buffer_append_string(b,"<D:prop>\n"); + + buffer_append_string_buffer(b, prop_404); + + buffer_append_string(b,"</D:prop>\n"); + + buffer_append_string(b,"<D:status>HTTP/1.1 404 Not Found</D:status>\n"); + + buffer_append_string(b,"</D:propstat>\n"); } + + buffer_append_string(b,"</D:response>\n"); } closedir(dir); + buffer_free(d.path); + buffer_free(d.rel_path); } break; } - + + if (req_props) { + size_t i; + for (i = 0; i < req_props->used; i++) { + free(req_props->ptr[i]->ns); + free(req_props->ptr[i]->prop); + free(req_props->ptr[i]); + } + free(req_props->ptr); + free(req_props); + } + buffer_append_string(b,"</D:multistatus>\n"); con->file_finished = 1; @@ -480,7 +1124,7 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { } else if (S_ISDIR(st.st_mode)) { buffer *multi_status_resp = buffer_init(); - if (webdav_unlink_dir(srv, con, NULL, multi_status_resp)) { + 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\"")); @@ -560,6 +1204,350 @@ URIHANDLER_FUNC(mod_webdav_subrequest_handler) { } return HANDLER_FINISHED; } + case HTTP_METHOD_MOVE: + case HTTP_METHOD_COPY: { + buffer *destination = NULL; + char *sep, *start; + int overwrite = 1; + + if (p->conf.is_readonly) { + con->http_status = 403; + return HANDLER_FINISHED; + } + + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Destination"))) { + destination = ds->value; + } else { + con->http_status = 400; + return HANDLER_FINISHED; + } + + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Overwrite"))) { + if (ds->value->used != 2 || + (ds->value->ptr[0] != 'F' && + ds->value->ptr[0] != 'T') ) { + con->http_status = 400; + return HANDLER_FINISHED; + } + overwrite = (ds->value->ptr[0] == 'F' ? 0 : 1); + } + /* let's parse the Destination + * + * http://127.0.0.1:1025/dav/litmus/copydest + * + * - host has to be the same as the Host: header we got + * - we have to stay inside the document root + * - the query string is thrown away + * */ + + buffer_reset(p->uri.scheme); + buffer_reset(p->uri.path_raw); + buffer_reset(p->uri.authority); + + start = destination->ptr; + + if (NULL == (sep = strstr(start, "://"))) { + con->http_status = 400; + return HANDLER_FINISHED; + } + buffer_copy_string_len(p->uri.scheme, start, sep - start); + + start = sep + 3; + + if (NULL == (sep = strchr(start, '/'))) { + con->http_status = 400; + return HANDLER_FINISHED; + } + buffer_copy_string_len(p->uri.authority, start, sep - start); + + start = sep + 1; + + if (NULL == (sep = strchr(start, '?'))) { + /* no query string, good */ + buffer_copy_string(p->uri.path_raw, start); + } else { + buffer_copy_string_len(p->uri.path_raw, start, sep - start); + } + + if (!buffer_is_equal(p->uri.authority, con->uri.authority)) { + /* not the same host */ + con->http_status = 502; + return HANDLER_FINISHED; + } + + buffer_copy_string_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->conf.document_root); + buffer_copy_string_buffer(p->physical.rel_path, p->uri.path); + + if (con->conf.force_lower_case) { + 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); + + /* 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); + } + + /* let's see if the source is a directory + * if yes, we fail with 501 */ + + if (-1 == stat(con->physical.path->ptr, &st)) { + /* don't about it yet, unlink will fail too */ + switch(errno) { + case ENOENT: + con->http_status = 404; + break; + default: + con->http_status = 403; + break; + } + } else if (S_ISDIR(st.st_mode)) { + int r; + /* src is a directory */ + + if (-1 == stat(p->physical.path->ptr, &st)) { + if (-1 == mkdir(p->physical.path->ptr, 0700)) { + con->http_status = 403; + return HANDLER_FINISHED; + } + } else if (!S_ISDIR(st.st_mode)) { + if (overwrite == 0) { + /* copying into a non-dir ? */ + con->http_status = 409; + return HANDLER_FINISHED; + } else { + unlink(p->physical.path->ptr); + if (-1 == mkdir(p->physical.path->ptr, 0700)) { + con->http_status = 403; + return HANDLER_FINISHED; + } + } + } + + /* copy the content of src to dest */ + if (0 != (r = webdav_copy_dir(srv, con, p, &(con->physical), &(p->physical), overwrite))) { + con->http_status = r; + return HANDLER_FINISHED; + } + if (con->request.http_method == HTTP_METHOD_MOVE) { + b = buffer_init(); + webdav_delete_dir(srv, con, p, &(con->physical), b); + buffer_free(b); + } + con->http_status = 201; + } else { + /* it is just a file, good */ + int r; + + /* destination exists */ + if (0 == (r = stat(p->physical.path->ptr, &st))) { + if (S_ISDIR(st.st_mode)) { + /* file to dir/ + * append basename to physical path */ + + if (NULL != (sep = strrchr(con->physical.path->ptr, '/'))) { + buffer_append_string(p->physical.path, sep); + r = stat(p->physical.path->ptr, &st); + } + } + } + + if (-1 == r) { + con->http_status = 201; /* we will create a new one */ + + switch(errno) { + case ENOTDIR: + con->http_status = 409; + return HANDLER_FINISHED; + } + } else if (overwrite == 0) { + /* destination exists, but overwrite is not set */ + con->http_status = 412; + return HANDLER_FINISHED; + } else { + con->http_status = 204; /* resource already existed */ + } + + if (con->request.http_method == HTTP_METHOD_MOVE) { + /* try a rename */ + + if (0 == rename(con->physical.path->ptr, p->physical.path->ptr)) { + sqlite3_stmt *stmt = p->conf.stmt_move_uri; + + sqlite3_reset(stmt); + + /* bind the values to the insert */ + sqlite3_bind_text(stmt, 1, + p->uri.path->ptr, + p->uri.path->used - 1, + SQLITE_TRANSIENT); + + sqlite3_bind_text(stmt, 2, + 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 failed:", sqlite3_errmsg(p->conf.sql)); + } + return HANDLER_FINISHED; + } + + /* rename failed, fall back to COPY + DELETE */ + } + + if (0 != (r = webdav_copy_file(srv, con, p, &(con->physical), &(p->physical), overwrite))) { + con->http_status = r; + + return HANDLER_FINISHED; + } + + if (con->request.http_method == HTTP_METHOD_MOVE) { + b = buffer_init(); + webdav_delete_file(srv, con, p, &(con->physical), b); + buffer_free(b); + } + } + + return HANDLER_FINISHED; + } + case HTTP_METHOD_PROPPATCH: { + + if (con->request.content_length) { + xmlDocPtr xml; + buffer *xmldoc = con->request.content; + + if (NULL != (xml = xmlReadMemory(xmldoc->ptr, xmldoc->used - 1, "DAV", NULL, 0))) { + xmlNode *rootnode = xmlDocGetRootElement(xml); + + if (0 == xmlStrcmp(rootnode->name, BAD_CAST "propertyupdate")) { + xmlNode *cmd; + char *err = NULL; + int empty_ns = 0; /* send 400 on a empty namespace attribute */ + + /* start response */ + + if (SQLITE_OK != sqlite3_exec(p->conf.sql, "BEGIN TRANSACTION", NULL, NULL, &err)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "can't open transaction:", err); + sqlite3_free(err); + + goto propmatch_cleanup; + } + + /* a UPDATE request, we know 'set' and 'remove' */ + for (cmd = rootnode->children; cmd; cmd = cmd->next) { + xmlNode *props; + /* either set or remove */ + + if ((0 == xmlStrcmp(cmd->name, BAD_CAST "set")) || + (0 == xmlStrcmp(cmd->name, BAD_CAST "remove"))) { + + sqlite3_stmt *stmt; + + stmt = (0 == xmlStrcmp(cmd->name, BAD_CAST "remove")) ? + p->conf.stmt_delete_prop : p->conf.stmt_update_prop; + + for (props = cmd->children; props; props = props->next) { + if (0 == xmlStrcmp(props->name, BAD_CAST "prop")) { + xmlNode *prop; + int r; + + prop = props->children; + + if (prop->ns && + (0 == xmlStrcmp(prop->ns->href, BAD_CAST "")) && + (0 != xmlStrcmp(prop->ns->prefix, BAD_CAST ""))) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "no name space for:", + prop->name); + + empty_ns = 1; + break; + } + + 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); + sqlite3_bind_text(stmt, 2, + prop->name, + strlen((char *)prop->name), + SQLITE_TRANSIENT); + if (prop->ns) { + sqlite3_bind_text(stmt, 3, + prop->ns->href, + strlen((char *)prop->ns->href), + SQLITE_TRANSIENT); + } else { + sqlite3_bind_text(stmt, 3, + "", + 0, + SQLITE_TRANSIENT); + } + if (stmt == p->conf.stmt_update_prop) { + sqlite3_bind_text(stmt, 4, + xmlNodeGetContent(prop), + strlen(xmlNodeGetContent(prop)), + 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 (empty_ns) break; + } + } + + if (empty_ns) { + if (SQLITE_OK != sqlite3_exec(p->conf.sql, "ROLLBACK", NULL, NULL, &err)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "can't rollback transaction:", err); + sqlite3_free(err); + + goto propmatch_cleanup; + } + + con->http_status = 400; + } else { + if (SQLITE_OK != sqlite3_exec(p->conf.sql, "COMMIT", NULL, NULL, &err)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "can't commit transaction:", err); + sqlite3_free(err); + + goto propmatch_cleanup; + } + con->http_status = 200; + } + con->file_finished = 1; + + return HANDLER_FINISHED; + } + +propmatch_cleanup: + xmlFreeDoc(xml); + } else { + con->http_status = 400; + return HANDLER_FINISHED; + } + } + + con->http_status = 501; + return HANDLER_FINISHED; + } default: break; } diff --git a/src/request.c b/src/request.c index 40778e6f..f07e4595 100644 --- a/src/request.c +++ b/src/request.c @@ -958,10 +958,12 @@ int http_request_parse(server *srv, connection *con) { { server_socket *srv_socket = con->srv_socket; +#if 0 if (con->request.http_content_type == NULL) { log_error_write(srv, __FILE__, __LINE__, "s", - "POST request, but content-type not set"); + "Content-Length request, but content-type not set"); } +#endif if (con_length_set == 0) { /* content-length is missing */ |