47e08e733afd798482c04d1273e8793003883e98
[nginx.git] / nginx / src / http / modules / ngx_http_range_filter_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 /*
13  * the single part format:
14  *
15  * "HTTP/1.0 206 Partial Content" CRLF
16  * ... header ...
17  * "Content-Type: image/jpeg" CRLF
18  * "Content-Length: SIZE" CRLF
19  * "Content-Range: bytes START-END/SIZE" CRLF
20  * CRLF
21  * ... data ...
22  *
23  *
24  * the mutlipart format:
25  *
26  * "HTTP/1.0 206 Partial Content" CRLF
27  * ... header ...
28  * "Content-Type: multipart/byteranges; boundary=0123456789" CRLF
29  * CRLF
30  * CRLF
31  * "--0123456789" CRLF
32  * "Content-Type: image/jpeg" CRLF
33  * "Content-Range: bytes START0-END0/SIZE" CRLF
34  * CRLF
35  * ... data ...
36  * CRLF
37  * "--0123456789" CRLF
38  * "Content-Type: image/jpeg" CRLF
39  * "Content-Range: bytes START1-END1/SIZE" CRLF
40  * CRLF
41  * ... data ...
42  * CRLF
43  * "--0123456789--" CRLF
44  */
45
46
47 typedef struct {
48     off_t        start;
49     off_t        end;
50     ngx_str_t    content_range;
51 } ngx_http_range_t;
52
53
54 typedef struct {
55     off_t        offset;
56     ngx_str_t    boundary_header;
57     ngx_array_t  ranges;
58 } ngx_http_range_filter_ctx_t;
59
60
61 ngx_int_t ngx_http_range_parse(ngx_http_request_t *r,
62     ngx_http_range_filter_ctx_t *ctx);
63 static ngx_int_t ngx_http_range_singlepart_header(ngx_http_request_t *r,
64     ngx_http_range_filter_ctx_t *ctx);
65 static ngx_int_t ngx_http_range_multipart_header(ngx_http_request_t *r,
66     ngx_http_range_filter_ctx_t *ctx);
67 static ngx_int_t ngx_http_range_not_satisfiable(ngx_http_request_t *r);
68 static ngx_int_t ngx_http_range_test_overlapped(ngx_http_request_t *r,
69     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
70 static ngx_int_t ngx_http_range_singlepart_body(ngx_http_request_t *r,
71     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
72 static ngx_int_t ngx_http_range_multipart_body(ngx_http_request_t *r,
73     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
74
75 static ngx_int_t ngx_http_range_header_filter_init(ngx_conf_t *cf);
76 static ngx_int_t ngx_http_range_body_filter_init(ngx_conf_t *cf);
77
78
79 static ngx_http_module_t  ngx_http_range_header_filter_module_ctx = {
80     NULL,                                  /* preconfiguration */
81     ngx_http_range_header_filter_init,     /* postconfiguration */
82
83     NULL,                                  /* create main configuration */
84     NULL,                                  /* init main configuration */
85
86     NULL,                                  /* create server configuration */
87     NULL,                                  /* merge server configuration */
88
89     NULL,                                  /* create location configuration */
90     NULL,                                  /* merge location configuration */
91 };
92
93
94 ngx_module_t  ngx_http_range_header_filter_module = {
95     NGX_MODULE_V1,
96     &ngx_http_range_header_filter_module_ctx, /* module context */
97     NULL,                                  /* module directives */
98     NGX_HTTP_MODULE,                       /* module type */
99     NULL,                                  /* init master */
100     NULL,                                  /* init module */
101     NULL,                                  /* init process */
102     NULL,                                  /* init thread */
103     NULL,                                  /* exit thread */
104     NULL,                                  /* exit process */
105     NULL,                                  /* exit master */
106     NGX_MODULE_V1_PADDING
107 };
108
109
110 static ngx_http_module_t  ngx_http_range_body_filter_module_ctx = {
111     NULL,                                  /* preconfiguration */
112     ngx_http_range_body_filter_init,       /* postconfiguration */
113
114     NULL,                                  /* create main configuration */
115     NULL,                                  /* init main configuration */
116
117     NULL,                                  /* create server configuration */
118     NULL,                                  /* merge server configuration */
119
120     NULL,                                  /* create location configuration */
121     NULL,                                  /* merge location configuration */
122 };
123
124
125 ngx_module_t  ngx_http_range_body_filter_module = {
126     NGX_MODULE_V1,
127     &ngx_http_range_body_filter_module_ctx, /* module context */
128     NULL,                                  /* module directives */
129     NGX_HTTP_MODULE,                       /* module type */
130     NULL,                                  /* init master */
131     NULL,                                  /* init module */
132     NULL,                                  /* init process */
133     NULL,                                  /* init thread */
134     NULL,                                  /* exit thread */
135     NULL,                                  /* exit process */
136     NULL,                                  /* exit master */
137     NGX_MODULE_V1_PADDING
138 };
139
140
141 static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
142 static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
143
144
145 static ngx_int_t
146 ngx_http_range_header_filter(ngx_http_request_t *r)
147 {
148     time_t                        if_range;
149     ngx_int_t                     rc;
150     ngx_http_range_filter_ctx_t  *ctx;
151
152     if (r->http_version < NGX_HTTP_VERSION_10
153         || r->headers_out.status != NGX_HTTP_OK
154         || r != r->main
155         || r->headers_out.content_length_n == -1
156         || !r->allow_ranges)
157     {
158         return ngx_http_next_header_filter(r);
159     }
160
161     if (r->headers_in.range == NULL
162         || r->headers_in.range->value.len < 7
163         || ngx_strncasecmp(r->headers_in.range->value.data,
164                            (u_char *) "bytes=", 6)
165            != 0)
166     {
167         goto next_filter;
168     }
169
170     if (r->headers_in.if_range && r->headers_out.last_modified_time != -1) {
171
172         if_range = ngx_http_parse_time(r->headers_in.if_range->value.data,
173                                        r->headers_in.if_range->value.len);
174
175         ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
176                        "http ir:%d lm:%d",
177                        if_range, r->headers_out.last_modified_time);
178
179         if (if_range != r->headers_out.last_modified_time) {
180             goto next_filter;
181         }
182     }
183
184     ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_range_filter_ctx_t));
185     if (ctx == NULL) {
186         return NGX_ERROR;
187     }
188
189     if (ngx_array_init(&ctx->ranges, r->pool, 1, sizeof(ngx_http_range_t))
190         != NGX_OK)
191     {
192         return NGX_ERROR;
193     }
194
195     rc = ngx_http_range_parse(r, ctx);
196
197     if (rc == NGX_OK) {
198
199         ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
200
201         r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
202
203         if (ctx->ranges.nelts == 1) {
204             return ngx_http_range_singlepart_header(r, ctx);
205         }
206
207         return ngx_http_range_multipart_header(r, ctx);
208     }
209
210     if (rc == NGX_HTTP_RANGE_NOT_SATISFIABLE) {
211         return ngx_http_range_not_satisfiable(r);
212     }
213
214     /* rc == NGX_ERROR */
215
216     return rc;
217
218 next_filter:
219
220     r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers);
221     if (r->headers_out.accept_ranges == NULL) {
222         return NGX_ERROR;
223     }
224
225     r->headers_out.accept_ranges->hash = 1;
226     r->headers_out.accept_ranges->key.len = sizeof("Accept-Ranges") - 1;
227     r->headers_out.accept_ranges->key.data = (u_char *) "Accept-Ranges";
228     r->headers_out.accept_ranges->value.len = sizeof("bytes") - 1;
229     r->headers_out.accept_ranges->value.data = (u_char *) "bytes";
230
231     return ngx_http_next_header_filter(r);
232 }
233
234
235 ngx_int_t
236 ngx_http_range_parse(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx)
237 {
238     u_char            *p;
239     off_t              start, end;
240     ngx_uint_t         suffix;
241     ngx_http_range_t  *range;
242
243     p = r->headers_in.range->value.data + 6;
244
245     for ( ;; ) {
246         start = 0;
247         end = 0;
248         suffix = 0;
249
250         while (*p == ' ') { p++; }
251
252         if (*p != '-') {
253             if (*p < '0' || *p > '9') {
254                 return NGX_HTTP_RANGE_NOT_SATISFIABLE;
255             }
256
257             while (*p >= '0' && *p <= '9') {
258                 start = start * 10 + *p++ - '0';
259             }
260
261             while (*p == ' ') { p++; }
262
263             if (*p++ != '-') {
264                 return NGX_HTTP_RANGE_NOT_SATISFIABLE;
265             }
266
267             if (start >= r->headers_out.content_length_n) {
268                 return NGX_HTTP_RANGE_NOT_SATISFIABLE;
269             }
270
271             while (*p == ' ') { p++; }
272
273             if (*p == ',' || *p == '\0') {
274                 range = ngx_array_push(&ctx->ranges);
275                 if (range == NULL) {
276                     return NGX_ERROR;
277                 }
278
279                 range->start = start;
280                 range->end = r->headers_out.content_length_n;
281
282                 if (*p++ != ',') {
283                     return NGX_OK;
284                 }
285
286                 continue;
287             }
288
289         } else {
290             suffix = 1;
291             p++;
292         }
293
294         if (*p < '0' || *p > '9') {
295             return NGX_HTTP_RANGE_NOT_SATISFIABLE;
296         }
297
298         while (*p >= '0' && *p <= '9') {
299             end = end * 10 + *p++ - '0';
300         }
301
302         while (*p == ' ') { p++; }
303
304         if (*p != ',' && *p != '\0') {
305             return NGX_HTTP_RANGE_NOT_SATISFIABLE;
306         }
307
308         if (suffix) {
309            start = r->headers_out.content_length_n - end;
310            end = r->headers_out.content_length_n - 1;
311         }
312
313         if (start > end) {
314             return NGX_HTTP_RANGE_NOT_SATISFIABLE;
315         }
316
317         range = ngx_array_push(&ctx->ranges);
318         if (range == NULL) {
319             return NGX_ERROR;
320         }
321
322         range->start = start;
323
324         if (end >= r->headers_out.content_length_n) {
325             /*
326              * Download Accelerator sends the last byte position
327              * that equals to the file length
328              */
329             range->end = r->headers_out.content_length_n;
330
331         } else {
332             range->end = end + 1;
333         }
334
335         if (*p++ != ',') {
336             return NGX_OK;
337         }
338     }
339 }
340
341
342 static ngx_int_t
343 ngx_http_range_singlepart_header(ngx_http_request_t *r,
344     ngx_http_range_filter_ctx_t *ctx)
345 {
346     ngx_table_elt_t   *content_range;
347     ngx_http_range_t  *range;
348
349     content_range = ngx_list_push(&r->headers_out.headers);
350     if (content_range == NULL) {
351         return NGX_ERROR;
352     }
353
354     r->headers_out.content_range = content_range;
355
356     content_range->hash = 1;
357     content_range->key.len = sizeof("Content-Range") - 1;
358     content_range->key.data = (u_char *) "Content-Range";
359
360     content_range->value.data = ngx_pnalloc(r->pool,
361                                     sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN);
362     if (content_range->value.data == NULL) {
363         return NGX_ERROR;
364     }
365
366     /* "Content-Range: bytes SSSS-EEEE/TTTT" header */
367
368     range = ctx->ranges.elts;
369
370     content_range->value.len = ngx_sprintf(content_range->value.data,
371                                            "bytes %O-%O/%O",
372                                            range->start, range->end - 1,
373                                            r->headers_out.content_length_n)
374                                - content_range->value.data;
375
376     r->headers_out.content_length_n = range->end - range->start;
377
378     if (r->headers_out.content_length) {
379         r->headers_out.content_length->hash = 0;
380         r->headers_out.content_length = NULL;
381     }
382
383     return ngx_http_next_header_filter(r);
384 }
385
386
387 static ngx_int_t
388 ngx_http_range_multipart_header(ngx_http_request_t *r,
389     ngx_http_range_filter_ctx_t *ctx)
390 {
391     size_t              len;
392     ngx_uint_t          i;
393     ngx_http_range_t   *range;
394     ngx_atomic_uint_t   boundary;
395
396     len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
397           + sizeof(CRLF "Content-Type: ") - 1
398           + r->headers_out.content_type.len
399           + sizeof(CRLF "Content-Range: bytes ") - 1;
400
401     if (r->headers_out.charset.len) {
402         len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
403     }
404
405     ctx->boundary_header.data = ngx_pnalloc(r->pool, len);
406     if (ctx->boundary_header.data == NULL) {
407         return NGX_ERROR;
408     }
409
410     boundary = ngx_next_temp_number(0);
411
412     /*
413      * The boundary header of the range:
414      * CRLF
415      * "--0123456789" CRLF
416      * "Content-Type: image/jpeg" CRLF
417      * "Content-Range: bytes "
418      */
419
420     if (r->headers_out.charset.len) {
421         ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
422                                            CRLF "--%0muA" CRLF
423                                            "Content-Type: %V; charset=%V" CRLF
424                                            "Content-Range: bytes ",
425                                            boundary,
426                                            &r->headers_out.content_type,
427                                            &r->headers_out.charset)
428                                    - ctx->boundary_header.data;
429
430         r->headers_out.charset.len = 0;
431
432     } else if (r->headers_out.content_type.len) {
433         ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
434                                            CRLF "--%0muA" CRLF
435                                            "Content-Type: %V" CRLF
436                                            "Content-Range: bytes ",
437                                            boundary,
438                                            &r->headers_out.content_type)
439                                    - ctx->boundary_header.data;
440
441     } else {
442         ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
443                                            CRLF "--%0muA" CRLF
444                                            "Content-Range: bytes ",
445                                            boundary)
446                                    - ctx->boundary_header.data;
447     }
448
449     r->headers_out.content_type.data =
450         ngx_pnalloc(r->pool,
451                     sizeof("Content-Type: multipart/byteranges; boundary=") - 1
452                     + NGX_ATOMIC_T_LEN);
453
454     if (r->headers_out.content_type.data == NULL) {
455         return NGX_ERROR;
456     }
457
458     /* "Content-Type: multipart/byteranges; boundary=0123456789" */
459
460     r->headers_out.content_type.len =
461                            ngx_sprintf(r->headers_out.content_type.data,
462                                        "multipart/byteranges; boundary=%0muA",
463                                        boundary)
464                            - r->headers_out.content_type.data;
465
466
467     /* the size of the last boundary CRLF "--0123456789--" CRLF */
468
469     len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1;
470
471     range = ctx->ranges.elts;
472     for (i = 0; i < ctx->ranges.nelts; i++) {
473
474         /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */
475
476         range[i].content_range.data =
477                                ngx_pnalloc(r->pool, 3 * NGX_OFF_T_LEN + 2 + 4);
478
479         if (range[i].content_range.data == NULL) {
480             return NGX_ERROR;
481         }
482
483         range[i].content_range.len = ngx_sprintf(range[i].content_range.data,
484                                                "%O-%O/%O" CRLF CRLF,
485                                                range[i].start, range[i].end - 1,
486                                                r->headers_out.content_length_n)
487                                      - range[i].content_range.data;
488
489         len += ctx->boundary_header.len + range[i].content_range.len
490                                     + (size_t) (range[i].end - range[i].start);
491     }
492
493     r->headers_out.content_length_n = len;
494
495     if (r->headers_out.content_length) {
496         r->headers_out.content_length->hash = 0;
497         r->headers_out.content_length = NULL;
498     }
499
500     return ngx_http_next_header_filter(r);
501 }
502
503
504 static ngx_int_t
505 ngx_http_range_not_satisfiable(ngx_http_request_t *r)
506 {
507     ngx_table_elt_t  *content_range;
508
509     r->headers_out.status = NGX_HTTP_RANGE_NOT_SATISFIABLE;
510
511     content_range = ngx_list_push(&r->headers_out.headers);
512     if (content_range == NULL) {
513         return NGX_ERROR;
514     }
515
516     r->headers_out.content_range = content_range;
517
518     content_range->hash = 1;
519     content_range->key.len = sizeof("Content-Range") - 1;
520     content_range->key.data = (u_char *) "Content-Range";
521
522     content_range->value.data = ngx_pnalloc(r->pool,
523                                        sizeof("bytes */") - 1 + NGX_OFF_T_LEN);
524     if (content_range->value.data == NULL) {
525         return NGX_ERROR;
526     }
527
528     content_range->value.len = ngx_sprintf(content_range->value.data,
529                                            "bytes */%O",
530                                            r->headers_out.content_length_n)
531                                - content_range->value.data;
532
533     ngx_http_clear_content_length(r);
534
535     return NGX_HTTP_RANGE_NOT_SATISFIABLE;
536 }
537
538
539 static ngx_int_t
540 ngx_http_range_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
541 {
542     ngx_http_range_filter_ctx_t  *ctx;
543
544     if (in == NULL) {
545         return ngx_http_next_body_filter(r, in);
546     }
547
548     ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module);
549
550     if (ctx == NULL) {
551         return ngx_http_next_body_filter(r, in);
552     }
553
554     if (ctx->ranges.nelts == 1) {
555         return ngx_http_range_singlepart_body(r, ctx, in);
556     }
557
558     /*
559      * multipart ranges are supported only if whole body is in a single buffer
560      */
561
562     if (ngx_buf_special(in->buf)) {
563         return ngx_http_next_body_filter(r, in);
564     }
565
566     if (ngx_http_range_test_overlapped(r, ctx, in) != NGX_OK) {
567         return NGX_ERROR;
568     }
569
570     return ngx_http_range_multipart_body(r, ctx, in);
571 }
572
573
574 static ngx_int_t
575 ngx_http_range_test_overlapped(ngx_http_request_t *r,
576     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
577 {
578     off_t              start, last;
579     ngx_buf_t         *buf;
580     ngx_uint_t         i;
581     ngx_http_range_t  *range;
582
583     if (ctx->offset) {
584         goto overlapped;
585     }
586
587     buf = in->buf;
588
589     if (!buf->last_buf) {
590
591         if (buf->in_file) {
592             start = buf->file_pos + ctx->offset;
593             last = buf->file_last + ctx->offset;
594
595         } else {
596             start = buf->pos - buf->start + ctx->offset;
597             last = buf->last - buf->start + ctx->offset;
598         }
599
600         range = ctx->ranges.elts;
601         for (i = 0; i < ctx->ranges.nelts; i++) {
602             if (start > range[i].start || last < range[i].end) {
603                  goto overlapped;
604             }
605         }
606     }
607
608     ctx->offset = ngx_buf_size(buf);
609
610     return NGX_OK;
611
612 overlapped:
613
614     ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
615                   "range in overlapped buffers");
616
617     return NGX_ERROR;
618 }
619
620
621 static ngx_int_t
622 ngx_http_range_singlepart_body(ngx_http_request_t *r,
623     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
624 {
625     off_t              start, last;
626     ngx_buf_t         *buf;
627     ngx_chain_t       *out, *cl, **ll;
628     ngx_http_range_t  *range;
629
630     out = NULL;
631     ll = &out;
632     range = ctx->ranges.elts;
633
634     for (cl = in; cl; cl = cl->next) {
635
636         buf = cl->buf;
637
638         start = ctx->offset;
639         last = ctx->offset + ngx_buf_size(buf);
640
641         ctx->offset = last;
642
643         ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
644                        "http range body buf: %O-%O", start, last);
645
646         if (ngx_buf_special(buf)) {
647             *ll = cl;
648             ll = &cl->next;
649             continue;
650         }
651
652         if (range->end <= start || range->start >= last) {
653
654             ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
655                            "http range body skip");
656
657             if (buf->in_file) {
658                 buf->file_pos = buf->file_last;
659             }
660
661             buf->pos = buf->last;
662             buf->sync = 1;
663
664             continue;
665         }
666
667         if (range->start > start) {
668
669             if (buf->in_file) {
670                 buf->file_pos += range->start - start;
671             }
672
673             if (ngx_buf_in_memory(buf)) {
674                 buf->pos += (size_t) (range->start - start);
675             }
676         }
677
678         if (range->end <= last) {
679
680             if (buf->in_file) {
681                 buf->file_last -= last - range->end;
682             }
683
684             if (ngx_buf_in_memory(buf)) {
685                 buf->last -= (size_t) (last - range->end);
686             }
687
688             buf->last_buf = 1;
689             *ll = cl;
690             cl->next = NULL;
691
692             break;
693         }
694
695         *ll = cl;
696         ll = &cl->next;
697     }
698
699     if (out == NULL) {
700         return NGX_OK;
701     }
702
703     return ngx_http_next_body_filter(r, out);
704 }
705
706
707 static ngx_int_t
708 ngx_http_range_multipart_body(ngx_http_request_t *r,
709     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
710 {
711     ngx_buf_t         *b, *buf;
712     ngx_uint_t         i;
713     ngx_chain_t       *out, *hcl, *rcl, *dcl, **ll;
714     ngx_http_range_t  *range;
715
716     ll = &out;
717     buf = in->buf;
718     range = ctx->ranges.elts;
719
720     for (i = 0; i < ctx->ranges.nelts; i++) {
721
722         /*
723          * The boundary header of the range:
724          * CRLF
725          * "--0123456789" CRLF
726          * "Content-Type: image/jpeg" CRLF
727          * "Content-Range: bytes "
728          */
729
730         b = ngx_calloc_buf(r->pool);
731         if (b == NULL) {
732             return NGX_ERROR;
733         }
734
735         b->memory = 1;
736         b->pos = ctx->boundary_header.data;
737         b->last = ctx->boundary_header.data + ctx->boundary_header.len;
738
739         hcl = ngx_alloc_chain_link(r->pool);
740         if (hcl == NULL) {
741             return NGX_ERROR;
742         }
743
744         hcl->buf = b;
745
746
747         /* "SSSS-EEEE/TTTT" CRLF CRLF */
748
749         b = ngx_calloc_buf(r->pool);
750         if (b == NULL) {
751             return NGX_ERROR;
752         }
753
754         b->temporary = 1;
755         b->pos = range[i].content_range.data;
756         b->last = range[i].content_range.data + range[i].content_range.len;
757
758         rcl = ngx_alloc_chain_link(r->pool);
759         if (rcl == NULL) {
760             return NGX_ERROR;
761         }
762
763         rcl->buf = b;
764
765
766         /* the range data */
767
768         b = ngx_calloc_buf(r->pool);
769         if (b == NULL) {
770             return NGX_ERROR;
771         }
772
773         b->in_file = buf->in_file;
774         b->temporary = buf->temporary;
775         b->memory = buf->memory;
776         b->mmap = buf->mmap;
777         b->file = buf->file;
778
779         if (buf->in_file) {
780             b->file_pos = range[i].start;
781             b->file_last = range[i].end;
782         }
783
784         if (ngx_buf_in_memory(buf)) {
785             b->pos = buf->start + (size_t) range[i].start;
786             b->last = buf->start + (size_t) range[i].end;
787         }
788
789         dcl = ngx_alloc_chain_link(r->pool);
790         if (dcl == NULL) {
791             return NGX_ERROR;
792         }
793
794         dcl->buf = b;
795
796         *ll = hcl;
797         hcl->next = rcl;
798         rcl->next = dcl;
799         ll = &dcl->next;
800     }
801
802     /* the last boundary CRLF "--0123456789--" CRLF  */
803
804     b = ngx_calloc_buf(r->pool);
805     if (b == NULL) {
806         return NGX_ERROR;
807     }
808
809     b->temporary = 1;
810     b->last_buf = 1;
811
812     b->pos = ngx_pnalloc(r->pool, sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
813                                   + sizeof("--" CRLF) - 1);
814     if (b->pos == NULL) {
815         return NGX_ERROR;
816     }
817
818     b->last = ngx_cpymem(b->pos, ctx->boundary_header.data,
819                          sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN);
820     *b->last++ = '-'; *b->last++ = '-';
821     *b->last++ = CR; *b->last++ = LF;
822
823     hcl = ngx_alloc_chain_link(r->pool);
824     if (hcl == NULL) {
825         return NGX_ERROR;
826     }
827
828     hcl->buf = b;
829     hcl->next = NULL;
830
831     *ll = hcl;
832
833     return ngx_http_next_body_filter(r, out);
834 }
835
836
837 static ngx_int_t
838 ngx_http_range_header_filter_init(ngx_conf_t *cf)
839 {
840     ngx_http_next_header_filter = ngx_http_top_header_filter;
841     ngx_http_top_header_filter = ngx_http_range_header_filter;
842
843     return NGX_OK;
844 }
845
846
847 static ngx_int_t
848 ngx_http_range_body_filter_init(ngx_conf_t *cf)
849 {
850     ngx_http_next_body_filter = ngx_http_top_body_filter;
851     ngx_http_top_body_filter = ngx_http_range_body_filter;
852
853     return NGX_OK;
854 }