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