This commit is contained in:
Ivan Ovchinnikov 2026-05-23 04:36:31 +00:00 committed by GitHub
commit 3f79dbdb9a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -23,9 +23,20 @@ typedef struct {
ngx_uint_t access;
ngx_uint_t min_delete_depth;
ngx_flag_t create_full_put_path;
ngx_flag_t follow_symlinks;
} ngx_http_dav_loc_conf_t;
typedef struct {
unsigned follow_symlinks:1;
} ngx_http_dav_delete_ctx_t;
typedef struct {
unsigned found_symlink:1;
} ngx_http_dav_prescan_ctx_t;
typedef struct {
ngx_str_t path;
size_t len;
@ -41,6 +52,9 @@ static ngx_int_t ngx_http_dav_delete_path(ngx_http_request_t *r,
ngx_str_t *path, ngx_uint_t dir);
static ngx_int_t ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path);
static ngx_int_t ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path);
static ngx_int_t ngx_http_dav_delete_pre_tree(ngx_tree_ctx_t *ctx,
ngx_str_t *path);
static ngx_int_t ngx_http_dav_is_symlink(ngx_log_t *log, u_char *path);
static ngx_int_t ngx_http_dav_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path);
static ngx_int_t ngx_http_dav_mkcol_handler(ngx_http_request_t *r,
@ -51,6 +65,8 @@ static void ngx_http_dav_merge_slashes(ngx_str_t *path);
static ngx_int_t ngx_http_dav_validate_paths(ngx_http_request_t *r,
ngx_str_t *src, ngx_str_t *dst, ngx_uint_t slash, ngx_table_elt_t *dest);
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_check_symlink(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,
@ -93,6 +109,13 @@ static ngx_command_t ngx_http_dav_commands[] = {
offsetof(ngx_http_dav_loc_conf_t, create_full_put_path),
NULL },
{ ngx_string("dav_follow_symlinks"),
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_loc_conf_t, follow_symlinks),
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,
@ -408,17 +431,50 @@ ok:
static ngx_int_t
ngx_http_dav_delete_path(ngx_http_request_t *r, ngx_str_t *path, ngx_uint_t dir)
{
char *failed;
ngx_tree_ctx_t tree;
char *failed;
ngx_tree_ctx_t tree;
ngx_http_dav_delete_ctx_t ctx;
ngx_http_dav_loc_conf_t *dlcf;
if (dir) {
dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
ctx.follow_symlinks = dlcf->follow_symlinks;
if (!ctx.follow_symlinks) {
ngx_int_t src;
path->data[path->len] = '\0';
src = ngx_http_dav_is_symlink(r->connection->log, path->data);
if (src == -1) {
path->data[path->len] = '/';
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (src == 1) {
if (ngx_delete_file(path->data) == NGX_FILE_ERROR) {
return ngx_http_dav_error(r->connection->log, ngx_errno,
NGX_HTTP_NOT_FOUND,
ngx_delete_file_n,
path->data);
}
path->data[path->len] = '/';
return NGX_OK;
}
path->data[path->len] = '/';
}
tree.init_handler = NULL;
tree.file_handler = ngx_http_dav_delete_file;
tree.pre_tree_handler = ngx_http_dav_noop;
tree.pre_tree_handler = ctx.follow_symlinks
? ngx_http_dav_noop
: ngx_http_dav_delete_pre_tree;
tree.post_tree_handler = ngx_http_dav_delete_dir;
tree.spec_handler = ngx_http_dav_delete_file;
tree.data = NULL;
tree.data = &ctx;
tree.alloc = 0;
tree.log = r->connection->log;
@ -484,6 +540,80 @@ ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
}
static ngx_int_t
ngx_http_dav_is_symlink(ngx_log_t *log, u_char *path)
{
ngx_file_info_t fi;
if (ngx_link_info(path, &fi) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ERR, log, ngx_errno,
ngx_link_info_n " \"%s\" failed", path);
return -1;
}
return ngx_is_link(&fi);
}
static ngx_int_t
ngx_http_dav_delete_pre_tree(ngx_tree_ctx_t *ctx, ngx_str_t *path)
{
ngx_int_t rc;
ngx_http_dav_delete_ctx_t *dctx;
dctx = ctx->data;
if (dctx->follow_symlinks) {
return NGX_OK;
}
rc = ngx_http_dav_is_symlink(ctx->log, path->data);
if (rc == -1) {
return NGX_DECLINED;
}
if (rc == 1) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
"http delete symlink: \"%s\"", path->data);
if (ngx_delete_file(path->data) == NGX_FILE_ERROR) {
(void) ngx_http_dav_error(ctx->log, ngx_errno, 0,
ngx_delete_file_n, path->data);
}
return NGX_DECLINED;
}
return NGX_OK;
}
static ngx_int_t
ngx_http_dav_check_symlink(ngx_tree_ctx_t *ctx, ngx_str_t *path)
{
ngx_int_t rc;
ngx_http_dav_prescan_ctx_t *ps;
ps = ctx->data;
rc = ngx_http_dav_is_symlink(ctx->log, path->data);
if (rc == 1) {
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
"symlink found in \"%s\"", path->data);
ps->found_symlink = 1;
return NGX_ABORT;
}
if (rc == -1) {
return NGX_ABORT;
}
return NGX_OK;
}
static ngx_int_t
ngx_http_dav_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path)
{
@ -559,6 +689,8 @@ ngx_http_dav_copy_move_handler(ngx_http_request_t *r)
return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
}
dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
dest = r->headers_in.destination;
if (dest == NULL) {
@ -793,6 +925,44 @@ overwrite_done:
return NGX_HTTP_BAD_REQUEST;
}
path.len -= 2; /* omit "/\0" */
if (!dlcf->follow_symlinks) {
ngx_int_t src;
ngx_http_dav_prescan_ctx_t ps;
path.data[path.len] = '\0';
src = ngx_http_dav_is_symlink(r->connection->log, path.data);
path.data[path.len] = '/';
if (src == -1) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (src == 1) {
return NGX_HTTP_FORBIDDEN;
}
ngx_memzero(&ps, sizeof(ngx_http_dav_prescan_ctx_t));
tree.init_handler = NULL;
tree.file_handler = ngx_http_dav_check_symlink;
tree.pre_tree_handler = ngx_http_dav_check_symlink;
tree.post_tree_handler = ngx_http_dav_noop;
tree.spec_handler = ngx_http_dav_check_symlink;
tree.data = &ps;
tree.alloc = 0;
tree.log = r->connection->log;
if (ngx_walk_tree(&tree, &path) != NGX_OK) {
if (ps.found_symlink) {
return NGX_HTTP_FORBIDDEN;
}
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
}
if (overwrite) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http delete: \"%s\"", copy.path.data);
@ -803,11 +973,6 @@ overwrite_done:
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) {
@ -850,9 +1015,6 @@ overwrite_done:
} else {
if (r->method == NGX_HTTP_MOVE) {
dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
ext.access = 0;
ext.path_access = dlcf->access;
ext.time = -1;
@ -1130,7 +1292,12 @@ ngx_http_dav_error(ngx_log_t *log, ngx_err_t err, ngx_int_t not_found,
level = NGX_LOG_ERR;
rc = not_found;
} else if (err == NGX_EACCES || err == NGX_EPERM) {
} else if (err == NGX_EACCES || err == NGX_EPERM
#if (NGX_HAVE_OPENAT)
|| err == NGX_ELOOP || err == NGX_EMLINK
#endif
)
{
level = NGX_LOG_ERR;
rc = NGX_HTTP_FORBIDDEN;
@ -1212,6 +1379,7 @@ ngx_http_dav_create_loc_conf(ngx_conf_t *cf)
conf->min_delete_depth = NGX_CONF_UNSET_UINT;
conf->access = NGX_CONF_UNSET_UINT;
conf->create_full_put_path = NGX_CONF_UNSET;
conf->follow_symlinks = NGX_CONF_UNSET;
return conf;
}
@ -1234,6 +1402,9 @@ ngx_http_dav_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_value(conf->create_full_put_path,
prev->create_full_put_path, 0);
ngx_conf_merge_value(conf->follow_symlinks,
prev->follow_symlinks, 1);
return NGX_CONF_OK;
}