3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Nginx, Inc.
8 #include <ngx_config.h>
14 * the single part format:
16 * "HTTP/1.0 206 Partial Content" CRLF
18 * "Content-Type: image/jpeg" CRLF
19 * "Content-Length: SIZE" CRLF
20 * "Content-Range: bytes START-END/SIZE" CRLF
25 * the mutlipart format:
27 * "HTTP/1.0 206 Partial Content" CRLF
29 * "Content-Type: multipart/byteranges; boundary=0123456789" CRLF
33 * "Content-Type: image/jpeg" CRLF
34 * "Content-Range: bytes START0-END0/SIZE" CRLF
39 * "Content-Type: image/jpeg" CRLF
40 * "Content-Range: bytes START1-END1/SIZE" CRLF
44 * "--0123456789--" CRLF
51 ngx_str_t content_range
;
57 ngx_str_t boundary_header
;
59 } ngx_http_range_filter_ctx_t
;
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
);
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
);
80 static ngx_http_module_t ngx_http_range_header_filter_module_ctx
= {
81 NULL
, /* preconfiguration */
82 ngx_http_range_header_filter_init
, /* postconfiguration */
84 NULL
, /* create main configuration */
85 NULL
, /* init main configuration */
87 NULL
, /* create server configuration */
88 NULL
, /* merge server configuration */
90 NULL
, /* create location configuration */
91 NULL
, /* merge location configuration */
95 ngx_module_t ngx_http_range_header_filter_module
= {
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
111 static ngx_http_module_t ngx_http_range_body_filter_module_ctx
= {
112 NULL
, /* preconfiguration */
113 ngx_http_range_body_filter_init
, /* postconfiguration */
115 NULL
, /* create main configuration */
116 NULL
, /* init main configuration */
118 NULL
, /* create server configuration */
119 NULL
, /* merge server configuration */
121 NULL
, /* create location configuration */
122 NULL
, /* merge location configuration */
126 ngx_module_t ngx_http_range_body_filter_module
= {
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
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
;
147 ngx_http_range_header_filter(ngx_http_request_t
*r
)
149 time_t if_range_time
;
150 ngx_str_t
*if_range
, *etag
;
151 ngx_http_core_loc_conf_t
*clcf
;
152 ngx_http_range_filter_ctx_t
*ctx
;
154 if (r
->http_version
< NGX_HTTP_VERSION_10
155 || r
->headers_out
.status
!= NGX_HTTP_OK
157 || r
->headers_out
.content_length_n
== -1
160 return ngx_http_next_header_filter(r
);
163 clcf
= ngx_http_get_module_loc_conf(r
, ngx_http_core_module
);
165 if (clcf
->max_ranges
== 0) {
166 return ngx_http_next_header_filter(r
);
169 if (r
->headers_in
.range
== NULL
170 || r
->headers_in
.range
->value
.len
< 7
171 || ngx_strncasecmp(r
->headers_in
.range
->value
.data
,
172 (u_char
*) "bytes=", 6)
178 if (r
->headers_in
.if_range
) {
180 if_range
= &r
->headers_in
.if_range
->value
;
182 if (if_range
->len
>= 2 && if_range
->data
[if_range
->len
- 1] == '"') {
184 if (r
->headers_out
.etag
== NULL
) {
188 etag
= &r
->headers_out
.etag
->value
;
190 ngx_log_debug2(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
191 "http ir:%V etag:%V", if_range
, etag
);
193 if (if_range
->len
!= etag
->len
194 || ngx_strncmp(if_range
->data
, etag
->data
, etag
->len
) != 0)
202 if (r
->headers_out
.last_modified_time
== (time_t) -1) {
206 if_range_time
= ngx_http_parse_time(if_range
->data
, if_range
->len
);
208 ngx_log_debug2(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
210 if_range_time
, r
->headers_out
.last_modified_time
);
212 if (if_range_time
!= r
->headers_out
.last_modified_time
) {
219 ctx
= ngx_pcalloc(r
->pool
, sizeof(ngx_http_range_filter_ctx_t
));
224 if (ngx_array_init(&ctx
->ranges
, r
->pool
, 1, sizeof(ngx_http_range_t
))
230 switch (ngx_http_range_parse(r
, ctx
, clcf
->max_ranges
)) {
233 ngx_http_set_ctx(r
, ctx
, ngx_http_range_body_filter_module
);
235 r
->headers_out
.status
= NGX_HTTP_PARTIAL_CONTENT
;
236 r
->headers_out
.status_line
.len
= 0;
238 if (ctx
->ranges
.nelts
== 1) {
239 return ngx_http_range_singlepart_header(r
, ctx
);
242 return ngx_http_range_multipart_header(r
, ctx
);
244 case NGX_HTTP_RANGE_NOT_SATISFIABLE
:
245 return ngx_http_range_not_satisfiable(r
);
250 default: /* NGX_DECLINED */
256 r
->headers_out
.accept_ranges
= ngx_list_push(&r
->headers_out
.headers
);
257 if (r
->headers_out
.accept_ranges
== NULL
) {
261 r
->headers_out
.accept_ranges
->hash
= 1;
262 ngx_str_set(&r
->headers_out
.accept_ranges
->key
, "Accept-Ranges");
263 ngx_str_set(&r
->headers_out
.accept_ranges
->value
, "bytes");
265 return ngx_http_next_header_filter(r
);
270 ngx_http_range_parse(ngx_http_request_t
*r
, ngx_http_range_filter_ctx_t
*ctx
,
274 off_t start
, end
, size
, content_length
;
276 ngx_http_range_t
*range
;
278 p
= r
->headers_in
.range
->value
.data
+ 6;
280 content_length
= r
->headers_out
.content_length_n
;
287 while (*p
== ' ') { p
++; }
290 if (*p
< '0' || *p
> '9') {
291 return NGX_HTTP_RANGE_NOT_SATISFIABLE
;
294 while (*p
>= '0' && *p
<= '9') {
295 start
= start
* 10 + *p
++ - '0';
298 while (*p
== ' ') { p
++; }
301 return NGX_HTTP_RANGE_NOT_SATISFIABLE
;
304 while (*p
== ' ') { p
++; }
306 if (*p
== ',' || *p
== '\0') {
307 end
= content_length
;
316 if (*p
< '0' || *p
> '9') {
317 return NGX_HTTP_RANGE_NOT_SATISFIABLE
;
320 while (*p
>= '0' && *p
<= '9') {
321 end
= end
* 10 + *p
++ - '0';
324 while (*p
== ' ') { p
++; }
326 if (*p
!= ',' && *p
!= '\0') {
327 return NGX_HTTP_RANGE_NOT_SATISFIABLE
;
331 start
= content_length
- end
;
332 end
= content_length
- 1;
335 if (end
>= content_length
) {
336 end
= content_length
;
345 range
= ngx_array_push(&ctx
->ranges
);
350 range
->start
= start
;
365 if (ctx
->ranges
.nelts
== 0) {
366 return NGX_HTTP_RANGE_NOT_SATISFIABLE
;
369 if (size
> content_length
) {
378 ngx_http_range_singlepart_header(ngx_http_request_t
*r
,
379 ngx_http_range_filter_ctx_t
*ctx
)
381 ngx_table_elt_t
*content_range
;
382 ngx_http_range_t
*range
;
384 content_range
= ngx_list_push(&r
->headers_out
.headers
);
385 if (content_range
== NULL
) {
389 r
->headers_out
.content_range
= content_range
;
391 content_range
->hash
= 1;
392 ngx_str_set(&content_range
->key
, "Content-Range");
394 content_range
->value
.data
= ngx_pnalloc(r
->pool
,
395 sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN
);
396 if (content_range
->value
.data
== NULL
) {
400 /* "Content-Range: bytes SSSS-EEEE/TTTT" header */
402 range
= ctx
->ranges
.elts
;
404 content_range
->value
.len
= ngx_sprintf(content_range
->value
.data
,
406 range
->start
, range
->end
- 1,
407 r
->headers_out
.content_length_n
)
408 - content_range
->value
.data
;
410 r
->headers_out
.content_length_n
= range
->end
- range
->start
;
412 if (r
->headers_out
.content_length
) {
413 r
->headers_out
.content_length
->hash
= 0;
414 r
->headers_out
.content_length
= NULL
;
417 return ngx_http_next_header_filter(r
);
422 ngx_http_range_multipart_header(ngx_http_request_t
*r
,
423 ngx_http_range_filter_ctx_t
*ctx
)
427 ngx_http_range_t
*range
;
428 ngx_atomic_uint_t boundary
;
430 len
= sizeof(CRLF
"--") - 1 + NGX_ATOMIC_T_LEN
431 + sizeof(CRLF
"Content-Type: ") - 1
432 + r
->headers_out
.content_type
.len
433 + sizeof(CRLF
"Content-Range: bytes ") - 1;
435 if (r
->headers_out
.charset
.len
) {
436 len
+= sizeof("; charset=") - 1 + r
->headers_out
.charset
.len
;
439 ctx
->boundary_header
.data
= ngx_pnalloc(r
->pool
, len
);
440 if (ctx
->boundary_header
.data
== NULL
) {
444 boundary
= ngx_next_temp_number(0);
447 * The boundary header of the range:
449 * "--0123456789" CRLF
450 * "Content-Type: image/jpeg" CRLF
451 * "Content-Range: bytes "
454 if (r
->headers_out
.charset
.len
) {
455 ctx
->boundary_header
.len
= ngx_sprintf(ctx
->boundary_header
.data
,
457 "Content-Type: %V; charset=%V" CRLF
458 "Content-Range: bytes ",
460 &r
->headers_out
.content_type
,
461 &r
->headers_out
.charset
)
462 - ctx
->boundary_header
.data
;
464 r
->headers_out
.charset
.len
= 0;
466 } else if (r
->headers_out
.content_type
.len
) {
467 ctx
->boundary_header
.len
= ngx_sprintf(ctx
->boundary_header
.data
,
469 "Content-Type: %V" CRLF
470 "Content-Range: bytes ",
472 &r
->headers_out
.content_type
)
473 - ctx
->boundary_header
.data
;
476 ctx
->boundary_header
.len
= ngx_sprintf(ctx
->boundary_header
.data
,
478 "Content-Range: bytes ",
480 - ctx
->boundary_header
.data
;
483 r
->headers_out
.content_type
.data
=
485 sizeof("Content-Type: multipart/byteranges; boundary=") - 1
488 if (r
->headers_out
.content_type
.data
== NULL
) {
492 r
->headers_out
.content_type_lowcase
= NULL
;
494 /* "Content-Type: multipart/byteranges; boundary=0123456789" */
496 r
->headers_out
.content_type
.len
=
497 ngx_sprintf(r
->headers_out
.content_type
.data
,
498 "multipart/byteranges; boundary=%0muA",
500 - r
->headers_out
.content_type
.data
;
502 r
->headers_out
.content_type_len
= r
->headers_out
.content_type
.len
;
504 /* the size of the last boundary CRLF "--0123456789--" CRLF */
506 len
= sizeof(CRLF
"--") - 1 + NGX_ATOMIC_T_LEN
+ sizeof("--" CRLF
) - 1;
508 range
= ctx
->ranges
.elts
;
509 for (i
= 0; i
< ctx
->ranges
.nelts
; i
++) {
511 /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */
513 range
[i
].content_range
.data
=
514 ngx_pnalloc(r
->pool
, 3 * NGX_OFF_T_LEN
+ 2 + 4);
516 if (range
[i
].content_range
.data
== NULL
) {
520 range
[i
].content_range
.len
= ngx_sprintf(range
[i
].content_range
.data
,
521 "%O-%O/%O" CRLF CRLF
,
522 range
[i
].start
, range
[i
].end
- 1,
523 r
->headers_out
.content_length_n
)
524 - range
[i
].content_range
.data
;
526 len
+= ctx
->boundary_header
.len
+ range
[i
].content_range
.len
527 + (size_t) (range
[i
].end
- range
[i
].start
);
530 r
->headers_out
.content_length_n
= len
;
532 if (r
->headers_out
.content_length
) {
533 r
->headers_out
.content_length
->hash
= 0;
534 r
->headers_out
.content_length
= NULL
;
537 return ngx_http_next_header_filter(r
);
542 ngx_http_range_not_satisfiable(ngx_http_request_t
*r
)
544 ngx_table_elt_t
*content_range
;
546 r
->headers_out
.status
= NGX_HTTP_RANGE_NOT_SATISFIABLE
;
548 content_range
= ngx_list_push(&r
->headers_out
.headers
);
549 if (content_range
== NULL
) {
553 r
->headers_out
.content_range
= content_range
;
555 content_range
->hash
= 1;
556 ngx_str_set(&content_range
->key
, "Content-Range");
558 content_range
->value
.data
= ngx_pnalloc(r
->pool
,
559 sizeof("bytes */") - 1 + NGX_OFF_T_LEN
);
560 if (content_range
->value
.data
== NULL
) {
564 content_range
->value
.len
= ngx_sprintf(content_range
->value
.data
,
566 r
->headers_out
.content_length_n
)
567 - content_range
->value
.data
;
569 ngx_http_clear_content_length(r
);
571 return NGX_HTTP_RANGE_NOT_SATISFIABLE
;
576 ngx_http_range_body_filter(ngx_http_request_t
*r
, ngx_chain_t
*in
)
578 ngx_http_range_filter_ctx_t
*ctx
;
581 return ngx_http_next_body_filter(r
, in
);
584 ctx
= ngx_http_get_module_ctx(r
, ngx_http_range_body_filter_module
);
587 return ngx_http_next_body_filter(r
, in
);
590 if (ctx
->ranges
.nelts
== 1) {
591 return ngx_http_range_singlepart_body(r
, ctx
, in
);
595 * multipart ranges are supported only if whole body is in a single buffer
598 if (ngx_buf_special(in
->buf
)) {
599 return ngx_http_next_body_filter(r
, in
);
602 if (ngx_http_range_test_overlapped(r
, ctx
, in
) != NGX_OK
) {
606 return ngx_http_range_multipart_body(r
, ctx
, in
);
611 ngx_http_range_test_overlapped(ngx_http_request_t
*r
,
612 ngx_http_range_filter_ctx_t
*ctx
, ngx_chain_t
*in
)
617 ngx_http_range_t
*range
;
625 if (!buf
->last_buf
) {
627 last
= ctx
->offset
+ ngx_buf_size(buf
);
629 range
= ctx
->ranges
.elts
;
630 for (i
= 0; i
< ctx
->ranges
.nelts
; i
++) {
631 if (start
> range
[i
].start
|| last
< range
[i
].end
) {
637 ctx
->offset
= ngx_buf_size(buf
);
643 ngx_log_error(NGX_LOG_ALERT
, r
->connection
->log
, 0,
644 "range in overlapped buffers");
651 ngx_http_range_singlepart_body(ngx_http_request_t
*r
,
652 ngx_http_range_filter_ctx_t
*ctx
, ngx_chain_t
*in
)
656 ngx_chain_t
*out
, *cl
, **ll
;
657 ngx_http_range_t
*range
;
661 range
= ctx
->ranges
.elts
;
663 for (cl
= in
; cl
; cl
= cl
->next
) {
668 last
= ctx
->offset
+ ngx_buf_size(buf
);
672 ngx_log_debug2(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
673 "http range body buf: %O-%O", start
, last
);
675 if (ngx_buf_special(buf
)) {
681 if (range
->end
<= start
|| range
->start
>= last
) {
683 ngx_log_debug0(NGX_LOG_DEBUG_HTTP
, r
->connection
->log
, 0,
684 "http range body skip");
687 buf
->file_pos
= buf
->file_last
;
690 buf
->pos
= buf
->last
;
696 if (range
->start
> start
) {
699 buf
->file_pos
+= range
->start
- start
;
702 if (ngx_buf_in_memory(buf
)) {
703 buf
->pos
+= (size_t) (range
->start
- start
);
707 if (range
->end
<= last
) {
710 buf
->file_last
-= last
- range
->end
;
713 if (ngx_buf_in_memory(buf
)) {
714 buf
->last
-= (size_t) (last
- range
->end
);
732 return ngx_http_next_body_filter(r
, out
);
737 ngx_http_range_multipart_body(ngx_http_request_t
*r
,
738 ngx_http_range_filter_ctx_t
*ctx
, ngx_chain_t
*in
)
742 ngx_chain_t
*out
, *hcl
, *rcl
, *dcl
, **ll
;
743 ngx_http_range_t
*range
;
747 range
= ctx
->ranges
.elts
;
749 for (i
= 0; i
< ctx
->ranges
.nelts
; i
++) {
752 * The boundary header of the range:
754 * "--0123456789" CRLF
755 * "Content-Type: image/jpeg" CRLF
756 * "Content-Range: bytes "
759 b
= ngx_calloc_buf(r
->pool
);
765 b
->pos
= ctx
->boundary_header
.data
;
766 b
->last
= ctx
->boundary_header
.data
+ ctx
->boundary_header
.len
;
768 hcl
= ngx_alloc_chain_link(r
->pool
);
776 /* "SSSS-EEEE/TTTT" CRLF CRLF */
778 b
= ngx_calloc_buf(r
->pool
);
784 b
->pos
= range
[i
].content_range
.data
;
785 b
->last
= range
[i
].content_range
.data
+ range
[i
].content_range
.len
;
787 rcl
= ngx_alloc_chain_link(r
->pool
);
797 b
= ngx_calloc_buf(r
->pool
);
802 b
->in_file
= buf
->in_file
;
803 b
->temporary
= buf
->temporary
;
804 b
->memory
= buf
->memory
;
809 b
->file_pos
= buf
->file_pos
+ range
[i
].start
;
810 b
->file_last
= buf
->file_pos
+ range
[i
].end
;
813 if (ngx_buf_in_memory(buf
)) {
814 b
->pos
= buf
->pos
+ (size_t) range
[i
].start
;
815 b
->last
= buf
->pos
+ (size_t) range
[i
].end
;
818 dcl
= ngx_alloc_chain_link(r
->pool
);
831 /* the last boundary CRLF "--0123456789--" CRLF */
833 b
= ngx_calloc_buf(r
->pool
);
841 b
->pos
= ngx_pnalloc(r
->pool
, sizeof(CRLF
"--") - 1 + NGX_ATOMIC_T_LEN
842 + sizeof("--" CRLF
) - 1);
843 if (b
->pos
== NULL
) {
847 b
->last
= ngx_cpymem(b
->pos
, ctx
->boundary_header
.data
,
848 sizeof(CRLF
"--") - 1 + NGX_ATOMIC_T_LEN
);
849 *b
->last
++ = '-'; *b
->last
++ = '-';
850 *b
->last
++ = CR
; *b
->last
++ = LF
;
852 hcl
= ngx_alloc_chain_link(r
->pool
);
862 return ngx_http_next_body_filter(r
, out
);
867 ngx_http_range_header_filter_init(ngx_conf_t
*cf
)
869 ngx_http_next_header_filter
= ngx_http_top_header_filter
;
870 ngx_http_top_header_filter
= ngx_http_range_header_filter
;
877 ngx_http_range_body_filter_init(ngx_conf_t
*cf
)
879 ngx_http_next_body_filter
= ngx_http_top_body_filter
;
880 ngx_http_top_body_filter
= ngx_http_range_body_filter
;