src/http/modules/ngx_http_range_filter_module.c - nginx-1.7.10

Global variables defined

Data types defined

Functions defined

Source code


  1. /*
  2. * Copyright (C) Igor Sysoev
  3. * Copyright (C) Nginx, Inc.
  4. */


  5. #include <ngx_config.h>
  6. #include <ngx_core.h>
  7. #include <ngx_http.h>


  8. /*
  9. * the single part format:
  10. *
  11. * "HTTP/1.0 206 Partial Content" CRLF
  12. * ... header ...
  13. * "Content-Type: image/jpeg" CRLF
  14. * "Content-Length: SIZE" CRLF
  15. * "Content-Range: bytes START-END/SIZE" CRLF
  16. * CRLF
  17. * ... data ...
  18. *
  19. *
  20. * the multipart format:
  21. *
  22. * "HTTP/1.0 206 Partial Content" CRLF
  23. * ... header ...
  24. * "Content-Type: multipart/byteranges; boundary=0123456789" CRLF
  25. * CRLF
  26. * CRLF
  27. * "--0123456789" CRLF
  28. * "Content-Type: image/jpeg" CRLF
  29. * "Content-Range: bytes START0-END0/SIZE" CRLF
  30. * CRLF
  31. * ... data ...
  32. * CRLF
  33. * "--0123456789" CRLF
  34. * "Content-Type: image/jpeg" CRLF
  35. * "Content-Range: bytes START1-END1/SIZE" CRLF
  36. * CRLF
  37. * ... data ...
  38. * CRLF
  39. * "--0123456789--" CRLF
  40. */


  41. typedef struct {
  42.     off_t        start;
  43.     off_t        end;
  44.     ngx_str_t    content_range;
  45. } ngx_http_range_t;


  46. typedef struct {
  47.     off_t        offset;
  48.     ngx_str_t    boundary_header;
  49.     ngx_array_t  ranges;
  50. } ngx_http_range_filter_ctx_t;


  51. static ngx_int_t ngx_http_range_parse(ngx_http_request_t *r,
  52.     ngx_http_range_filter_ctx_t *ctx, ngx_uint_t ranges);
  53. static ngx_int_t ngx_http_range_singlepart_header(ngx_http_request_t *r,
  54.     ngx_http_range_filter_ctx_t *ctx);
  55. static ngx_int_t ngx_http_range_multipart_header(ngx_http_request_t *r,
  56.     ngx_http_range_filter_ctx_t *ctx);
  57. static ngx_int_t ngx_http_range_not_satisfiable(ngx_http_request_t *r);
  58. static ngx_int_t ngx_http_range_test_overlapped(ngx_http_request_t *r,
  59.     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
  60. static ngx_int_t ngx_http_range_singlepart_body(ngx_http_request_t *r,
  61.     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
  62. static ngx_int_t ngx_http_range_multipart_body(ngx_http_request_t *r,
  63.     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);

  64. static ngx_int_t ngx_http_range_header_filter_init(ngx_conf_t *cf);
  65. static ngx_int_t ngx_http_range_body_filter_init(ngx_conf_t *cf);


  66. static ngx_http_module_t  ngx_http_range_header_filter_module_ctx = {
  67.     NULL,                                  /* preconfiguration */
  68.     ngx_http_range_header_filter_init,     /* postconfiguration */

  69.     NULL,                                  /* create main configuration */
  70.     NULL,                                  /* init main configuration */

  71.     NULL,                                  /* create server configuration */
  72.     NULL,                                  /* merge server configuration */

  73.     NULL,                                  /* create location configuration */
  74.     NULL,                                  /* merge location configuration */
  75. };


  76. ngx_module_t  ngx_http_range_header_filter_module = {
  77.     NGX_MODULE_V1,
  78.     &ngx_http_range_header_filter_module_ctx, /* module context */
  79.     NULL,                                  /* module directives */
  80.     NGX_HTTP_MODULE,                       /* module type */
  81.     NULL,                                  /* init master */
  82.     NULL,                                  /* init module */
  83.     NULL,                                  /* init process */
  84.     NULL,                                  /* init thread */
  85.     NULL,                                  /* exit thread */
  86.     NULL,                                  /* exit process */
  87.     NULL,                                  /* exit master */
  88.     NGX_MODULE_V1_PADDING
  89. };


  90. static ngx_http_module_t  ngx_http_range_body_filter_module_ctx = {
  91.     NULL,                                  /* preconfiguration */
  92.     ngx_http_range_body_filter_init,       /* postconfiguration */

  93.     NULL,                                  /* create main configuration */
  94.     NULL,                                  /* init main configuration */

  95.     NULL,                                  /* create server configuration */
  96.     NULL,                                  /* merge server configuration */

  97.     NULL,                                  /* create location configuration */
  98.     NULL,                                  /* merge location configuration */
  99. };


  100. ngx_module_t  ngx_http_range_body_filter_module = {
  101.     NGX_MODULE_V1,
  102.     &ngx_http_range_body_filter_module_ctx, /* module context */
  103.     NULL,                                  /* module directives */
  104.     NGX_HTTP_MODULE,                       /* module type */
  105.     NULL,                                  /* init master */
  106.     NULL,                                  /* init module */
  107.     NULL,                                  /* init process */
  108.     NULL,                                  /* init thread */
  109.     NULL,                                  /* exit thread */
  110.     NULL,                                  /* exit process */
  111.     NULL,                                  /* exit master */
  112.     NGX_MODULE_V1_PADDING
  113. };


  114. static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
  115. static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;


  116. static ngx_int_t
  117. ngx_http_range_header_filter(ngx_http_request_t *r)
  118. {
  119.     time_t                        if_range_time;
  120.     ngx_str_t                    *if_range, *etag;
  121.     ngx_uint_t                    ranges;
  122.     ngx_http_core_loc_conf_t     *clcf;
  123.     ngx_http_range_filter_ctx_t  *ctx;

  124.     if (r->http_version < NGX_HTTP_VERSION_10
  125.         || r->headers_out.status != NGX_HTTP_OK
  126.         || r != r->main
  127.         || r->headers_out.content_length_n == -1
  128.         || !r->allow_ranges)
  129.     {
  130.         return ngx_http_next_header_filter(r);
  131.     }

  132.     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

  133.     if (clcf->max_ranges == 0) {
  134.         return ngx_http_next_header_filter(r);
  135.     }

  136.     if (r->headers_in.range == NULL
  137.         || r->headers_in.range->value.len < 7
  138.         || ngx_strncasecmp(r->headers_in.range->value.data,
  139.                            (u_char *) "bytes=", 6)
  140.            != 0)
  141.     {
  142.         goto next_filter;
  143.     }

  144.     if (r->headers_in.if_range) {

  145.         if_range = &r->headers_in.if_range->value;

  146.         if (if_range->len >= 2 && if_range->data[if_range->len - 1] == '"') {

  147.             if (r->headers_out.etag == NULL) {
  148.                 goto next_filter;
  149.             }

  150.             etag = &r->headers_out.etag->value;

  151.             ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  152.                            "http ir:%V etag:%V", if_range, etag);

  153.             if (if_range->len != etag->len
  154.                 || ngx_strncmp(if_range->data, etag->data, etag->len) != 0)
  155.             {
  156.                 goto next_filter;
  157.             }

  158.             goto parse;
  159.         }

  160.         if (r->headers_out.last_modified_time == (time_t) -1) {
  161.             goto next_filter;
  162.         }

  163.         if_range_time = ngx_http_parse_time(if_range->data, if_range->len);

  164.         ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  165.                        "http ir:%d lm:%d",
  166.                        if_range_time, r->headers_out.last_modified_time);

  167.         if (if_range_time != r->headers_out.last_modified_time) {
  168.             goto next_filter;
  169.         }
  170.     }

  171. parse:

  172.     ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_range_filter_ctx_t));
  173.     if (ctx == NULL) {
  174.         return NGX_ERROR;
  175.     }

  176.     if (ngx_array_init(&ctx->ranges, r->pool, 1, sizeof(ngx_http_range_t))
  177.         != NGX_OK)
  178.     {
  179.         return NGX_ERROR;
  180.     }

  181.     ranges = r->single_range ? 1 : clcf->max_ranges;

  182.     switch (ngx_http_range_parse(r, ctx, ranges)) {

  183.     case NGX_OK:
  184.         ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);

  185.         r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
  186.         r->headers_out.status_line.len = 0;

  187.         if (ctx->ranges.nelts == 1) {
  188.             return ngx_http_range_singlepart_header(r, ctx);
  189.         }

  190.         return ngx_http_range_multipart_header(r, ctx);

  191.     case NGX_HTTP_RANGE_NOT_SATISFIABLE:
  192.         return ngx_http_range_not_satisfiable(r);

  193.     case NGX_ERROR:
  194.         return NGX_ERROR;

  195.     default: /* NGX_DECLINED */
  196.         break;
  197.     }

  198. next_filter:

  199.     r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers);
  200.     if (r->headers_out.accept_ranges == NULL) {
  201.         return NGX_ERROR;
  202.     }

  203.     r->headers_out.accept_ranges->hash = 1;
  204.     ngx_str_set(&r->headers_out.accept_ranges->key, "Accept-Ranges");
  205.     ngx_str_set(&r->headers_out.accept_ranges->value, "bytes");

  206.     return ngx_http_next_header_filter(r);
  207. }


  208. static ngx_int_t
  209. ngx_http_range_parse(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx,
  210.     ngx_uint_t ranges)
  211. {
  212.     u_char            *p;
  213.     off_t              start, end, size, content_length;
  214.     ngx_uint_t         suffix;
  215.     ngx_http_range_t  *range;

  216.     p = r->headers_in.range->value.data + 6;
  217.     size = 0;
  218.     content_length = r->headers_out.content_length_n;

  219.     for ( ;; ) {
  220.         start = 0;
  221.         end = 0;
  222.         suffix = 0;

  223.         while (*p == ' ') { p++; }

  224.         if (*p != '-') {
  225.             if (*p < '0' || *p > '9') {
  226.                 return NGX_HTTP_RANGE_NOT_SATISFIABLE;
  227.             }

  228.             while (*p >= '0' && *p <= '9') {
  229.                 start = start * 10 + *p++ - '0';
  230.             }

  231.             while (*p == ' ') { p++; }

  232.             if (*p++ != '-') {
  233.                 return NGX_HTTP_RANGE_NOT_SATISFIABLE;
  234.             }

  235.             while (*p == ' ') { p++; }

  236.             if (*p == ',' || *p == '\0') {
  237.                 end = content_length;
  238.                 goto found;
  239.             }

  240.         } else {
  241.             suffix = 1;
  242.             p++;
  243.         }

  244.         if (*p < '0' || *p > '9') {
  245.             return NGX_HTTP_RANGE_NOT_SATISFIABLE;
  246.         }

  247.         while (*p >= '0' && *p <= '9') {
  248.             end = end * 10 + *p++ - '0';
  249.         }

  250.         while (*p == ' ') { p++; }

  251.         if (*p != ',' && *p != '\0') {
  252.             return NGX_HTTP_RANGE_NOT_SATISFIABLE;
  253.         }

  254.         if (suffix) {
  255.             start = content_length - end;
  256.             end = content_length - 1;
  257.         }

  258.         if (end >= content_length) {
  259.             end = content_length;

  260.         } else {
  261.             end++;
  262.         }

  263.     found:

  264.         if (start < end) {
  265.             range = ngx_array_push(&ctx->ranges);
  266.             if (range == NULL) {
  267.                 return NGX_ERROR;
  268.             }

  269.             range->start = start;
  270.             range->end = end;

  271.             size += end - start;

  272.             if (ranges-- == 0) {
  273.                 return NGX_DECLINED;
  274.             }
  275.         }

  276.         if (*p++ != ',') {
  277.             break;
  278.         }
  279.     }

  280.     if (ctx->ranges.nelts == 0) {
  281.         return NGX_HTTP_RANGE_NOT_SATISFIABLE;
  282.     }

  283.     if (size > content_length) {
  284.         return NGX_DECLINED;
  285.     }

  286.     return NGX_OK;
  287. }


  288. static ngx_int_t
  289. ngx_http_range_singlepart_header(ngx_http_request_t *r,
  290.     ngx_http_range_filter_ctx_t *ctx)
  291. {
  292.     ngx_table_elt_t   *content_range;
  293.     ngx_http_range_t  *range;

  294.     content_range = ngx_list_push(&r->headers_out.headers);
  295.     if (content_range == NULL) {
  296.         return NGX_ERROR;
  297.     }

  298.     r->headers_out.content_range = content_range;

  299.     content_range->hash = 1;
  300.     ngx_str_set(&content_range->key, "Content-Range");

  301.     content_range->value.data = ngx_pnalloc(r->pool,
  302.                                     sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN);
  303.     if (content_range->value.data == NULL) {
  304.         return NGX_ERROR;
  305.     }

  306.     /* "Content-Range: bytes SSSS-EEEE/TTTT" header */

  307.     range = ctx->ranges.elts;

  308.     content_range->value.len = ngx_sprintf(content_range->value.data,
  309.                                            "bytes %O-%O/%O",
  310.                                            range->start, range->end - 1,
  311.                                            r->headers_out.content_length_n)
  312.                                - content_range->value.data;

  313.     r->headers_out.content_length_n = range->end - range->start;

  314.     if (r->headers_out.content_length) {
  315.         r->headers_out.content_length->hash = 0;
  316.         r->headers_out.content_length = NULL;
  317.     }

  318.     return ngx_http_next_header_filter(r);
  319. }


  320. static ngx_int_t
  321. ngx_http_range_multipart_header(ngx_http_request_t *r,
  322.     ngx_http_range_filter_ctx_t *ctx)
  323. {
  324.     size_t              len;
  325.     ngx_uint_t          i;
  326.     ngx_http_range_t   *range;
  327.     ngx_atomic_uint_t   boundary;

  328.     len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
  329.           + sizeof(CRLF "Content-Type: ") - 1
  330.           + r->headers_out.content_type.len
  331.           + sizeof(CRLF "Content-Range: bytes ") - 1;

  332.     if (r->headers_out.content_type_len == r->headers_out.content_type.len
  333.         && r->headers_out.charset.len)
  334.     {
  335.         len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
  336.     }

  337.     ctx->boundary_header.data = ngx_pnalloc(r->pool, len);
  338.     if (ctx->boundary_header.data == NULL) {
  339.         return NGX_ERROR;
  340.     }

  341.     boundary = ngx_next_temp_number(0);

  342.     /*
  343.      * The boundary header of the range:
  344.      * CRLF
  345.      * "--0123456789" CRLF
  346.      * "Content-Type: image/jpeg" CRLF
  347.      * "Content-Range: bytes "
  348.      */

  349.     if (r->headers_out.content_type_len == r->headers_out.content_type.len
  350.         && r->headers_out.charset.len)
  351.     {
  352.         ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
  353.                                            CRLF "--%0muA" CRLF
  354.                                            "Content-Type: %V; charset=%V" CRLF
  355.                                            "Content-Range: bytes ",
  356.                                            boundary,
  357.                                            &r->headers_out.content_type,
  358.                                            &r->headers_out.charset)
  359.                                    - ctx->boundary_header.data;

  360.     } else if (r->headers_out.content_type.len) {
  361.         ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
  362.                                            CRLF "--%0muA" CRLF
  363.                                            "Content-Type: %V" CRLF
  364.                                            "Content-Range: bytes ",
  365.                                            boundary,
  366.                                            &r->headers_out.content_type)
  367.                                    - ctx->boundary_header.data;

  368.     } else {
  369.         ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
  370.                                            CRLF "--%0muA" CRLF
  371.                                            "Content-Range: bytes ",
  372.                                            boundary)
  373.                                    - ctx->boundary_header.data;
  374.     }

  375.     r->headers_out.content_type.data =
  376.         ngx_pnalloc(r->pool,
  377.                     sizeof("Content-Type: multipart/byteranges; boundary=") - 1
  378.                     + NGX_ATOMIC_T_LEN);

  379.     if (r->headers_out.content_type.data == NULL) {
  380.         return NGX_ERROR;
  381.     }

  382.     r->headers_out.content_type_lowcase = NULL;

  383.     /* "Content-Type: multipart/byteranges; boundary=0123456789" */

  384.     r->headers_out.content_type.len =
  385.                            ngx_sprintf(r->headers_out.content_type.data,
  386.                                        "multipart/byteranges; boundary=%0muA",
  387.                                        boundary)
  388.                            - r->headers_out.content_type.data;

  389.     r->headers_out.content_type_len = r->headers_out.content_type.len;

  390.     r->headers_out.charset.len = 0;

  391.     /* the size of the last boundary CRLF "--0123456789--" CRLF */

  392.     len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1;

  393.     range = ctx->ranges.elts;
  394.     for (i = 0; i < ctx->ranges.nelts; i++) {

  395.         /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */

  396.         range[i].content_range.data =
  397.                                ngx_pnalloc(r->pool, 3 * NGX_OFF_T_LEN + 2 + 4);

  398.         if (range[i].content_range.data == NULL) {
  399.             return NGX_ERROR;
  400.         }

  401.         range[i].content_range.len = ngx_sprintf(range[i].content_range.data,
  402.                                                "%O-%O/%O" CRLF CRLF,
  403.                                                range[i].start, range[i].end - 1,
  404.                                                r->headers_out.content_length_n)
  405.                                      - range[i].content_range.data;

  406.         len += ctx->boundary_header.len + range[i].content_range.len
  407.                                     + (size_t) (range[i].end - range[i].start);
  408.     }

  409.     r->headers_out.content_length_n = len;

  410.     if (r->headers_out.content_length) {
  411.         r->headers_out.content_length->hash = 0;
  412.         r->headers_out.content_length = NULL;
  413.     }

  414.     return ngx_http_next_header_filter(r);
  415. }


  416. static ngx_int_t
  417. ngx_http_range_not_satisfiable(ngx_http_request_t *r)
  418. {
  419.     ngx_table_elt_t  *content_range;

  420.     r->headers_out.status = NGX_HTTP_RANGE_NOT_SATISFIABLE;

  421.     content_range = ngx_list_push(&r->headers_out.headers);
  422.     if (content_range == NULL) {
  423.         return NGX_ERROR;
  424.     }

  425.     r->headers_out.content_range = content_range;

  426.     content_range->hash = 1;
  427.     ngx_str_set(&content_range->key, "Content-Range");

  428.     content_range->value.data = ngx_pnalloc(r->pool,
  429.                                        sizeof("bytes */") - 1 + NGX_OFF_T_LEN);
  430.     if (content_range->value.data == NULL) {
  431.         return NGX_ERROR;
  432.     }

  433.     content_range->value.len = ngx_sprintf(content_range->value.data,
  434.                                            "bytes */%O",
  435.                                            r->headers_out.content_length_n)
  436.                                - content_range->value.data;

  437.     ngx_http_clear_content_length(r);

  438.     return NGX_HTTP_RANGE_NOT_SATISFIABLE;
  439. }


  440. static ngx_int_t
  441. ngx_http_range_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
  442. {
  443.     ngx_http_range_filter_ctx_t  *ctx;

  444.     if (in == NULL) {
  445.         return ngx_http_next_body_filter(r, in);
  446.     }

  447.     ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module);

  448.     if (ctx == NULL) {
  449.         return ngx_http_next_body_filter(r, in);
  450.     }

  451.     if (ctx->ranges.nelts == 1) {
  452.         return ngx_http_range_singlepart_body(r, ctx, in);
  453.     }

  454.     /*
  455.      * multipart ranges are supported only if whole body is in a single buffer
  456.      */

  457.     if (ngx_buf_special(in->buf)) {
  458.         return ngx_http_next_body_filter(r, in);
  459.     }

  460.     if (ngx_http_range_test_overlapped(r, ctx, in) != NGX_OK) {
  461.         return NGX_ERROR;
  462.     }

  463.     return ngx_http_range_multipart_body(r, ctx, in);
  464. }


  465. static ngx_int_t
  466. ngx_http_range_test_overlapped(ngx_http_request_t *r,
  467.     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
  468. {
  469.     off_t              start, last;
  470.     ngx_buf_t         *buf;
  471.     ngx_uint_t         i;
  472.     ngx_http_range_t  *range;

  473.     if (ctx->offset) {
  474.         goto overlapped;
  475.     }

  476.     buf = in->buf;

  477.     if (!buf->last_buf) {
  478.         start = ctx->offset;
  479.         last = ctx->offset + ngx_buf_size(buf);

  480.         range = ctx->ranges.elts;
  481.         for (i = 0; i < ctx->ranges.nelts; i++) {
  482.             if (start > range[i].start || last < range[i].end) {
  483.                  goto overlapped;
  484.             }
  485.         }
  486.     }

  487.     ctx->offset = ngx_buf_size(buf);

  488.     return NGX_OK;

  489. overlapped:

  490.     ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
  491.                   "range in overlapped buffers");

  492.     return NGX_ERROR;
  493. }


  494. static ngx_int_t
  495. ngx_http_range_singlepart_body(ngx_http_request_t *r,
  496.     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
  497. {
  498.     off_t              start, last;
  499.     ngx_buf_t         *buf;
  500.     ngx_chain_t       *out, *cl, **ll;
  501.     ngx_http_range_t  *range;

  502.     out = NULL;
  503.     ll = &out;
  504.     range = ctx->ranges.elts;

  505.     for (cl = in; cl; cl = cl->next) {

  506.         buf = cl->buf;

  507.         start = ctx->offset;
  508.         last = ctx->offset + ngx_buf_size(buf);

  509.         ctx->offset = last;

  510.         ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  511.                        "http range body buf: %O-%O", start, last);

  512.         if (ngx_buf_special(buf)) {
  513.             *ll = cl;
  514.             ll = &cl->next;
  515.             continue;
  516.         }

  517.         if (range->end <= start || range->start >= last) {

  518.             ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  519.                            "http range body skip");

  520.             if (buf->in_file) {
  521.                 buf->file_pos = buf->file_last;
  522.             }

  523.             buf->pos = buf->last;
  524.             buf->sync = 1;

  525.             continue;
  526.         }

  527.         if (range->start > start) {

  528.             if (buf->in_file) {
  529.                 buf->file_pos += range->start - start;
  530.             }

  531.             if (ngx_buf_in_memory(buf)) {
  532.                 buf->pos += (size_t) (range->start - start);
  533.             }
  534.         }

  535.         if (range->end <= last) {

  536.             if (buf->in_file) {
  537.                 buf->file_last -= last - range->end;
  538.             }

  539.             if (ngx_buf_in_memory(buf)) {
  540.                 buf->last -= (size_t) (last - range->end);
  541.             }

  542.             buf->last_buf = 1;
  543.             *ll = cl;
  544.             cl->next = NULL;

  545.             break;
  546.         }

  547.         *ll = cl;
  548.         ll = &cl->next;
  549.     }

  550.     if (out == NULL) {
  551.         return NGX_OK;
  552.     }

  553.     return ngx_http_next_body_filter(r, out);
  554. }


  555. static ngx_int_t
  556. ngx_http_range_multipart_body(ngx_http_request_t *r,
  557.     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
  558. {
  559.     ngx_buf_t         *b, *buf;
  560.     ngx_uint_t         i;
  561.     ngx_chain_t       *out, *hcl, *rcl, *dcl, **ll;
  562.     ngx_http_range_t  *range;

  563.     ll = &out;
  564.     buf = in->buf;
  565.     range = ctx->ranges.elts;

  566.     for (i = 0; i < ctx->ranges.nelts; i++) {

  567.         /*
  568.          * The boundary header of the range:
  569.          * CRLF
  570.          * "--0123456789" CRLF
  571.          * "Content-Type: image/jpeg" CRLF
  572.          * "Content-Range: bytes "
  573.          */

  574.         b = ngx_calloc_buf(r->pool);
  575.         if (b == NULL) {
  576.             return NGX_ERROR;
  577.         }

  578.         b->memory = 1;
  579.         b->pos = ctx->boundary_header.data;
  580.         b->last = ctx->boundary_header.data + ctx->boundary_header.len;

  581.         hcl = ngx_alloc_chain_link(r->pool);
  582.         if (hcl == NULL) {
  583.             return NGX_ERROR;
  584.         }

  585.         hcl->buf = b;


  586.         /* "SSSS-EEEE/TTTT" CRLF CRLF */

  587.         b = ngx_calloc_buf(r->pool);
  588.         if (b == NULL) {
  589.             return NGX_ERROR;
  590.         }

  591.         b->temporary = 1;
  592.         b->pos = range[i].content_range.data;
  593.         b->last = range[i].content_range.data + range[i].content_range.len;

  594.         rcl = ngx_alloc_chain_link(r->pool);
  595.         if (rcl == NULL) {
  596.             return NGX_ERROR;
  597.         }

  598.         rcl->buf = b;


  599.         /* the range data */

  600.         b = ngx_calloc_buf(r->pool);
  601.         if (b == NULL) {
  602.             return NGX_ERROR;
  603.         }

  604.         b->in_file = buf->in_file;
  605.         b->temporary = buf->temporary;
  606.         b->memory = buf->memory;
  607.         b->mmap = buf->mmap;
  608.         b->file = buf->file;

  609.         if (buf->in_file) {
  610.             b->file_pos = buf->file_pos + range[i].start;
  611.             b->file_last = buf->file_pos + range[i].end;
  612.         }

  613.         if (ngx_buf_in_memory(buf)) {
  614.             b->pos = buf->pos + (size_t) range[i].start;
  615.             b->last = buf->pos + (size_t) range[i].end;
  616.         }

  617.         dcl = ngx_alloc_chain_link(r->pool);
  618.         if (dcl == NULL) {
  619.             return NGX_ERROR;
  620.         }

  621.         dcl->buf = b;

  622.         *ll = hcl;
  623.         hcl->next = rcl;
  624.         rcl->next = dcl;
  625.         ll = &dcl->next;
  626.     }

  627.     /* the last boundary CRLF "--0123456789--" CRLF  */

  628.     b = ngx_calloc_buf(r->pool);
  629.     if (b == NULL) {
  630.         return NGX_ERROR;
  631.     }

  632.     b->temporary = 1;
  633.     b->last_buf = 1;

  634.     b->pos = ngx_pnalloc(r->pool, sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
  635.                                   + sizeof("--" CRLF) - 1);
  636.     if (b->pos == NULL) {
  637.         return NGX_ERROR;
  638.     }

  639.     b->last = ngx_cpymem(b->pos, ctx->boundary_header.data,
  640.                          sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN);
  641.     *b->last++ = '-'; *b->last++ = '-';
  642.     *b->last++ = CR; *b->last++ = LF;

  643.     hcl = ngx_alloc_chain_link(r->pool);
  644.     if (hcl == NULL) {
  645.         return NGX_ERROR;
  646.     }

  647.     hcl->buf = b;
  648.     hcl->next = NULL;

  649.     *ll = hcl;

  650.     return ngx_http_next_body_filter(r, out);
  651. }


  652. static ngx_int_t
  653. ngx_http_range_header_filter_init(ngx_conf_t *cf)
  654. {
  655.     ngx_http_next_header_filter = ngx_http_top_header_filter;
  656.     ngx_http_top_header_filter = ngx_http_range_header_filter;

  657.     return NGX_OK;
  658. }


  659. static ngx_int_t
  660. ngx_http_range_body_filter_init(ngx_conf_t *cf)
  661. {
  662.     ngx_http_next_body_filter = ngx_http_top_body_filter;
  663.     ngx_http_top_body_filter = ngx_http_range_body_filter;

  664.     return NGX_OK;
  665. }