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

709 lines
16 KiB
C

/*
* Copyright (C) Starfields
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include "ngx_http_webdav_utils.h"
#define NGX_HTTP_DAV_NO_DEPTH -3
#define NGX_HTTP_DAV_INVALID_DEPTH -2
#define NGX_HTTP_DAV_INFINITY_DEPTH -1
static ngx_int_t ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path);
static ngx_int_t ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path);
ngx_int_t
ngx_http_dav_error(ngx_log_t *log, ngx_err_t err, ngx_int_t not_found,
char *failed, u_char *path)
{
ngx_int_t rc;
ngx_uint_t level;
if (err == NGX_ENOENT || err == NGX_ENOTDIR || err == NGX_ENAMETOOLONG) {
level = NGX_LOG_ERR;
rc = not_found;
} else if (err == NGX_EACCES || err == NGX_EPERM) {
level = NGX_LOG_ERR;
rc = NGX_HTTP_FORBIDDEN;
} else if (err == NGX_EEXIST) {
level = NGX_LOG_ERR;
rc = NGX_HTTP_NOT_ALLOWED;
} else if (err == NGX_ENOSPC) {
level = NGX_LOG_CRIT;
rc = NGX_HTTP_INSUFFICIENT_STORAGE;
} else {
level = NGX_LOG_CRIT;
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_log_error(level, log, err, "%s \"%s\" failed", failed, path);
return rc;
}
ngx_int_t
ngx_http_dav_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path)
{
return NGX_OK;
}
ngx_int_t ngx_http_dav_ext_strip_uri(ngx_http_request_t *r,
ngx_str_t *uri) {
u_char *p, *last, *host;
size_t len;
if (uri->data[0] == '/') {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http dav_ext strip uri:\"%V\" unchanged", uri);
return NGX_OK;
}
len = r->headers_in.server.len;
if (len == 0) {
goto failed;
}
#if (NGX_HTTP_SSL)
if (r->connection->ssl) {
if (ngx_strncmp(uri->data, "https://", sizeof("https://") - 1) != 0) {
goto failed;
}
host = uri->data + sizeof("https://") - 1;
} else
#endif
{
if (ngx_strncmp(uri->data, "http://", sizeof("http://") - 1) != 0) {
goto failed;
}
host = uri->data + sizeof("http://") - 1;
}
if (ngx_strncmp(host, r->headers_in.server.data, len) != 0) {
goto failed;
}
last = uri->data + uri->len;
for (p = host + len; p != last; p++) {
if (*p == '/') {
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http dav_ext strip uri \"%V\" \"%*s\"", uri, last - p, p);
uri->data = p;
uri->len = last - p;
return NGX_OK;
}
}
failed:
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http dav_ext strip uri \"%V\" failed", uri);
return NGX_DECLINED;
}
ngx_int_t
ngx_http_dav_delete_path(ngx_http_request_t *r, ngx_str_t *path, ngx_uint_t dir)
{
char *failed;
ngx_tree_ctx_t tree;
if (dir) {
tree.init_handler = NULL;
tree.file_handler = ngx_http_dav_delete_file;
tree.pre_tree_handler = ngx_http_dav_noop;
tree.post_tree_handler = ngx_http_dav_delete_dir;
tree.spec_handler = ngx_http_dav_delete_file;
tree.data = NULL;
tree.alloc = 0;
tree.log = r->connection->log;
/* TODO: 207 */
if (ngx_walk_tree(&tree, path) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (ngx_delete_dir(path->data) != NGX_FILE_ERROR) {
return NGX_OK;
}
failed = ngx_delete_dir_n;
} else {
if (ngx_delete_file(path->data) != NGX_FILE_ERROR) {
return NGX_OK;
}
failed = ngx_delete_file_n;
}
return ngx_http_dav_error(r->connection->log, ngx_errno,
NGX_HTTP_NOT_FOUND, failed, path->data);
}
static ngx_int_t
ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path)
{
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
"http delete dir: \"%s\"", path->data);
if (ngx_delete_dir(path->data) == NGX_FILE_ERROR) {
/* TODO: add to 207 */
(void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_delete_dir_n,
path->data);
}
return NGX_OK;
}
static ngx_int_t
ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
{
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
"http delete file: \"%s\"", path->data);
if (ngx_delete_file(path->data) == NGX_FILE_ERROR) {
/* TODO: add to 207 */
(void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_delete_file_n,
path->data);
}
return NGX_OK;
}
ngx_int_t ensure_trailing_slash(ngx_http_request_t *r,
ngx_str_t *path) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"initial path: %s", path->data);
u_char *p = ngx_pnalloc(r->pool, path->len + 1);
if (p == NULL) {
return 0;
}
u_char *last = ngx_cpystrn(p, path->data, path->len + 1);
*last++ = '/';
*last++ = '\0';
path->data = p;
path->len += 1;
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"rewritten the path with a trailing slash: %s", path->data);
return 1;
}
ngx_int_t
ngx_http_dav_depth(ngx_http_request_t *r, ngx_int_t dflt)
{
ngx_table_elt_t *depth;
depth = r->headers_in.depth;
if (depth == NULL) {
return dflt;
}
if (depth->value.len == 1) {
if (depth->value.data[0] == '0') {
return 0;
}
if (depth->value.data[0] == '1') {
return 1;
}
} else {
if (depth->value.len == sizeof("infinity") - 1
&& ngx_strcmp(depth->value.data, "infinity") == 0)
{
return NGX_HTTP_DAV_INFINITY_DEPTH;
}
}
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"client sent invalid \"Depth\" header: \"%V\"",
&depth->value);
return NGX_HTTP_DAV_INVALID_DEPTH;
}
ngx_int_t ngx_http_dav_ext_depth(ngx_http_request_t *r,
ngx_int_t default_depth) {
ngx_table_elt_t *depth;
depth = r->headers_in.depth;
if (depth == NULL) {
return default_depth;
}
if (depth->value.len == 1) {
if (depth->value.data[0] == '0') {
return 0;
}
if (depth->value.data[0] == '1') {
return 1;
}
} else {
if (depth->value.len == sizeof("infinity") - 1 &&
ngx_strcmp(depth->value.data, "infinity") == 0) {
return NGX_MAX_INT_T_VALUE;
}
}
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"client sent invalid \"Depth\" header: \"%V\"", &depth->value);
return NGX_ERROR;
}
ngx_int_t
ngx_http_dav_location(ngx_http_request_t *r)
{
u_char *p;
size_t len;
uintptr_t escape;
r->headers_out.location = ngx_list_push(&r->headers_out.headers);
if (r->headers_out.location == NULL) {
return NGX_ERROR;
}
r->headers_out.location->hash = 1;
r->headers_out.location->next = NULL;
ngx_str_set(&r->headers_out.location->key, "Location");
escape = 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, NGX_ESCAPE_URI);
if (escape) {
len = r->uri.len + escape;
p = ngx_pnalloc(r->pool, len);
if (p == NULL) {
ngx_http_clear_location(r);
return NGX_ERROR;
}
r->headers_out.location->value.len = len;
r->headers_out.location->value.data = p;
ngx_escape_uri(p, r->uri.data, r->uri.len, NGX_ESCAPE_URI);
} else {
r->headers_out.location->value = r->uri;
}
return NGX_OK;
}
uint32_t ngx_http_dav_ext_if(ngx_http_request_t *r, ngx_str_t *uri) {
u_char *p, ch;
uint32_t token;
ngx_str_t tag;
ngx_uint_t i, n;
ngx_list_part_t *part;
ngx_table_elt_t *header;
static u_char name[] = "if";
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http dav_ext if \"%V\"", uri);
part = &r->headers_in.headers.part;
header = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
for (n = 0; n < sizeof(name) - 1 && n < header[i].key.len; n++) {
ch = header[i].key.data[n];
if (ch >= 'A' && ch <= 'Z') {
ch |= 0x20;
}
if (name[n] != ch) {
break;
}
}
if (n == sizeof(name) - 1 && n == header[i].key.len) {
p = header[i].value.data;
tag = r->uri;
while (*p != '\0') {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http dav_ext if list \"%s\"", p);
while (*p == ' ') {
p++;
}
if (*p == '<') {
tag.data = ++p;
while (*p != '\0' && *p != '>') {
p++;
}
if (*p == '\0') {
break;
}
tag.len = p++ - tag.data;
(void)ngx_http_dav_ext_strip_uri(r, &tag);
while (*p == ' ') {
p++;
}
}
if (*p != '(') {
break;
}
p++;
if (tag.len == 0 || tag.len > uri->len ||
(tag.len < uri->len && tag.data[tag.len - 1] != '/') ||
ngx_memcmp(tag.data, uri->data, tag.len)) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http dav_ext if tag mismatch \"%V\"", &tag);
while (*p != '\0' && *p != ')') {
p++;
}
if (*p == ')') {
p++;
}
continue;
}
while (*p != '\0') {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http dav_ext if condition \"%s\"", p);
while (*p == ' ') {
p++;
}
if (ngx_strncmp(p, "Not", 3) == 0) {
p += 3;
while (*p == ' ') {
p++;
}
goto next;
}
if (*p == '[') {
p++;
while (*p != '\0' && *p != ']') {
p++;
}
goto next;
}
if (ngx_strncmp(p, "<urn:", 5)) {
goto next;
}
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;
}
goto next;
}
if (*p != '>') {
goto next;
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http dav_ext if token: %uxD", token);
return token;
next:
while (*p != '\0' && *p != ' ' && *p != ')') {
p++;
}
if (*p == ')') {
p++;
break;
}
}
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http dav_ext if header mismatch");
}
}
return 0;
}
ngx_http_dav_ext_node_t *
ngx_http_dav_ext_lock_lookup(ngx_http_request_t *r,
ngx_http_dav_ext_lock_t *lock, ngx_str_t *uri,
ngx_int_t depth) {
time_t now;
ngx_queue_t *q;
ngx_http_dav_ext_node_t *node;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http dav_ext lock lookup \"%V\"", uri);
if (uri->len == 0) {
return NULL;
}
now = ngx_time();
while (!ngx_queue_empty(&lock->sh->queue)) {
q = ngx_queue_head(&lock->sh->queue);
node = (ngx_http_dav_ext_node_t *)q;
if (node->expire >= now) {
break;
}
ngx_queue_remove(q);
ngx_slab_free_locked(lock->shpool, node);
}
for (q = ngx_queue_head(&lock->sh->queue);
q != ngx_queue_sentinel(&lock->sh->queue); q = ngx_queue_next(q)) {
node = (ngx_http_dav_ext_node_t *)q;
if (uri->len >= node->len) {
if (ngx_memcmp(uri->data, node->data, node->len)) {
continue;
}
if (uri->len > node->len) {
if (node->data[node->len - 1] != '/') {
continue;
}
if (!node->infinite &&
ngx_strlchr(uri->data + node->len, uri->data + uri->len - 1, '/')) {
continue;
}
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http dav_ext lock found \"%*s\"", node->len, node->data);
return node;
}
/* uri->len < node->len */
if (depth >= 0) {
if (ngx_memcmp(node->data, uri->data, uri->len)) {
continue;
}
if (uri->data[uri->len - 1] != '/') {
continue;
}
if (depth == 0 &&
ngx_strlchr(node->data + uri->len, node->data + node->len - 1, '/')) {
continue;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http dav_ext lock found \"%*s\"", node->len, node->data);
return node;
}
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http dav_ext lock not found");
return NULL;
}
uintptr_t
ngx_http_dav_ext_format_lockdiscovery(ngx_http_request_t *r, u_char *dst,
ngx_http_dav_ext_entry_t *entry) {
size_t len;
time_t now;
if (dst == NULL) {
if (entry->lock_token == 0) {
return sizeof("<D:lockdiscovery/>\n") - 1;
}
len = sizeof("<D:lockdiscovery>\n"
"<D:activelock>\n"
"<D:locktype><D:write/></D:locktype>\n"
"<D:lockscope><D:exclusive/></D:lockscope>\n"
"<D:depth>infinity</D:depth>\n"
"<D:timeout>Second-</D:timeout>\n"
"<D:locktoken><D:href></D:href></D:locktoken>\n"
"<D:lockroot><D:href></D:href></D:lockroot>\n"
"</D:activelock>\n"
"</D:lockdiscovery>\n") -
1;
/* timeout */
len += NGX_TIME_T_LEN;
/* token */
len += ngx_http_dav_ext_format_token(NULL, entry->lock_token, 0);
/* lockroot */
len += entry->lock_root.len +
ngx_escape_html(NULL, entry->lock_root.data, entry->lock_root.len);
return len;
}
if (entry->lock_token == 0) {
dst = ngx_cpymem(dst, "<D:lockdiscovery/>\n",
sizeof("<D:lockdiscovery/>\n") - 1);
return (uintptr_t)dst;
}
now = ngx_time();
dst =
ngx_cpymem(dst, "<D:lockdiscovery>\n", sizeof("<D:lockdiscovery>\n") - 1);
dst = ngx_cpymem(dst, "<D:activelock>\n", sizeof("<D:activelock>\n") - 1);
dst = ngx_cpymem(dst, "<D:locktype><D:write/></D:locktype>\n",
sizeof("<D:locktype><D:write/></D:locktype>\n") - 1);
dst = ngx_cpymem(dst, "<D:lockscope><D:exclusive/></D:lockscope>\n",
sizeof("<D:lockscope><D:exclusive/></D:lockscope>\n") - 1);
dst = ngx_sprintf(dst, "<D:depth>%s</D:depth>\n",
entry->lock_infinite ? "infinity" : "0");
dst = ngx_sprintf(dst, "<D:timeout>Second-%T</D:timeout>\n",
entry->lock_expire - now);
dst = ngx_cpymem(dst, "<D:locktoken><D:href>",
sizeof("<D:locktoken><D:href>") - 1);
dst = (u_char *)ngx_http_dav_ext_format_token(dst, entry->lock_token, 0);
dst = ngx_cpymem(dst, "</D:href></D:locktoken>\n",
sizeof("</D:href></D:locktoken>\n") - 1);
dst = ngx_cpymem(dst, "<D:lockroot><D:href>",
sizeof("<D:lockroot><D:href>") - 1);
dst = (u_char *)ngx_escape_html(dst, entry->lock_root.data,
entry->lock_root.len);
dst = ngx_cpymem(dst, "</D:href></D:lockroot>\n",
sizeof("</D:href></D:lockroot>\n") - 1);
dst = ngx_cpymem(dst, "</D:activelock>\n", sizeof("</D:activelock>\n") - 1);
dst = ngx_cpymem(dst, "</D:lockdiscovery>\n",
sizeof("</D:lockdiscovery>\n") - 1);
return (uintptr_t)dst;
}
uintptr_t ngx_http_dav_ext_format_token(u_char *dst, uint32_t token,
ngx_uint_t brackets) {
ngx_uint_t n;
static u_char hex[] = "0123456789abcdef";
if (dst == NULL) {
return sizeof("<urn:deadbeef>") - 1 + (brackets ? 2 : 0);
}
if (brackets) {
*dst++ = '<';
}
dst = ngx_cpymem(dst, "urn:", 4);
for (n = 0; n < 4; n++) {
*dst++ = hex[token >> 28];
*dst++ = hex[(token >> 24) & 0xf];
token <<= 8;
}
if (brackets) {
*dst++ = '>';
}
return (uintptr_t)dst;
}