/* * 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; }