Files
nginx-sf-webdav-module/ngx_http_webdav_module.c

2431 lines
67 KiB
C

/*
* Copyright (C) Starfields
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <libxml/parser.h>
#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("webdav_lock_zone"), NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE12,
ngx_http_dav_ext_lock_zone, 0, 0, NULL},
{ngx_string("webdav_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("webdav_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("webdav_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_webdav_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_webdav_module = {
NGX_MODULE_V1,
&ngx_http_webdav_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_webdav_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_webdav_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_webdav_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.data[0] == '.') {
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_CRIT, r->connection->log, ngx_errno,
ngx_de_info_n " \"%s\" failed", filename);
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
break;
}
}
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[] =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<D:multistatus xmlns:D=\"DAV:\">\n";
static u_char tail[] =
"</D:multistatus>\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);
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)
- 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_webdav_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[] = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<D:prop xmlns:D=\"DAV:\">\n";
static u_char tail[] = "</D:prop>\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_webdav_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, "<urn:", 5)) {
return 0;
}
p += 5;
token = 0;
for (n = 0; n < 8; n++) {
ch = *p++;
if (ch >= '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[] =
"<D:response>\n"
"<D:href>";
/* uri */
static u_char prop[] =
"</D:href>\n"
"<D:propstat>\n"
"<D:prop>\n";
/* properties */
static u_char tail[] =
"</D:prop>\n"
"<D:status>HTTP/1.1 200 OK</D:status>\n"
"</D:propstat>\n"
"</D:response>\n";
static u_char names[] =
"<D:displayname/>\n"
"<D:getcontentlength/>\n"
"<D:getlastmodified/>\n"
"<D:resourcetype/>\n"
"<D:lockdiscovery/>\n"
"<D:supportedlock/>\n";
static u_char supportedlock[] =
"<D:lockentry>\n"
"<D:lockscope><D:exclusive/></D:lockscope>\n"
"<D:locktype><D:write/></D:locktype>\n"
"</D:lockentry>\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("<D:displayname>"
"</D:displayname>\n"
"<D:getcontentlength>"
"</D:getcontentlength>\n"
"<D:getlastmodified>"
"Mon, 28 Sep 1970 06:00:00 GMT"
"</D:getlastmodified>\n"
"<D:resourcetype>"
"<D:collection/>"
"</D:resourcetype>\n"
"<D:supportedlock>\n"
"</D:supportedlock>\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, "<D:displayname>",
sizeof("<D:displayname>") - 1);
dst = (u_char *) ngx_escape_html(dst, entry->name.data,
entry->name.len);
dst = ngx_cpymem(dst, "</D:displayname>\n",
sizeof("</D:displayname>\n") - 1);
}
if (props & NGX_HTTP_DAV_EXT_PROP_GETCONTENTLENGTH) {
if (!entry->dir) {
dst = ngx_sprintf(dst, "<D:getcontentlength>%O"
"</D:getcontentlength>\n", entry->size);
}
}
if (props & NGX_HTTP_DAV_EXT_PROP_GETLASTMODIFIED) {
dst = ngx_cpymem(dst, "<D:getlastmodified>",
sizeof("<D:getlastmodified>") - 1);
dst = ngx_http_time(dst, entry->mtime);
dst = ngx_cpymem(dst, "</D:getlastmodified>\n",
sizeof("</D:getlastmodified>\n") - 1);
}
if (props & NGX_HTTP_DAV_EXT_PROP_RESOURCETYPE) {
dst = ngx_cpymem(dst, "<D:resourcetype>",
sizeof("<D:resourcetype>") - 1);
if (entry->dir) {
dst = ngx_cpymem(dst, "<D:collection/>",
sizeof("<D:collection/>") - 1);
}
dst = ngx_cpymem(dst, "</D:resourcetype>\n",
sizeof("</D:resourcetype>\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, "<D:supportedlock>\n",
sizeof("<D:supportedlock>\n") - 1);
if (entry->lock_supported) {
dst = ngx_cpymem(dst, supportedlock, sizeof(supportedlock) - 1);
}
dst = ngx_cpymem(dst, "</D:supportedlock>\n",
sizeof("</D:supportedlock>\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_webdav_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_webdav_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_webdav_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_webdav_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, &copy.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, &copy.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 = &copy;
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_webdav_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, &copy.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_webdav_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;
}