1 
2 /*
3  * Copyright (C) Igor Sysoev
4  * Copyright (C) Nginx, Inc.
5  */
6 
7 
8 #include <ngx_config.h>
9 #include <ngx_core.h>
10 #include <ngx_http.h>
11 
12 
13 #define NGX_HTTP_DAV_OFF             2
14 
15 
16 #define NGX_HTTP_DAV_NO_DEPTH        -3
17 #define NGX_HTTP_DAV_INVALID_DEPTH   -2
18 #define NGX_HTTP_DAV_INFINITY_DEPTH  -1
19 
20 
21 typedef struct {
22     ngx_uint_t  methods;
23     ngx_uint_t  access;
24     ngx_uint_t  min_delete_depth;
25     ngx_flag_t  create_full_put_path;
26 } ngx_http_dav_loc_conf_t;
27 
28 
29 typedef struct {
30     ngx_str_t   path;
31     size_t      len;
32 } ngx_http_dav_copy_ctx_t;
33 
34 
35 static ngx_int_t ngx_http_dav_handler(ngx_http_request_t *r);
36 
37 static void ngx_http_dav_put_handler(ngx_http_request_t *r);
38 
39 static ngx_int_t ngx_http_dav_delete_handler(ngx_http_request_t *r);
40 static ngx_int_t ngx_http_dav_delete_path(ngx_http_request_t *r,
41     ngx_str_t *path, ngx_uint_t dir);
42 static ngx_int_t ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path);
43 static ngx_int_t ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path);
44 static ngx_int_t ngx_http_dav_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path);
45 
46 static ngx_int_t ngx_http_dav_mkcol_handler(ngx_http_request_t *r,
47     ngx_http_dav_loc_conf_t *dlcf);
48 
49 static ngx_int_t ngx_http_dav_copy_move_handler(ngx_http_request_t *r);
50 static ngx_int_t ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path);
51 static ngx_int_t ngx_http_dav_copy_dir_time(ngx_tree_ctx_t *ctx,
52     ngx_str_t *path);
53 static ngx_int_t ngx_http_dav_copy_tree_file(ngx_tree_ctx_t *ctx,
54     ngx_str_t *path);
55 
56 static ngx_int_t ngx_http_dav_depth(ngx_http_request_t *r, ngx_int_t dflt);
57 static ngx_int_t ngx_http_dav_error(ngx_log_t *log, ngx_err_t err,
58     ngx_int_t not_found, char *failed, u_char *path);
59 static ngx_int_t ngx_http_dav_location(ngx_http_request_t *r, u_char *path);
60 static void *ngx_http_dav_create_loc_conf(ngx_conf_t *cf);
61 static char *ngx_http_dav_merge_loc_conf(ngx_conf_t *cf,
62     void *parent, void *child);
63 static ngx_int_t ngx_http_dav_init(ngx_conf_t *cf);
64 
65 
66 static ngx_conf_bitmask_t  ngx_http_dav_methods_mask[] = {
67     { ngx_string("off"), NGX_HTTP_DAV_OFF },
68     { ngx_string("put"), NGX_HTTP_PUT },
69     { ngx_string("delete"), NGX_HTTP_DELETE },
70     { ngx_string("mkcol"), NGX_HTTP_MKCOL },
71     { ngx_string("copy"), NGX_HTTP_COPY },
72     { ngx_string("move"), NGX_HTTP_MOVE },
73     { ngx_null_string, 0 }
74 };
75 
76 
77 static ngx_command_t  ngx_http_dav_commands[] = {
78 
79     { ngx_string("dav_methods"),
80       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
81       ngx_conf_set_bitmask_slot,
82       NGX_HTTP_LOC_CONF_OFFSET,
83       offsetof(ngx_http_dav_loc_conf_t, methods),
84       &ngx_http_dav_methods_mask },
85 
86     { ngx_string("create_full_put_path"),
87       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
88       ngx_conf_set_flag_slot,
89       NGX_HTTP_LOC_CONF_OFFSET,
90       offsetof(ngx_http_dav_loc_conf_t, create_full_put_path),
91       NULL },
92 
93     { ngx_string("min_delete_depth"),
94       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
95       ngx_conf_set_num_slot,
96       NGX_HTTP_LOC_CONF_OFFSET,
97       offsetof(ngx_http_dav_loc_conf_t, min_delete_depth),
98       NULL },
99 
100     { ngx_string("dav_access"),
101       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
102       ngx_conf_set_access_slot,
103       NGX_HTTP_LOC_CONF_OFFSET,
104       offsetof(ngx_http_dav_loc_conf_t, access),
105       NULL },
106 
107       ngx_null_command
108 };
109 
110 
111 static ngx_http_module_t  ngx_http_dav_module_ctx = {
112     NULL,                                  /* preconfiguration */
113     ngx_http_dav_init,                     /* postconfiguration */
114 
115     NULL,                                  /* create main configuration */
116     NULL,                                  /* init main configuration */
117 
118     NULL,                                  /* create server configuration */
119     NULL,                                  /* merge server configuration */
120 
121     ngx_http_dav_create_loc_conf,          /* create location configuration */
122     ngx_http_dav_merge_loc_conf            /* merge location configuration */
123 };
124 
125 
126 ngx_module_t  ngx_http_dav_module = {
127     NGX_MODULE_V1,
128     &ngx_http_dav_module_ctx,              /* module context */
129     ngx_http_dav_commands,                 /* module directives */
130     NGX_HTTP_MODULE,                       /* module type */
131     NULL,                                  /* init master */
132     NULL,                                  /* init module */
133     NULL,                                  /* init process */
134     NULL,                                  /* init thread */
135     NULL,                                  /* exit thread */
136     NULL,                                  /* exit process */
137     NULL,                                  /* exit master */
138     NGX_MODULE_V1_PADDING
139 };
140 
141 
142 static ngx_int_t
ngx_http_dav_handler(ngx_http_request_t * r)143 ngx_http_dav_handler(ngx_http_request_t *r)
144 {
145     ngx_int_t                 rc;
146     ngx_http_dav_loc_conf_t  *dlcf;
147 
148     dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
149 
150     if (!(r->method & dlcf->methods)) {
151         return NGX_DECLINED;
152     }
153 
154     switch (r->method) {
155 
156     case NGX_HTTP_PUT:
157 
158         if (r->uri.data[r->uri.len - 1] == '/') {
159             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
160                           "cannot PUT to a collection");
161             return NGX_HTTP_CONFLICT;
162         }
163 
164         if (r->headers_in.content_range) {
165             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
166                           "PUT with range is unsupported");
167             return NGX_HTTP_NOT_IMPLEMENTED;
168         }
169 
170         r->request_body_in_file_only = 1;
171         r->request_body_in_persistent_file = 1;
172         r->request_body_in_clean_file = 1;
173         r->request_body_file_group_access = 1;
174         r->request_body_file_log_level = 0;
175 
176         rc = ngx_http_read_client_request_body(r, ngx_http_dav_put_handler);
177 
178         if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
179             return rc;
180         }
181 
182         return NGX_DONE;
183 
184     case NGX_HTTP_DELETE:
185 
186         return ngx_http_dav_delete_handler(r);
187 
188     case NGX_HTTP_MKCOL:
189 
190         return ngx_http_dav_mkcol_handler(r, dlcf);
191 
192     case NGX_HTTP_COPY:
193 
194         return ngx_http_dav_copy_move_handler(r);
195 
196     case NGX_HTTP_MOVE:
197 
198         return ngx_http_dav_copy_move_handler(r);
199     }
200 
201     return NGX_DECLINED;
202 }
203 
204 
205 static void
ngx_http_dav_put_handler(ngx_http_request_t * r)206 ngx_http_dav_put_handler(ngx_http_request_t *r)
207 {
208     size_t                    root;
209     time_t                    date;
210     ngx_str_t                *temp, path;
211     ngx_uint_t                status;
212     ngx_file_info_t           fi;
213     ngx_ext_rename_file_t     ext;
214     ngx_http_dav_loc_conf_t  *dlcf;
215 
216     if (r->request_body == NULL) {
217         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
218                       "PUT request body is unavailable");
219         ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
220         return;
221     }
222 
223     if (r->request_body->temp_file == NULL) {
224         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
225                       "PUT request body must be in a file");
226         ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
227         return;
228     }
229 
230     if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
231         ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
232         return;
233     }
234 
235     path.len--;
236 
237     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
238                    "http put filename: \"%s\"", path.data);
239 
240     temp = &r->request_body->temp_file->file.name;
241 
242     if (ngx_file_info(path.data, &fi) == NGX_FILE_ERROR) {
243         status = NGX_HTTP_CREATED;
244 
245     } else {
246         status = NGX_HTTP_NO_CONTENT;
247 
248         if (ngx_is_dir(&fi)) {
249             ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EISDIR,
250                           "\"%s\" could not be created", path.data);
251 
252             if (ngx_delete_file(temp->data) == NGX_FILE_ERROR) {
253                 ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
254                               ngx_delete_file_n " \"%s\" failed",
255                               temp->data);
256             }
257 
258             ngx_http_finalize_request(r, NGX_HTTP_CONFLICT);
259             return;
260         }
261     }
262 
263     dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
264 
265     ext.access = dlcf->access;
266     ext.path_access = dlcf->access;
267     ext.time = -1;
268     ext.create_path = dlcf->create_full_put_path;
269     ext.delete_file = 1;
270     ext.log = r->connection->log;
271 
272     if (r->headers_in.date) {
273         date = ngx_parse_http_time(r->headers_in.date->value.data,
274                                    r->headers_in.date->value.len);
275 
276         if (date != NGX_ERROR) {
277             ext.time = date;
278             ext.fd = r->request_body->temp_file->file.fd;
279         }
280     }
281 
282     if (ngx_ext_rename_file(temp, &path, &ext) != NGX_OK) {
283         ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
284         return;
285     }
286 
287     if (status == NGX_HTTP_CREATED) {
288         if (ngx_http_dav_location(r, path.data) != NGX_OK) {
289             ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
290             return;
291         }
292 
293         r->headers_out.content_length_n = 0;
294     }
295 
296     r->headers_out.status = status;
297     r->header_only = 1;
298 
299     ngx_http_finalize_request(r, ngx_http_send_header(r));
300     return;
301 }
302 
303 
304 static ngx_int_t
ngx_http_dav_delete_handler(ngx_http_request_t * r)305 ngx_http_dav_delete_handler(ngx_http_request_t *r)
306 {
307     size_t                    root;
308     ngx_err_t                 err;
309     ngx_int_t                 rc, depth;
310     ngx_uint_t                i, d, dir;
311     ngx_str_t                 path;
312     ngx_file_info_t           fi;
313     ngx_http_dav_loc_conf_t  *dlcf;
314 
315     if (r->headers_in.content_length_n > 0) {
316         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
317                       "DELETE with body is unsupported");
318         return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
319     }
320 
321     dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
322 
323     if (dlcf->min_delete_depth) {
324         d = 0;
325 
326         for (i = 0; i < r->uri.len; /* void */) {
327             if (r->uri.data[i++] == '/') {
328                 if (++d >= dlcf->min_delete_depth && i < r->uri.len) {
329                     goto ok;
330                 }
331             }
332         }
333 
334         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
335                       "insufficient URI depth:%i to DELETE", d);
336         return NGX_HTTP_CONFLICT;
337     }
338 
339 ok:
340 
341     if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
342         return NGX_HTTP_INTERNAL_SERVER_ERROR;
343     }
344 
345     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
346                    "http delete filename: \"%s\"", path.data);
347 
348     if (ngx_link_info(path.data, &fi) == NGX_FILE_ERROR) {
349         err = ngx_errno;
350 
351         rc = (err == NGX_ENOTDIR) ? NGX_HTTP_CONFLICT : NGX_HTTP_NOT_FOUND;
352 
353         return ngx_http_dav_error(r->connection->log, err,
354                                   rc, ngx_link_info_n, path.data);
355     }
356 
357     if (ngx_is_dir(&fi)) {
358 
359         if (r->uri.data[r->uri.len - 1] != '/') {
360             ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EISDIR,
361                           "DELETE \"%s\" failed", path.data);
362             return NGX_HTTP_CONFLICT;
363         }
364 
365         depth = ngx_http_dav_depth(r, NGX_HTTP_DAV_INFINITY_DEPTH);
366 
367         if (depth != NGX_HTTP_DAV_INFINITY_DEPTH) {
368             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
369                           "\"Depth\" header must be infinity");
370             return NGX_HTTP_BAD_REQUEST;
371         }
372 
373         path.len -= 2;  /* omit "/\0" */
374 
375         dir = 1;
376 
377     } else {
378 
379         /*
380          * we do not need to test (r->uri.data[r->uri.len - 1] == '/')
381          * because ngx_link_info("/file/") returned NGX_ENOTDIR above
382          */
383 
384         depth = ngx_http_dav_depth(r, 0);
385 
386         if (depth != 0 && depth != NGX_HTTP_DAV_INFINITY_DEPTH) {
387             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
388                           "\"Depth\" header must be 0 or infinity");
389             return NGX_HTTP_BAD_REQUEST;
390         }
391 
392         dir = 0;
393     }
394 
395     rc = ngx_http_dav_delete_path(r, &path, dir);
396 
397     if (rc == NGX_OK) {
398         return NGX_HTTP_NO_CONTENT;
399     }
400 
401     return rc;
402 }
403 
404 
405 static ngx_int_t
ngx_http_dav_delete_path(ngx_http_request_t * r,ngx_str_t * path,ngx_uint_t dir)406 ngx_http_dav_delete_path(ngx_http_request_t *r, ngx_str_t *path, ngx_uint_t dir)
407 {
408     char            *failed;
409     ngx_tree_ctx_t   tree;
410 
411     if (dir) {
412 
413         tree.init_handler = NULL;
414         tree.file_handler = ngx_http_dav_delete_file;
415         tree.pre_tree_handler = ngx_http_dav_noop;
416         tree.post_tree_handler = ngx_http_dav_delete_dir;
417         tree.spec_handler = ngx_http_dav_delete_file;
418         tree.data = NULL;
419         tree.alloc = 0;
420         tree.log = r->connection->log;
421 
422         /* TODO: 207 */
423 
424         if (ngx_walk_tree(&tree, path) != NGX_OK) {
425             return NGX_HTTP_INTERNAL_SERVER_ERROR;
426         }
427 
428         if (ngx_delete_dir(path->data) != NGX_FILE_ERROR) {
429             return NGX_OK;
430         }
431 
432         failed = ngx_delete_dir_n;
433 
434     } else {
435 
436         if (ngx_delete_file(path->data) != NGX_FILE_ERROR) {
437             return NGX_OK;
438         }
439 
440         failed = ngx_delete_file_n;
441     }
442 
443     return ngx_http_dav_error(r->connection->log, ngx_errno,
444                               NGX_HTTP_NOT_FOUND, failed, path->data);
445 }
446 
447 
448 static ngx_int_t
ngx_http_dav_delete_dir(ngx_tree_ctx_t * ctx,ngx_str_t * path)449 ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path)
450 {
451     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
452                    "http delete dir: \"%s\"", path->data);
453 
454     if (ngx_delete_dir(path->data) == NGX_FILE_ERROR) {
455 
456         /* TODO: add to 207 */
457 
458         (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_delete_dir_n,
459                                   path->data);
460     }
461 
462     return NGX_OK;
463 }
464 
465 
466 static ngx_int_t
ngx_http_dav_delete_file(ngx_tree_ctx_t * ctx,ngx_str_t * path)467 ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
468 {
469     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
470                    "http delete file: \"%s\"", path->data);
471 
472     if (ngx_delete_file(path->data) == NGX_FILE_ERROR) {
473 
474         /* TODO: add to 207 */
475 
476         (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_delete_file_n,
477                                   path->data);
478     }
479 
480     return NGX_OK;
481 }
482 
483 
484 static ngx_int_t
ngx_http_dav_noop(ngx_tree_ctx_t * ctx,ngx_str_t * path)485 ngx_http_dav_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path)
486 {
487     return NGX_OK;
488 }
489 
490 
491 static ngx_int_t
ngx_http_dav_mkcol_handler(ngx_http_request_t * r,ngx_http_dav_loc_conf_t * dlcf)492 ngx_http_dav_mkcol_handler(ngx_http_request_t *r, ngx_http_dav_loc_conf_t *dlcf)
493 {
494     u_char    *p;
495     size_t     root;
496     ngx_str_t  path;
497 
498     if (r->headers_in.content_length_n > 0) {
499         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
500                       "MKCOL with body is unsupported");
501         return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
502     }
503 
504     if (r->uri.data[r->uri.len - 1] != '/') {
505         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
506                       "MKCOL can create a collection only");
507         return NGX_HTTP_CONFLICT;
508     }
509 
510     p = ngx_http_map_uri_to_path(r, &path, &root, 0);
511     if (p == NULL) {
512         return NGX_HTTP_INTERNAL_SERVER_ERROR;
513     }
514 
515     *(p - 1) = '\0';
516     r->uri.len--;
517 
518     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
519                    "http mkcol path: \"%s\"", path.data);
520 
521     if (ngx_create_dir(path.data, ngx_dir_access(dlcf->access))
522         != NGX_FILE_ERROR)
523     {
524         if (ngx_http_dav_location(r, path.data) != NGX_OK) {
525             return NGX_HTTP_INTERNAL_SERVER_ERROR;
526         }
527 
528         return NGX_HTTP_CREATED;
529     }
530 
531     return ngx_http_dav_error(r->connection->log, ngx_errno,
532                               NGX_HTTP_CONFLICT, ngx_create_dir_n, path.data);
533 }
534 
535 
536 static ngx_int_t
ngx_http_dav_copy_move_handler(ngx_http_request_t * r)537 ngx_http_dav_copy_move_handler(ngx_http_request_t *r)
538 {
539     u_char                   *p, *host, *last, ch;
540     size_t                    len, root;
541     ngx_err_t                 err;
542     ngx_int_t                 rc, depth;
543     ngx_uint_t                overwrite, slash, dir, flags;
544     ngx_str_t                 path, uri, duri, args;
545     ngx_tree_ctx_t            tree;
546     ngx_copy_file_t           cf;
547     ngx_file_info_t           fi;
548     ngx_table_elt_t          *dest, *over;
549     ngx_ext_rename_file_t     ext;
550     ngx_http_dav_copy_ctx_t   copy;
551     ngx_http_dav_loc_conf_t  *dlcf;
552 
553     if (r->headers_in.content_length_n > 0) {
554         return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
555     }
556 
557     dest = r->headers_in.destination;
558 
559     if (dest == NULL) {
560         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
561                       "client sent no \"Destination\" header");
562         return NGX_HTTP_BAD_REQUEST;
563     }
564 
565     p = dest->value.data;
566     /* there is always '\0' even after empty header value */
567     if (p[0] == '/') {
568         last = p + dest->value.len;
569         goto destination_done;
570     }
571 
572     len = r->headers_in.server.len;
573 
574     if (len == 0) {
575         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
576                       "client sent no \"Host\" header");
577         return NGX_HTTP_BAD_REQUEST;
578     }
579 
580 #if (NGX_HTTP_SSL)
581 
582     if (r->connection->ssl) {
583         if (ngx_strncmp(dest->value.data, "https://", sizeof("https://") - 1)
584             != 0)
585         {
586             goto invalid_destination;
587         }
588 
589         host = dest->value.data + sizeof("https://") - 1;
590 
591     } else
592 #endif
593     {
594         if (ngx_strncmp(dest->value.data, "http://", sizeof("http://") - 1)
595             != 0)
596         {
597             goto invalid_destination;
598         }
599 
600         host = dest->value.data + sizeof("http://") - 1;
601     }
602 
603     if (ngx_strncmp(host, r->headers_in.server.data, len) != 0) {
604         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
605                       "\"Destination\" URI \"%V\" is handled by "
606                       "different repository than the source URI",
607                       &dest->value);
608         return NGX_HTTP_BAD_REQUEST;
609     }
610 
611     last = dest->value.data + dest->value.len;
612 
613     for (p = host + len; p < last; p++) {
614         if (*p == '/') {
615             goto destination_done;
616         }
617     }
618 
619 invalid_destination:
620 
621     ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
622                   "client sent invalid \"Destination\" header: \"%V\"",
623                   &dest->value);
624     return NGX_HTTP_BAD_REQUEST;
625 
626 destination_done:
627 
628     duri.len = last - p;
629     duri.data = p;
630     flags = NGX_HTTP_LOG_UNSAFE;
631 
632     if (ngx_http_parse_unsafe_uri(r, &duri, &args, &flags) != NGX_OK) {
633         goto invalid_destination;
634     }
635 
636     if ((r->uri.data[r->uri.len - 1] == '/' && *(last - 1) != '/')
637         || (r->uri.data[r->uri.len - 1] != '/' && *(last - 1) == '/'))
638     {
639         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
640                       "both URI \"%V\" and \"Destination\" URI \"%V\" "
641                       "should be either collections or non-collections",
642                       &r->uri, &dest->value);
643         return NGX_HTTP_CONFLICT;
644     }
645 
646     depth = ngx_http_dav_depth(r, NGX_HTTP_DAV_INFINITY_DEPTH);
647 
648     if (depth != NGX_HTTP_DAV_INFINITY_DEPTH) {
649 
650         if (r->method == NGX_HTTP_COPY) {
651             if (depth != 0) {
652                 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
653                               "\"Depth\" header must be 0 or infinity");
654                 return NGX_HTTP_BAD_REQUEST;
655             }
656 
657         } else {
658             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
659                           "\"Depth\" header must be infinity");
660             return NGX_HTTP_BAD_REQUEST;
661         }
662     }
663 
664     over = r->headers_in.overwrite;
665 
666     if (over) {
667         if (over->value.len == 1) {
668             ch = over->value.data[0];
669 
670             if (ch == 'T' || ch == 't') {
671                 overwrite = 1;
672                 goto overwrite_done;
673             }
674 
675             if (ch == 'F' || ch == 'f') {
676                 overwrite = 0;
677                 goto overwrite_done;
678             }
679 
680         }
681 
682         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
683                       "client sent invalid \"Overwrite\" header: \"%V\"",
684                       &over->value);
685         return NGX_HTTP_BAD_REQUEST;
686     }
687 
688     overwrite = 1;
689 
690 overwrite_done:
691 
692     if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
693         return NGX_HTTP_INTERNAL_SERVER_ERROR;
694     }
695 
696     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
697                    "http copy from: \"%s\"", path.data);
698 
699     uri = r->uri;
700     r->uri = duri;
701 
702     if (ngx_http_map_uri_to_path(r, &copy.path, &root, 0) == NULL) {
703         return NGX_HTTP_INTERNAL_SERVER_ERROR;
704     }
705 
706     r->uri = uri;
707 
708     copy.path.len--;  /* omit "\0" */
709 
710     if (copy.path.data[copy.path.len - 1] == '/') {
711         slash = 1;
712         copy.path.len--;
713         copy.path.data[copy.path.len] = '\0';
714 
715     } else {
716         slash = 0;
717     }
718 
719     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
720                    "http copy to: \"%s\"", copy.path.data);
721 
722     if (ngx_link_info(copy.path.data, &fi) == NGX_FILE_ERROR) {
723         err = ngx_errno;
724 
725         if (err != NGX_ENOENT) {
726             return ngx_http_dav_error(r->connection->log, err,
727                                       NGX_HTTP_NOT_FOUND, ngx_link_info_n,
728                                       copy.path.data);
729         }
730 
731         /* destination does not exist */
732 
733         overwrite = 0;
734         dir = 0;
735 
736     } else {
737 
738         /* destination exists */
739 
740         if (ngx_is_dir(&fi) && !slash) {
741             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
742                           "\"%V\" could not be %Ved to collection \"%V\"",
743                           &r->uri, &r->method_name, &dest->value);
744             return NGX_HTTP_CONFLICT;
745         }
746 
747         if (!overwrite) {
748             ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EEXIST,
749                           "\"%s\" could not be created", copy.path.data);
750             return NGX_HTTP_PRECONDITION_FAILED;
751         }
752 
753         dir = ngx_is_dir(&fi);
754     }
755 
756     if (ngx_link_info(path.data, &fi) == NGX_FILE_ERROR) {
757         return ngx_http_dav_error(r->connection->log, ngx_errno,
758                                   NGX_HTTP_NOT_FOUND, ngx_link_info_n,
759                                   path.data);
760     }
761 
762     if (ngx_is_dir(&fi)) {
763 
764         if (r->uri.data[r->uri.len - 1] != '/') {
765             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
766                           "\"%V\" is collection", &r->uri);
767             return NGX_HTTP_BAD_REQUEST;
768         }
769 
770         if (overwrite) {
771             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
772                            "http delete: \"%s\"", copy.path.data);
773 
774             rc = ngx_http_dav_delete_path(r, &copy.path, dir);
775 
776             if (rc != NGX_OK) {
777                 return rc;
778             }
779         }
780     }
781 
782     if (ngx_is_dir(&fi)) {
783 
784         path.len -= 2;  /* omit "/\0" */
785 
786         if (r->method == NGX_HTTP_MOVE) {
787             if (ngx_rename_file(path.data, copy.path.data) != NGX_FILE_ERROR) {
788                 return NGX_HTTP_CREATED;
789             }
790         }
791 
792         if (ngx_create_dir(copy.path.data, ngx_file_access(&fi))
793             == NGX_FILE_ERROR)
794         {
795             return ngx_http_dav_error(r->connection->log, ngx_errno,
796                                       NGX_HTTP_NOT_FOUND,
797                                       ngx_create_dir_n, copy.path.data);
798         }
799 
800         copy.len = path.len;
801 
802         tree.init_handler = NULL;
803         tree.file_handler = ngx_http_dav_copy_tree_file;
804         tree.pre_tree_handler = ngx_http_dav_copy_dir;
805         tree.post_tree_handler = ngx_http_dav_copy_dir_time;
806         tree.spec_handler = ngx_http_dav_noop;
807         tree.data = &copy;
808         tree.alloc = 0;
809         tree.log = r->connection->log;
810 
811         if (ngx_walk_tree(&tree, &path) == NGX_OK) {
812 
813             if (r->method == NGX_HTTP_MOVE) {
814                 rc = ngx_http_dav_delete_path(r, &path, 1);
815 
816                 if (rc != NGX_OK) {
817                     return rc;
818                 }
819             }
820 
821             return NGX_HTTP_CREATED;
822         }
823 
824     } else {
825 
826         if (r->method == NGX_HTTP_MOVE) {
827 
828             dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
829 
830             ext.access = 0;
831             ext.path_access = dlcf->access;
832             ext.time = -1;
833             ext.create_path = 1;
834             ext.delete_file = 0;
835             ext.log = r->connection->log;
836 
837             if (ngx_ext_rename_file(&path, &copy.path, &ext) == NGX_OK) {
838                 return NGX_HTTP_NO_CONTENT;
839             }
840 
841             return NGX_HTTP_INTERNAL_SERVER_ERROR;
842         }
843 
844         cf.size = ngx_file_size(&fi);
845         cf.buf_size = 0;
846         cf.access = ngx_file_access(&fi);
847         cf.time = ngx_file_mtime(&fi);
848         cf.log = r->connection->log;
849 
850         if (ngx_copy_file(path.data, copy.path.data, &cf) == NGX_OK) {
851             return NGX_HTTP_NO_CONTENT;
852         }
853     }
854 
855     return NGX_HTTP_INTERNAL_SERVER_ERROR;
856 }
857 
858 
859 static ngx_int_t
ngx_http_dav_copy_dir(ngx_tree_ctx_t * ctx,ngx_str_t * path)860 ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path)
861 {
862     u_char                   *p, *dir;
863     size_t                    len;
864     ngx_http_dav_copy_ctx_t  *copy;
865 
866     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
867                    "http copy dir: \"%s\"", path->data);
868 
869     copy = ctx->data;
870 
871     len = copy->path.len + path->len;
872 
873     dir = ngx_alloc(len + 1, ctx->log);
874     if (dir == NULL) {
875         return NGX_ABORT;
876     }
877 
878     p = ngx_cpymem(dir, copy->path.data, copy->path.len);
879     (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1);
880 
881     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
882                    "http copy dir to: \"%s\"", dir);
883 
884     if (ngx_create_dir(dir, ngx_dir_access(ctx->access)) == NGX_FILE_ERROR) {
885         (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_create_dir_n,
886                                   dir);
887     }
888 
889     ngx_free(dir);
890 
891     return NGX_OK;
892 }
893 
894 
895 static ngx_int_t
ngx_http_dav_copy_dir_time(ngx_tree_ctx_t * ctx,ngx_str_t * path)896 ngx_http_dav_copy_dir_time(ngx_tree_ctx_t *ctx, ngx_str_t *path)
897 {
898     u_char                   *p, *dir;
899     size_t                    len;
900     ngx_http_dav_copy_ctx_t  *copy;
901 
902     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
903                    "http copy dir time: \"%s\"", path->data);
904 
905     copy = ctx->data;
906 
907     len = copy->path.len + path->len;
908 
909     dir = ngx_alloc(len + 1, ctx->log);
910     if (dir == NULL) {
911         return NGX_ABORT;
912     }
913 
914     p = ngx_cpymem(dir, copy->path.data, copy->path.len);
915     (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1);
916 
917     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
918                    "http copy dir time to: \"%s\"", dir);
919 
920 #if (NGX_WIN32)
921     {
922     ngx_fd_t  fd;
923 
924     fd = ngx_open_file(dir, NGX_FILE_RDWR, NGX_FILE_OPEN, 0);
925 
926     if (fd == NGX_INVALID_FILE) {
927         (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_open_file_n, dir);
928         goto failed;
929     }
930 
931     if (ngx_set_file_time(NULL, fd, ctx->mtime) != NGX_OK) {
932         ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
933                       ngx_set_file_time_n " \"%s\" failed", dir);
934     }
935 
936     if (ngx_close_file(fd) == NGX_FILE_ERROR) {
937         ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
938                       ngx_close_file_n " \"%s\" failed", dir);
939     }
940     }
941 
942 failed:
943 
944 #else
945 
946     if (ngx_set_file_time(dir, 0, ctx->mtime) != NGX_OK) {
947         ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
948                       ngx_set_file_time_n " \"%s\" failed", dir);
949     }
950 
951 #endif
952 
953     ngx_free(dir);
954 
955     return NGX_OK;
956 }
957 
958 
959 static ngx_int_t
ngx_http_dav_copy_tree_file(ngx_tree_ctx_t * ctx,ngx_str_t * path)960 ngx_http_dav_copy_tree_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
961 {
962     u_char                   *p, *file;
963     size_t                    len;
964     ngx_copy_file_t           cf;
965     ngx_http_dav_copy_ctx_t  *copy;
966 
967     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
968                    "http copy file: \"%s\"", path->data);
969 
970     copy = ctx->data;
971 
972     len = copy->path.len + path->len;
973 
974     file = ngx_alloc(len + 1, ctx->log);
975     if (file == NULL) {
976         return NGX_ABORT;
977     }
978 
979     p = ngx_cpymem(file, copy->path.data, copy->path.len);
980     (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1);
981 
982     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
983                    "http copy file to: \"%s\"", file);
984 
985     cf.size = ctx->size;
986     cf.buf_size = 0;
987     cf.access = ctx->access;
988     cf.time = ctx->mtime;
989     cf.log = ctx->log;
990 
991     (void) ngx_copy_file(path->data, file, &cf);
992 
993     ngx_free(file);
994 
995     return NGX_OK;
996 }
997 
998 
999 static ngx_int_t
ngx_http_dav_depth(ngx_http_request_t * r,ngx_int_t dflt)1000 ngx_http_dav_depth(ngx_http_request_t *r, ngx_int_t dflt)
1001 {
1002     ngx_table_elt_t  *depth;
1003 
1004     depth = r->headers_in.depth;
1005 
1006     if (depth == NULL) {
1007         return dflt;
1008     }
1009 
1010     if (depth->value.len == 1) {
1011 
1012         if (depth->value.data[0] == '0') {
1013             return 0;
1014         }
1015 
1016         if (depth->value.data[0] == '1') {
1017             return 1;
1018         }
1019 
1020     } else {
1021 
1022         if (depth->value.len == sizeof("infinity") - 1
1023             && ngx_strcmp(depth->value.data, "infinity") == 0)
1024         {
1025             return NGX_HTTP_DAV_INFINITY_DEPTH;
1026         }
1027     }
1028 
1029     ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
1030                   "client sent invalid \"Depth\" header: \"%V\"",
1031                   &depth->value);
1032 
1033     return NGX_HTTP_DAV_INVALID_DEPTH;
1034 }
1035 
1036 
1037 static 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)1038 ngx_http_dav_error(ngx_log_t *log, ngx_err_t err, ngx_int_t not_found,
1039     char *failed, u_char *path)
1040 {
1041     ngx_int_t   rc;
1042     ngx_uint_t  level;
1043 
1044     if (err == NGX_ENOENT || err == NGX_ENOTDIR || err == NGX_ENAMETOOLONG) {
1045         level = NGX_LOG_ERR;
1046         rc = not_found;
1047 
1048     } else if (err == NGX_EACCES || err == NGX_EPERM) {
1049         level = NGX_LOG_ERR;
1050         rc = NGX_HTTP_FORBIDDEN;
1051 
1052     } else if (err == NGX_EEXIST) {
1053         level = NGX_LOG_ERR;
1054         rc = NGX_HTTP_NOT_ALLOWED;
1055 
1056     } else if (err == NGX_ENOSPC) {
1057         level = NGX_LOG_CRIT;
1058         rc = NGX_HTTP_INSUFFICIENT_STORAGE;
1059 
1060     } else {
1061         level = NGX_LOG_CRIT;
1062         rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
1063     }
1064 
1065     ngx_log_error(level, log, err, "%s \"%s\" failed", failed, path);
1066 
1067     return rc;
1068 }
1069 
1070 
1071 static ngx_int_t
ngx_http_dav_location(ngx_http_request_t * r,u_char * path)1072 ngx_http_dav_location(ngx_http_request_t *r, u_char *path)
1073 {
1074     u_char                    *location;
1075     ngx_http_core_loc_conf_t  *clcf;
1076 
1077     r->headers_out.location = ngx_list_push(&r->headers_out.headers);
1078     if (r->headers_out.location == NULL) {
1079         return NGX_ERROR;
1080     }
1081 
1082     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
1083 
1084     if (!clcf->alias && clcf->root_lengths == NULL) {
1085         location = path + clcf->root.len;
1086 
1087     } else {
1088         location = ngx_pnalloc(r->pool, r->uri.len);
1089         if (location == NULL) {
1090             ngx_http_clear_location(r);
1091             return NGX_ERROR;
1092         }
1093 
1094         ngx_memcpy(location, r->uri.data, r->uri.len);
1095     }
1096 
1097     r->headers_out.location->hash = 1;
1098     ngx_str_set(&r->headers_out.location->key, "Location");
1099     r->headers_out.location->value.len = r->uri.len;
1100     r->headers_out.location->value.data = location;
1101 
1102     return NGX_OK;
1103 }
1104 
1105 
1106 static void *
ngx_http_dav_create_loc_conf(ngx_conf_t * cf)1107 ngx_http_dav_create_loc_conf(ngx_conf_t *cf)
1108 {
1109     ngx_http_dav_loc_conf_t  *conf;
1110 
1111     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_dav_loc_conf_t));
1112     if (conf == NULL) {
1113         return NULL;
1114     }
1115 
1116     /*
1117      * set by ngx_pcalloc():
1118      *
1119      *     conf->methods = 0;
1120      */
1121 
1122     conf->min_delete_depth = NGX_CONF_UNSET_UINT;
1123     conf->access = NGX_CONF_UNSET_UINT;
1124     conf->create_full_put_path = NGX_CONF_UNSET;
1125 
1126     return conf;
1127 }
1128 
1129 
1130 static char *
ngx_http_dav_merge_loc_conf(ngx_conf_t * cf,void * parent,void * child)1131 ngx_http_dav_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
1132 {
1133     ngx_http_dav_loc_conf_t  *prev = parent;
1134     ngx_http_dav_loc_conf_t  *conf = child;
1135 
1136     ngx_conf_merge_bitmask_value(conf->methods, prev->methods,
1137                          (NGX_CONF_BITMASK_SET|NGX_HTTP_DAV_OFF));
1138 
1139     ngx_conf_merge_uint_value(conf->min_delete_depth,
1140                          prev->min_delete_depth, 0);
1141 
1142     ngx_conf_merge_uint_value(conf->access, prev->access, 0600);
1143 
1144     ngx_conf_merge_value(conf->create_full_put_path,
1145                          prev->create_full_put_path, 0);
1146 
1147     return NGX_CONF_OK;
1148 }
1149 
1150 
1151 static ngx_int_t
ngx_http_dav_init(ngx_conf_t * cf)1152 ngx_http_dav_init(ngx_conf_t *cf)
1153 {
1154     ngx_http_handler_pt        *h;
1155     ngx_http_core_main_conf_t  *cmcf;
1156 
1157     cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
1158 
1159     h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
1160     if (h == NULL) {
1161         return NGX_ERROR;
1162     }
1163 
1164     *h = ngx_http_dav_handler;
1165 
1166     return NGX_OK;
1167 }
1168