summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Kneschke <jan@kneschke.de>2005-08-20 19:10:44 +0000
committerJan Kneschke <jan@kneschke.de>2005-08-20 19:10:44 +0000
commit163c25a2a9d4e58e5ae17e09baf8fff234c8696b (patch)
treec421df8c3f45c8fdb5b492c6468689fb350e487b
parent30b1973266a42988a36a09f5ec95239c84193aee (diff)
downloadlighttpd-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.in39
-rw-r--r--src/Makefile.am4
-rw-r--r--src/connections.c4
-rw-r--r--src/keyvalue.c3
-rw-r--r--src/keyvalue.h2
-rw-r--r--src/mod_webdav.c1298
-rw-r--r--src/request.c4
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 */