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