Mercurial > hg > nginx-vendor-0-6
comparison src/http/modules/ngx_http_range_filter.c @ 0:f0b350454894 NGINX_0_1_0
nginx 0.1.0
*) The first public version.
author | Igor Sysoev <http://sysoev.ru> |
---|---|
date | Mon, 04 Oct 2004 00:00:00 +0400 |
parents | |
children | 4b2dafa26fe2 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:f0b350454894 |
---|---|
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 { | |
48 ngx_str_t boundary_header; | |
49 } ngx_http_range_filter_ctx_t; | |
50 | |
51 | |
52 static ngx_int_t ngx_http_range_header_filter_init(ngx_cycle_t *cycle); | |
53 static ngx_int_t ngx_http_range_body_filter_init(ngx_cycle_t *cycle); | |
54 | |
55 | |
56 static ngx_http_module_t ngx_http_range_header_filter_module_ctx = { | |
57 NULL, /* pre conf */ | |
58 | |
59 NULL, /* create main configuration */ | |
60 NULL, /* init main configuration */ | |
61 | |
62 NULL, /* create server configuration */ | |
63 NULL, /* merge server configuration */ | |
64 | |
65 NULL, /* create location configuration */ | |
66 NULL, /* merge location configuration */ | |
67 }; | |
68 | |
69 | |
70 ngx_module_t ngx_http_range_header_filter_module = { | |
71 NGX_MODULE, | |
72 &ngx_http_range_header_filter_module_ctx, /* module context */ | |
73 NULL, /* module directives */ | |
74 NGX_HTTP_MODULE, /* module type */ | |
75 ngx_http_range_header_filter_init, /* init module */ | |
76 NULL /* init child */ | |
77 }; | |
78 | |
79 | |
80 static ngx_http_module_t ngx_http_range_body_filter_module_ctx = { | |
81 NULL, /* pre conf */ | |
82 | |
83 NULL, /* create main configuration */ | |
84 NULL, /* init main configuration */ | |
85 | |
86 NULL, /* create server configuration */ | |
87 NULL, /* merge server configuration */ | |
88 | |
89 NULL, /* create location configuration */ | |
90 NULL, /* merge location configuration */ | |
91 }; | |
92 | |
93 | |
94 ngx_module_t ngx_http_range_body_filter_module = { | |
95 NGX_MODULE, | |
96 &ngx_http_range_body_filter_module_ctx, /* module context */ | |
97 NULL, /* module directives */ | |
98 NGX_HTTP_MODULE, /* module type */ | |
99 ngx_http_range_body_filter_init, /* init module */ | |
100 NULL /* init child */ | |
101 }; | |
102 | |
103 | |
104 static ngx_http_output_header_filter_pt ngx_http_next_header_filter; | |
105 static ngx_http_output_body_filter_pt ngx_http_next_body_filter; | |
106 | |
107 | |
108 static ngx_int_t ngx_http_range_header_filter(ngx_http_request_t *r) | |
109 { | |
110 ngx_int_t rc; | |
111 ngx_uint_t boundary, suffix, i; | |
112 u_char *p; | |
113 size_t len; | |
114 off_t start, end; | |
115 ngx_http_range_t *range; | |
116 ngx_http_range_filter_ctx_t *ctx; | |
117 | |
118 if (r->http_version < NGX_HTTP_VERSION_10 | |
119 || r->headers_out.status != NGX_HTTP_OK | |
120 || r->headers_out.content_length_n == -1 | |
121 || !r->filter_allow_ranges) | |
122 { | |
123 return ngx_http_next_header_filter(r); | |
124 } | |
125 | |
126 if (r->headers_in.range == NULL | |
127 || r->headers_in.range->value.len < 7 | |
128 || ngx_strncasecmp(r->headers_in.range->value.data, "bytes=", 6) != 0) | |
129 { | |
130 | |
131 r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers); | |
132 if (r->headers_out.accept_ranges == NULL) { | |
133 return NGX_ERROR; | |
134 } | |
135 | |
136 r->headers_out.accept_ranges->key.len = sizeof("Accept-Ranges") - 1; | |
137 r->headers_out.accept_ranges->key.data = (u_char *) "Accept-Ranges"; | |
138 r->headers_out.accept_ranges->value.len = sizeof("bytes") - 1; | |
139 r->headers_out.accept_ranges->value.data = (u_char *) "bytes"; | |
140 | |
141 return ngx_http_next_header_filter(r); | |
142 } | |
143 | |
144 ngx_init_array(r->headers_out.ranges, r->pool, 5, sizeof(ngx_http_range_t), | |
145 NGX_ERROR); | |
146 | |
147 rc = 0; | |
148 range = NULL; | |
149 p = r->headers_in.range->value.data + 6; | |
150 | |
151 for ( ;; ) { | |
152 start = 0; | |
153 end = 0; | |
154 suffix = 0; | |
155 | |
156 while (*p == ' ') { p++; } | |
157 | |
158 if (*p != '-') { | |
159 if (*p < '0' || *p > '9') { | |
160 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
161 break; | |
162 } | |
163 | |
164 while (*p >= '0' && *p <= '9') { | |
165 start = start * 10 + *p++ - '0'; | |
166 } | |
167 | |
168 while (*p == ' ') { p++; } | |
169 | |
170 if (*p++ != '-') { | |
171 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
172 break; | |
173 } | |
174 | |
175 if (start >= r->headers_out.content_length_n) { | |
176 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
177 break; | |
178 } | |
179 | |
180 while (*p == ' ') { p++; } | |
181 | |
182 if (*p == ',' || *p == '\0') { | |
183 ngx_test_null(range, ngx_push_array(&r->headers_out.ranges), | |
184 NGX_ERROR); | |
185 range->start = start; | |
186 range->end = r->headers_out.content_length_n; | |
187 | |
188 if (*p++ != ',') { | |
189 break; | |
190 } | |
191 | |
192 continue; | |
193 } | |
194 | |
195 } else { | |
196 suffix = 1; | |
197 p++; | |
198 } | |
199 | |
200 if (*p < '0' || *p > '9') { | |
201 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
202 break; | |
203 } | |
204 | |
205 while (*p >= '0' && *p <= '9') { | |
206 end = end * 10 + *p++ - '0'; | |
207 } | |
208 | |
209 while (*p == ' ') { p++; } | |
210 | |
211 if (*p != ',' && *p != '\0') { | |
212 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
213 break; | |
214 } | |
215 | |
216 if (suffix) { | |
217 start = r->headers_out.content_length_n - end; | |
218 end = r->headers_out.content_length_n - 1; | |
219 } | |
220 | |
221 if (start > end) { | |
222 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
223 break; | |
224 } | |
225 | |
226 ngx_test_null(range, ngx_push_array(&r->headers_out.ranges), NGX_ERROR); | |
227 range->start = start; | |
228 | |
229 if (end >= r->headers_out.content_length_n) { | |
230 /* | |
231 * Download Accelerator sends the last byte position | |
232 * that equals to the file length | |
233 */ | |
234 range->end = r->headers_out.content_length_n; | |
235 | |
236 } else { | |
237 range->end = end + 1; | |
238 } | |
239 | |
240 if (*p++ != ',') { | |
241 break; | |
242 } | |
243 } | |
244 | |
245 if (rc) { | |
246 | |
247 /* rc == NGX_HTTP_RANGE_NOT_SATISFIABLE */ | |
248 | |
249 r->headers_out.status = rc; | |
250 r->headers_out.ranges.nelts = 0; | |
251 | |
252 r->headers_out.content_range = ngx_list_push(&r->headers_out.headers); | |
253 if (r->headers_out.content_range == NULL) { | |
254 return NGX_ERROR; | |
255 } | |
256 | |
257 r->headers_out.content_range->key.len = sizeof("Content-Range") - 1; | |
258 r->headers_out.content_range->key.data = (u_char *) "Content-Range"; | |
259 | |
260 r->headers_out.content_range->value.data = | |
261 ngx_palloc(r->pool, 8 + 20 + 1); | |
262 if (r->headers_out.content_range->value.data == NULL) { | |
263 return NGX_ERROR; | |
264 } | |
265 | |
266 r->headers_out.content_range->value.len = | |
267 ngx_snprintf((char *) r->headers_out.content_range->value.data, | |
268 8 + 20 + 1, "bytes */" OFF_T_FMT, | |
269 r->headers_out.content_length_n); | |
270 | |
271 r->headers_out.content_length_n = -1; | |
272 if (r->headers_out.content_length) { | |
273 r->headers_out.content_length->key.len = 0; | |
274 r->headers_out.content_length = NULL; | |
275 } | |
276 | |
277 return rc; | |
278 | |
279 } else { | |
280 r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT; | |
281 | |
282 if (r->headers_out.ranges.nelts == 1) { | |
283 | |
284 r->headers_out.content_range = | |
285 ngx_list_push(&r->headers_out.headers); | |
286 if (r->headers_out.content_range == NULL) { | |
287 return NGX_ERROR; | |
288 } | |
289 | |
290 r->headers_out.content_range->key.len = sizeof("Content-Range") - 1; | |
291 r->headers_out.content_range->key.data = (u_char *) "Content-Range"; | |
292 | |
293 ngx_test_null(r->headers_out.content_range->value.data, | |
294 ngx_palloc(r->pool, 6 + 20 + 1 + 20 + 1 + 20 + 1), | |
295 NGX_ERROR); | |
296 | |
297 /* "Content-Range: bytes SSSS-EEEE/TTTT" header */ | |
298 | |
299 r->headers_out.content_range->value.len = | |
300 ngx_snprintf((char *) | |
301 r->headers_out.content_range->value.data, | |
302 6 + 20 + 1 + 20 + 1 + 20 + 1, | |
303 "bytes " OFF_T_FMT "-" OFF_T_FMT "/" OFF_T_FMT, | |
304 range->start, range->end - 1, | |
305 r->headers_out.content_length_n); | |
306 | |
307 r->headers_out.content_length_n = range->end - range->start; | |
308 | |
309 } else { | |
310 | |
311 #if 0 | |
312 /* TODO: what if no content_type ?? */ | |
313 | |
314 if (!(r->headers_out.content_type = | |
315 ngx_http_add_header(&r->headers_out, ngx_http_headers_out))) | |
316 { | |
317 return NGX_ERROR; | |
318 } | |
319 #endif | |
320 | |
321 ngx_http_create_ctx(r, ctx, ngx_http_range_body_filter_module, | |
322 sizeof(ngx_http_range_filter_ctx_t), NGX_ERROR); | |
323 | |
324 len = 4 + 10 + 2 + 14 + r->headers_out.content_type->value.len | |
325 + 2 + 21 + 1; | |
326 | |
327 if (r->headers_out.charset.len) { | |
328 len += 10 + r->headers_out.charset.len; | |
329 } | |
330 | |
331 ngx_test_null(ctx->boundary_header.data, ngx_palloc(r->pool, len), | |
332 NGX_ERROR); | |
333 | |
334 boundary = ngx_next_temp_number(0); | |
335 | |
336 /* | |
337 * The boundary header of the range: | |
338 * CRLF | |
339 * "--0123456789" CRLF | |
340 * "Content-Type: image/jpeg" CRLF | |
341 * "Content-Range: bytes " | |
342 */ | |
343 | |
344 if (r->headers_out.charset.len) { | |
345 ctx->boundary_header.len = | |
346 ngx_snprintf((char *) ctx->boundary_header.data, len, | |
347 CRLF "--%010" NGX_UINT_T_FMT CRLF | |
348 "Content-Type: %s; charset=%s" CRLF | |
349 "Content-Range: bytes ", | |
350 boundary, | |
351 r->headers_out.content_type->value.data, | |
352 r->headers_out.charset.data); | |
353 | |
354 r->headers_out.charset.len = 0; | |
355 | |
356 } else { | |
357 ctx->boundary_header.len = | |
358 ngx_snprintf((char *) ctx->boundary_header.data, len, | |
359 CRLF "--%010" NGX_UINT_T_FMT CRLF | |
360 "Content-Type: %s" CRLF | |
361 "Content-Range: bytes ", | |
362 boundary, | |
363 r->headers_out.content_type->value.data); | |
364 } | |
365 | |
366 ngx_test_null(r->headers_out.content_type->value.data, | |
367 ngx_palloc(r->pool, 31 + 10 + 1), | |
368 NGX_ERROR); | |
369 | |
370 /* "Content-Type: multipart/byteranges; boundary=0123456789" */ | |
371 | |
372 r->headers_out.content_type->value.len = | |
373 ngx_snprintf((char *) | |
374 r->headers_out.content_type->value.data, | |
375 31 + 10 + 1, | |
376 "multipart/byteranges; boundary=%010" | |
377 NGX_UINT_T_FMT, | |
378 boundary); | |
379 | |
380 /* the size of the last boundary CRLF "--0123456789--" CRLF */ | |
381 len = 4 + 10 + 4; | |
382 | |
383 range = r->headers_out.ranges.elts; | |
384 for (i = 0; i < r->headers_out.ranges.nelts; i++) { | |
385 ngx_test_null(range[i].content_range.data, | |
386 ngx_palloc(r->pool, 20 + 1 + 20 + 1 + 20 + 5), | |
387 NGX_ERROR); | |
388 | |
389 /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */ | |
390 | |
391 range[i].content_range.len = | |
392 ngx_snprintf((char *) range[i].content_range.data, | |
393 20 + 1 + 20 + 1 + 20 + 5, | |
394 OFF_T_FMT "-" OFF_T_FMT "/" OFF_T_FMT CRLF CRLF, | |
395 range[i].start, range[i].end - 1, | |
396 r->headers_out.content_length_n); | |
397 | |
398 len += ctx->boundary_header.len + range[i].content_range.len | |
399 + (size_t) (range[i].end - range[i].start); | |
400 } | |
401 | |
402 r->headers_out.content_length_n = len; | |
403 r->headers_out.content_length = NULL; | |
404 } | |
405 } | |
406 | |
407 return ngx_http_next_header_filter(r); | |
408 } | |
409 | |
410 | |
411 static ngx_int_t ngx_http_range_body_filter(ngx_http_request_t *r, | |
412 ngx_chain_t *in) | |
413 { | |
414 ngx_uint_t i; | |
415 ngx_buf_t *b; | |
416 ngx_chain_t *out, *hcl, *rcl, *dcl, **ll; | |
417 ngx_http_range_t *range; | |
418 ngx_http_range_filter_ctx_t *ctx; | |
419 | |
420 if (r->headers_out.ranges.nelts == 0) { | |
421 return ngx_http_next_body_filter(r, in); | |
422 } | |
423 | |
424 /* | |
425 * the optimized version for the static files only | |
426 * that are passed in the single file buf | |
427 */ | |
428 | |
429 if (in && in->buf->in_file && in->buf->last_buf) { | |
430 range = r->headers_out.ranges.elts; | |
431 | |
432 if (r->headers_out.ranges.nelts == 1) { | |
433 in->buf->file_pos = range->start; | |
434 in->buf->file_last = range->end; | |
435 | |
436 return ngx_http_next_body_filter(r, in); | |
437 } | |
438 | |
439 ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module); | |
440 ll = &out; | |
441 | |
442 for (i = 0; i < r->headers_out.ranges.nelts; i++) { | |
443 | |
444 /* | |
445 * The boundary header of the range: | |
446 * CRLF | |
447 * "--0123456789" CRLF | |
448 * "Content-Type: image/jpeg" CRLF | |
449 * "Content-Range: bytes " | |
450 */ | |
451 | |
452 ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR); | |
453 b->memory = 1; | |
454 b->pos = ctx->boundary_header.data; | |
455 b->last = ctx->boundary_header.data + ctx->boundary_header.len; | |
456 | |
457 ngx_test_null(hcl, ngx_alloc_chain_link(r->pool), NGX_ERROR); | |
458 hcl->buf = b; | |
459 | |
460 /* "SSSS-EEEE/TTTT" CRLF CRLF */ | |
461 | |
462 ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR); | |
463 b->temporary = 1; | |
464 b->pos = range[i].content_range.data; | |
465 b->last = range[i].content_range.data + range[i].content_range.len; | |
466 | |
467 ngx_test_null(rcl, ngx_alloc_chain_link(r->pool), NGX_ERROR); | |
468 rcl->buf = b; | |
469 | |
470 /* the range data */ | |
471 | |
472 ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR); | |
473 b->in_file = 1; | |
474 b->file_pos = range[i].start; | |
475 b->file_last = range[i].end; | |
476 b->file = in->buf->file; | |
477 | |
478 ngx_alloc_link_and_set_buf(dcl, b, r->pool, NGX_ERROR); | |
479 | |
480 *ll = hcl; | |
481 hcl->next = rcl; | |
482 rcl->next = dcl; | |
483 ll = &dcl->next; | |
484 } | |
485 | |
486 /* the last boundary CRLF "--0123456789--" CRLF */ | |
487 | |
488 ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR); | |
489 b->temporary = 1; | |
490 b->last_buf = 1; | |
491 ngx_test_null(b->pos, ngx_palloc(r->pool, 4 + 10 + 4), NGX_ERROR); | |
492 b->last = ngx_cpymem(b->pos, ctx->boundary_header.data, 4 + 10); | |
493 *b->last++ = '-'; *b->last++ = '-'; | |
494 *b->last++ = CR; *b->last++ = LF; | |
495 | |
496 ngx_alloc_link_and_set_buf(hcl, b, r->pool, NGX_ERROR); | |
497 *ll = hcl; | |
498 | |
499 return ngx_http_next_body_filter(r, out); | |
500 } | |
501 | |
502 /* TODO: alert */ | |
503 | |
504 return ngx_http_next_body_filter(r, in); | |
505 } | |
506 | |
507 | |
508 static ngx_int_t ngx_http_range_header_filter_init(ngx_cycle_t *cycle) | |
509 { | |
510 ngx_http_next_header_filter = ngx_http_top_header_filter; | |
511 ngx_http_top_header_filter = ngx_http_range_header_filter; | |
512 | |
513 return NGX_OK; | |
514 } | |
515 | |
516 | |
517 static ngx_int_t ngx_http_range_body_filter_init(ngx_cycle_t *cycle) | |
518 { | |
519 ngx_http_next_body_filter = ngx_http_top_body_filter; | |
520 ngx_http_top_body_filter = ngx_http_range_body_filter; | |
521 | |
522 return NGX_OK; | |
523 } |