Groonga 3.0.9 Source Code Document
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
ngx_http_range_filter_module.c
Go to the documentation of this file.
1 
2 /*
3  * Copyright (C) Igor Sysoev
4  * Copyright (C) Nginx, Inc.
5  */
6 
7 
8 #include <ngx_config.h>
9 #include <ngx_core.h>
10 #include <ngx_http.h>
11 
12 
13 /*
14  * the single part format:
15  *
16  * "HTTP/1.0 206 Partial Content" CRLF
17  * ... header ...
18  * "Content-Type: image/jpeg" CRLF
19  * "Content-Length: SIZE" CRLF
20  * "Content-Range: bytes START-END/SIZE" CRLF
21  * CRLF
22  * ... data ...
23  *
24  *
25  * the mutlipart format:
26  *
27  * "HTTP/1.0 206 Partial Content" CRLF
28  * ... header ...
29  * "Content-Type: multipart/byteranges; boundary=0123456789" CRLF
30  * CRLF
31  * CRLF
32  * "--0123456789" CRLF
33  * "Content-Type: image/jpeg" CRLF
34  * "Content-Range: bytes START0-END0/SIZE" CRLF
35  * CRLF
36  * ... data ...
37  * CRLF
38  * "--0123456789" CRLF
39  * "Content-Type: image/jpeg" CRLF
40  * "Content-Range: bytes START1-END1/SIZE" CRLF
41  * CRLF
42  * ... data ...
43  * CRLF
44  * "--0123456789--" CRLF
45  */
46 
47 
48 typedef struct {
49  off_t start;
50  off_t end;
53 
54 
55 typedef struct {
56  off_t offset;
60 
61 
62 static ngx_int_t ngx_http_range_parse(ngx_http_request_t *r,
64 static ngx_int_t ngx_http_range_singlepart_header(ngx_http_request_t *r,
66 static ngx_int_t ngx_http_range_multipart_header(ngx_http_request_t *r,
68 static ngx_int_t ngx_http_range_not_satisfiable(ngx_http_request_t *r);
69 static ngx_int_t ngx_http_range_test_overlapped(ngx_http_request_t *r,
71 static ngx_int_t ngx_http_range_singlepart_body(ngx_http_request_t *r,
73 static ngx_int_t ngx_http_range_multipart_body(ngx_http_request_t *r,
75 
76 static ngx_int_t ngx_http_range_header_filter_init(ngx_conf_t *cf);
77 static ngx_int_t ngx_http_range_body_filter_init(ngx_conf_t *cf);
78 
79 
80 static ngx_http_module_t ngx_http_range_header_filter_module_ctx = {
81  NULL, /* preconfiguration */
82  ngx_http_range_header_filter_init, /* postconfiguration */
83 
84  NULL, /* create main configuration */
85  NULL, /* init main configuration */
86 
87  NULL, /* create server configuration */
88  NULL, /* merge server configuration */
89 
90  NULL, /* create location configuration */
91  NULL, /* merge location configuration */
92 };
93 
94 
97  &ngx_http_range_header_filter_module_ctx, /* module context */
98  NULL, /* module directives */
99  NGX_HTTP_MODULE, /* module type */
100  NULL, /* init master */
101  NULL, /* init module */
102  NULL, /* init process */
103  NULL, /* init thread */
104  NULL, /* exit thread */
105  NULL, /* exit process */
106  NULL, /* exit master */
108 };
109 
110 
111 static ngx_http_module_t ngx_http_range_body_filter_module_ctx = {
112  NULL, /* preconfiguration */
113  ngx_http_range_body_filter_init, /* postconfiguration */
114 
115  NULL, /* create main configuration */
116  NULL, /* init main configuration */
117 
118  NULL, /* create server configuration */
119  NULL, /* merge server configuration */
120 
121  NULL, /* create location configuration */
122  NULL, /* merge location configuration */
123 };
124 
125 
128  &ngx_http_range_body_filter_module_ctx, /* module context */
129  NULL, /* module directives */
130  NGX_HTTP_MODULE, /* module type */
131  NULL, /* init master */
132  NULL, /* init module */
133  NULL, /* init process */
134  NULL, /* init thread */
135  NULL, /* exit thread */
136  NULL, /* exit process */
137  NULL, /* exit master */
139 };
140 
141 
142 static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
143 static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
144 
145 
146 static ngx_int_t
147 ngx_http_range_header_filter(ngx_http_request_t *r)
148 {
149  time_t if_range_time;
150  ngx_str_t *if_range, *etag;
153 
156  || r != r->main
157  || r->headers_out.content_length_n == -1
158  || !r->allow_ranges)
159  {
160  return ngx_http_next_header_filter(r);
161  }
162 
164 
165  if (clcf->max_ranges == 0) {
166  return ngx_http_next_header_filter(r);
167  }
168 
169  if (r->headers_in.range == NULL
170  || r->headers_in.range->value.len < 7
172  (u_char *) "bytes=", 6)
173  != 0)
174  {
175  goto next_filter;
176  }
177 
178  if (r->headers_in.if_range) {
179 
180  if_range = &r->headers_in.if_range->value;
181 
182  if (if_range->len >= 2 && if_range->data[if_range->len - 1] == '"') {
183 
184  if (r->headers_out.etag == NULL) {
185  goto next_filter;
186  }
187 
188  etag = &r->headers_out.etag->value;
189 
191  "http ir:%V etag:%V", if_range, etag);
192 
193  if (if_range->len != etag->len
194  || ngx_strncmp(if_range->data, etag->data, etag->len) != 0)
195  {
196  goto next_filter;
197  }
198 
199  goto parse;
200  }
201 
202  if (r->headers_out.last_modified_time == (time_t) -1) {
203  goto next_filter;
204  }
205 
206  if_range_time = ngx_http_parse_time(if_range->data, if_range->len);
207 
209  "http ir:%d lm:%d",
210  if_range_time, r->headers_out.last_modified_time);
211 
212  if (if_range_time != r->headers_out.last_modified_time) {
213  goto next_filter;
214  }
215  }
216 
217 parse:
218 
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_OK)
226  {
227  return NGX_ERROR;
228  }
229 
230  switch (ngx_http_range_parse(r, ctx, clcf->max_ranges)) {
231 
232  case NGX_OK:
233  ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
234 
236  r->headers_out.status_line.len = 0;
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 
245  return ngx_http_range_not_satisfiable(r);
246 
247  case NGX_ERROR:
248  return NGX_ERROR;
249 
250  default: /* NGX_DECLINED */
251  break;
252  }
253 
254 next_filter:
255 
257  if (r->headers_out.accept_ranges == NULL) {
258  return NGX_ERROR;
259  }
260 
262  ngx_str_set(&r->headers_out.accept_ranges->key, "Accept-Ranges");
264 
265  return ngx_http_next_header_filter(r);
266 }
267 
268 
269 static ngx_int_t
270 ngx_http_range_parse(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx,
271  ngx_uint_t ranges)
272 {
273  u_char *p;
274  off_t start, end, size, content_length;
275  ngx_uint_t suffix;
276  ngx_http_range_t *range;
277 
278  p = r->headers_in.range->value.data + 6;
279  size = 0;
280  content_length = r->headers_out.content_length_n;
281 
282  for ( ;; ) {
283  start = 0;
284  end = 0;
285  suffix = 0;
286 
287  while (*p == ' ') { p++; }
288 
289  if (*p != '-') {
290  if (*p < '0' || *p > '9') {
292  }
293 
294  while (*p >= '0' && *p <= '9') {
295  start = start * 10 + *p++ - '0';
296  }
297 
298  while (*p == ' ') { p++; }
299 
300  if (*p++ != '-') {
302  }
303 
304  while (*p == ' ') { p++; }
305 
306  if (*p == ',' || *p == '\0') {
307  end = content_length;
308  goto found;
309  }
310 
311  } else {
312  suffix = 1;
313  p++;
314  }
315 
316  if (*p < '0' || *p > '9') {
318  }
319 
320  while (*p >= '0' && *p <= '9') {
321  end = end * 10 + *p++ - '0';
322  }
323 
324  while (*p == ' ') { p++; }
325 
326  if (*p != ',' && *p != '\0') {
328  }
329 
330  if (suffix) {
331  start = content_length - end;
332  end = content_length - 1;
333  }
334 
335  if (end >= content_length) {
336  end = content_length;
337 
338  } else {
339  end++;
340  }
341 
342  found:
343 
344  if (start < end) {
345  range = ngx_array_push(&ctx->ranges);
346  if (range == NULL) {
347  return NGX_ERROR;
348  }
349 
350  range->start = start;
351  range->end = end;
352 
353  size += end - start;
354 
355  if (ranges-- == 0) {
356  return NGX_DECLINED;
357  }
358  }
359 
360  if (*p++ != ',') {
361  break;
362  }
363  }
364 
365  if (ctx->ranges.nelts == 0) {
367  }
368 
369  if (size > content_length) {
370  return NGX_DECLINED;
371  }
372 
373  return NGX_OK;
374 }
375 
376 
377 static ngx_int_t
378 ngx_http_range_singlepart_header(ngx_http_request_t *r,
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  }
388 
389  r->headers_out.content_range = content_range;
390 
391  content_range->hash = 1;
392  ngx_str_set(&content_range->key, "Content-Range");
393 
394  content_range->value.data = ngx_pnalloc(r->pool,
395  sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN);
396  if (content_range->value.data == NULL) {
397  return NGX_ERROR;
398  }
399 
400  /* "Content-Range: bytes SSSS-EEEE/TTTT" header */
401 
402  range = ctx->ranges.elts;
403 
404  content_range->value.len = ngx_sprintf(content_range->value.data,
405  "bytes %O-%O/%O",
406  range->start, range->end - 1,
408  - content_range->value.data;
409 
410  r->headers_out.content_length_n = range->end - range->start;
411 
412  if (r->headers_out.content_length) {
414  r->headers_out.content_length = NULL;
415  }
416 
417  return ngx_http_next_header_filter(r);
418 }
419 
420 
421 static ngx_int_t
422 ngx_http_range_multipart_header(ngx_http_request_t *r,
424 {
425  size_t len;
426  ngx_uint_t i;
427  ngx_http_range_t *range;
428  ngx_atomic_uint_t boundary;
429 
430  len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
431  + sizeof(CRLF "Content-Type: ") - 1
433  + sizeof(CRLF "Content-Range: bytes ") - 1;
434 
435  if (r->headers_out.charset.len) {
436  len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
437  }
438 
439  ctx->boundary_header.data = ngx_pnalloc(r->pool, len);
440  if (ctx->boundary_header.data == NULL) {
441  return NGX_ERROR;
442  }
443 
444  boundary = ngx_next_temp_number(0);
445 
446  /*
447  * The boundary header of the range:
448  * CRLF
449  * "--0123456789" CRLF
450  * "Content-Type: image/jpeg" CRLF
451  * "Content-Range: bytes "
452  */
453 
454  if (r->headers_out.charset.len) {
456  CRLF "--%0muA" CRLF
457  "Content-Type: %V; charset=%V" CRLF
458  "Content-Range: bytes ",
459  boundary,
461  &r->headers_out.charset)
462  - ctx->boundary_header.data;
463 
464  r->headers_out.charset.len = 0;
465 
466  } else if (r->headers_out.content_type.len) {
468  CRLF "--%0muA" CRLF
469  "Content-Type: %V" CRLF
470  "Content-Range: bytes ",
471  boundary,
473  - ctx->boundary_header.data;
474 
475  } else {
477  CRLF "--%0muA" CRLF
478  "Content-Range: bytes ",
479  boundary)
480  - ctx->boundary_header.data;
481  }
482 
484  ngx_pnalloc(r->pool,
485  sizeof("Content-Type: multipart/byteranges; boundary=") - 1
486  + NGX_ATOMIC_T_LEN);
487 
488  if (r->headers_out.content_type.data == NULL) {
489  return NGX_ERROR;
490  }
491 
493 
494  /* "Content-Type: multipart/byteranges; boundary=0123456789" */
495 
498  "multipart/byteranges; boundary=%0muA",
499  boundary)
501 
503 
504  /* the size of the last boundary CRLF "--0123456789--" CRLF */
505 
506  len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1;
507 
508  range = ctx->ranges.elts;
509  for (i = 0; i < ctx->ranges.nelts; i++) {
510 
511  /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */
512 
513  range[i].content_range.data =
514  ngx_pnalloc(r->pool, 3 * NGX_OFF_T_LEN + 2 + 4);
515 
516  if (range[i].content_range.data == NULL) {
517  return NGX_ERROR;
518  }
519 
520  range[i].content_range.len = ngx_sprintf(range[i].content_range.data,
521  "%O-%O/%O" CRLF CRLF,
522  range[i].start, range[i].end - 1,
524  - range[i].content_range.data;
525 
526  len += ctx->boundary_header.len + range[i].content_range.len
527  + (size_t) (range[i].end - range[i].start);
528  }
529 
530  r->headers_out.content_length_n = len;
531 
532  if (r->headers_out.content_length) {
534  r->headers_out.content_length = NULL;
535  }
536 
537  return ngx_http_next_header_filter(r);
538 }
539 
540 
541 static ngx_int_t
542 ngx_http_range_not_satisfiable(ngx_http_request_t *r)
543 {
544  ngx_table_elt_t *content_range;
545 
547 
548  content_range = ngx_list_push(&r->headers_out.headers);
549  if (content_range == NULL) {
550  return NGX_ERROR;
551  }
552 
553  r->headers_out.content_range = content_range;
554 
555  content_range->hash = 1;
556  ngx_str_set(&content_range->key, "Content-Range");
557 
558  content_range->value.data = ngx_pnalloc(r->pool,
559  sizeof("bytes */") - 1 + NGX_OFF_T_LEN);
560  if (content_range->value.data == NULL) {
561  return NGX_ERROR;
562  }
563 
564  content_range->value.len = ngx_sprintf(content_range->value.data,
565  "bytes */%O",
567  - content_range->value.data;
568 
570 
572 }
573 
574 
575 static ngx_int_t
576 ngx_http_range_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
577 {
579 
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 
590  if (ctx->ranges.nelts == 1) {
591  return ngx_http_range_singlepart_body(r, ctx, in);
592  }
593 
594  /*
595  * multipart ranges are supported only if whole body is in a single buffer
596  */
597 
598  if (ngx_buf_special(in->buf)) {
599  return ngx_http_next_body_filter(r, in);
600  }
601 
602  if (ngx_http_range_test_overlapped(r, ctx, in) != NGX_OK) {
603  return NGX_ERROR;
604  }
605 
606  return ngx_http_range_multipart_body(r, ctx, in);
607 }
608 
609 
610 static ngx_int_t
611 ngx_http_range_test_overlapped(ngx_http_request_t *r,
613 {
614  off_t start, last;
615  ngx_buf_t *buf;
616  ngx_uint_t i;
617  ngx_http_range_t *range;
618 
619  if (ctx->offset) {
620  goto overlapped;
621  }
622 
623  buf = in->buf;
624 
625  if (!buf->last_buf) {
626  start = ctx->offset;
627  last = ctx->offset + ngx_buf_size(buf);
628 
629  range = ctx->ranges.elts;
630  for (i = 0; i < ctx->ranges.nelts; i++) {
631  if (start > range[i].start || last < range[i].end) {
632  goto overlapped;
633  }
634  }
635  }
636 
637  ctx->offset = ngx_buf_size(buf);
638 
639  return NGX_OK;
640 
641 overlapped:
642 
644  "range in overlapped buffers");
645 
646  return NGX_ERROR;
647 }
648 
649 
650 static ngx_int_t
651 ngx_http_range_singlepart_body(ngx_http_request_t *r,
653 {
654  off_t start, last;
655  ngx_buf_t *buf;
656  ngx_chain_t *out, *cl, **ll;
657  ngx_http_range_t *range;
658 
659  out = NULL;
660  ll = &out;
661  range = ctx->ranges.elts;
662 
663  for (cl = in; cl; cl = cl->next) {
664 
665  buf = cl->buf;
666 
667  start = ctx->offset;
668  last = ctx->offset + ngx_buf_size(buf);
669 
670  ctx->offset = last;
671 
673  "http range body buf: %O-%O", start, last);
674 
675  if (ngx_buf_special(buf)) {
676  *ll = cl;
677  ll = &cl->next;
678  continue;
679  }
680 
681  if (range->end <= start || range->start >= last) {
682 
684  "http range body skip");
685 
686  if (buf->in_file) {
687  buf->file_pos = buf->file_last;
688  }
689 
690  buf->pos = buf->last;
691  buf->sync = 1;
692 
693  continue;
694  }
695 
696  if (range->start > start) {
697 
698  if (buf->in_file) {
699  buf->file_pos += range->start - start;
700  }
701 
702  if (ngx_buf_in_memory(buf)) {
703  buf->pos += (size_t) (range->start - start);
704  }
705  }
706 
707  if (range->end <= last) {
708 
709  if (buf->in_file) {
710  buf->file_last -= last - range->end;
711  }
712 
713  if (ngx_buf_in_memory(buf)) {
714  buf->last -= (size_t) (last - range->end);
715  }
716 
717  buf->last_buf = 1;
718  *ll = cl;
719  cl->next = NULL;
720 
721  break;
722  }
723 
724  *ll = cl;
725  ll = &cl->next;
726  }
727 
728  if (out == NULL) {
729  return NGX_OK;
730  }
731 
732  return ngx_http_next_body_filter(r, out);
733 }
734 
735 
736 static ngx_int_t
737 ngx_http_range_multipart_body(ngx_http_request_t *r,
739 {
740  ngx_buf_t *b, *buf;
741  ngx_uint_t i;
742  ngx_chain_t *out, *hcl, *rcl, *dcl, **ll;
743  ngx_http_range_t *range;
744 
745  ll = &out;
746  buf = in->buf;
747  range = ctx->ranges.elts;
748 
749  for (i = 0; i < ctx->ranges.nelts; i++) {
750 
751  /*
752  * The boundary header of the range:
753  * CRLF
754  * "--0123456789" CRLF
755  * "Content-Type: image/jpeg" CRLF
756  * "Content-Range: bytes "
757  */
758 
759  b = ngx_calloc_buf(r->pool);
760  if (b == NULL) {
761  return NGX_ERROR;
762  }
763 
764  b->memory = 1;
765  b->pos = ctx->boundary_header.data;
766  b->last = ctx->boundary_header.data + ctx->boundary_header.len;
767 
768  hcl = ngx_alloc_chain_link(r->pool);
769  if (hcl == NULL) {
770  return NGX_ERROR;
771  }
772 
773  hcl->buf = b;
774 
775 
776  /* "SSSS-EEEE/TTTT" CRLF CRLF */
777 
778  b = ngx_calloc_buf(r->pool);
779  if (b == NULL) {
780  return NGX_ERROR;
781  }
782 
783  b->temporary = 1;
784  b->pos = range[i].content_range.data;
785  b->last = range[i].content_range.data + range[i].content_range.len;
786 
787  rcl = ngx_alloc_chain_link(r->pool);
788  if (rcl == NULL) {
789  return NGX_ERROR;
790  }
791 
792  rcl->buf = b;
793 
794 
795  /* the range data */
796 
797  b = ngx_calloc_buf(r->pool);
798  if (b == NULL) {
799  return NGX_ERROR;
800  }
801 
802  b->in_file = buf->in_file;
803  b->temporary = buf->temporary;
804  b->memory = buf->memory;
805  b->mmap = buf->mmap;
806  b->file = buf->file;
807 
808  if (buf->in_file) {
809  b->file_pos = buf->file_pos + range[i].start;
810  b->file_last = buf->file_pos + range[i].end;
811  }
812 
813  if (ngx_buf_in_memory(buf)) {
814  b->pos = buf->pos + (size_t) range[i].start;
815  b->last = buf->pos + (size_t) range[i].end;
816  }
817 
818  dcl = ngx_alloc_chain_link(r->pool);
819  if (dcl == NULL) {
820  return NGX_ERROR;
821  }
822 
823  dcl->buf = b;
824 
825  *ll = hcl;
826  hcl->next = rcl;
827  rcl->next = dcl;
828  ll = &dcl->next;
829  }
830 
831  /* the last boundary CRLF "--0123456789--" CRLF */
832 
833  b = ngx_calloc_buf(r->pool);
834  if (b == NULL) {
835  return NGX_ERROR;
836  }
837 
838  b->temporary = 1;
839  b->last_buf = 1;
840 
841  b->pos = ngx_pnalloc(r->pool, sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
842  + sizeof("--" CRLF) - 1);
843  if (b->pos == NULL) {
844  return NGX_ERROR;
845  }
846 
847  b->last = ngx_cpymem(b->pos, ctx->boundary_header.data,
848  sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN);
849  *b->last++ = '-'; *b->last++ = '-';
850  *b->last++ = CR; *b->last++ = LF;
851 
852  hcl = ngx_alloc_chain_link(r->pool);
853  if (hcl == NULL) {
854  return NGX_ERROR;
855  }
856 
857  hcl->buf = b;
858  hcl->next = NULL;
859 
860  *ll = hcl;
861 
862  return ngx_http_next_body_filter(r, out);
863 }
864 
865 
866 static ngx_int_t
867 ngx_http_range_header_filter_init(ngx_conf_t *cf)
868 {
869  ngx_http_next_header_filter = ngx_http_top_header_filter;
870  ngx_http_top_header_filter = ngx_http_range_header_filter;
871 
872  return NGX_OK;
873 }
874 
875 
876 static ngx_int_t
877 ngx_http_range_body_filter_init(ngx_conf_t *cf)
878 {
879  ngx_http_next_body_filter = ngx_http_top_body_filter;
880  ngx_http_top_body_filter = ngx_http_range_body_filter;
881 
882  return NGX_OK;
883 }