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 
48 typedef struct {
49     off_t        start;
50     off_t        end;
51     ngx_str_t    content_range;
52 } ngx_http_range_t;
53 
54 
55 typedef 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 
62 static ngx_int_t ngx_http_range_parse(ngx_http_request_t *r,
63     ngx_http_range_filter_ctx_t *ctx, ngx_uint_t ranges);
64 static ngx_int_t ngx_http_range_singlepart_header(ngx_http_request_t *r,
65     ngx_http_range_filter_ctx_t *ctx);
66 static ngx_int_t ngx_http_range_multipart_header(ngx_http_request_t *r,
67     ngx_http_range_filter_ctx_t *ctx);
68 static ngx_int_t ngx_http_range_not_satisfiable(ngx_http_request_t *r);
69 static 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);
71 static 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);
73 static 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 
76 static ngx_int_t ngx_http_range_header_filter_init(ngx_conf_t *cf);
77 static ngx_int_t ngx_http_range_body_filter_init(ngx_conf_t *cf);
78 
79 
80 static 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 
95 ngx_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 
111 static 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 
126 ngx_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 
142 static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
143 static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
144 
145 
146 static ngx_int_t
ngx_http_range_header_filter(ngx_http_request_t * r)147 ngx_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 
218 parse:
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 
253 next_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 
268 static ngx_int_t
ngx_http_range_parse(ngx_http_request_t * r,ngx_http_range_filter_ctx_t * ctx,ngx_uint_t ranges)269 ngx_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 = (end < content_length) ? content_length - end : 0;
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             if (size > NGX_MAX_OFF_T_VALUE - (end - start)) {
381                 return NGX_HTTP_RANGE_NOT_SATISFIABLE;
382             }
383 
384             size += end - start;
385 
386             if (ranges-- == 0) {
387                 return NGX_DECLINED;
388             }
389 
390         } else if (start == 0) {
391             return NGX_DECLINED;
392         }
393 
394         if (*p++ != ',') {
395             break;
396         }
397     }
398 
399     if (ctx->ranges.nelts == 0) {
400         return NGX_HTTP_RANGE_NOT_SATISFIABLE;
401     }
402 
403     if (size > content_length) {
404         return NGX_DECLINED;
405     }
406 
407     return NGX_OK;
408 }
409 
410 
411 static ngx_int_t
ngx_http_range_singlepart_header(ngx_http_request_t * r,ngx_http_range_filter_ctx_t * ctx)412 ngx_http_range_singlepart_header(ngx_http_request_t *r,
413     ngx_http_range_filter_ctx_t *ctx)
414 {
415     ngx_table_elt_t   *content_range;
416     ngx_http_range_t  *range;
417 
418     if (r != r->main) {
419         return ngx_http_next_header_filter(r);
420     }
421 
422     content_range = ngx_list_push(&r->headers_out.headers);
423     if (content_range == NULL) {
424         return NGX_ERROR;
425     }
426 
427     r->headers_out.content_range = content_range;
428 
429     content_range->hash = 1;
430     ngx_str_set(&content_range->key, "Content-Range");
431 
432     content_range->value.data = ngx_pnalloc(r->pool,
433                                     sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN);
434     if (content_range->value.data == NULL) {
435         content_range->hash = 0;
436         r->headers_out.content_range = NULL;
437         return NGX_ERROR;
438     }
439 
440     /* "Content-Range: bytes SSSS-EEEE/TTTT" header */
441 
442     range = ctx->ranges.elts;
443 
444     content_range->value.len = ngx_sprintf(content_range->value.data,
445                                            "bytes %O-%O/%O",
446                                            range->start, range->end - 1,
447                                            r->headers_out.content_length_n)
448                                - content_range->value.data;
449 
450     r->headers_out.content_length_n = range->end - range->start;
451     r->headers_out.content_offset = range->start;
452 
453     if (r->headers_out.content_length) {
454         r->headers_out.content_length->hash = 0;
455         r->headers_out.content_length = NULL;
456     }
457 
458     return ngx_http_next_header_filter(r);
459 }
460 
461 
462 static ngx_int_t
ngx_http_range_multipart_header(ngx_http_request_t * r,ngx_http_range_filter_ctx_t * ctx)463 ngx_http_range_multipart_header(ngx_http_request_t *r,
464     ngx_http_range_filter_ctx_t *ctx)
465 {
466     off_t               len;
467     size_t              size;
468     ngx_uint_t          i;
469     ngx_http_range_t   *range;
470     ngx_atomic_uint_t   boundary;
471 
472     size = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
473            + sizeof(CRLF "Content-Type: ") - 1
474            + r->headers_out.content_type.len
475            + sizeof(CRLF "Content-Range: bytes ") - 1;
476 
477     if (r->headers_out.content_type_len == r->headers_out.content_type.len
478         && r->headers_out.charset.len)
479     {
480         size += sizeof("; charset=") - 1 + r->headers_out.charset.len;
481     }
482 
483     ctx->boundary_header.data = ngx_pnalloc(r->pool, size);
484     if (ctx->boundary_header.data == NULL) {
485         return NGX_ERROR;
486     }
487 
488     boundary = ngx_next_temp_number(0);
489 
490     /*
491      * The boundary header of the range:
492      * CRLF
493      * "--0123456789" CRLF
494      * "Content-Type: image/jpeg" CRLF
495      * "Content-Range: bytes "
496      */
497 
498     if (r->headers_out.content_type_len == r->headers_out.content_type.len
499         && r->headers_out.charset.len)
500     {
501         ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
502                                            CRLF "--%0muA" CRLF
503                                            "Content-Type: %V; charset=%V" CRLF
504                                            "Content-Range: bytes ",
505                                            boundary,
506                                            &r->headers_out.content_type,
507                                            &r->headers_out.charset)
508                                    - ctx->boundary_header.data;
509 
510     } else if (r->headers_out.content_type.len) {
511         ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
512                                            CRLF "--%0muA" CRLF
513                                            "Content-Type: %V" CRLF
514                                            "Content-Range: bytes ",
515                                            boundary,
516                                            &r->headers_out.content_type)
517                                    - ctx->boundary_header.data;
518 
519     } else {
520         ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
521                                            CRLF "--%0muA" CRLF
522                                            "Content-Range: bytes ",
523                                            boundary)
524                                    - ctx->boundary_header.data;
525     }
526 
527     r->headers_out.content_type.data =
528         ngx_pnalloc(r->pool,
529                     sizeof("Content-Type: multipart/byteranges; boundary=") - 1
530                     + NGX_ATOMIC_T_LEN);
531 
532     if (r->headers_out.content_type.data == NULL) {
533         return NGX_ERROR;
534     }
535 
536     r->headers_out.content_type_lowcase = NULL;
537 
538     /* "Content-Type: multipart/byteranges; boundary=0123456789" */
539 
540     r->headers_out.content_type.len =
541                            ngx_sprintf(r->headers_out.content_type.data,
542                                        "multipart/byteranges; boundary=%0muA",
543                                        boundary)
544                            - r->headers_out.content_type.data;
545 
546     r->headers_out.content_type_len = r->headers_out.content_type.len;
547 
548     r->headers_out.charset.len = 0;
549 
550     /* the size of the last boundary CRLF "--0123456789--" CRLF */
551 
552     len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1;
553 
554     range = ctx->ranges.elts;
555     for (i = 0; i < ctx->ranges.nelts; i++) {
556 
557         /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */
558 
559         range[i].content_range.data =
560                                ngx_pnalloc(r->pool, 3 * NGX_OFF_T_LEN + 2 + 4);
561 
562         if (range[i].content_range.data == NULL) {
563             return NGX_ERROR;
564         }
565 
566         range[i].content_range.len = ngx_sprintf(range[i].content_range.data,
567                                                "%O-%O/%O" CRLF CRLF,
568                                                range[i].start, range[i].end - 1,
569                                                r->headers_out.content_length_n)
570                                      - range[i].content_range.data;
571 
572         len += ctx->boundary_header.len + range[i].content_range.len
573                                              + (range[i].end - range[i].start);
574     }
575 
576     r->headers_out.content_length_n = len;
577 
578     if (r->headers_out.content_length) {
579         r->headers_out.content_length->hash = 0;
580         r->headers_out.content_length = NULL;
581     }
582 
583     return ngx_http_next_header_filter(r);
584 }
585 
586 
587 static ngx_int_t
ngx_http_range_not_satisfiable(ngx_http_request_t * r)588 ngx_http_range_not_satisfiable(ngx_http_request_t *r)
589 {
590     ngx_table_elt_t  *content_range;
591 
592     r->headers_out.status = NGX_HTTP_RANGE_NOT_SATISFIABLE;
593 
594     content_range = ngx_list_push(&r->headers_out.headers);
595     if (content_range == NULL) {
596         return NGX_ERROR;
597     }
598 
599     r->headers_out.content_range = content_range;
600 
601     content_range->hash = 1;
602     ngx_str_set(&content_range->key, "Content-Range");
603 
604     content_range->value.data = ngx_pnalloc(r->pool,
605                                        sizeof("bytes */") - 1 + NGX_OFF_T_LEN);
606     if (content_range->value.data == NULL) {
607         content_range->hash = 0;
608         r->headers_out.content_range = NULL;
609         return NGX_ERROR;
610     }
611 
612     content_range->value.len = ngx_sprintf(content_range->value.data,
613                                            "bytes */%O",
614                                            r->headers_out.content_length_n)
615                                - content_range->value.data;
616 
617     ngx_http_clear_content_length(r);
618 
619     return NGX_HTTP_RANGE_NOT_SATISFIABLE;
620 }
621 
622 
623 static ngx_int_t
ngx_http_range_body_filter(ngx_http_request_t * r,ngx_chain_t * in)624 ngx_http_range_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
625 {
626     ngx_http_range_filter_ctx_t  *ctx;
627 
628     if (in == NULL) {
629         return ngx_http_next_body_filter(r, in);
630     }
631 
632     ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module);
633 
634     if (ctx == NULL) {
635         return ngx_http_next_body_filter(r, in);
636     }
637 
638     if (ctx->ranges.nelts == 1) {
639         return ngx_http_range_singlepart_body(r, ctx, in);
640     }
641 
642     /*
643      * multipart ranges are supported only if whole body is in a single buffer
644      */
645 
646     if (ngx_buf_special(in->buf)) {
647         return ngx_http_next_body_filter(r, in);
648     }
649 
650     if (ngx_http_range_test_overlapped(r, ctx, in) != NGX_OK) {
651         return NGX_ERROR;
652     }
653 
654     return ngx_http_range_multipart_body(r, ctx, in);
655 }
656 
657 
658 static ngx_int_t
ngx_http_range_test_overlapped(ngx_http_request_t * r,ngx_http_range_filter_ctx_t * ctx,ngx_chain_t * in)659 ngx_http_range_test_overlapped(ngx_http_request_t *r,
660     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
661 {
662     off_t              start, last;
663     ngx_buf_t         *buf;
664     ngx_uint_t         i;
665     ngx_http_range_t  *range;
666 
667     if (ctx->offset) {
668         goto overlapped;
669     }
670 
671     buf = in->buf;
672 
673     if (!buf->last_buf) {
674         start = ctx->offset;
675         last = ctx->offset + ngx_buf_size(buf);
676 
677         range = ctx->ranges.elts;
678         for (i = 0; i < ctx->ranges.nelts; i++) {
679             if (start > range[i].start || last < range[i].end) {
680                 goto overlapped;
681             }
682         }
683     }
684 
685     ctx->offset = ngx_buf_size(buf);
686 
687     return NGX_OK;
688 
689 overlapped:
690 
691     ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
692                   "range in overlapped buffers");
693 
694     return NGX_ERROR;
695 }
696 
697 
698 static ngx_int_t
ngx_http_range_singlepart_body(ngx_http_request_t * r,ngx_http_range_filter_ctx_t * ctx,ngx_chain_t * in)699 ngx_http_range_singlepart_body(ngx_http_request_t *r,
700     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
701 {
702     off_t              start, last;
703     ngx_int_t          rc;
704     ngx_buf_t         *buf;
705     ngx_chain_t       *out, *cl, *tl, **ll;
706     ngx_http_range_t  *range;
707 
708     out = NULL;
709     ll = &out;
710     range = ctx->ranges.elts;
711 
712     for (cl = in; cl; cl = cl->next) {
713 
714         buf = cl->buf;
715 
716         start = ctx->offset;
717         last = ctx->offset + ngx_buf_size(buf);
718 
719         ctx->offset = last;
720 
721         ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
722                        "http range body buf: %O-%O", start, last);
723 
724         if (ngx_buf_special(buf)) {
725 
726             if (range->end <= start) {
727                 continue;
728             }
729 
730             tl = ngx_alloc_chain_link(r->pool);
731             if (tl == NULL) {
732                 return NGX_ERROR;
733             }
734 
735             tl->buf = buf;
736             tl->next = NULL;
737 
738             *ll = tl;
739             ll = &tl->next;
740 
741             continue;
742         }
743 
744         if (range->end <= start || range->start >= last) {
745 
746             ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
747                            "http range body skip");
748 
749             if (buf->in_file) {
750                 buf->file_pos = buf->file_last;
751             }
752 
753             buf->pos = buf->last;
754             buf->sync = 1;
755 
756             continue;
757         }
758 
759         if (range->start > start) {
760 
761             if (buf->in_file) {
762                 buf->file_pos += range->start - start;
763             }
764 
765             if (ngx_buf_in_memory(buf)) {
766                 buf->pos += (size_t) (range->start - start);
767             }
768         }
769 
770         if (range->end <= last) {
771 
772             if (buf->in_file) {
773                 buf->file_last -= last - range->end;
774             }
775 
776             if (ngx_buf_in_memory(buf)) {
777                 buf->last -= (size_t) (last - range->end);
778             }
779 
780             buf->last_buf = (r == r->main) ? 1 : 0;
781             buf->last_in_chain = 1;
782 
783             tl = ngx_alloc_chain_link(r->pool);
784             if (tl == NULL) {
785                 return NGX_ERROR;
786             }
787 
788             tl->buf = buf;
789             tl->next = NULL;
790 
791             *ll = tl;
792             ll = &tl->next;
793 
794             continue;
795         }
796 
797         tl = ngx_alloc_chain_link(r->pool);
798         if (tl == NULL) {
799             return NGX_ERROR;
800         }
801 
802         tl->buf = buf;
803         tl->next = NULL;
804 
805         *ll = tl;
806         ll = &tl->next;
807     }
808 
809     rc = ngx_http_next_body_filter(r, out);
810 
811     while (out) {
812         cl = out;
813         out = out->next;
814         ngx_free_chain(r->pool, cl);
815     }
816 
817     return rc;
818 }
819 
820 
821 static ngx_int_t
ngx_http_range_multipart_body(ngx_http_request_t * r,ngx_http_range_filter_ctx_t * ctx,ngx_chain_t * in)822 ngx_http_range_multipart_body(ngx_http_request_t *r,
823     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
824 {
825     ngx_buf_t         *b, *buf;
826     ngx_uint_t         i;
827     ngx_chain_t       *out, *hcl, *rcl, *dcl, **ll;
828     ngx_http_range_t  *range;
829 
830     ll = &out;
831     buf = in->buf;
832     range = ctx->ranges.elts;
833 
834     for (i = 0; i < ctx->ranges.nelts; i++) {
835 
836         /*
837          * The boundary header of the range:
838          * CRLF
839          * "--0123456789" CRLF
840          * "Content-Type: image/jpeg" CRLF
841          * "Content-Range: bytes "
842          */
843 
844         b = ngx_calloc_buf(r->pool);
845         if (b == NULL) {
846             return NGX_ERROR;
847         }
848 
849         b->memory = 1;
850         b->pos = ctx->boundary_header.data;
851         b->last = ctx->boundary_header.data + ctx->boundary_header.len;
852 
853         hcl = ngx_alloc_chain_link(r->pool);
854         if (hcl == NULL) {
855             return NGX_ERROR;
856         }
857 
858         hcl->buf = b;
859 
860 
861         /* "SSSS-EEEE/TTTT" CRLF CRLF */
862 
863         b = ngx_calloc_buf(r->pool);
864         if (b == NULL) {
865             return NGX_ERROR;
866         }
867 
868         b->temporary = 1;
869         b->pos = range[i].content_range.data;
870         b->last = range[i].content_range.data + range[i].content_range.len;
871 
872         rcl = ngx_alloc_chain_link(r->pool);
873         if (rcl == NULL) {
874             return NGX_ERROR;
875         }
876 
877         rcl->buf = b;
878 
879 
880         /* the range data */
881 
882         b = ngx_calloc_buf(r->pool);
883         if (b == NULL) {
884             return NGX_ERROR;
885         }
886 
887         b->in_file = buf->in_file;
888         b->temporary = buf->temporary;
889         b->memory = buf->memory;
890         b->mmap = buf->mmap;
891         b->file = buf->file;
892 
893         if (buf->in_file) {
894             b->file_pos = buf->file_pos + range[i].start;
895             b->file_last = buf->file_pos + range[i].end;
896         }
897 
898         if (ngx_buf_in_memory(buf)) {
899             b->pos = buf->pos + (size_t) range[i].start;
900             b->last = buf->pos + (size_t) range[i].end;
901         }
902 
903         dcl = ngx_alloc_chain_link(r->pool);
904         if (dcl == NULL) {
905             return NGX_ERROR;
906         }
907 
908         dcl->buf = b;
909 
910         *ll = hcl;
911         hcl->next = rcl;
912         rcl->next = dcl;
913         ll = &dcl->next;
914     }
915 
916     /* the last boundary CRLF "--0123456789--" CRLF  */
917 
918     b = ngx_calloc_buf(r->pool);
919     if (b == NULL) {
920         return NGX_ERROR;
921     }
922 
923     b->temporary = 1;
924     b->last_buf = 1;
925 
926     b->pos = ngx_pnalloc(r->pool, sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
927                                   + sizeof("--" CRLF) - 1);
928     if (b->pos == NULL) {
929         return NGX_ERROR;
930     }
931 
932     b->last = ngx_cpymem(b->pos, ctx->boundary_header.data,
933                          sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN);
934     *b->last++ = '-'; *b->last++ = '-';
935     *b->last++ = CR; *b->last++ = LF;
936 
937     hcl = ngx_alloc_chain_link(r->pool);
938     if (hcl == NULL) {
939         return NGX_ERROR;
940     }
941 
942     hcl->buf = b;
943     hcl->next = NULL;
944 
945     *ll = hcl;
946 
947     return ngx_http_next_body_filter(r, out);
948 }
949 
950 
951 static ngx_int_t
ngx_http_range_header_filter_init(ngx_conf_t * cf)952 ngx_http_range_header_filter_init(ngx_conf_t *cf)
953 {
954     ngx_http_next_header_filter = ngx_http_top_header_filter;
955     ngx_http_top_header_filter = ngx_http_range_header_filter;
956 
957     return NGX_OK;
958 }
959 
960 
961 static ngx_int_t
ngx_http_range_body_filter_init(ngx_conf_t * cf)962 ngx_http_range_body_filter_init(ngx_conf_t *cf)
963 {
964     ngx_http_next_body_filter = ngx_http_top_body_filter;
965     ngx_http_top_body_filter = ngx_http_range_body_filter;
966 
967     return NGX_OK;
968 }
969