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