diff --git a/config b/config new file mode 100755 index 0000000..007bbed --- /dev/null +++ b/config @@ -0,0 +1,27 @@ +ngx_addon_name=ngx_http_webdav_module + +HEADERS_MORE_SRCS="$ngx_addon_dir/ngx_http_webdav_module.c $ngx_addon_dir/ngx_http_webdav_utils.c" + +HEADERS_MORE_DEPS="$ngx_addon_dir/ngx_http_webdav_module.h $ngx_addon_dir/ngx_http_webdav_utils.h" + +if test -n "$ngx_module_link"; then + ngx_module_type=HTTP_AUX_FILTER + ngx_module_name=$ngx_addon_name + ngx_module_incs= + ngx_module_deps="$HEADERS_MORE_DEPS" + ngx_module_srcs="$HEADERS_MORE_SRCS" + + # nginx has robust builtin support for linking against + # libxml2+libxslt. This is definitelty the right way to go if + # building nginx with the xslt module, in which case libxslt will + # be linked anyway. In other cases libxslt is just redundant. + # If that's a big deal, libxml2 can be linked directly: + # ngx_module_libs=-lxml2 + ngx_module_libs=LIBXSLT + + . auto/module +else + HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES $ngx_addon_name" + NGX_ADDON_SRCS="$NGX_ADDON_SRCS $HEADERS_MORE_SRCS" + NGX_ADDON_DEPS="$NGX_ADDON_DEPS $HEADERS_MORE_DEPS" +fi diff --git a/ngx_http_webdav_module.c b/ngx_http_webdav_module.c new file mode 100644 index 0000000..177acae --- /dev/null +++ b/ngx_http_webdav_module.c @@ -0,0 +1,2412 @@ +/* + * Copyright (C) Starfields + */ + +#include +#include +#include +#include +#include "ngx_http_webdav_module.h" +#include "ngx_http_webdav_utils.h" + +#define NGX_HTTP_DAV_EXT_OFF 2 + +#define NGX_HTTP_DAV_EXT_PREALLOCATE 50 + +#define NGX_HTTP_DAV_EXT_NODE_PROPFIND 0x01 +#define NGX_HTTP_DAV_EXT_NODE_PROP 0x02 +#define NGX_HTTP_DAV_EXT_NODE_PROPNAME 0x04 +#define NGX_HTTP_DAV_EXT_NODE_ALLPROP 0x08 + +#define NGX_HTTP_DAV_EXT_PROP_DISPLAYNAME 0x01 +#define NGX_HTTP_DAV_EXT_PROP_GETCONTENTLENGTH 0x02 +#define NGX_HTTP_DAV_EXT_PROP_GETLASTMODIFIED 0x04 +#define NGX_HTTP_DAV_EXT_PROP_RESOURCETYPE 0x08 +#define NGX_HTTP_DAV_EXT_PROP_LOCKDISCOVERY 0x10 +#define NGX_HTTP_DAV_EXT_PROP_SUPPORTEDLOCK 0x20 + +#define NGX_HTTP_DAV_EXT_PROP_ALL 0x7f +#define NGX_HTTP_DAV_EXT_PROP_NAMES 0x80 + +#define NGX_HTTP_DAV_NO_DEPTH -3 +#define NGX_HTTP_DAV_INVALID_DEPTH -2 +#define NGX_HTTP_DAV_INFINITY_DEPTH -1 + + +typedef struct { + ngx_uint_t nodes; + ngx_uint_t props; +} ngx_http_dav_ext_xml_ctx_t; + + +typedef struct { + ngx_str_t path; + size_t len; +} ngx_http_dav_copy_ctx_t; + + +static ngx_int_t ngx_http_dav_ext_precontent_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_dav_ext_verify_lock(ngx_http_request_t *r, + ngx_str_t *uri, + ngx_uint_t delete_lock); +static ngx_int_t ngx_http_dav_ext_content_handler(ngx_http_request_t *r); +static void ngx_http_dav_ext_propfind_handler(ngx_http_request_t *r); +static void ngx_http_dav_ext_propfind_xml_start( + void *data, const xmlChar *localname, const xmlChar *prefix, + const xmlChar *uri, int nb_namespaces, const xmlChar **namespaces, + int nb_attributes, int nb_defaulted, const xmlChar **attributes); +static void ngx_http_dav_ext_propfind_xml_end(void *data, + const xmlChar *localname, + const xmlChar *prefix, + const xmlChar *uri); +static ngx_int_t ngx_http_dav_ext_propfind(ngx_http_request_t *r, + ngx_uint_t props); +static ngx_int_t ngx_http_dav_ext_propfind_response(ngx_http_request_t *r, + ngx_array_t *entries, + ngx_uint_t props); +static ngx_int_t ngx_http_dav_ext_lock_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_dav_ext_lock_response(ngx_http_request_t *r, + ngx_uint_t status, + time_t timeout, + ngx_uint_t depth, + uint32_t token); +static ngx_int_t ngx_http_dav_ext_unlock_handler(ngx_http_request_t *r); +static uint32_t ngx_http_dav_ext_lock_token(ngx_http_request_t *r); +static uintptr_t +ngx_http_dav_ext_format_propfind(ngx_http_request_t *r, u_char *dst, + ngx_http_dav_ext_entry_t *entry, + ngx_uint_t props); +static ngx_int_t ngx_http_dav_ext_init_zone(ngx_shm_zone_t *shm_zone, + void *data); +static void *ngx_http_dav_ext_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_dav_ext_merge_loc_conf(ngx_conf_t *cf, void *parent, + void *child); +static char *ngx_http_dav_ext_lock_zone(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_http_dav_ext_lock(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_http_dav_ext_init(ngx_conf_t *cf); + + + + + +static void ngx_http_dav_put_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_dav_delete_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_dav_mkcol_handler(ngx_http_request_t *r, + ngx_http_dav_ext_loc_conf_t *dlcf); +static ngx_int_t ngx_http_dav_copy_move_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path); +static ngx_int_t ngx_http_dav_copy_dir_time(ngx_tree_ctx_t *ctx, + ngx_str_t *path); +static ngx_int_t ngx_http_dav_copy_tree_file(ngx_tree_ctx_t *ctx, + ngx_str_t *path); +ngx_int_t ngx_http_dav_ext_set_locks(ngx_http_request_t *r, + ngx_http_dav_ext_entry_t *entry); + + +// An array tuple to define bitmasks with +static ngx_conf_bitmask_t ngx_http_dav_ext_methods_mask[] = { + { ngx_string("off"), NGX_HTTP_DAV_EXT_OFF }, + { ngx_string("put"), NGX_HTTP_PUT }, + { ngx_string("delete"), NGX_HTTP_DELETE }, + { ngx_string("mkcol"), NGX_HTTP_MKCOL }, + { ngx_string("copy"), NGX_HTTP_COPY }, + { ngx_string("move"), NGX_HTTP_MOVE }, + { ngx_string("propfind"), NGX_HTTP_PROPFIND }, + { ngx_string("options"), NGX_HTTP_OPTIONS }, + { ngx_string("lock"), NGX_HTTP_LOCK }, + { ngx_string("unlock"), NGX_HTTP_UNLOCK }, + { ngx_null_string, 0 } +}; + +// The module directives definition +static ngx_command_t ngx_http_dav_ext_commands[] = { + + {ngx_string("dav_ext_lock_zone"), NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE12, + ngx_http_dav_ext_lock_zone, 0, 0, NULL}, + + {ngx_string("dav_ext_lock"), + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | + NGX_CONF_TAKE1, + ngx_http_dav_ext_lock, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL}, + + { ngx_string("dav_ext_methods"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_conf_set_bitmask_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_dav_ext_loc_conf_t, methods), + &ngx_http_dav_ext_methods_mask }, + + { ngx_string("create_full_put_path"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_dav_ext_loc_conf_t, create_full_put_path), + NULL }, + + { ngx_string("min_delete_depth"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_dav_ext_loc_conf_t, min_delete_depth), + NULL }, + + { ngx_string("dav_access"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, + ngx_conf_set_access_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_dav_ext_loc_conf_t, access), + NULL }, + + ngx_null_command}; + +// Function references for nginx to create the configurations with +static ngx_http_module_t ngx_http_dav_ext_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_dav_ext_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_http_dav_ext_create_loc_conf, /* create location configuration */ + ngx_http_dav_ext_merge_loc_conf, /* merge location configuration */ +}; + +// The module definition +ngx_module_t ngx_http_dav_ext_module = { + NGX_MODULE_V1, + &ngx_http_dav_ext_module_ctx, /* module context */ + ngx_http_dav_ext_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + +// Performs initial request handling, mainly precondition checks +static ngx_int_t ngx_http_dav_ext_precontent_handler(ngx_http_request_t *r) { + ngx_str_t uri; + ngx_int_t rc; + ngx_uint_t delete_lock; + ngx_table_elt_t *dest; + ngx_http_dav_ext_loc_conf_t *dlcf; + + dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_ext_module); + + if (dlcf->shm_zone == NULL) { + return NGX_DECLINED; + } + + if (r->method & + (NGX_HTTP_PUT | NGX_HTTP_DELETE | NGX_HTTP_MKCOL | NGX_HTTP_MOVE)) { + + delete_lock = (r->method & (NGX_HTTP_DELETE | NGX_HTTP_MOVE)) ? 1 : 0; + + rc = ngx_http_dav_ext_verify_lock(r, &r->uri, delete_lock); + if (rc != NGX_OK) { + return rc; + } + } + + if (r->method & (NGX_HTTP_MOVE | NGX_HTTP_COPY)) { + dest = r->headers_in.destination; + if (dest == NULL) { + return NGX_DECLINED; + } + + uri.data = dest->value.data; + uri.len = dest->value.len; + + if (ngx_http_dav_ext_strip_uri(r, &uri) != NGX_OK) { + return NGX_DECLINED; + } + + rc = ngx_http_dav_ext_verify_lock(r, &uri, 0); + if (rc != NGX_OK) { + return rc; + } + } + + return NGX_DECLINED; +} + + +static ngx_int_t ngx_http_dav_ext_verify_lock(ngx_http_request_t *r, + ngx_str_t *uri, + ngx_uint_t delete_lock) { + uint32_t token; + ngx_http_dav_ext_node_t *node; + ngx_http_dav_ext_lock_t *lock; + ngx_http_dav_ext_loc_conf_t *dlcf; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext verify lock \"%V\"", uri); + + token = ngx_http_dav_ext_if(r, uri); + + dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_ext_module); + lock = dlcf->shm_zone->data; + + ngx_shmtx_lock(&lock->shpool->mutex); + + node = ngx_http_dav_ext_lock_lookup(r, lock, uri, -1); + if (node == NULL) { + ngx_shmtx_unlock(&lock->shpool->mutex); + return NGX_OK; + } + + if (token == 0) { + ngx_shmtx_unlock(&lock->shpool->mutex); + return 423; /* Locked */ + } + + if (token != node->token) { + ngx_shmtx_unlock(&lock->shpool->mutex); + return NGX_HTTP_PRECONDITION_FAILED; + } + + /* + * RFC4918: + * If a request causes the lock-root of any lock to become an + * unmapped URL, then the lock MUST also be deleted by that request. + */ + + if (delete_lock && node->len == uri->len) { + ngx_queue_remove(&node->queue); + ngx_slab_free_locked(lock->shpool, node); + } + + ngx_shmtx_unlock(&lock->shpool->mutex); + + return NGX_OK; +} + + +// Triggered by the postconfiguration handler as the starting point +static ngx_int_t ngx_http_dav_ext_content_handler(ngx_http_request_t *r) { + ngx_int_t rc; + ngx_table_elt_t *h; + ngx_http_dav_ext_loc_conf_t *dlcf; + + dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_ext_module); + + if (!(r->method & dlcf->methods)) { + return NGX_DECLINED; + } + + switch (r->method) { + + case NGX_HTTP_PROPFIND: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext propfind"); + + rc = + ngx_http_read_client_request_body(r, ngx_http_dav_ext_propfind_handler); + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + + return NGX_DONE; + + case NGX_HTTP_OPTIONS: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext options"); + + rc = ngx_http_discard_request_body(r); + + if (rc != NGX_OK) { + return rc; + } + + h = ngx_list_push(&r->headers_out.headers); + if (h == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_str_set(&h->key, "DAV"); + + if (dlcf->shm_zone) { + h->value.len = 4; + h->value.data = (u_char *)"1, 2"; + } else { + h->value.len = 1; + h->value.data = (u_char *)"1"; + } + + h->hash = 1; + + h = ngx_list_push(&r->headers_out.headers); + if (h == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + /* XXX */ + ngx_str_set(&h->key, "Allow"); + ngx_str_set(&h->value, "GET,HEAD,PUT,DELETE,MKCOL,COPY,MOVE,PROPFIND," + "OPTIONS,LOCK,UNLOCK"); + h->hash = 1; + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = 0; + + rc = ngx_http_send_header(r); + + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + return rc; + } + + return ngx_http_send_special(r, NGX_HTTP_LAST); + + case NGX_HTTP_LOCK: + + if (dlcf->shm_zone == NULL) { + return NGX_HTTP_NOT_ALLOWED; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext lock"); + + /* + * Body is expected to carry the requested lock type, but + * since we only support write/exclusive locks, we ignore it. + * Ideally we could throw an error if a lock of another type + * is requested, but the amount of work required for that is + * not worth it. + */ + + rc = ngx_http_discard_request_body(r); + + if (rc != NGX_OK) { + return rc; + } + + return ngx_http_dav_ext_lock_handler(r); + + case NGX_HTTP_UNLOCK: + + if (dlcf->shm_zone == NULL) { + return NGX_HTTP_NOT_ALLOWED; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext unlock"); + + rc = ngx_http_discard_request_body(r); + + if (rc != NGX_OK) { + return rc; + } + + return ngx_http_dav_ext_unlock_handler(r); + + case NGX_HTTP_PUT: + + if (r->uri.data[r->uri.len - 1] == '/') { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "cannot PUT to a collection"); + return NGX_HTTP_CONFLICT; + } + + if (r->headers_in.content_range) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "PUT with range is unsupported"); + return NGX_HTTP_NOT_IMPLEMENTED; + } + + r->request_body_in_file_only = 1; + r->request_body_in_persistent_file = 1; + r->request_body_in_clean_file = 1; + r->request_body_file_group_access = 1; + r->request_body_file_log_level = 0; + + rc = ngx_http_read_client_request_body(r, ngx_http_dav_put_handler); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + + return NGX_DONE; + + case NGX_HTTP_DELETE: + + return ngx_http_dav_delete_handler(r); + + case NGX_HTTP_MKCOL: + + return ngx_http_dav_mkcol_handler(r, dlcf); + + case NGX_HTTP_COPY: + + return ngx_http_dav_copy_move_handler(r); + + case NGX_HTTP_MOVE: + + return ngx_http_dav_copy_move_handler(r); + } + + return NGX_DECLINED; +} + + +static void ngx_http_dav_ext_propfind_handler(ngx_http_request_t *r) { + off_t len; + ngx_buf_t *b; + ngx_chain_t *cl; + xmlSAXHandler sax; + xmlParserCtxtPtr pctx; + ngx_http_dav_ext_xml_ctx_t xctx; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext propfind handler"); + + ngx_memzero(&xctx, sizeof(ngx_http_dav_ext_xml_ctx_t)); + ngx_memzero(&sax, sizeof(xmlSAXHandler)); + + sax.initialized = XML_SAX2_MAGIC; + sax.startElementNs = ngx_http_dav_ext_propfind_xml_start; + sax.endElementNs = ngx_http_dav_ext_propfind_xml_end; + + pctx = xmlCreatePushParserCtxt(&sax, &xctx, NULL, 0, NULL); + if (pctx == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "xmlCreatePushParserCtxt() failed"); + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + len = 0; + + for (cl = r->request_body->bufs; cl; cl = cl->next) { + b = cl->buf; + + if (b->in_file) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "PROPFIND client body is in file, " + "you may want to increase client_body_buffer_size"); + xmlFreeParserCtxt(pctx); + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + if (ngx_buf_special(b)) { + continue; + } + + len += b->last - b->pos; + + if (xmlParseChunk(pctx, (const char *)b->pos, b->last - b->pos, + b->last_buf)) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "xmlParseChunk() failed"); + xmlFreeParserCtxt(pctx); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return; + } + } + + xmlFreeParserCtxt(pctx); + + if (len == 0) { + + /* + * For easier debugging treat bodiless requests + * as if they expect all properties. + */ + + xctx.props = NGX_HTTP_DAV_EXT_PROP_ALL; + } + + ngx_http_finalize_request(r, ngx_http_dav_ext_propfind(r, xctx.props)); +} + +static void ngx_http_dav_ext_propfind_xml_start( + void *data, const xmlChar *localname, const xmlChar *prefix, + const xmlChar *uri, int nb_namespaces, const xmlChar **namespaces, + int nb_attributes, int nb_defaulted, const xmlChar **attributes) { + ngx_http_dav_ext_xml_ctx_t *xctx = data; + + if (ngx_strcmp(localname, "propfind") == 0) { + xctx->nodes ^= NGX_HTTP_DAV_EXT_NODE_PROPFIND; + } + + if (ngx_strcmp(localname, "prop") == 0) { + xctx->nodes ^= NGX_HTTP_DAV_EXT_NODE_PROP; + } + + if (ngx_strcmp(localname, "propname") == 0) { + xctx->nodes ^= NGX_HTTP_DAV_EXT_NODE_PROPNAME; + } + + if (ngx_strcmp(localname, "allprop") == 0) { + xctx->nodes ^= NGX_HTTP_DAV_EXT_NODE_ALLPROP; + } +} + +static void ngx_http_dav_ext_propfind_xml_end(void *data, + const xmlChar *localname, + const xmlChar *prefix, + const xmlChar *uri) { + ngx_http_dav_ext_xml_ctx_t *xctx = data; + + if (xctx->nodes & NGX_HTTP_DAV_EXT_NODE_PROPFIND) { + + if (xctx->nodes & NGX_HTTP_DAV_EXT_NODE_PROP) { + if (ngx_strcmp(localname, "displayname") == 0) { + xctx->props |= NGX_HTTP_DAV_EXT_PROP_DISPLAYNAME; + } + + if (ngx_strcmp(localname, "getcontentlength") == 0) { + xctx->props |= NGX_HTTP_DAV_EXT_PROP_GETCONTENTLENGTH; + } + + if (ngx_strcmp(localname, "getlastmodified") == 0) { + xctx->props |= NGX_HTTP_DAV_EXT_PROP_GETLASTMODIFIED; + } + + if (ngx_strcmp(localname, "resourcetype") == 0) { + xctx->props |= NGX_HTTP_DAV_EXT_PROP_RESOURCETYPE; + } + + if (ngx_strcmp(localname, "lockdiscovery") == 0) { + xctx->props |= NGX_HTTP_DAV_EXT_PROP_LOCKDISCOVERY; + } + + if (ngx_strcmp(localname, "supportedlock") == 0) { + xctx->props |= NGX_HTTP_DAV_EXT_PROP_SUPPORTEDLOCK; + } + } + + if (xctx->nodes & NGX_HTTP_DAV_EXT_NODE_PROPNAME) { + xctx->props |= NGX_HTTP_DAV_EXT_PROP_NAMES; + } + + if (xctx->nodes & NGX_HTTP_DAV_EXT_NODE_ALLPROP) { + xctx->props = NGX_HTTP_DAV_EXT_PROP_ALL; + } + } + + ngx_http_dav_ext_propfind_xml_start(data, localname, prefix, uri, 0, NULL, 0, + 0, NULL); +} + +static ngx_int_t ngx_http_dav_ext_propfind(ngx_http_request_t *r, + ngx_uint_t props) { + size_t root, allocated; + u_char *p, *last, *filename; + ngx_int_t rc; + ngx_err_t err; + ngx_str_t path, name; + ngx_dir_t dir; + ngx_uint_t depth; + ngx_array_t entries; + ngx_file_info_t fi; + ngx_http_dav_ext_entry_t *entry; + + if (ngx_array_init(&entries, r->pool, 40, sizeof(ngx_http_dav_ext_entry_t)) != + NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + rc = ngx_http_dav_ext_depth(r, 0); + + if (rc == NGX_ERROR) { + return NGX_HTTP_BAD_REQUEST; + } + + if (rc == NGX_MAX_INT_T_VALUE) { + + /* + * RFC4918: + * 403 Forbidden - A server MAY reject PROPFIND requests on + * collections with depth header of "Infinity", in which case + * it SHOULD use this error with the precondition code + * 'propfind-finite-depth' inside the error body. + */ + + return NGX_HTTP_FORBIDDEN; + } + + depth = rc; + + last = + ngx_http_map_uri_to_path(r, &path, &root, NGX_HTTP_DAV_EXT_PREALLOCATE); + if (last == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + allocated = path.len; + path.len = last - path.data; + + if (path.len > 1 && path.data[path.len - 1] == '/') { + path.len--; + + } else { + last++; + } + + path.data[path.len] = '\0'; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext propfind path: \"%s\"", path.data); + + if (ngx_file_info(path.data, &fi) == NGX_FILE_ERROR) { + return NGX_HTTP_NOT_FOUND; + } + + if (r->uri.len < 2) { + name = r->uri; + + } else { + name.data = &r->uri.data[r->uri.len - 1]; + name.len = (name.data[0] == '/') ? 0 : 1; + + while (name.data != r->uri.data) { + p = name.data - 1; + if (*p == '/') { + break; + } + + name.data--; + name.len++; + } + } + + entry = ngx_array_push(&entries); + if (entry == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_memzero(entry, sizeof(ngx_http_dav_ext_entry_t)); + + entry->uri = r->uri; + entry->name = name; + entry->dir = ngx_is_dir(&fi); + entry->mtime = ngx_file_mtime(&fi); + entry->size = ngx_file_size(&fi); + + if (ngx_http_dav_ext_set_locks(r, entry) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext propfind name:\"%V\", uri:\"%V\"", &entry->name, + &entry->uri); + + if (depth == 0 || !entry->dir) { + return ngx_http_dav_ext_propfind_response(r, &entries, props); + } + + if (ngx_open_dir(&path, &dir) == NGX_ERROR) { + ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, + ngx_open_dir_n " \"%s\" failed", path.data); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + rc = NGX_OK; + + filename = path.data; + filename[path.len] = '/'; + + for (;;) { + ngx_set_errno(0); + + if (ngx_read_dir(&dir) == NGX_ERROR) { + err = ngx_errno; + + if (err != NGX_ENOMOREFILES) { + ngx_log_error(NGX_LOG_CRIT, r->connection->log, err, + ngx_read_dir_n " \"%V\" failed", &path); + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + break; + } + + name.len = ngx_de_namelen(&dir); + name.data = ngx_de_name(&dir); + + if ((name.len == 1 && name.data[0] == '.') || + (name.len == 2 && name.data[0] == '.' && name.data[1] == '.')) { + continue; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext propfind child path: \"%s\"", name.data); + + entry = ngx_array_push(&entries); + if (entry == NULL) { + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + break; + } + + ngx_memzero(entry, sizeof(ngx_http_dav_ext_entry_t)); + + if (!dir.valid_info) { + + if (path.len + 1 + name.len + 1 > allocated) { + allocated = path.len + 1 + name.len + 1 + NGX_HTTP_DAV_EXT_PREALLOCATE; + + filename = ngx_pnalloc(r->pool, allocated); + if (filename == NULL) { + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + break; + } + + last = ngx_cpystrn(filename, path.data, path.len + 1); + *last++ = '/'; + } + + ngx_cpystrn(last, name.data, name.len + 1); + + if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno, + ngx_de_info_n " \"%s\" failed", filename); + continue; + } + } + + p = ngx_pnalloc(r->pool, name.len); + if (p == NULL) { + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + break; + } + + ngx_memcpy(p, name.data, name.len); + entry->name.data = p; + entry->name.len = name.len; + + p = ngx_pnalloc(r->pool, r->uri.len + 1 + name.len + 1); + if (p == NULL) { + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + break; + } + + entry->uri.data = p; + + p = ngx_cpymem(p, r->uri.data, r->uri.len); + if (r->uri.len && r->uri.data[r->uri.len - 1] != '/') { + *p++ = '/'; + } + + p = ngx_cpymem(p, name.data, name.len); + if (ngx_de_is_dir(&dir)) { + *p++ = '/'; + } + + entry->uri.len = p - entry->uri.data; + entry->dir = ngx_de_is_dir(&dir); + entry->mtime = ngx_de_mtime(&dir); + entry->size = ngx_de_size(&dir); + + if (ngx_http_dav_ext_set_locks(r, entry) != NGX_OK) { + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + break; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext propfind child name:\"%V\", uri:\"%V\"", + &entry->name, &entry->uri); + } + + if (ngx_close_dir(&dir) == NGX_ERROR) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno, + ngx_close_dir_n " \"%V\" failed", &path); + } + + if (rc != NGX_OK) { + return rc; + } + + return ngx_http_dav_ext_propfind_response(r, &entries, props); +} + + +static ngx_int_t ngx_http_dav_ext_propfind_response(ngx_http_request_t *r, + ngx_array_t *entries, + ngx_uint_t props) { + size_t len; + u_char *p; + uintptr_t escape; + ngx_buf_t *b; + ngx_int_t rc; + ngx_uint_t n; + ngx_chain_t cl; + ngx_http_dav_ext_entry_t *entry; + + static u_char head[] = "\n" + "\n"; + + static u_char tail[] = "\n"; + + entry = entries->elts; + + for (n = 0; n < entries->nelts; n++) { + escape = 2 * ngx_escape_uri(NULL, entry[n].uri.data, entry[n].uri.len, + NGX_ESCAPE_URI_COMPONENT); + if (escape == 0) { + continue; + } + + p = ngx_pnalloc(r->pool, entry[n].uri.len + escape); + if (p == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + entry[n].uri.len = + (u_char *)ngx_escape_uri(p, entry[n].uri.data, entry[n].uri.len, + NGX_ESCAPE_URI_COMPONENT) - + p; + entry[n].uri.data = p; + } + + len = sizeof(head) - 1 + sizeof(tail) - 1; + + for (n = 0; n < entries->nelts; n++) { + len += ngx_http_dav_ext_format_propfind(r, NULL, &entry[n], props); + } + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b->last = ngx_cpymem(b->last, head, sizeof(head) - 1); + + for (n = 0; n < entries->nelts; n++) { + b->last = (u_char *)ngx_http_dav_ext_format_propfind(r, b->last, &entry[n], + props); + } + + b->last = ngx_cpymem(b->last, tail, sizeof(tail) - 1); + + b->last_buf = (r == r->main) ? 1 : 0; + b->last_in_chain = 1; + + cl.buf = b; + cl.next = NULL; + + r->headers_out.status = 207; + ngx_str_set(&r->headers_out.status_line, "207 Multi-Status"); + + r->headers_out.content_length_n = b->last - b->pos; + + r->headers_out.content_type_len = sizeof("text/xml") - 1; + ngx_str_set(&r->headers_out.content_type, "text/xml"); + r->headers_out.content_type_lowcase = NULL; + + ngx_str_set(&r->headers_out.charset, "utf-8"); + + rc = ngx_http_send_header(r); + + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + return rc; + } + + return ngx_http_output_filter(r, &cl); +} + +static ngx_int_t ngx_http_dav_ext_lock_handler(ngx_http_request_t *r) { + u_char *last; + size_t n, root; + time_t now; + uint32_t token, new_token; + ngx_fd_t fd; + ngx_int_t rc, depth; + ngx_str_t path; + ngx_uint_t status; + ngx_file_info_t fi; + ngx_http_dav_ext_lock_t *lock; + ngx_http_dav_ext_node_t *node; + ngx_http_dav_ext_loc_conf_t *dlcf; + + if (r->uri.len == 0) { + return NGX_HTTP_BAD_REQUEST; + } + + dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_ext_module); + lock = dlcf->shm_zone->data; + + /* + * RFC4918: + * If no Depth header is submitted on a LOCK request, then the request + * MUST act as if a "Depth:infinity" had been submitted. + */ + + rc = ngx_http_dav_ext_depth(r, NGX_MAX_INT_T_VALUE); + + if (rc == NGX_ERROR || rc == 1) { + + /* + * RFC4918: + * Values other than 0 or infinity MUST NOT be used with the Depth + * header on a LOCK method. + */ + + return NGX_HTTP_BAD_REQUEST; + } + + depth = rc; + + token = ngx_http_dav_ext_if(r, &r->uri); + + do { + new_token = ngx_random(); + } while (new_token == 0); + + now = ngx_time(); + + ngx_shmtx_lock(&lock->shpool->mutex); + + node = ngx_http_dav_ext_lock_lookup(r, lock, &r->uri, depth); + + if (node) { + if (token == 0) { + ngx_shmtx_unlock(&lock->shpool->mutex); + return 423; /* Locked */ + } + + if (node->token != token) { + ngx_shmtx_unlock(&lock->shpool->mutex); + return NGX_HTTP_PRECONDITION_FAILED; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext refresh lock"); + + node->expire = now + lock->timeout; + + ngx_queue_remove(&node->queue); + ngx_queue_insert_tail(&lock->sh->queue, &node->queue); + + ngx_shmtx_unlock(&lock->shpool->mutex); + + return ngx_http_dav_ext_lock_response(r, NGX_HTTP_OK, lock->timeout, depth, + token); + } + + n = sizeof(ngx_http_dav_ext_node_t) + r->uri.len - 1; + + node = ngx_slab_alloc_locked(lock->shpool, n); + if (node == NULL) { + ngx_shmtx_unlock(&lock->shpool->mutex); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_memzero(node, sizeof(ngx_http_dav_ext_node_t)); + + ngx_memcpy(&node->data, r->uri.data, r->uri.len); + + node->len = r->uri.len; + node->token = new_token; + node->expire = now + lock->timeout; + node->infinite = (depth ? 1 : 0); + + ngx_queue_insert_tail(&lock->sh->queue, &node->queue); + + ngx_shmtx_unlock(&lock->shpool->mutex); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext add lock"); + + last = ngx_http_map_uri_to_path(r, &path, &root, 0); + if (last == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + *last = '\0'; + + status = NGX_HTTP_OK; + + if (ngx_file_info(path.data, &fi) == NGX_FILE_ERROR) { + + /* + * RFC4918: + * A successful lock request to an unmapped URL MUST result in the + * creation of a locked (non-collection) resource with empty content. + */ + + fd = ngx_open_file(path.data, NGX_FILE_RDONLY, NGX_FILE_CREATE_OR_OPEN, + NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + + /* + * RFC4918: + * 409 (Conflict) - A resource cannot be created at the destination + * until one or more intermediate collections have been created. + * The server MUST NOT create those intermediate collections + * automatically. + */ + + ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno, + ngx_open_file_n " \"%s\" failed", path.data); + return NGX_HTTP_CONFLICT; + } + + if (ngx_close_file(fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno, + ngx_close_file_n " \"%s\" failed", path.data); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + status = NGX_HTTP_CREATED; + } + + return ngx_http_dav_ext_lock_response(r, status, lock->timeout, depth, + new_token); +} + +static ngx_int_t ngx_http_dav_ext_lock_response(ngx_http_request_t *r, + ngx_uint_t status, + time_t timeout, + ngx_uint_t depth, + uint32_t token) { + size_t len; + time_t now; + u_char *p; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t cl; + ngx_table_elt_t *h; + ngx_http_dav_ext_entry_t entry; + + static u_char head[] = "\n" + "\n"; + + static u_char tail[] = "\n"; + + now = ngx_time(); + + ngx_memzero(&entry, sizeof(ngx_http_dav_ext_entry_t)); + + entry.lock_expire = now + timeout; + entry.lock_root = r->uri; + entry.lock_infinite = depth ? 1 : 0; + entry.lock_token = token; + + len = sizeof(head) - 1 + + ngx_http_dav_ext_format_lockdiscovery(r, NULL, &entry) + sizeof(tail) - + 1; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b->last = ngx_cpymem(b->last, head, sizeof(head) - 1); + b->last = (u_char *)ngx_http_dav_ext_format_lockdiscovery(r, b->last, &entry); + b->last = ngx_cpymem(b->last, tail, sizeof(tail) - 1); + + b->last_buf = (r == r->main) ? 1 : 0; + b->last_in_chain = 1; + + cl.buf = b; + cl.next = NULL; + + r->headers_out.status = status; + r->headers_out.content_length_n = b->last - b->pos; + + r->headers_out.content_type_len = sizeof("text/xml") - 1; + ngx_str_set(&r->headers_out.content_type, "text/xml"); + r->headers_out.content_type_lowcase = NULL; + + ngx_str_set(&r->headers_out.charset, "utf-8"); + + h = ngx_list_push(&r->headers_out.headers); + if (h == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_str_set(&h->key, "Lock-Token"); + + p = ngx_pnalloc(r->pool, ngx_http_dav_ext_format_token(NULL, token, 1)); + if (p == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + h->value.data = p; + h->value.len = (u_char *)ngx_http_dav_ext_format_token(p, token, 1) - p; + h->hash = 1; + + rc = ngx_http_send_header(r); + + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + return rc; + } + + return ngx_http_output_filter(r, &cl); +} + +static ngx_int_t ngx_http_dav_ext_unlock_handler(ngx_http_request_t *r) { + uint32_t token; + ngx_http_dav_ext_lock_t *lock; + ngx_http_dav_ext_node_t *node; + ngx_http_dav_ext_loc_conf_t *dlcf; + + token = ngx_http_dav_ext_lock_token(r); + + dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_ext_module); + lock = dlcf->shm_zone->data; + + ngx_shmtx_lock(&lock->shpool->mutex); + + node = ngx_http_dav_ext_lock_lookup(r, lock, &r->uri, -1); + + if (node == NULL || node->token != token) { + ngx_shmtx_unlock(&lock->shpool->mutex); + return NGX_HTTP_NO_CONTENT; + } + + ngx_queue_remove(&node->queue); + ngx_slab_free_locked(lock->shpool, node); + + ngx_shmtx_unlock(&lock->shpool->mutex); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext delete lock"); + + return NGX_HTTP_NO_CONTENT; +} + + +static uint32_t ngx_http_dav_ext_lock_token(ngx_http_request_t *r) { + u_char *p, ch; + uint32_t token; + ngx_uint_t i, n; + ngx_list_part_t *part; + ngx_table_elt_t *header; + + static u_char name[] = "lock-token"; + + part = &r->headers_in.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + for (n = 0; n < sizeof(name) - 1 && n < header[i].key.len; n++) { + ch = header[i].key.data[n]; + + if (ch >= 'A' && ch <= 'Z') { + ch |= 0x20; + } + + if (name[n] != ch) { + break; + } + } + + if (n == sizeof(name) - 1 && n == header[i].key.len) { + p = header[i].value.data; + + if (ngx_strncmp(p, "= '0' && ch <= '9') { + token = token * 16 + (ch - '0'); + continue; + } + + ch = (u_char)(ch | 0x20); + + if (ch >= 'a' && ch <= 'f') { + token = token * 16 + (ch - 'a' + 10); + continue; + } + + return 0; + } + + if (*p != '>') { + return 0; + } + + return token; + } + } + + return 0; +} + + +static uintptr_t +ngx_http_dav_ext_format_propfind(ngx_http_request_t *r, u_char *dst, + ngx_http_dav_ext_entry_t *entry, + ngx_uint_t props) { + size_t len; + + static u_char head[] = "\n" + ""; + + /* uri */ + + static u_char prop[] = "\n" + "\n" + "\n"; + + /* properties */ + + static u_char tail[] = "\n" + "HTTP/1.1 200 OK\n" + "\n" + "\n"; + + static u_char names[] = "\n" + "\n" + "\n" + "\n" + "\n" + "\n"; + + static u_char supportedlock[] = "\n" + "\n" + "\n" + "\n"; + + if (dst == NULL) { + len = sizeof(head) - 1 + sizeof(prop) - 1 + sizeof(tail) - 1; + + len += + entry->uri.len + ngx_escape_html(NULL, entry->uri.data, entry->uri.len); + + if (props & NGX_HTTP_DAV_EXT_PROP_NAMES) { + len += sizeof(names) - 1; + + } else { + len += sizeof("" + "\n" + + "" + "\n" + + "" + "Mon, 28 Sep 1970 06:00:00 GMT" + "\n" + + "" + "" + "\n" + + "\n" + "\n") - + 1; + + /* displayname */ + len += entry->name.len + + ngx_escape_html(NULL, entry->name.data, entry->name.len); + + /* getcontentlength */ + len += NGX_OFF_T_LEN; + + /* lockdiscovery */ + len += ngx_http_dav_ext_format_lockdiscovery(r, NULL, entry); + + /* supportedlock */ + if (entry->lock_supported) { + len += sizeof(supportedlock) - 1; + } + } + + return len; + } + + dst = ngx_cpymem(dst, head, sizeof(head) - 1); + dst = (u_char *)ngx_escape_html(dst, entry->uri.data, entry->uri.len); + dst = ngx_cpymem(dst, prop, sizeof(prop) - 1); + + if (props & NGX_HTTP_DAV_EXT_PROP_NAMES) { + dst = ngx_cpymem(dst, names, sizeof(names) - 1); + + } else { + if (props & NGX_HTTP_DAV_EXT_PROP_DISPLAYNAME) { + dst = ngx_cpymem(dst, "", sizeof("") - 1); + dst = (u_char *)ngx_escape_html(dst, entry->name.data, entry->name.len); + dst = ngx_cpymem(dst, "\n", + sizeof("\n") - 1); + } + + if (props & NGX_HTTP_DAV_EXT_PROP_GETCONTENTLENGTH) { + if (!entry->dir) { + dst = ngx_sprintf(dst, + "%O" + "\n", + entry->size); + } + } + + if (props & NGX_HTTP_DAV_EXT_PROP_GETLASTMODIFIED) { + dst = ngx_cpymem(dst, "", + sizeof("") - 1); + dst = ngx_http_time(dst, entry->mtime); + dst = ngx_cpymem(dst, "\n", + sizeof("\n") - 1); + } + + if (props & NGX_HTTP_DAV_EXT_PROP_RESOURCETYPE) { + dst = ngx_cpymem(dst, "", sizeof("") - 1); + + if (entry->dir) { + dst = ngx_cpymem(dst, "", sizeof("") - 1); + } + + dst = ngx_cpymem(dst, "\n", + sizeof("\n") - 1); + } + + if (props & NGX_HTTP_DAV_EXT_PROP_LOCKDISCOVERY) { + dst = (u_char *)ngx_http_dav_ext_format_lockdiscovery(r, dst, entry); + } + + if (props & NGX_HTTP_DAV_EXT_PROP_SUPPORTEDLOCK) { + dst = ngx_cpymem(dst, "\n", + sizeof("\n") - 1); + + if (entry->lock_supported) { + dst = ngx_cpymem(dst, supportedlock, sizeof(supportedlock) - 1); + } + + dst = ngx_cpymem(dst, "\n", + sizeof("\n") - 1); + } + } + + dst = ngx_cpymem(dst, tail, sizeof(tail) - 1); + + return (uintptr_t)dst; +} + + +static ngx_int_t ngx_http_dav_ext_init_zone(ngx_shm_zone_t *shm_zone, + void *data) { + ngx_http_dav_ext_lock_t *olock = data; + + size_t len; + ngx_http_dav_ext_lock_t *lock; + + lock = shm_zone->data; + + if (olock) { + lock->sh = olock->sh; + lock->shpool = olock->shpool; + return NGX_OK; + } + + lock->shpool = (ngx_slab_pool_t *)shm_zone->shm.addr; + + if (shm_zone->shm.exists) { + lock->sh = lock->shpool->data; + return NGX_OK; + } + + lock->sh = ngx_slab_alloc(lock->shpool, sizeof(ngx_http_dav_ext_lock_sh_t)); + if (lock->sh == NULL) { + return NGX_ERROR; + } + + lock->shpool->data = lock->sh; + + ngx_queue_init(&lock->sh->queue); + + len = sizeof(" in dav_ext zone \"\"") + shm_zone->shm.name.len; + + lock->shpool->log_ctx = ngx_slab_alloc(lock->shpool, len); + if (lock->shpool->log_ctx == NULL) { + return NGX_ERROR; + } + + ngx_sprintf(lock->shpool->log_ctx, " in dav_ext zone \"%V\"%Z", + &shm_zone->shm.name); + + return NGX_OK; +} + +// Creates location configuration +static void *ngx_http_dav_ext_create_loc_conf(ngx_conf_t *cf) { + ngx_http_dav_ext_loc_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_dav_ext_loc_conf_t)); + if (conf == NULL) { + return NULL; + } + + // Auth items + conf->realm = NGX_CONF_UNSET_PTR; + conf->user_file = NGX_CONF_UNSET_PTR; + + // dav items + conf->min_delete_depth = NGX_CONF_UNSET_UINT; + conf->access = NGX_CONF_UNSET_UINT; + conf->create_full_put_path = NGX_CONF_UNSET; + + /* + * set by ngx_pcalloc(): + * + * conf->shm_zone = NULL; + * conf->methods = 0; + */ + + return conf; +} + + +// Merges location configuration +static char * +ngx_http_dav_ext_merge_loc_conf(ngx_conf_t *cf, void *parent, + void *child) { + ngx_http_dav_ext_loc_conf_t *prev = parent; + ngx_http_dav_ext_loc_conf_t *conf = child; + + ngx_conf_merge_bitmask_value(conf->methods, prev->methods, + (NGX_CONF_BITMASK_SET | NGX_HTTP_DAV_EXT_OFF)); + + // Auth items + ngx_conf_merge_ptr_value(conf->realm, prev->realm, NULL); + ngx_conf_merge_ptr_value(conf->user_file, prev->user_file, NULL); + + if (conf->shm_zone == NULL) { + conf->shm_zone = prev->shm_zone; + } + + // dav items + ngx_conf_merge_uint_value(conf->min_delete_depth, + prev->min_delete_depth, 0); + ngx_conf_merge_uint_value(conf->access, prev->access, 0600); + ngx_conf_merge_value(conf->create_full_put_path, + prev->create_full_put_path, 0); + + return NGX_CONF_OK; +} + + +// Defines an nginx the lock zone +static char *ngx_http_dav_ext_lock_zone(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) { + u_char *p; + time_t timeout; + ssize_t size; + ngx_str_t *value, name, s; + ngx_uint_t i; + ngx_shm_zone_t *shm_zone; + ngx_http_dav_ext_lock_t *lock; + + value = cf->args->elts; + + name.len = 0; + size = 0; + timeout = 60; + + for (i = 1; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "zone=", 5) == 0) { + + name.data = value[i].data + 5; + + p = (u_char *)ngx_strchr(name.data, ':'); + + if (p == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid zone size \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + name.len = p - name.data; + + s.data = p + 1; + s.len = value[i].data + value[i].len - s.data; + + size = ngx_parse_size(&s); + + if (size == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid zone size \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + if (size < (ssize_t)(8 * ngx_pagesize)) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "zone \"%V\" is too small", + &value[i]); + return NGX_CONF_ERROR; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "timeout=", 8) == 0) { + + s.len = value[i].len - 8; + s.data = value[i].data + 8; + + timeout = ngx_parse_time(&s, 1); + if (timeout == (time_t)NGX_ERROR || timeout == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid timeout value \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + continue; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + if (name.len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" must have \"zone\" parameter", &cmd->name); + return NGX_CONF_ERROR; + } + + lock = ngx_pcalloc(cf->pool, sizeof(ngx_http_dav_ext_lock_t)); + if (lock == NULL) { + return NGX_CONF_ERROR; + } + + lock->timeout = timeout; + + shm_zone = ngx_shared_memory_add(cf, &name, size, &ngx_http_dav_ext_module); + if (shm_zone == NULL) { + return NGX_CONF_ERROR; + } + + if (shm_zone->data) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "duplicate zone \"%V\"", &name); + return NGX_CONF_ERROR; + } + + shm_zone->init = ngx_http_dav_ext_init_zone; + shm_zone->data = lock; + + return NGX_CONF_OK; +} + +// Sets the nginx lock zone +static char *ngx_http_dav_ext_lock(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) { + ngx_http_dav_ext_loc_conf_t *dlcf = conf; + + ngx_str_t *value, s; + ngx_uint_t i; + ngx_shm_zone_t *shm_zone; + + if (dlcf->shm_zone) { + return "is duplicate"; + } + + value = cf->args->elts; + + shm_zone = NULL; + + for (i = 1; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "zone=", 5) == 0) { + + s.len = value[i].len - 5; + s.data = value[i].data + 5; + + shm_zone = ngx_shared_memory_add(cf, &s, 0, &ngx_http_dav_ext_module); + if (shm_zone == NULL) { + return NGX_CONF_ERROR; + } + + continue; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + if (shm_zone == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" must have \"zone\" parameter", &cmd->name); + return NGX_CONF_ERROR; + } + + dlcf->shm_zone = shm_zone; + + return NGX_CONF_OK; +} + +// Postconfiguration level handler +static ngx_int_t ngx_http_dav_ext_init(ngx_conf_t *cf) { + ngx_http_handler_pt *h; + ngx_http_core_main_conf_t *cmcf; + + cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + + h = ngx_array_push(&cmcf->phases[NGX_HTTP_PRECONTENT_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_http_dav_ext_precontent_handler; + + h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_http_dav_ext_content_handler; + + return NGX_OK; +} + + + +static void +ngx_http_dav_put_handler(ngx_http_request_t *r) +{ + size_t root; + time_t date; + ngx_str_t *temp, path; + ngx_uint_t status; + ngx_file_info_t fi; + ngx_ext_rename_file_t ext; + ngx_http_dav_ext_loc_conf_t *dlcf; + + if (r->request_body == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "PUT request body is unavailable"); + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + if (r->request_body->temp_file == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "PUT request body must be in a file"); + /* Previously this was NGX_HTTP_INTERNAL_SERVER_ERROR but conflict fits better */ + ngx_http_finalize_request(r, NGX_HTTP_CONFLICT); + return; + } + + if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + path.len--; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http put filename: \"%s\"", path.data); + + temp = &r->request_body->temp_file->file.name; + + if (ngx_file_info(path.data, &fi) == NGX_FILE_ERROR) { + status = NGX_HTTP_CREATED; + + } else { + status = NGX_HTTP_NO_CONTENT; + + if (ngx_is_dir(&fi)) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EISDIR, + "\"%s\" could not be created", path.data); + + if (ngx_delete_file(temp->data) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, + ngx_delete_file_n " \"%s\" failed", + temp->data); + } + + ngx_http_finalize_request(r, NGX_HTTP_CONFLICT); + return; + } + } + + dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_ext_module); + + ext.access = dlcf->access; + ext.path_access = dlcf->access; + ext.time = -1; + ext.create_path = dlcf->create_full_put_path; + ext.delete_file = 1; + ext.log = r->connection->log; + + if (r->headers_in.date) { + date = ngx_parse_http_time(r->headers_in.date->value.data, + r->headers_in.date->value.len); + + if (date != NGX_ERROR) { + ext.time = date; + ext.fd = r->request_body->temp_file->file.fd; + } + } + + if (ngx_ext_rename_file(temp, &path, &ext) != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + if (status == NGX_HTTP_CREATED) { + if (ngx_http_dav_location(r) != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + r->headers_out.content_length_n = 0; + } + + r->headers_out.status = status; + r->header_only = 1; + + ngx_http_finalize_request(r, ngx_http_send_header(r)); + return; +} + + +static ngx_int_t +ngx_http_dav_delete_handler(ngx_http_request_t *r) +{ + size_t root; + ngx_err_t err; + ngx_int_t rc, depth; + ngx_uint_t i, d, dir; + ngx_str_t path; + ngx_file_info_t fi; + ngx_http_dav_ext_loc_conf_t *dlcf; + + if (r->headers_in.content_length_n > 0 || r->headers_in.chunked) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "DELETE with body is unsupported"); + return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE; + } + + dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_ext_module); + + if (dlcf->min_delete_depth) { + d = 0; + + for (i = 0; i < r->uri.len; /* void */) { + if (r->uri.data[i++] == '/') { + if (++d >= dlcf->min_delete_depth && i < r->uri.len) { + goto ok; + } + } + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "insufficient URI depth:%i to DELETE", d); + return NGX_HTTP_CONFLICT; + } + +ok: + + if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http delete filename: \"%s\"", path.data); + + if (ngx_link_info(path.data, &fi) == NGX_FILE_ERROR) { + err = ngx_errno; + + rc = (err == NGX_ENOTDIR) ? NGX_HTTP_CONFLICT : NGX_HTTP_NOT_FOUND; + + return ngx_http_dav_error(r->connection->log, err, + rc, ngx_link_info_n, path.data); + } + + if (ngx_is_dir(&fi)) { + + depth = ngx_http_dav_depth(r, NGX_HTTP_DAV_INFINITY_DEPTH); + + if (depth != NGX_HTTP_DAV_INFINITY_DEPTH) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "\"Depth\" header must be infinity"); + return NGX_HTTP_BAD_REQUEST; + } + + path.len -= 2; /* omit "/\0" */ + + dir = 1; + + } else { + + /* + * we do not need to test (r->uri.data[r->uri.len - 1] == '/') + * because ngx_link_info("/file/") returned NGX_ENOTDIR above + */ + + depth = ngx_http_dav_depth(r, 0); + + if (depth != 0 && depth != NGX_HTTP_DAV_INFINITY_DEPTH) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "\"Depth\" header must be 0 or infinity"); + return NGX_HTTP_BAD_REQUEST; + } + + dir = 0; + } + + rc = ngx_http_dav_delete_path(r, &path, dir); + + if (rc == NGX_OK) { + return NGX_HTTP_NO_CONTENT; + } + + return rc; +} + + +static ngx_int_t +ngx_http_dav_mkcol_handler(ngx_http_request_t *r, ngx_http_dav_ext_loc_conf_t *dlcf) +{ + u_char *p; + size_t root; + ngx_str_t path; + + if (r->headers_in.content_length_n > 0 || r->headers_in.chunked) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "MKCOL with body is unsupported"); + return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE; + } + + p = ngx_http_map_uri_to_path(r, &path, &root, 0); + if (p == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "http mkcol path BEFORE: \"%s\"!!", path.data); + + // Make sure the url ends with a slash, inept clients need this to work + if (!ensure_trailing_slash(r, &path)) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "http mkcol path AFTER: \"%s\"", path.data); + + // The url and path data are null terminated + // -1 for null termination and -1 for zero indexed arrays + if (path.data[path.len - 2] != '/') { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "MKCOL can create a collection only"); + return NGX_HTTP_CONFLICT; + } + + if (ngx_create_dir(path.data, ngx_dir_access(dlcf->access)) + != NGX_FILE_ERROR) + { + if (ngx_http_dav_location(r) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_HTTP_CREATED; + } + + ngx_http_dav_error(r->connection->log, ngx_errno, + NGX_HTTP_CONFLICT, ngx_create_dir_n, path.data); + + return NGX_HTTP_CONFLICT; +} + + +static ngx_int_t +ngx_http_dav_copy_move_handler(ngx_http_request_t *r) +{ + u_char *p, *host, *last, ch; + size_t len, root; + ngx_err_t err; + ngx_int_t rc, depth; + ngx_uint_t overwrite, dir, flags; + ngx_str_t path, uri, duri, args; + ngx_tree_ctx_t tree; + ngx_copy_file_t cf; + ngx_file_info_t fi; + ngx_table_elt_t *dest, *over; + ngx_ext_rename_file_t ext; + ngx_http_dav_copy_ctx_t copy; + ngx_http_dav_ext_loc_conf_t *dlcf; + + if (r->headers_in.content_length_n > 0 || r->headers_in.chunked) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "COPY and MOVE with body are unsupported"); + return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE; + } + + dest = r->headers_in.destination; + + if (dest == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent no \"Destination\" header"); + return NGX_HTTP_BAD_REQUEST; + } + + p = dest->value.data; + /* there is always '\0' even after empty header value */ + if (p[0] == '/') { + last = p + dest->value.len; + goto destination_done; + } + + len = r->headers_in.server.len; + + if (len == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent no \"Host\" header"); + return NGX_HTTP_BAD_REQUEST; + } + +#if (NGX_HTTP_SSL) + + if (r->connection->ssl) { + if (ngx_strncmp(dest->value.data, "https://", sizeof("https://") - 1) + != 0) + { + goto invalid_destination; + } + + host = dest->value.data + sizeof("https://") - 1; + + } else +#endif + { + if (ngx_strncmp(dest->value.data, "http://", sizeof("http://") - 1) + != 0) + { + goto invalid_destination; + } + + host = dest->value.data + sizeof("http://") - 1; + } + + if (ngx_strncmp(host, r->headers_in.server.data, len) != 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "\"Destination\" URI \"%V\" is handled by " + "different repository than the source URI", + &dest->value); + return NGX_HTTP_BAD_REQUEST; + } + + last = dest->value.data + dest->value.len; + + for (p = host + len; p < last; p++) { + if (*p == '/') { + goto destination_done; + } + } + +invalid_destination: + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid \"Destination\" header: \"%V\"", + &dest->value); + return NGX_HTTP_BAD_REQUEST; + +destination_done: + + duri.len = last - p; + duri.data = p; + flags = NGX_HTTP_LOG_UNSAFE; + + if (ngx_http_parse_unsafe_uri(r, &duri, &args, &flags) != NGX_OK) { + goto invalid_destination; + } + + depth = ngx_http_dav_depth(r, NGX_HTTP_DAV_INFINITY_DEPTH); + + if (depth != NGX_HTTP_DAV_INFINITY_DEPTH) { + + if (r->method == NGX_HTTP_COPY) { + if (depth != 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "\"Depth\" header must be 0 or infinity"); + return NGX_HTTP_BAD_REQUEST; + } + + } else { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "\"Depth\" header must be infinity"); + return NGX_HTTP_BAD_REQUEST; + } + } + + over = r->headers_in.overwrite; + + if (over) { + if (over->value.len == 1) { + ch = over->value.data[0]; + + if (ch == 'T' || ch == 't') { + overwrite = 1; + goto overwrite_done; + } + + if (ch == 'F' || ch == 'f') { + overwrite = 0; + goto overwrite_done; + } + + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid \"Overwrite\" header: \"%V\"", + &over->value); + return NGX_HTTP_BAD_REQUEST; + } + + overwrite = 1; + +overwrite_done: + + if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http copy from: \"%s\"", path.data); + + uri = r->uri; + r->uri = duri; + + if (ngx_http_map_uri_to_path(r, ©.path, &root, 0) == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + r->uri = uri; + + copy.path.len--; /* omit "\0" */ + + if (copy.path.data[copy.path.len - 1] == '/') { + copy.path.len--; + copy.path.data[copy.path.len] = '\0'; + + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http copy to: \"%s\"", copy.path.data); + + if (ngx_link_info(copy.path.data, &fi) == NGX_FILE_ERROR) { + err = ngx_errno; + + if (err != NGX_ENOENT) { + return ngx_http_dav_error(r->connection->log, err, + NGX_HTTP_NOT_FOUND, ngx_link_info_n, + copy.path.data); + } + + /* destination does not exist */ + + overwrite = 0; + dir = 0; + + } else { + + /* destination exists */ + + if (!overwrite) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EEXIST, + "\"%s\" could not be created", copy.path.data); + return NGX_HTTP_PRECONDITION_FAILED; + } + + dir = ngx_is_dir(&fi); + } + + if (ngx_link_info(path.data, &fi) == NGX_FILE_ERROR) { + return ngx_http_dav_error(r->connection->log, ngx_errno, + NGX_HTTP_NOT_FOUND, ngx_link_info_n, + path.data); + } + + if (ngx_is_dir(&fi)) { + + if (overwrite) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http delete: \"%s\"", copy.path.data); + + rc = ngx_http_dav_delete_path(r, ©.path, dir); + + if (rc != NGX_OK) { + return rc; + } + } + } + + if (ngx_is_dir(&fi)) { + + path.len -= 2; /* omit "/\0" */ + + if (r->method == NGX_HTTP_MOVE) { + if (ngx_rename_file(path.data, copy.path.data) != NGX_FILE_ERROR) { + return NGX_HTTP_CREATED; + } + } + + if (ngx_create_dir(copy.path.data, ngx_file_access(&fi)) + == NGX_FILE_ERROR) + { + return ngx_http_dav_error(r->connection->log, ngx_errno, + NGX_HTTP_NOT_FOUND, + ngx_create_dir_n, copy.path.data); + } + + copy.len = path.len; + + tree.init_handler = NULL; + tree.file_handler = ngx_http_dav_copy_tree_file; + tree.pre_tree_handler = ngx_http_dav_copy_dir; + tree.post_tree_handler = ngx_http_dav_copy_dir_time; + tree.spec_handler = ngx_http_dav_noop; + tree.data = © + tree.alloc = 0; + tree.log = r->connection->log; + + if (ngx_walk_tree(&tree, &path) == NGX_OK) { + + if (r->method == NGX_HTTP_MOVE) { + rc = ngx_http_dav_delete_path(r, &path, 1); + + if (rc != NGX_OK) { + return rc; + } + } + + return NGX_HTTP_CREATED; + } + + } else { + + if (r->method == NGX_HTTP_MOVE) { + + dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_ext_module); + + ext.access = 0; + ext.path_access = dlcf->access; + ext.time = -1; + ext.create_path = 1; + ext.delete_file = 0; + ext.log = r->connection->log; + + if (ngx_ext_rename_file(&path, ©.path, &ext) == NGX_OK) { + return NGX_HTTP_NO_CONTENT; + } + + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + cf.size = ngx_file_size(&fi); + cf.buf_size = 0; + cf.access = ngx_file_access(&fi); + cf.time = ngx_file_mtime(&fi); + cf.log = r->connection->log; + + if (ngx_copy_file(path.data, copy.path.data, &cf) == NGX_OK) { + return NGX_HTTP_NO_CONTENT; + } + } + + return NGX_HTTP_INTERNAL_SERVER_ERROR; +} + + +static ngx_int_t +ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + u_char *p, *dir; + size_t len; + ngx_http_dav_copy_ctx_t *copy; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy dir: \"%s\"", path->data); + + copy = ctx->data; + + len = copy->path.len + path->len; + + dir = ngx_alloc(len + 1, ctx->log); + if (dir == NULL) { + return NGX_ABORT; + } + + p = ngx_cpymem(dir, copy->path.data, copy->path.len); + (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy dir to: \"%s\"", dir); + + if (ngx_create_dir(dir, ngx_dir_access(ctx->access)) == NGX_FILE_ERROR) { + (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_create_dir_n, + dir); + } + + ngx_free(dir); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_dav_copy_dir_time(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + u_char *p, *dir; + size_t len; + ngx_http_dav_copy_ctx_t *copy; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy dir time: \"%s\"", path->data); + + copy = ctx->data; + + len = copy->path.len + path->len; + + dir = ngx_alloc(len + 1, ctx->log); + if (dir == NULL) { + return NGX_ABORT; + } + + p = ngx_cpymem(dir, copy->path.data, copy->path.len); + (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy dir time to: \"%s\"", dir); + +#if (NGX_WIN32) + { + ngx_fd_t fd; + + fd = ngx_open_file(dir, NGX_FILE_RDWR, NGX_FILE_OPEN, 0); + + if (fd == NGX_INVALID_FILE) { + (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_open_file_n, dir); + goto failed; + } + + if (ngx_set_file_time(NULL, fd, ctx->mtime) != NGX_OK) { + ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno, + ngx_set_file_time_n " \"%s\" failed", dir); + } + + if (ngx_close_file(fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno, + ngx_close_file_n " \"%s\" failed", dir); + } + } + +failed: + +#else + + if (ngx_set_file_time(dir, 0, ctx->mtime) != NGX_OK) { + ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno, + ngx_set_file_time_n " \"%s\" failed", dir); + } + +#endif + + ngx_free(dir); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_dav_copy_tree_file(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + u_char *p, *file; + size_t len; + ngx_copy_file_t cf; + ngx_http_dav_copy_ctx_t *copy; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy file: \"%s\"", path->data); + + copy = ctx->data; + + len = copy->path.len + path->len; + + file = ngx_alloc(len + 1, ctx->log); + if (file == NULL) { + return NGX_ABORT; + } + + p = ngx_cpymem(file, copy->path.data, copy->path.len); + (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy file to: \"%s\"", file); + + cf.size = ctx->size; + cf.buf_size = 0; + cf.access = ctx->access; + cf.time = ctx->mtime; + cf.log = ctx->log; + + (void) ngx_copy_file(path->data, file, &cf); + + ngx_free(file); + + return NGX_OK; +} + + +ngx_int_t ngx_http_dav_ext_set_locks(ngx_http_request_t *r, + ngx_http_dav_ext_entry_t *entry) { + ngx_http_dav_ext_node_t *node; + ngx_http_dav_ext_lock_t *lock; + ngx_http_dav_ext_loc_conf_t *dlcf; + + dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_ext_module); + + if (dlcf->shm_zone == NULL) { + entry->lock_supported = 0; + return NGX_OK; + } + + entry->lock_supported = 1; + + lock = dlcf->shm_zone->data; + + ngx_shmtx_lock(&lock->shpool->mutex); + + node = ngx_http_dav_ext_lock_lookup(r, lock, &entry->uri, -1); + if (node == NULL) { + ngx_shmtx_unlock(&lock->shpool->mutex); + return NGX_OK; + } + + entry->lock_infinite = node->infinite ? 1 : 0; + entry->lock_expire = node->expire; + entry->lock_token = node->token; + + entry->lock_root.data = ngx_pnalloc(r->pool, node->len); + if (entry->lock_root.data == NULL) { + ngx_shmtx_unlock(&lock->shpool->mutex); + return NGX_ERROR; + } + + ngx_memcpy(entry->lock_root.data, node->data, node->len); + entry->lock_root.len = node->len; + + ngx_shmtx_unlock(&lock->shpool->mutex); + + return NGX_OK; +} diff --git a/ngx_http_webdav_module.h b/ngx_http_webdav_module.h new file mode 100644 index 0000000..c6834a9 --- /dev/null +++ b/ngx_http_webdav_module.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) Starfields + */ + +#include + +#ifndef NGX_HTTP_WEBDAV_MODULE_H +#define NGX_HTTP_WEBDAV_MODULE_H + + +// The module location configuration +typedef struct { + ngx_uint_t methods; + ngx_shm_zone_t *shm_zone; + ngx_http_complex_value_t *realm; + ngx_http_complex_value_t *user_file; + ngx_uint_t access; + ngx_uint_t min_delete_depth; + ngx_flag_t create_full_put_path; +} ngx_http_dav_ext_loc_conf_t; + +void ngx_http_dav_ext_proppatch_handler(ngx_http_request_t *r); + +#endif diff --git a/ngx_http_webdav_utils.c b/ngx_http_webdav_utils.c new file mode 100644 index 0000000..35a0990 --- /dev/null +++ b/ngx_http_webdav_utils.c @@ -0,0 +1,708 @@ +/* + * Copyright (C) Starfields + */ + +#include +#include +#include +#include "ngx_http_webdav_utils.h" + + +#define NGX_HTTP_DAV_NO_DEPTH -3 +#define NGX_HTTP_DAV_INVALID_DEPTH -2 +#define NGX_HTTP_DAV_INFINITY_DEPTH -1 + + +static ngx_int_t ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path); +static ngx_int_t ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path); + + +ngx_int_t +ngx_http_dav_error(ngx_log_t *log, ngx_err_t err, ngx_int_t not_found, + char *failed, u_char *path) +{ + ngx_int_t rc; + ngx_uint_t level; + + if (err == NGX_ENOENT || err == NGX_ENOTDIR || err == NGX_ENAMETOOLONG) { + level = NGX_LOG_ERR; + rc = not_found; + + } else if (err == NGX_EACCES || err == NGX_EPERM) { + level = NGX_LOG_ERR; + rc = NGX_HTTP_FORBIDDEN; + + } else if (err == NGX_EEXIST) { + level = NGX_LOG_ERR; + rc = NGX_HTTP_NOT_ALLOWED; + + } else if (err == NGX_ENOSPC) { + level = NGX_LOG_CRIT; + rc = NGX_HTTP_INSUFFICIENT_STORAGE; + + } else { + level = NGX_LOG_CRIT; + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_log_error(level, log, err, "%s \"%s\" failed", failed, path); + + return rc; +} + + +ngx_int_t +ngx_http_dav_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + return NGX_OK; +} + + +ngx_int_t ngx_http_dav_ext_strip_uri(ngx_http_request_t *r, + ngx_str_t *uri) { + u_char *p, *last, *host; + size_t len; + + if (uri->data[0] == '/') { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext strip uri:\"%V\" unchanged", uri); + return NGX_OK; + } + + len = r->headers_in.server.len; + + if (len == 0) { + goto failed; + } + +#if (NGX_HTTP_SSL) + + if (r->connection->ssl) { + if (ngx_strncmp(uri->data, "https://", sizeof("https://") - 1) != 0) { + goto failed; + } + + host = uri->data + sizeof("https://") - 1; + + } else +#endif + { + if (ngx_strncmp(uri->data, "http://", sizeof("http://") - 1) != 0) { + goto failed; + } + + host = uri->data + sizeof("http://") - 1; + } + + if (ngx_strncmp(host, r->headers_in.server.data, len) != 0) { + goto failed; + } + + last = uri->data + uri->len; + + for (p = host + len; p != last; p++) { + if (*p == '/') { + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext strip uri \"%V\" \"%*s\"", uri, last - p, p); + + uri->data = p; + uri->len = last - p; + + return NGX_OK; + } + } + +failed: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext strip uri \"%V\" failed", uri); + + return NGX_DECLINED; +} + + +ngx_int_t +ngx_http_dav_delete_path(ngx_http_request_t *r, ngx_str_t *path, ngx_uint_t dir) +{ + char *failed; + ngx_tree_ctx_t tree; + + if (dir) { + + tree.init_handler = NULL; + tree.file_handler = ngx_http_dav_delete_file; + tree.pre_tree_handler = ngx_http_dav_noop; + tree.post_tree_handler = ngx_http_dav_delete_dir; + tree.spec_handler = ngx_http_dav_delete_file; + tree.data = NULL; + tree.alloc = 0; + tree.log = r->connection->log; + + /* TODO: 207 */ + + if (ngx_walk_tree(&tree, path) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (ngx_delete_dir(path->data) != NGX_FILE_ERROR) { + return NGX_OK; + } + + failed = ngx_delete_dir_n; + + } else { + + if (ngx_delete_file(path->data) != NGX_FILE_ERROR) { + return NGX_OK; + } + + failed = ngx_delete_file_n; + } + + return ngx_http_dav_error(r->connection->log, ngx_errno, + NGX_HTTP_NOT_FOUND, failed, path->data); +} + + +static ngx_int_t +ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http delete dir: \"%s\"", path->data); + + if (ngx_delete_dir(path->data) == NGX_FILE_ERROR) { + + /* TODO: add to 207 */ + + (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_delete_dir_n, + path->data); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http delete file: \"%s\"", path->data); + + if (ngx_delete_file(path->data) == NGX_FILE_ERROR) { + + /* TODO: add to 207 */ + + (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_delete_file_n, + path->data); + } + + return NGX_OK; +} + + +ngx_int_t ensure_trailing_slash(ngx_http_request_t *r, + ngx_str_t *path) { + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "initial path: %s", path->data); + + u_char *p = ngx_pnalloc(r->pool, path->len + 1); + if (p == NULL) { + return 0; + } + u_char *last = ngx_cpystrn(p, path->data, path->len + 1); + *last++ = '/'; + *last++ = '\0'; + path->data = p; + path->len += 1; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "rewritten the path with a trailing slash: %s", path->data); + + return 1; +} + + +ngx_int_t +ngx_http_dav_depth(ngx_http_request_t *r, ngx_int_t dflt) +{ + ngx_table_elt_t *depth; + + depth = r->headers_in.depth; + + if (depth == NULL) { + return dflt; + } + + if (depth->value.len == 1) { + + if (depth->value.data[0] == '0') { + return 0; + } + + if (depth->value.data[0] == '1') { + return 1; + } + + } else { + + if (depth->value.len == sizeof("infinity") - 1 + && ngx_strcmp(depth->value.data, "infinity") == 0) + { + return NGX_HTTP_DAV_INFINITY_DEPTH; + } + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid \"Depth\" header: \"%V\"", + &depth->value); + + return NGX_HTTP_DAV_INVALID_DEPTH; +} + + +ngx_int_t ngx_http_dav_ext_depth(ngx_http_request_t *r, + ngx_int_t default_depth) { + ngx_table_elt_t *depth; + + depth = r->headers_in.depth; + + if (depth == NULL) { + return default_depth; + } + + if (depth->value.len == 1) { + + if (depth->value.data[0] == '0') { + return 0; + } + + if (depth->value.data[0] == '1') { + return 1; + } + + } else { + + if (depth->value.len == sizeof("infinity") - 1 && + ngx_strcmp(depth->value.data, "infinity") == 0) { + return NGX_MAX_INT_T_VALUE; + } + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid \"Depth\" header: \"%V\"", &depth->value); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_dav_location(ngx_http_request_t *r) +{ + u_char *p; + size_t len; + uintptr_t escape; + + r->headers_out.location = ngx_list_push(&r->headers_out.headers); + if (r->headers_out.location == NULL) { + return NGX_ERROR; + } + + r->headers_out.location->hash = 1; + r->headers_out.location->next = NULL; + ngx_str_set(&r->headers_out.location->key, "Location"); + + escape = 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, NGX_ESCAPE_URI); + + if (escape) { + len = r->uri.len + escape; + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + ngx_http_clear_location(r); + return NGX_ERROR; + } + + r->headers_out.location->value.len = len; + r->headers_out.location->value.data = p; + + ngx_escape_uri(p, r->uri.data, r->uri.len, NGX_ESCAPE_URI); + + } else { + r->headers_out.location->value = r->uri; + } + + return NGX_OK; +} + + +uint32_t ngx_http_dav_ext_if(ngx_http_request_t *r, ngx_str_t *uri) { + u_char *p, ch; + uint32_t token; + ngx_str_t tag; + ngx_uint_t i, n; + ngx_list_part_t *part; + ngx_table_elt_t *header; + + static u_char name[] = "if"; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext if \"%V\"", uri); + + part = &r->headers_in.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + for (n = 0; n < sizeof(name) - 1 && n < header[i].key.len; n++) { + ch = header[i].key.data[n]; + + if (ch >= 'A' && ch <= 'Z') { + ch |= 0x20; + } + + if (name[n] != ch) { + break; + } + } + + if (n == sizeof(name) - 1 && n == header[i].key.len) { + p = header[i].value.data; + tag = r->uri; + + while (*p != '\0') { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext if list \"%s\"", p); + + while (*p == ' ') { + p++; + } + + if (*p == '<') { + tag.data = ++p; + + while (*p != '\0' && *p != '>') { + p++; + } + + if (*p == '\0') { + break; + } + + tag.len = p++ - tag.data; + + (void)ngx_http_dav_ext_strip_uri(r, &tag); + + while (*p == ' ') { + p++; + } + } + + if (*p != '(') { + break; + } + + p++; + + if (tag.len == 0 || tag.len > uri->len || + (tag.len < uri->len && tag.data[tag.len - 1] != '/') || + ngx_memcmp(tag.data, uri->data, tag.len)) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext if tag mismatch \"%V\"", &tag); + + while (*p != '\0' && *p != ')') { + p++; + } + + if (*p == ')') { + p++; + } + + continue; + } + + while (*p != '\0') { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext if condition \"%s\"", p); + + while (*p == ' ') { + p++; + } + + if (ngx_strncmp(p, "Not", 3) == 0) { + p += 3; + while (*p == ' ') { + p++; + } + goto next; + } + + if (*p == '[') { + p++; + while (*p != '\0' && *p != ']') { + p++; + } + goto next; + } + + if (ngx_strncmp(p, "= '0' && ch <= '9') { + token = token * 16 + (ch - '0'); + continue; + } + + ch = (u_char)(ch | 0x20); + + if (ch >= 'a' && ch <= 'f') { + token = token * 16 + (ch - 'a' + 10); + continue; + } + + goto next; + } + + if (*p != '>') { + goto next; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext if token: %uxD", token); + + return token; + + next: + + while (*p != '\0' && *p != ' ' && *p != ')') { + p++; + } + + if (*p == ')') { + p++; + break; + } + } + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext if header mismatch"); + } + } + + return 0; +} + + +ngx_http_dav_ext_node_t * +ngx_http_dav_ext_lock_lookup(ngx_http_request_t *r, + ngx_http_dav_ext_lock_t *lock, ngx_str_t *uri, + ngx_int_t depth) { + time_t now; + ngx_queue_t *q; + ngx_http_dav_ext_node_t *node; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext lock lookup \"%V\"", uri); + + if (uri->len == 0) { + return NULL; + } + + now = ngx_time(); + + while (!ngx_queue_empty(&lock->sh->queue)) { + q = ngx_queue_head(&lock->sh->queue); + node = (ngx_http_dav_ext_node_t *)q; + + if (node->expire >= now) { + break; + } + + ngx_queue_remove(q); + ngx_slab_free_locked(lock->shpool, node); + } + + for (q = ngx_queue_head(&lock->sh->queue); + q != ngx_queue_sentinel(&lock->sh->queue); q = ngx_queue_next(q)) { + node = (ngx_http_dav_ext_node_t *)q; + + if (uri->len >= node->len) { + if (ngx_memcmp(uri->data, node->data, node->len)) { + continue; + } + + if (uri->len > node->len) { + if (node->data[node->len - 1] != '/') { + continue; + } + + if (!node->infinite && + ngx_strlchr(uri->data + node->len, uri->data + uri->len - 1, '/')) { + continue; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext lock found \"%*s\"", node->len, node->data); + + return node; + } + + /* uri->len < node->len */ + + if (depth >= 0) { + if (ngx_memcmp(node->data, uri->data, uri->len)) { + continue; + } + + if (uri->data[uri->len - 1] != '/') { + continue; + } + + if (depth == 0 && + ngx_strlchr(node->data + uri->len, node->data + node->len - 1, '/')) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext lock found \"%*s\"", node->len, node->data); + + return node; + } + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http dav_ext lock not found"); + + return NULL; +} + + +uintptr_t +ngx_http_dav_ext_format_lockdiscovery(ngx_http_request_t *r, u_char *dst, + ngx_http_dav_ext_entry_t *entry) { + size_t len; + time_t now; + + if (dst == NULL) { + if (entry->lock_token == 0) { + return sizeof("\n") - 1; + } + + len = sizeof("\n" + "\n" + "\n" + "\n" + "infinity\n" + "Second-\n" + "\n" + "\n" + "\n" + "\n") - + 1; + + /* timeout */ + len += NGX_TIME_T_LEN; + + /* token */ + len += ngx_http_dav_ext_format_token(NULL, entry->lock_token, 0); + + /* lockroot */ + len += entry->lock_root.len + + ngx_escape_html(NULL, entry->lock_root.data, entry->lock_root.len); + return len; + } + + if (entry->lock_token == 0) { + dst = ngx_cpymem(dst, "\n", + sizeof("\n") - 1); + return (uintptr_t)dst; + } + + now = ngx_time(); + + dst = + ngx_cpymem(dst, "\n", sizeof("\n") - 1); + + dst = ngx_cpymem(dst, "\n", sizeof("\n") - 1); + + dst = ngx_cpymem(dst, "\n", + sizeof("\n") - 1); + + dst = ngx_cpymem(dst, "\n", + sizeof("\n") - 1); + + dst = ngx_sprintf(dst, "%s\n", + entry->lock_infinite ? "infinity" : "0"); + + dst = ngx_sprintf(dst, "Second-%T\n", + entry->lock_expire - now); + + dst = ngx_cpymem(dst, "", + sizeof("") - 1); + dst = (u_char *)ngx_http_dav_ext_format_token(dst, entry->lock_token, 0); + dst = ngx_cpymem(dst, "\n", + sizeof("\n") - 1); + + dst = ngx_cpymem(dst, "", + sizeof("") - 1); + dst = (u_char *)ngx_escape_html(dst, entry->lock_root.data, + entry->lock_root.len); + dst = ngx_cpymem(dst, "\n", + sizeof("\n") - 1); + + dst = ngx_cpymem(dst, "\n", sizeof("\n") - 1); + + dst = ngx_cpymem(dst, "\n", + sizeof("\n") - 1); + + return (uintptr_t)dst; +} + + +uintptr_t ngx_http_dav_ext_format_token(u_char *dst, uint32_t token, + ngx_uint_t brackets) { + ngx_uint_t n; + + static u_char hex[] = "0123456789abcdef"; + + if (dst == NULL) { + return sizeof("") - 1 + (brackets ? 2 : 0); + } + + if (brackets) { + *dst++ = '<'; + } + + dst = ngx_cpymem(dst, "urn:", 4); + + for (n = 0; n < 4; n++) { + *dst++ = hex[token >> 28]; + *dst++ = hex[(token >> 24) & 0xf]; + token <<= 8; + } + + if (brackets) { + *dst++ = '>'; + } + + return (uintptr_t)dst; +} diff --git a/ngx_http_webdav_utils.h b/ngx_http_webdav_utils.h new file mode 100644 index 0000000..a16e4b2 --- /dev/null +++ b/ngx_http_webdav_utils.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) Starfields + */ + +#include + +#ifndef NGX_HTTP_DAV_EXT_CONF_H +#define NGX_HTTP_DAV_EXT_CONF_H + + +typedef struct { + ngx_str_t uri; + ngx_str_t name; + time_t mtime; + off_t size; + + time_t lock_expire; + ngx_str_t lock_root; + uint32_t lock_token; + + unsigned dir : 1; + unsigned lock_supported : 1; + unsigned lock_infinite : 1; +} ngx_http_dav_ext_entry_t; + + +typedef struct { + ngx_queue_t queue; +} ngx_http_dav_ext_lock_sh_t; + + +typedef struct { + time_t timeout; + ngx_slab_pool_t *shpool; + ngx_http_dav_ext_lock_sh_t *sh; +} ngx_http_dav_ext_lock_t; + + +typedef struct { + ngx_queue_t queue; + uint32_t token; + time_t expire; + ngx_uint_t infinite; /* unsigned infinite:1; */ + size_t len; + u_char data[1]; +} ngx_http_dav_ext_node_t; + + +ngx_int_t +ngx_http_dav_delete_path(ngx_http_request_t *r, ngx_str_t *path, ngx_uint_t dir); +ngx_int_t ensure_trailing_slash(ngx_http_request_t *r, ngx_str_t *uri); +ngx_int_t ngx_http_dav_depth(ngx_http_request_t *r, + ngx_int_t default_depth); +ngx_int_t ngx_http_dav_ext_depth(ngx_http_request_t *r, + ngx_int_t default_depth); +ngx_int_t ngx_http_dav_location(ngx_http_request_t *r); +ngx_int_t +ngx_http_dav_error(ngx_log_t *log, ngx_err_t err, ngx_int_t not_found, + char *failed, u_char *path); +uint32_t ngx_http_dav_ext_if(ngx_http_request_t *r, ngx_str_t *uri); +ngx_http_dav_ext_node_t * +ngx_http_dav_ext_lock_lookup(ngx_http_request_t *r, + ngx_http_dav_ext_lock_t *lock, ngx_str_t *uri, + ngx_int_t depth); +ngx_int_t ngx_http_dav_ext_set_locks(ngx_http_request_t *r, + ngx_http_dav_ext_entry_t *entry); +uintptr_t +ngx_http_dav_ext_format_lockdiscovery(ngx_http_request_t *r, u_char *dst, + ngx_http_dav_ext_entry_t *entry); +uintptr_t ngx_http_dav_ext_format_token(u_char *dst, uint32_t token, + ngx_uint_t brackets); +ngx_int_t ngx_http_dav_ext_strip_uri(ngx_http_request_t *r, + ngx_str_t *uri); +ngx_int_t +ngx_http_dav_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path); + + +#endif