Mercurial > hg > nginx-ranges
annotate src/http/modules/ngx_http_range_filter_module.c @ 424:44a61c599bb2
Merge with current.
author | Maxim Dounin <mdounin@mdounin.ru> |
---|---|
date | Wed, 15 Oct 2008 15:39:40 +0400 |
parents | 77df96611112 b246022ef454 |
children | 8fbdd980b527 |
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)) | |
225 == NGX_ERROR) | |
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; | |
237 | |
238 if (ctx->ranges.nelts == 1) { | |
239 return ngx_http_range_singlepart_header(r, ctx); | |
240 } | |
241 | |
242 return ngx_http_range_multipart_header(r, ctx); | |
243 } | |
244 | |
245 if (rc == NGX_HTTP_RANGE_NOT_SATISFIABLE) { | |
246 return ngx_http_range_not_satisfiable(r); | |
247 } | |
248 | |
249 /* rc == NGX_ERROR */ | |
250 | |
251 return rc; | |
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 r->headers_out.accept_ranges->key.len = sizeof("Accept-Ranges") - 1; | |
262 r->headers_out.accept_ranges->key.data = (u_char *) "Accept-Ranges"; | |
263 r->headers_out.accept_ranges->value.len = sizeof("bytes") - 1; | |
264 r->headers_out.accept_ranges->value.data = (u_char *) "bytes"; | |
265 | |
266 return ngx_http_next_header_filter(r); | |
267 } | |
268 | |
269 | |
270 ngx_int_t | |
271 ngx_http_range_parse(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx) | |
272 { | |
273 u_char *p; | |
274 off_t start, end; | |
275 ngx_uint_t suffix; | |
276 ngx_http_range_t *range; | |
277 | |
50 | 278 p = r->headers_in.range->value.data + 6; |
279 | |
280 for ( ;; ) { | |
281 start = 0; | |
282 end = 0; | |
283 suffix = 0; | |
284 | |
285 while (*p == ' ') { p++; } | |
286 | |
287 if (*p != '-') { | |
288 if (*p < '0' || *p > '9') { | |
392 | 289 return NGX_HTTP_RANGE_NOT_SATISFIABLE; |
50 | 290 } |
291 | |
292 while (*p >= '0' && *p <= '9') { | |
293 start = start * 10 + *p++ - '0'; | |
294 } | |
295 | |
296 while (*p == ' ') { p++; } | |
297 | |
298 if (*p++ != '-') { | |
392 | 299 return NGX_HTTP_RANGE_NOT_SATISFIABLE; |
50 | 300 } |
301 | |
302 if (start >= r->headers_out.content_length_n) { | |
392 | 303 return NGX_HTTP_RANGE_NOT_SATISFIABLE; |
50 | 304 } |
305 | |
306 while (*p == ' ') { p++; } | |
307 | |
308 if (*p == ',' || *p == '\0') { | |
272 | 309 range = ngx_array_push(&ctx->ranges); |
50 | 310 if (range == NULL) { |
311 return NGX_ERROR; | |
312 } | |
313 | |
314 range->start = start; | |
315 range->end = r->headers_out.content_length_n; | |
316 | |
317 if (*p++ != ',') { | |
392 | 318 return NGX_OK; |
50 | 319 } |
320 | |
321 continue; | |
322 } | |
323 | |
324 } else { | |
325 suffix = 1; | |
326 p++; | |
327 } | |
328 | |
329 if (*p < '0' || *p > '9') { | |
392 | 330 return NGX_HTTP_RANGE_NOT_SATISFIABLE; |
50 | 331 } |
332 | |
333 while (*p >= '0' && *p <= '9') { | |
334 end = end * 10 + *p++ - '0'; | |
335 } | |
336 | |
337 while (*p == ' ') { p++; } | |
338 | |
339 if (*p != ',' && *p != '\0') { | |
392 | 340 return NGX_HTTP_RANGE_NOT_SATISFIABLE; |
50 | 341 } |
342 | |
343 if (suffix) { | |
344 start = r->headers_out.content_length_n - end; | |
345 end = r->headers_out.content_length_n - 1; | |
346 } | |
347 | |
348 if (start > end) { | |
392 | 349 return NGX_HTTP_RANGE_NOT_SATISFIABLE; |
50 | 350 } |
351 | |
272 | 352 range = ngx_array_push(&ctx->ranges); |
50 | 353 if (range == NULL) { |
354 return NGX_ERROR; | |
355 } | |
356 | |
357 range->start = start; | |
358 | |
359 if (end >= r->headers_out.content_length_n) { | |
360 /* | |
361 * Download Accelerator sends the last byte position | |
362 * that equals to the file length | |
363 */ | |
364 range->end = r->headers_out.content_length_n; | |
365 | |
366 } else { | |
367 range->end = end + 1; | |
368 } | |
369 | |
370 if (*p++ != ',') { | |
392 | 371 return NGX_OK; |
50 | 372 } |
373 } | |
392 | 374 } |
375 | |
376 | |
377 static ngx_int_t | |
378 ngx_http_range_singlepart_header(ngx_http_request_t *r, | |
379 ngx_http_range_filter_ctx_t *ctx) | |
380 { | |
381 ngx_table_elt_t *content_range; | |
382 ngx_http_range_t *range; | |
383 | |
384 content_range = ngx_list_push(&r->headers_out.headers); | |
385 if (content_range == NULL) { | |
386 return NGX_ERROR; | |
387 } | |
50 | 388 |
392 | 389 r->headers_out.content_range = content_range; |
50 | 390 |
392 | 391 content_range->hash = 1; |
392 content_range->key.len = sizeof("Content-Range") - 1; | |
393 content_range->key.data = (u_char *) "Content-Range"; | |
50 | 394 |
392 | 395 content_range->value.data = ngx_pnalloc(r->pool, |
396 sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN); | |
397 if (content_range->value.data == NULL) { | |
398 return NGX_ERROR; | |
50 | 399 } |
400 | |
392 | 401 /* "Content-Range: bytes SSSS-EEEE/TTTT" header */ |
50 | 402 |
392 | 403 range = ctx->ranges.elts; |
50 | 404 |
392 | 405 content_range->value.len = ngx_sprintf(content_range->value.data, |
406 "bytes %O-%O/%O", | |
407 range->start, range->end - 1, | |
408 r->headers_out.content_length_n) | |
409 - content_range->value.data; | |
50 | 410 |
392 | 411 r->headers_out.content_length_n = range->end - range->start; |
50 | 412 |
392 | 413 if (r->headers_out.content_length) { |
414 r->headers_out.content_length->hash = 0; | |
415 r->headers_out.content_length = NULL; | |
50 | 416 } |
417 | |
392 | 418 return ngx_http_next_header_filter(r); |
419 } | |
50 | 420 |
392 | 421 |
422 static ngx_int_t | |
423 ngx_http_range_multipart_header(ngx_http_request_t *r, | |
424 ngx_http_range_filter_ctx_t *ctx) | |
425 { | |
426 size_t len; | |
427 ngx_uint_t i; | |
428 ngx_http_range_t *range; | |
429 ngx_atomic_uint_t boundary; | |
50 | 430 |
431 len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN | |
432 + sizeof(CRLF "Content-Type: ") - 1 | |
58 | 433 + r->headers_out.content_type.len |
50 | 434 + sizeof(CRLF "Content-Range: bytes ") - 1; |
435 | |
436 if (r->headers_out.charset.len) { | |
437 len += sizeof("; charset=") - 1 + r->headers_out.charset.len; | |
438 } | |
439 | |
382 | 440 ctx->boundary_header.data = ngx_pnalloc(r->pool, len); |
50 | 441 if (ctx->boundary_header.data == NULL) { |
442 return NGX_ERROR; | |
443 } | |
444 | |
445 boundary = ngx_next_temp_number(0); | |
446 | |
447 /* | |
448 * The boundary header of the range: | |
449 * CRLF | |
450 * "--0123456789" CRLF | |
451 * "Content-Type: image/jpeg" CRLF | |
452 * "Content-Range: bytes " | |
453 */ | |
454 | |
455 if (r->headers_out.charset.len) { | |
456 ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data, | |
457 CRLF "--%0muA" CRLF | |
458 "Content-Type: %V; charset=%V" CRLF | |
459 "Content-Range: bytes ", | |
460 boundary, | |
58 | 461 &r->headers_out.content_type, |
50 | 462 &r->headers_out.charset) |
463 - ctx->boundary_header.data; | |
464 | |
465 r->headers_out.charset.len = 0; | |
466 | |
392 | 467 } else if (r->headers_out.content_type.len) { |
50 | 468 ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data, |
469 CRLF "--%0muA" CRLF | |
470 "Content-Type: %V" CRLF | |
471 "Content-Range: bytes ", | |
472 boundary, | |
58 | 473 &r->headers_out.content_type) |
50 | 474 - ctx->boundary_header.data; |
392 | 475 |
476 } else { | |
477 ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data, | |
478 CRLF "--%0muA" CRLF | |
479 "Content-Range: bytes ", | |
480 boundary) | |
481 - ctx->boundary_header.data; | |
50 | 482 } |
483 | |
58 | 484 r->headers_out.content_type.data = |
382 | 485 ngx_pnalloc(r->pool, |
486 sizeof("Content-Type: multipart/byteranges; boundary=") - 1 | |
487 + NGX_ATOMIC_T_LEN); | |
50 | 488 |
58 | 489 if (r->headers_out.content_type.data == NULL) { |
50 | 490 return NGX_ERROR; |
491 } | |
492 | |
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 |
501 | |
502 /* the size of the last boundary CRLF "--0123456789--" CRLF */ | |
503 | |
504 len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1; | |
505 | |
272 | 506 range = ctx->ranges.elts; |
507 for (i = 0; i < ctx->ranges.nelts; i++) { | |
50 | 508 |
509 /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */ | |
510 | |
511 range[i].content_range.data = | |
382 | 512 ngx_pnalloc(r->pool, 3 * NGX_OFF_T_LEN + 2 + 4); |
50 | 513 |
514 if (range[i].content_range.data == NULL) { | |
515 return NGX_ERROR; | |
516 } | |
517 | |
518 range[i].content_range.len = ngx_sprintf(range[i].content_range.data, | |
519 "%O-%O/%O" CRLF CRLF, | |
520 range[i].start, range[i].end - 1, | |
521 r->headers_out.content_length_n) | |
522 - range[i].content_range.data; | |
523 | |
524 len += ctx->boundary_header.len + range[i].content_range.len | |
525 + (size_t) (range[i].end - range[i].start); | |
526 } | |
527 | |
528 r->headers_out.content_length_n = len; | |
272 | 529 |
530 if (r->headers_out.content_length) { | |
531 r->headers_out.content_length->hash = 0; | |
532 r->headers_out.content_length = NULL; | |
533 } | |
50 | 534 |
535 return ngx_http_next_header_filter(r); | |
392 | 536 } |
346 | 537 |
538 | |
392 | 539 static ngx_int_t |
540 ngx_http_range_not_satisfiable(ngx_http_request_t *r) | |
541 { | |
542 ngx_table_elt_t *content_range; | |
543 | |
544 r->headers_out.status = NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
545 | |
546 content_range = ngx_list_push(&r->headers_out.headers); | |
547 if (content_range == NULL) { | |
346 | 548 return NGX_ERROR; |
549 } | |
550 | |
392 | 551 r->headers_out.content_range = content_range; |
552 | |
553 content_range->hash = 1; | |
554 content_range->key.len = sizeof("Content-Range") - 1; | |
555 content_range->key.data = (u_char *) "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 { |
751 ngx_buf_t *b, *buf; | |
752 ngx_uint_t i; | |
753 ngx_chain_t *out, *hcl, *rcl, *dcl, **ll; | |
754 ngx_http_range_t *range; | |
755 | |
272 | 756 ll = &out; |
392 | 757 buf = in->buf; |
758 range = ctx->ranges.elts; | |
272 | 759 |
760 for (i = 0; i < ctx->ranges.nelts; i++) { | |
50 | 761 |
272 | 762 /* |
763 * The boundary header of the range: | |
764 * CRLF | |
765 * "--0123456789" CRLF | |
766 * "Content-Type: image/jpeg" CRLF | |
767 * "Content-Range: bytes " | |
768 */ | |
50 | 769 |
272 | 770 b = ngx_calloc_buf(r->pool); |
771 if (b == NULL) { | |
772 return NGX_ERROR; | |
773 } | |
50 | 774 |
272 | 775 b->memory = 1; |
776 b->pos = ctx->boundary_header.data; | |
777 b->last = ctx->boundary_header.data + ctx->boundary_header.len; | |
50 | 778 |
272 | 779 hcl = ngx_alloc_chain_link(r->pool); |
780 if (hcl == NULL) { | |
781 return NGX_ERROR; | |
782 } | |
50 | 783 |
272 | 784 hcl->buf = b; |
50 | 785 |
786 | |
272 | 787 /* "SSSS-EEEE/TTTT" CRLF CRLF */ |
50 | 788 |
789 b = ngx_calloc_buf(r->pool); | |
790 if (b == NULL) { | |
791 return NGX_ERROR; | |
792 } | |
793 | |
794 b->temporary = 1; | |
272 | 795 b->pos = range[i].content_range.data; |
796 b->last = range[i].content_range.data + range[i].content_range.len; | |
797 | |
798 rcl = ngx_alloc_chain_link(r->pool); | |
799 if (rcl == NULL) { | |
800 return NGX_ERROR; | |
801 } | |
802 | |
803 rcl->buf = b; | |
804 | |
805 | |
806 /* the range data */ | |
807 | |
808 b = ngx_calloc_buf(r->pool); | |
809 if (b == NULL) { | |
810 return NGX_ERROR; | |
811 } | |
50 | 812 |
272 | 813 b->in_file = buf->in_file; |
814 b->temporary = buf->temporary; | |
815 b->memory = buf->memory; | |
816 b->mmap = buf->mmap; | |
817 b->file = buf->file; | |
818 | |
819 if (buf->in_file) { | |
282 | 820 b->file_pos = range[i].start; |
821 b->file_last = range[i].end; | |
272 | 822 } |
823 | |
824 if (ngx_buf_in_memory(buf)) { | |
282 | 825 b->pos = buf->start + (size_t) range[i].start; |
826 b->last = buf->start + (size_t) range[i].end; | |
272 | 827 } |
828 | |
829 dcl = ngx_alloc_chain_link(r->pool); | |
830 if (dcl == NULL) { | |
50 | 831 return NGX_ERROR; |
832 } | |
833 | |
272 | 834 dcl->buf = b; |
50 | 835 |
836 *ll = hcl; | |
272 | 837 hcl->next = rcl; |
838 rcl->next = dcl; | |
839 ll = &dcl->next; | |
840 } | |
50 | 841 |
272 | 842 /* the last boundary CRLF "--0123456789--" CRLF */ |
843 | |
844 b = ngx_calloc_buf(r->pool); | |
845 if (b == NULL) { | |
846 return NGX_ERROR; | |
847 } | |
848 | |
849 b->temporary = 1; | |
850 b->last_buf = 1; | |
851 | |
382 | 852 b->pos = ngx_pnalloc(r->pool, sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN |
853 + sizeof("--" CRLF) - 1); | |
272 | 854 if (b->pos == NULL) { |
855 return NGX_ERROR; | |
50 | 856 } |
857 | |
300 | 858 b->last = ngx_cpymem(b->pos, ctx->boundary_header.data, |
859 sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN); | |
272 | 860 *b->last++ = '-'; *b->last++ = '-'; |
861 *b->last++ = CR; *b->last++ = LF; | |
862 | |
863 hcl = ngx_alloc_chain_link(r->pool); | |
864 if (hcl == NULL) { | |
865 return NGX_ERROR; | |
866 } | |
50 | 867 |
272 | 868 hcl->buf = b; |
869 hcl->next = NULL; | |
870 | |
871 *ll = hcl; | |
872 | |
873 return ngx_http_next_body_filter(r, out); | |
50 | 874 } |
875 | |
876 | |
877 static ngx_int_t | |
391
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
878 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
|
879 { |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
880 if (!r->allow_ranges || r->late_ranges) { |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
881 return ngx_http_next_body_early_filter(r, in); |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
882 } |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
883 |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
884 return ngx_http_range_body_generic_filter(r, in, |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
885 ngx_http_next_body_early_filter); |
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 |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
888 |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
889 static ngx_int_t |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
890 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
|
891 { |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
892 if (!r->allow_ranges || !r->late_ranges) { |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
893 return ngx_http_next_body_late_filter(r, in); |
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 return ngx_http_range_body_generic_filter(r, in, |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
897 ngx_http_next_body_late_filter); |
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 |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
900 |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
901 static ngx_int_t |
230 | 902 ngx_http_range_header_filter_init(ngx_conf_t *cf) |
50 | 903 { |
904 ngx_http_next_header_filter = ngx_http_top_header_filter; | |
905 ngx_http_top_header_filter = ngx_http_range_header_filter; | |
906 | |
907 return NGX_OK; | |
908 } | |
909 | |
910 | |
911 static ngx_int_t | |
230 | 912 ngx_http_range_body_filter_init(ngx_conf_t *cf) |
50 | 913 { |
391
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
914 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
|
915 ngx_http_top_body_filter = ngx_http_range_body_early_filter; |
50 | 916 |
917 return NGX_OK; | |
918 } | |
391
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
919 |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
920 |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
921 static ngx_int_t |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
922 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
|
923 { |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
924 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
|
925 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
|
926 |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
927 return NGX_OK; |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
928 } |