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