Mercurial > hg > nginx-ranges
annotate src/http/modules/ngx_http_range_filter_module.c @ 517:8fbdd980b527
Merge with current.
author | Maxim Dounin <mdounin@mdounin.ru> |
---|---|
date | Mon, 13 Jul 2009 23:56:24 +0400 |
parents | 44a61c599bb2 499474178a11 |
children | e67b227c8dbb |
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; | |
262 r->headers_out.accept_ranges->key.len = sizeof("Accept-Ranges") - 1; | |
263 r->headers_out.accept_ranges->key.data = (u_char *) "Accept-Ranges"; | |
264 r->headers_out.accept_ranges->value.len = sizeof("bytes") - 1; | |
265 r->headers_out.accept_ranges->value.data = (u_char *) "bytes"; | |
266 | |
267 return ngx_http_next_header_filter(r); | |
268 } | |
269 | |
270 | |
271 ngx_int_t | |
272 ngx_http_range_parse(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx) | |
273 { | |
274 u_char *p; | |
275 off_t start, end; | |
276 ngx_uint_t suffix; | |
277 ngx_http_range_t *range; | |
278 | |
50 | 279 p = r->headers_in.range->value.data + 6; |
280 | |
281 for ( ;; ) { | |
282 start = 0; | |
283 end = 0; | |
284 suffix = 0; | |
285 | |
286 while (*p == ' ') { p++; } | |
287 | |
288 if (*p != '-') { | |
289 if (*p < '0' || *p > '9') { | |
392 | 290 return NGX_HTTP_RANGE_NOT_SATISFIABLE; |
50 | 291 } |
292 | |
293 while (*p >= '0' && *p <= '9') { | |
294 start = start * 10 + *p++ - '0'; | |
295 } | |
296 | |
297 while (*p == ' ') { p++; } | |
298 | |
299 if (*p++ != '-') { | |
392 | 300 return NGX_HTTP_RANGE_NOT_SATISFIABLE; |
50 | 301 } |
302 | |
303 if (start >= r->headers_out.content_length_n) { | |
392 | 304 return NGX_HTTP_RANGE_NOT_SATISFIABLE; |
50 | 305 } |
306 | |
307 while (*p == ' ') { p++; } | |
308 | |
309 if (*p == ',' || *p == '\0') { | |
272 | 310 range = ngx_array_push(&ctx->ranges); |
50 | 311 if (range == NULL) { |
312 return NGX_ERROR; | |
313 } | |
314 | |
315 range->start = start; | |
316 range->end = r->headers_out.content_length_n; | |
317 | |
318 if (*p++ != ',') { | |
392 | 319 return NGX_OK; |
50 | 320 } |
321 | |
322 continue; | |
323 } | |
324 | |
325 } else { | |
326 suffix = 1; | |
327 p++; | |
328 } | |
329 | |
330 if (*p < '0' || *p > '9') { | |
392 | 331 return NGX_HTTP_RANGE_NOT_SATISFIABLE; |
50 | 332 } |
333 | |
334 while (*p >= '0' && *p <= '9') { | |
335 end = end * 10 + *p++ - '0'; | |
336 } | |
337 | |
338 while (*p == ' ') { p++; } | |
339 | |
340 if (*p != ',' && *p != '\0') { | |
392 | 341 return NGX_HTTP_RANGE_NOT_SATISFIABLE; |
50 | 342 } |
343 | |
344 if (suffix) { | |
345 start = r->headers_out.content_length_n - end; | |
346 end = r->headers_out.content_length_n - 1; | |
347 } | |
348 | |
349 if (start > end) { | |
392 | 350 return NGX_HTTP_RANGE_NOT_SATISFIABLE; |
50 | 351 } |
352 | |
272 | 353 range = ngx_array_push(&ctx->ranges); |
50 | 354 if (range == NULL) { |
355 return NGX_ERROR; | |
356 } | |
357 | |
358 range->start = start; | |
359 | |
360 if (end >= r->headers_out.content_length_n) { | |
361 /* | |
362 * Download Accelerator sends the last byte position | |
363 * that equals to the file length | |
364 */ | |
365 range->end = r->headers_out.content_length_n; | |
366 | |
367 } else { | |
368 range->end = end + 1; | |
369 } | |
370 | |
371 if (*p++ != ',') { | |
392 | 372 return NGX_OK; |
50 | 373 } |
374 } | |
392 | 375 } |
376 | |
377 | |
378 static ngx_int_t | |
379 ngx_http_range_singlepart_header(ngx_http_request_t *r, | |
380 ngx_http_range_filter_ctx_t *ctx) | |
381 { | |
382 ngx_table_elt_t *content_range; | |
383 ngx_http_range_t *range; | |
384 | |
385 content_range = ngx_list_push(&r->headers_out.headers); | |
386 if (content_range == NULL) { | |
387 return NGX_ERROR; | |
388 } | |
50 | 389 |
392 | 390 r->headers_out.content_range = content_range; |
50 | 391 |
392 | 392 content_range->hash = 1; |
393 content_range->key.len = sizeof("Content-Range") - 1; | |
394 content_range->key.data = (u_char *) "Content-Range"; | |
50 | 395 |
392 | 396 content_range->value.data = ngx_pnalloc(r->pool, |
397 sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN); | |
398 if (content_range->value.data == NULL) { | |
399 return NGX_ERROR; | |
50 | 400 } |
401 | |
392 | 402 /* "Content-Range: bytes SSSS-EEEE/TTTT" header */ |
50 | 403 |
392 | 404 range = ctx->ranges.elts; |
50 | 405 |
392 | 406 content_range->value.len = ngx_sprintf(content_range->value.data, |
407 "bytes %O-%O/%O", | |
408 range->start, range->end - 1, | |
409 r->headers_out.content_length_n) | |
410 - content_range->value.data; | |
50 | 411 |
392 | 412 r->headers_out.content_length_n = range->end - range->start; |
50 | 413 |
392 | 414 if (r->headers_out.content_length) { |
415 r->headers_out.content_length->hash = 0; | |
416 r->headers_out.content_length = NULL; | |
50 | 417 } |
418 | |
392 | 419 return ngx_http_next_header_filter(r); |
420 } | |
50 | 421 |
392 | 422 |
423 static ngx_int_t | |
424 ngx_http_range_multipart_header(ngx_http_request_t *r, | |
425 ngx_http_range_filter_ctx_t *ctx) | |
426 { | |
427 size_t len; | |
428 ngx_uint_t i; | |
429 ngx_http_range_t *range; | |
430 ngx_atomic_uint_t boundary; | |
50 | 431 |
432 len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN | |
433 + sizeof(CRLF "Content-Type: ") - 1 | |
58 | 434 + r->headers_out.content_type.len |
50 | 435 + sizeof(CRLF "Content-Range: bytes ") - 1; |
436 | |
437 if (r->headers_out.charset.len) { | |
438 len += sizeof("; charset=") - 1 + r->headers_out.charset.len; | |
439 } | |
440 | |
382 | 441 ctx->boundary_header.data = ngx_pnalloc(r->pool, len); |
50 | 442 if (ctx->boundary_header.data == NULL) { |
443 return NGX_ERROR; | |
444 } | |
445 | |
446 boundary = ngx_next_temp_number(0); | |
447 | |
448 /* | |
449 * The boundary header of the range: | |
450 * CRLF | |
451 * "--0123456789" CRLF | |
452 * "Content-Type: image/jpeg" CRLF | |
453 * "Content-Range: bytes " | |
454 */ | |
455 | |
456 if (r->headers_out.charset.len) { | |
457 ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data, | |
458 CRLF "--%0muA" CRLF | |
459 "Content-Type: %V; charset=%V" CRLF | |
460 "Content-Range: bytes ", | |
461 boundary, | |
58 | 462 &r->headers_out.content_type, |
50 | 463 &r->headers_out.charset) |
464 - ctx->boundary_header.data; | |
465 | |
466 r->headers_out.charset.len = 0; | |
467 | |
392 | 468 } else if (r->headers_out.content_type.len) { |
50 | 469 ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data, |
470 CRLF "--%0muA" CRLF | |
471 "Content-Type: %V" CRLF | |
472 "Content-Range: bytes ", | |
473 boundary, | |
58 | 474 &r->headers_out.content_type) |
50 | 475 - ctx->boundary_header.data; |
392 | 476 |
477 } else { | |
478 ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data, | |
479 CRLF "--%0muA" CRLF | |
480 "Content-Range: bytes ", | |
481 boundary) | |
482 - ctx->boundary_header.data; | |
50 | 483 } |
484 | |
58 | 485 r->headers_out.content_type.data = |
382 | 486 ngx_pnalloc(r->pool, |
487 sizeof("Content-Type: multipart/byteranges; boundary=") - 1 | |
488 + NGX_ATOMIC_T_LEN); | |
50 | 489 |
58 | 490 if (r->headers_out.content_type.data == NULL) { |
50 | 491 return NGX_ERROR; |
492 } | |
493 | |
503 | 494 r->headers_out.content_type_lowcase = NULL; |
495 | |
50 | 496 /* "Content-Type: multipart/byteranges; boundary=0123456789" */ |
497 | |
58 | 498 r->headers_out.content_type.len = |
499 ngx_sprintf(r->headers_out.content_type.data, | |
50 | 500 "multipart/byteranges; boundary=%0muA", |
501 boundary) | |
58 | 502 - r->headers_out.content_type.data; |
50 | 503 |
503 | 504 r->headers_out.content_type_len = r->headers_out.content_type.len; |
50 | 505 |
506 /* the size of the last boundary CRLF "--0123456789--" CRLF */ | |
507 | |
508 len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1; | |
509 | |
272 | 510 range = ctx->ranges.elts; |
511 for (i = 0; i < ctx->ranges.nelts; i++) { | |
50 | 512 |
513 /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */ | |
514 | |
515 range[i].content_range.data = | |
382 | 516 ngx_pnalloc(r->pool, 3 * NGX_OFF_T_LEN + 2 + 4); |
50 | 517 |
518 if (range[i].content_range.data == NULL) { | |
519 return NGX_ERROR; | |
520 } | |
521 | |
522 range[i].content_range.len = ngx_sprintf(range[i].content_range.data, | |
523 "%O-%O/%O" CRLF CRLF, | |
524 range[i].start, range[i].end - 1, | |
525 r->headers_out.content_length_n) | |
526 - range[i].content_range.data; | |
527 | |
528 len += ctx->boundary_header.len + range[i].content_range.len | |
529 + (size_t) (range[i].end - range[i].start); | |
530 } | |
531 | |
532 r->headers_out.content_length_n = len; | |
272 | 533 |
534 if (r->headers_out.content_length) { | |
535 r->headers_out.content_length->hash = 0; | |
536 r->headers_out.content_length = NULL; | |
537 } | |
50 | 538 |
539 return ngx_http_next_header_filter(r); | |
392 | 540 } |
346 | 541 |
542 | |
392 | 543 static ngx_int_t |
544 ngx_http_range_not_satisfiable(ngx_http_request_t *r) | |
545 { | |
546 ngx_table_elt_t *content_range; | |
547 | |
548 r->headers_out.status = NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
549 | |
550 content_range = ngx_list_push(&r->headers_out.headers); | |
551 if (content_range == NULL) { | |
346 | 552 return NGX_ERROR; |
553 } | |
554 | |
392 | 555 r->headers_out.content_range = content_range; |
556 | |
557 content_range->hash = 1; | |
558 content_range->key.len = sizeof("Content-Range") - 1; | |
559 content_range->key.data = (u_char *) "Content-Range"; | |
346 | 560 |
392 | 561 content_range->value.data = ngx_pnalloc(r->pool, |
562 sizeof("bytes */") - 1 + NGX_OFF_T_LEN); | |
563 if (content_range->value.data == NULL) { | |
564 return NGX_ERROR; | |
565 } | |
566 | |
567 content_range->value.len = ngx_sprintf(content_range->value.data, | |
568 "bytes */%O", | |
569 r->headers_out.content_length_n) | |
570 - content_range->value.data; | |
571 | |
572 ngx_http_clear_content_length(r); | |
573 | |
574 return NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
50 | 575 } |
576 | |
577 | |
578 static ngx_int_t | |
391
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
579 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
|
580 ngx_http_output_body_filter_pt ngx_http_next_body_filter) |
50 | 581 { |
582 ngx_http_range_filter_ctx_t *ctx; | |
583 | |
272 | 584 if (in == NULL) { |
585 return ngx_http_next_body_filter(r, in); | |
586 } | |
587 | |
588 ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module); | |
589 | |
590 if (ctx == NULL) { | |
591 return ngx_http_next_body_filter(r, in); | |
592 } | |
593 | |
392 | 594 if (ctx->ranges.nelts == 1) { |
396 | 595 return ngx_http_range_singlepart_body(r, ctx, in, |
596 ngx_http_next_body_filter); | |
392 | 597 } |
598 | |
599 /* | |
600 * multipart ranges are supported only if whole body is in a single buffer | |
601 */ | |
272 | 602 |
603 if (ngx_buf_special(in->buf)) { | |
50 | 604 return ngx_http_next_body_filter(r, in); |
605 } | |
606 | |
392 | 607 if (ngx_http_range_test_overlapped(r, ctx, in) != NGX_OK) { |
608 return NGX_ERROR; | |
609 } | |
610 | |
396 | 611 return ngx_http_range_multipart_body(r, ctx, in, |
612 ngx_http_next_body_filter); | |
392 | 613 } |
614 | |
615 | |
616 static ngx_int_t | |
617 ngx_http_range_test_overlapped(ngx_http_request_t *r, | |
618 ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in) | |
619 { | |
620 off_t start, last; | |
621 ngx_buf_t *buf; | |
622 ngx_uint_t i; | |
623 ngx_http_range_t *range; | |
624 | |
272 | 625 if (ctx->offset) { |
626 goto overlapped; | |
627 } | |
628 | |
392 | 629 buf = in->buf; |
272 | 630 |
631 if (!buf->last_buf) { | |
632 | |
633 if (buf->in_file) { | |
634 start = buf->file_pos + ctx->offset; | |
635 last = buf->file_last + ctx->offset; | |
636 | |
637 } else { | |
638 start = buf->pos - buf->start + ctx->offset; | |
639 last = buf->last - buf->start + ctx->offset; | |
640 } | |
641 | |
392 | 642 range = ctx->ranges.elts; |
272 | 643 for (i = 0; i < ctx->ranges.nelts; i++) { |
644 if (start > range[i].start || last < range[i].end) { | |
645 goto overlapped; | |
646 } | |
647 } | |
648 } | |
649 | |
650 ctx->offset = ngx_buf_size(buf); | |
651 | |
392 | 652 return NGX_OK; |
653 | |
654 overlapped: | |
655 | |
656 ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, | |
657 "range in overlapped buffers"); | |
658 | |
659 return NGX_ERROR; | |
660 } | |
661 | |
662 | |
663 static ngx_int_t | |
664 ngx_http_range_singlepart_body(ngx_http_request_t *r, | |
396 | 665 ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in, |
666 ngx_http_output_body_filter_pt ngx_http_next_body_filter) | |
392 | 667 { |
668 off_t start, last; | |
669 ngx_buf_t *buf; | |
670 ngx_chain_t *out, *cl, **ll; | |
671 ngx_http_range_t *range; | |
672 | |
673 out = NULL; | |
674 ll = &out; | |
675 range = ctx->ranges.elts; | |
50 | 676 |
392 | 677 for (cl = in; cl; cl = cl->next) { |
678 | |
679 buf = cl->buf; | |
680 | |
681 start = ctx->offset; | |
682 last = ctx->offset + ngx_buf_size(buf); | |
683 | |
684 ctx->offset = last; | |
685 | |
686 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | |
687 "http range body buf: %O-%O", start, last); | |
688 | |
689 if (ngx_buf_special(buf)) { | |
690 *ll = cl; | |
691 ll = &cl->next; | |
692 continue; | |
693 } | |
694 | |
695 if (range->end <= start || range->start >= last) { | |
696 | |
697 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | |
698 "http range body skip"); | |
699 | |
420 | 700 if (buf->in_file) { |
701 buf->file_pos = buf->file_last; | |
702 } | |
703 | |
392 | 704 buf->pos = buf->last; |
420 | 705 buf->sync = 1; |
706 | |
392 | 707 continue; |
272 | 708 } |
50 | 709 |
392 | 710 if (range->start > start) { |
711 | |
712 if (buf->in_file) { | |
713 buf->file_pos += range->start - start; | |
714 } | |
715 | |
716 if (ngx_buf_in_memory(buf)) { | |
717 buf->pos += (size_t) (range->start - start); | |
718 } | |
50 | 719 } |
720 | |
392 | 721 if (range->end <= last) { |
722 | |
723 if (buf->in_file) { | |
724 buf->file_last -= last - range->end; | |
725 } | |
726 | |
727 if (ngx_buf_in_memory(buf)) { | |
728 buf->last -= (size_t) (last - range->end); | |
729 } | |
730 | |
731 buf->last_buf = 1; | |
732 *ll = cl; | |
733 cl->next = NULL; | |
734 | |
735 break; | |
736 } | |
737 | |
738 *ll = cl; | |
739 ll = &cl->next; | |
272 | 740 } |
50 | 741 |
392 | 742 if (out == NULL) { |
743 return NGX_OK; | |
744 } | |
745 | |
746 return ngx_http_next_body_filter(r, out); | |
747 } | |
748 | |
749 | |
750 static ngx_int_t | |
751 ngx_http_range_multipart_body(ngx_http_request_t *r, | |
396 | 752 ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in, |
753 ngx_http_output_body_filter_pt ngx_http_next_body_filter) | |
392 | 754 { |
473 | 755 off_t body_start; |
392 | 756 ngx_buf_t *b, *buf; |
757 ngx_uint_t i; | |
758 ngx_chain_t *out, *hcl, *rcl, *dcl, **ll; | |
759 ngx_http_range_t *range; | |
760 | |
272 | 761 ll = &out; |
392 | 762 buf = in->buf; |
763 range = ctx->ranges.elts; | |
272 | 764 |
473 | 765 #if (NGX_HTTP_CACHE) |
766 body_start = r->cached ? r->cache->body_start : 0; | |
767 #else | |
768 body_start = 0; | |
769 #endif | |
770 | |
272 | 771 for (i = 0; i < ctx->ranges.nelts; i++) { |
50 | 772 |
272 | 773 /* |
774 * The boundary header of the range: | |
775 * CRLF | |
776 * "--0123456789" CRLF | |
777 * "Content-Type: image/jpeg" CRLF | |
778 * "Content-Range: bytes " | |
779 */ | |
50 | 780 |
272 | 781 b = ngx_calloc_buf(r->pool); |
782 if (b == NULL) { | |
783 return NGX_ERROR; | |
784 } | |
50 | 785 |
272 | 786 b->memory = 1; |
787 b->pos = ctx->boundary_header.data; | |
788 b->last = ctx->boundary_header.data + ctx->boundary_header.len; | |
50 | 789 |
272 | 790 hcl = ngx_alloc_chain_link(r->pool); |
791 if (hcl == NULL) { | |
792 return NGX_ERROR; | |
793 } | |
50 | 794 |
272 | 795 hcl->buf = b; |
50 | 796 |
797 | |
272 | 798 /* "SSSS-EEEE/TTTT" CRLF CRLF */ |
50 | 799 |
800 b = ngx_calloc_buf(r->pool); | |
801 if (b == NULL) { | |
802 return NGX_ERROR; | |
803 } | |
804 | |
805 b->temporary = 1; | |
272 | 806 b->pos = range[i].content_range.data; |
807 b->last = range[i].content_range.data + range[i].content_range.len; | |
808 | |
809 rcl = ngx_alloc_chain_link(r->pool); | |
810 if (rcl == NULL) { | |
811 return NGX_ERROR; | |
812 } | |
813 | |
814 rcl->buf = b; | |
815 | |
816 | |
817 /* the range data */ | |
818 | |
819 b = ngx_calloc_buf(r->pool); | |
820 if (b == NULL) { | |
821 return NGX_ERROR; | |
822 } | |
50 | 823 |
272 | 824 b->in_file = buf->in_file; |
825 b->temporary = buf->temporary; | |
826 b->memory = buf->memory; | |
827 b->mmap = buf->mmap; | |
828 b->file = buf->file; | |
829 | |
830 if (buf->in_file) { | |
473 | 831 b->file_pos = body_start + range[i].start; |
832 b->file_last = body_start + range[i].end; | |
272 | 833 } |
834 | |
835 if (ngx_buf_in_memory(buf)) { | |
282 | 836 b->pos = buf->start + (size_t) range[i].start; |
837 b->last = buf->start + (size_t) range[i].end; | |
272 | 838 } |
839 | |
840 dcl = ngx_alloc_chain_link(r->pool); | |
841 if (dcl == NULL) { | |
50 | 842 return NGX_ERROR; |
843 } | |
844 | |
272 | 845 dcl->buf = b; |
50 | 846 |
847 *ll = hcl; | |
272 | 848 hcl->next = rcl; |
849 rcl->next = dcl; | |
850 ll = &dcl->next; | |
851 } | |
50 | 852 |
272 | 853 /* the last boundary CRLF "--0123456789--" CRLF */ |
854 | |
855 b = ngx_calloc_buf(r->pool); | |
856 if (b == NULL) { | |
857 return NGX_ERROR; | |
858 } | |
859 | |
860 b->temporary = 1; | |
861 b->last_buf = 1; | |
862 | |
382 | 863 b->pos = ngx_pnalloc(r->pool, sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN |
864 + sizeof("--" CRLF) - 1); | |
272 | 865 if (b->pos == NULL) { |
866 return NGX_ERROR; | |
50 | 867 } |
868 | |
300 | 869 b->last = ngx_cpymem(b->pos, ctx->boundary_header.data, |
870 sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN); | |
272 | 871 *b->last++ = '-'; *b->last++ = '-'; |
872 *b->last++ = CR; *b->last++ = LF; | |
873 | |
874 hcl = ngx_alloc_chain_link(r->pool); | |
875 if (hcl == NULL) { | |
876 return NGX_ERROR; | |
877 } | |
50 | 878 |
272 | 879 hcl->buf = b; |
880 hcl->next = NULL; | |
881 | |
882 *ll = hcl; | |
883 | |
884 return ngx_http_next_body_filter(r, out); | |
50 | 885 } |
886 | |
887 | |
888 static ngx_int_t | |
391
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
889 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
|
890 { |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
891 if (!r->allow_ranges || r->late_ranges) { |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
892 return ngx_http_next_body_early_filter(r, in); |
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 return ngx_http_range_body_generic_filter(r, in, |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
896 ngx_http_next_body_early_filter); |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
897 } |
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 static ngx_int_t |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
901 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
|
902 { |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
903 if (!r->allow_ranges || !r->late_ranges) { |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
904 return ngx_http_next_body_late_filter(r, in); |
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 return ngx_http_range_body_generic_filter(r, in, |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
908 ngx_http_next_body_late_filter); |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
909 } |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
910 |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
911 |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
912 static ngx_int_t |
230 | 913 ngx_http_range_header_filter_init(ngx_conf_t *cf) |
50 | 914 { |
915 ngx_http_next_header_filter = ngx_http_top_header_filter; | |
916 ngx_http_top_header_filter = ngx_http_range_header_filter; | |
917 | |
918 return NGX_OK; | |
919 } | |
920 | |
921 | |
922 static ngx_int_t | |
230 | 923 ngx_http_range_body_filter_init(ngx_conf_t *cf) |
50 | 924 { |
391
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
925 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
|
926 ngx_http_top_body_filter = ngx_http_range_body_early_filter; |
50 | 927 |
928 return NGX_OK; | |
929 } | |
391
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 |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
932 static ngx_int_t |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
933 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
|
934 { |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
935 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
|
936 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
|
937 |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
938 return NGX_OK; |
1d9bef53cd8e
Range filter: late_ranges functionality.
Maxim Dounin <mdounin@mdounin.ru>
parents:
390
diff
changeset
|
939 } |