Back to home page

Nginx displayed by LXR

Source navigation ]
Diff markup ]
Identifier search ]
general search ]
 
 
Version: nginx-1.13.12 ]​[ nginx-1.12.2 ]​

0001 
0002 /*
0003  * Copyright (C) Igor Sysoev
0004  * Copyright (C) Nginx, Inc.
0005  */
0006 
0007 
0008 #include <ngx_config.h>
0009 #include <ngx_core.h>
0010 #include <ngx_http.h>
0011 
0012 
0013 /*
0014  * the single part format:
0015  *
0016  * "HTTP/1.0 206 Partial Content" CRLF
0017  * ... header ...
0018  * "Content-Type: image/jpeg" CRLF
0019  * "Content-Length: SIZE" CRLF
0020  * "Content-Range: bytes START-END/SIZE" CRLF
0021  * CRLF
0022  * ... data ...
0023  *
0024  *
0025  * the multipart format:
0026  *
0027  * "HTTP/1.0 206 Partial Content" CRLF
0028  * ... header ...
0029  * "Content-Type: multipart/byteranges; boundary=0123456789" CRLF
0030  * CRLF
0031  * CRLF
0032  * "--0123456789" CRLF
0033  * "Content-Type: image/jpeg" CRLF
0034  * "Content-Range: bytes START0-END0/SIZE" CRLF
0035  * CRLF
0036  * ... data ...
0037  * CRLF
0038  * "--0123456789" CRLF
0039  * "Content-Type: image/jpeg" CRLF
0040  * "Content-Range: bytes START1-END1/SIZE" CRLF
0041  * CRLF
0042  * ... data ...
0043  * CRLF
0044  * "--0123456789--" CRLF
0045  */
0046 
0047 
0048 typedef struct {
0049     off_t        start;
0050     off_t        end;
0051     ngx_str_t    content_range;
0052 } ngx_http_range_t;
0053 
0054 
0055 typedef struct {
0056     off_t        offset;
0057     ngx_str_t    boundary_header;
0058     ngx_array_t  ranges;
0059 } ngx_http_range_filter_ctx_t;
0060 
0061 
0062 static ngx_int_t ngx_http_range_parse(ngx_http_request_t *r,
0063     ngx_http_range_filter_ctx_t *ctx, ngx_uint_t ranges);
0064 static ngx_int_t ngx_http_range_singlepart_header(ngx_http_request_t *r,
0065     ngx_http_range_filter_ctx_t *ctx);
0066 static ngx_int_t ngx_http_range_multipart_header(ngx_http_request_t *r,
0067     ngx_http_range_filter_ctx_t *ctx);
0068 static ngx_int_t ngx_http_range_not_satisfiable(ngx_http_request_t *r);
0069 static ngx_int_t ngx_http_range_test_overlapped(ngx_http_request_t *r,
0070     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
0071 static ngx_int_t ngx_http_range_singlepart_body(ngx_http_request_t *r,
0072     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
0073 static ngx_int_t ngx_http_range_multipart_body(ngx_http_request_t *r,
0074     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
0075 
0076 static ngx_int_t ngx_http_range_header_filter_init(ngx_conf_t *cf);
0077 static ngx_int_t ngx_http_range_body_filter_init(ngx_conf_t *cf);
0078 
0079 
0080 static ngx_http_module_t  ngx_http_range_header_filter_module_ctx = {
0081     NULL,                                  /* preconfiguration */
0082     ngx_http_range_header_filter_init,     /* postconfiguration */
0083 
0084     NULL,                                  /* create main configuration */
0085     NULL,                                  /* init main configuration */
0086 
0087     NULL,                                  /* create server configuration */
0088     NULL,                                  /* merge server configuration */
0089 
0090     NULL,                                  /* create location configuration */
0091     NULL,                                  /* merge location configuration */
0092 };
0093 
0094 
0095 ngx_module_t  ngx_http_range_header_filter_module = {
0096     NGX_MODULE_V1,
0097     &ngx_http_range_header_filter_module_ctx, /* module context */
0098     NULL,                                  /* module directives */
0099     NGX_HTTP_MODULE,                       /* module type */
0100     NULL,                                  /* init master */
0101     NULL,                                  /* init module */
0102     NULL,                                  /* init process */
0103     NULL,                                  /* init thread */
0104     NULL,                                  /* exit thread */
0105     NULL,                                  /* exit process */
0106     NULL,                                  /* exit master */
0107     NGX_MODULE_V1_PADDING
0108 };
0109 
0110 
0111 static ngx_http_module_t  ngx_http_range_body_filter_module_ctx = {
0112     NULL,                                  /* preconfiguration */
0113     ngx_http_range_body_filter_init,       /* postconfiguration */
0114 
0115     NULL,                                  /* create main configuration */
0116     NULL,                                  /* init main configuration */
0117 
0118     NULL,                                  /* create server configuration */
0119     NULL,                                  /* merge server configuration */
0120 
0121     NULL,                                  /* create location configuration */
0122     NULL,                                  /* merge location configuration */
0123 };
0124 
0125 
0126 ngx_module_t  ngx_http_range_body_filter_module = {
0127     NGX_MODULE_V1,
0128     &ngx_http_range_body_filter_module_ctx, /* module context */
0129     NULL,                                  /* module directives */
0130     NGX_HTTP_MODULE,                       /* module type */
0131     NULL,                                  /* init master */
0132     NULL,                                  /* init module */
0133     NULL,                                  /* init process */
0134     NULL,                                  /* init thread */
0135     NULL,                                  /* exit thread */
0136     NULL,                                  /* exit process */
0137     NULL,                                  /* exit master */
0138     NGX_MODULE_V1_PADDING
0139 };
0140 
0141 
0142 static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
0143 static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
0144 
0145 
0146 static ngx_int_t
0147 ngx_http_range_header_filter(ngx_http_request_t *r)
0148 {
0149     time_t                        if_range_time;
0150     ngx_str_t                    *if_range, *etag;
0151     ngx_uint_t                    ranges;
0152     ngx_http_core_loc_conf_t     *clcf;
0153     ngx_http_range_filter_ctx_t  *ctx;
0154 
0155     if (r->http_version < NGX_HTTP_VERSION_10
0156         || r->headers_out.status != NGX_HTTP_OK
0157         || (r != r->main && !r->subrequest_ranges)
0158         || r->headers_out.content_length_n == -1
0159         || !r->allow_ranges)
0160     {
0161         return ngx_http_next_header_filter(r);
0162     }
0163 
0164     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
0165 
0166     if (clcf->max_ranges == 0) {
0167         return ngx_http_next_header_filter(r);
0168     }
0169 
0170     if (r->headers_in.range == NULL
0171         || r->headers_in.range->value.len < 7
0172         || ngx_strncasecmp(r->headers_in.range->value.data,
0173                            (u_char *) "bytes=", 6)
0174            != 0)
0175     {
0176         goto next_filter;
0177     }
0178 
0179     if (r->headers_in.if_range) {
0180 
0181         if_range = &r->headers_in.if_range->value;
0182 
0183         if (if_range->len >= 2 && if_range->data[if_range->len - 1] == '"') {
0184 
0185             if (r->headers_out.etag == NULL) {
0186                 goto next_filter;
0187             }
0188 
0189             etag = &r->headers_out.etag->value;
0190 
0191             ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0192                            "http ir:%V etag:%V", if_range, etag);
0193 
0194             if (if_range->len != etag->len
0195                 || ngx_strncmp(if_range->data, etag->data, etag->len) != 0)
0196             {
0197                 goto next_filter;
0198             }
0199 
0200             goto parse;
0201         }
0202 
0203         if (r->headers_out.last_modified_time == (time_t) -1) {
0204             goto next_filter;
0205         }
0206 
0207         if_range_time = ngx_parse_http_time(if_range->data, if_range->len);
0208 
0209         ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0210                        "http ir:%T lm:%T",
0211                        if_range_time, r->headers_out.last_modified_time);
0212 
0213         if (if_range_time != r->headers_out.last_modified_time) {
0214             goto next_filter;
0215         }
0216     }
0217 
0218 parse:
0219 
0220     ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_range_filter_ctx_t));
0221     if (ctx == NULL) {
0222         return NGX_ERROR;
0223     }
0224 
0225     ctx->offset = r->headers_out.content_offset;
0226 
0227     ranges = r->single_range ? 1 : clcf->max_ranges;
0228 
0229     switch (ngx_http_range_parse(r, ctx, ranges)) {
0230 
0231     case NGX_OK:
0232         ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
0233 
0234         r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
0235         r->headers_out.status_line.len = 0;
0236 
0237         if (ctx->ranges.nelts == 1) {
0238             return ngx_http_range_singlepart_header(r, ctx);
0239         }
0240 
0241         return ngx_http_range_multipart_header(r, ctx);
0242 
0243     case NGX_HTTP_RANGE_NOT_SATISFIABLE:
0244         return ngx_http_range_not_satisfiable(r);
0245 
0246     case NGX_ERROR:
0247         return NGX_ERROR;
0248 
0249     default: /* NGX_DECLINED */
0250         break;
0251     }
0252 
0253 next_filter:
0254 
0255     r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers);
0256     if (r->headers_out.accept_ranges == NULL) {
0257         return NGX_ERROR;
0258     }
0259 
0260     r->headers_out.accept_ranges->hash = 1;
0261     ngx_str_set(&r->headers_out.accept_ranges->key, "Accept-Ranges");
0262     ngx_str_set(&r->headers_out.accept_ranges->value, "bytes");
0263 
0264     return ngx_http_next_header_filter(r);
0265 }
0266 
0267 
0268 static ngx_int_t
0269 ngx_http_range_parse(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx,
0270     ngx_uint_t ranges)
0271 {
0272     u_char                       *p;
0273     off_t                         start, end, size, content_length, cutoff,
0274                                   cutlim;
0275     ngx_uint_t                    suffix;
0276     ngx_http_range_t             *range;
0277     ngx_http_range_filter_ctx_t  *mctx;
0278 
0279     if (r != r->main) {
0280         mctx = ngx_http_get_module_ctx(r->main,
0281                                        ngx_http_range_body_filter_module);
0282         if (mctx) {
0283             ctx->ranges = mctx->ranges;
0284             return NGX_OK;
0285         }
0286     }
0287 
0288     if (ngx_array_init(&ctx->ranges, r->pool, 1, sizeof(ngx_http_range_t))
0289         != NGX_OK)
0290     {
0291         return NGX_ERROR;
0292     }
0293 
0294     p = r->headers_in.range->value.data + 6;
0295     size = 0;
0296     content_length = r->headers_out.content_length_n;
0297 
0298     cutoff = NGX_MAX_OFF_T_VALUE / 10;
0299     cutlim = NGX_MAX_OFF_T_VALUE % 10;
0300 
0301     for ( ;; ) {
0302         start = 0;
0303         end = 0;
0304         suffix = 0;
0305 
0306         while (*p == ' ') { p++; }
0307 
0308         if (*p != '-') {
0309             if (*p < '0' || *p > '9') {
0310                 return NGX_HTTP_RANGE_NOT_SATISFIABLE;
0311             }
0312 
0313             while (*p >= '0' && *p <= '9') {
0314                 if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) {
0315                     return NGX_HTTP_RANGE_NOT_SATISFIABLE;
0316                 }
0317 
0318                 start = start * 10 + *p++ - '0';
0319             }
0320 
0321             while (*p == ' ') { p++; }
0322 
0323             if (*p++ != '-') {
0324                 return NGX_HTTP_RANGE_NOT_SATISFIABLE;
0325             }
0326 
0327             while (*p == ' ') { p++; }
0328 
0329             if (*p == ',' || *p == '\0') {
0330                 end = content_length;
0331                 goto found;
0332             }
0333 
0334         } else {
0335             suffix = 1;
0336             p++;
0337         }
0338 
0339         if (*p < '0' || *p > '9') {
0340             return NGX_HTTP_RANGE_NOT_SATISFIABLE;
0341         }
0342 
0343         while (*p >= '0' && *p <= '9') {
0344             if (end >= cutoff && (end > cutoff || *p - '0' > cutlim)) {
0345                 return NGX_HTTP_RANGE_NOT_SATISFIABLE;
0346             }
0347 
0348             end = end * 10 + *p++ - '0';
0349         }
0350 
0351         while (*p == ' ') { p++; }
0352 
0353         if (*p != ',' && *p != '\0') {
0354             return NGX_HTTP_RANGE_NOT_SATISFIABLE;
0355         }
0356 
0357         if (suffix) {
0358             start = content_length - end;
0359             end = content_length - 1;
0360         }
0361 
0362         if (end >= content_length) {
0363             end = content_length;
0364 
0365         } else {
0366             end++;
0367         }
0368 
0369     found:
0370 
0371         if (start < end) {
0372             range = ngx_array_push(&ctx->ranges);
0373             if (range == NULL) {
0374                 return NGX_ERROR;
0375             }
0376 
0377             range->start = start;
0378             range->end = end;
0379 
0380             if (size > NGX_MAX_OFF_T_VALUE - (end - start)) {
0381                 return NGX_HTTP_RANGE_NOT_SATISFIABLE;
0382             }
0383 
0384             size += end - start;
0385 
0386             if (ranges-- == 0) {
0387                 return NGX_DECLINED;
0388             }
0389         }
0390 
0391         if (*p++ != ',') {
0392             break;
0393         }
0394     }
0395 
0396     if (ctx->ranges.nelts == 0) {
0397         return NGX_HTTP_RANGE_NOT_SATISFIABLE;
0398     }
0399 
0400     if (size > content_length) {
0401         return NGX_DECLINED;
0402     }
0403 
0404     return NGX_OK;
0405 }
0406 
0407 
0408 static ngx_int_t
0409 ngx_http_range_singlepart_header(ngx_http_request_t *r,
0410     ngx_http_range_filter_ctx_t *ctx)
0411 {
0412     ngx_table_elt_t   *content_range;
0413     ngx_http_range_t  *range;
0414 
0415     if (r != r->main) {
0416         return ngx_http_next_header_filter(r);
0417     }
0418 
0419     content_range = ngx_list_push(&r->headers_out.headers);
0420     if (content_range == NULL) {
0421         return NGX_ERROR;
0422     }
0423 
0424     r->headers_out.content_range = content_range;
0425 
0426     content_range->hash = 1;
0427     ngx_str_set(&content_range->key, "Content-Range");
0428 
0429     content_range->value.data = ngx_pnalloc(r->pool,
0430                                     sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN);
0431     if (content_range->value.data == NULL) {
0432         return NGX_ERROR;
0433     }
0434 
0435     /* "Content-Range: bytes SSSS-EEEE/TTTT" header */
0436 
0437     range = ctx->ranges.elts;
0438 
0439     content_range->value.len = ngx_sprintf(content_range->value.data,
0440                                            "bytes %O-%O/%O",
0441                                            range->start, range->end - 1,
0442                                            r->headers_out.content_length_n)
0443                                - content_range->value.data;
0444 
0445     r->headers_out.content_length_n = range->end - range->start;
0446     r->headers_out.content_offset = range->start;
0447 
0448     if (r->headers_out.content_length) {
0449         r->headers_out.content_length->hash = 0;
0450         r->headers_out.content_length = NULL;
0451     }
0452 
0453     return ngx_http_next_header_filter(r);
0454 }
0455 
0456 
0457 static ngx_int_t
0458 ngx_http_range_multipart_header(ngx_http_request_t *r,
0459     ngx_http_range_filter_ctx_t *ctx)
0460 {
0461     off_t               len;
0462     size_t              size;
0463     ngx_uint_t          i;
0464     ngx_http_range_t   *range;
0465     ngx_atomic_uint_t   boundary;
0466 
0467     size = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
0468            + sizeof(CRLF "Content-Type: ") - 1
0469            + r->headers_out.content_type.len
0470            + sizeof(CRLF "Content-Range: bytes ") - 1;
0471 
0472     if (r->headers_out.content_type_len == r->headers_out.content_type.len
0473         && r->headers_out.charset.len)
0474     {
0475         size += sizeof("; charset=") - 1 + r->headers_out.charset.len;
0476     }
0477 
0478     ctx->boundary_header.data = ngx_pnalloc(r->pool, size);
0479     if (ctx->boundary_header.data == NULL) {
0480         return NGX_ERROR;
0481     }
0482 
0483     boundary = ngx_next_temp_number(0);
0484 
0485     /*
0486      * The boundary header of the range:
0487      * CRLF
0488      * "--0123456789" CRLF
0489      * "Content-Type: image/jpeg" CRLF
0490      * "Content-Range: bytes "
0491      */
0492 
0493     if (r->headers_out.content_type_len == r->headers_out.content_type.len
0494         && r->headers_out.charset.len)
0495     {
0496         ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
0497                                            CRLF "--%0muA" CRLF
0498                                            "Content-Type: %V; charset=%V" CRLF
0499                                            "Content-Range: bytes ",
0500                                            boundary,
0501                                            &r->headers_out.content_type,
0502                                            &r->headers_out.charset)
0503                                    - ctx->boundary_header.data;
0504 
0505     } else if (r->headers_out.content_type.len) {
0506         ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
0507                                            CRLF "--%0muA" CRLF
0508                                            "Content-Type: %V" CRLF
0509                                            "Content-Range: bytes ",
0510                                            boundary,
0511                                            &r->headers_out.content_type)
0512                                    - ctx->boundary_header.data;
0513 
0514     } else {
0515         ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
0516                                            CRLF "--%0muA" CRLF
0517                                            "Content-Range: bytes ",
0518                                            boundary)
0519                                    - ctx->boundary_header.data;
0520     }
0521 
0522     r->headers_out.content_type.data =
0523         ngx_pnalloc(r->pool,
0524                     sizeof("Content-Type: multipart/byteranges; boundary=") - 1
0525                     + NGX_ATOMIC_T_LEN);
0526 
0527     if (r->headers_out.content_type.data == NULL) {
0528         return NGX_ERROR;
0529     }
0530 
0531     r->headers_out.content_type_lowcase = NULL;
0532 
0533     /* "Content-Type: multipart/byteranges; boundary=0123456789" */
0534 
0535     r->headers_out.content_type.len =
0536                            ngx_sprintf(r->headers_out.content_type.data,
0537                                        "multipart/byteranges; boundary=%0muA",
0538                                        boundary)
0539                            - r->headers_out.content_type.data;
0540 
0541     r->headers_out.content_type_len = r->headers_out.content_type.len;
0542 
0543     r->headers_out.charset.len = 0;
0544 
0545     /* the size of the last boundary CRLF "--0123456789--" CRLF */
0546 
0547     len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1;
0548 
0549     range = ctx->ranges.elts;
0550     for (i = 0; i < ctx->ranges.nelts; i++) {
0551 
0552         /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */
0553 
0554         range[i].content_range.data =
0555                                ngx_pnalloc(r->pool, 3 * NGX_OFF_T_LEN + 2 + 4);
0556 
0557         if (range[i].content_range.data == NULL) {
0558             return NGX_ERROR;
0559         }
0560 
0561         range[i].content_range.len = ngx_sprintf(range[i].content_range.data,
0562                                                "%O-%O/%O" CRLF CRLF,
0563                                                range[i].start, range[i].end - 1,
0564                                                r->headers_out.content_length_n)
0565                                      - range[i].content_range.data;
0566 
0567         len += ctx->boundary_header.len + range[i].content_range.len
0568                                              + (range[i].end - range[i].start);
0569     }
0570 
0571     r->headers_out.content_length_n = len;
0572 
0573     if (r->headers_out.content_length) {
0574         r->headers_out.content_length->hash = 0;
0575         r->headers_out.content_length = NULL;
0576     }
0577 
0578     return ngx_http_next_header_filter(r);
0579 }
0580 
0581 
0582 static ngx_int_t
0583 ngx_http_range_not_satisfiable(ngx_http_request_t *r)
0584 {
0585     ngx_table_elt_t  *content_range;
0586 
0587     r->headers_out.status = NGX_HTTP_RANGE_NOT_SATISFIABLE;
0588 
0589     content_range = ngx_list_push(&r->headers_out.headers);
0590     if (content_range == NULL) {
0591         return NGX_ERROR;
0592     }
0593 
0594     r->headers_out.content_range = content_range;
0595 
0596     content_range->hash = 1;
0597     ngx_str_set(&content_range->key, "Content-Range");
0598 
0599     content_range->value.data = ngx_pnalloc(r->pool,
0600                                        sizeof("bytes */") - 1 + NGX_OFF_T_LEN);
0601     if (content_range->value.data == NULL) {
0602         return NGX_ERROR;
0603     }
0604 
0605     content_range->value.len = ngx_sprintf(content_range->value.data,
0606                                            "bytes */%O",
0607                                            r->headers_out.content_length_n)
0608                                - content_range->value.data;
0609 
0610     ngx_http_clear_content_length(r);
0611 
0612     return NGX_HTTP_RANGE_NOT_SATISFIABLE;
0613 }
0614 
0615 
0616 static ngx_int_t
0617 ngx_http_range_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
0618 {
0619     ngx_http_range_filter_ctx_t  *ctx;
0620 
0621     if (in == NULL) {
0622         return ngx_http_next_body_filter(r, in);
0623     }
0624 
0625     ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module);
0626 
0627     if (ctx == NULL) {
0628         return ngx_http_next_body_filter(r, in);
0629     }
0630 
0631     if (ctx->ranges.nelts == 1) {
0632         return ngx_http_range_singlepart_body(r, ctx, in);
0633     }
0634 
0635     /*
0636      * multipart ranges are supported only if whole body is in a single buffer
0637      */
0638 
0639     if (ngx_buf_special(in->buf)) {
0640         return ngx_http_next_body_filter(r, in);
0641     }
0642 
0643     if (ngx_http_range_test_overlapped(r, ctx, in) != NGX_OK) {
0644         return NGX_ERROR;
0645     }
0646 
0647     return ngx_http_range_multipart_body(r, ctx, in);
0648 }
0649 
0650 
0651 static ngx_int_t
0652 ngx_http_range_test_overlapped(ngx_http_request_t *r,
0653     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
0654 {
0655     off_t              start, last;
0656     ngx_buf_t         *buf;
0657     ngx_uint_t         i;
0658     ngx_http_range_t  *range;
0659 
0660     if (ctx->offset) {
0661         goto overlapped;
0662     }
0663 
0664     buf = in->buf;
0665 
0666     if (!buf->last_buf) {
0667         start = ctx->offset;
0668         last = ctx->offset + ngx_buf_size(buf);
0669 
0670         range = ctx->ranges.elts;
0671         for (i = 0; i < ctx->ranges.nelts; i++) {
0672             if (start > range[i].start || last < range[i].end) {
0673                 goto overlapped;
0674             }
0675         }
0676     }
0677 
0678     ctx->offset = ngx_buf_size(buf);
0679 
0680     return NGX_OK;
0681 
0682 overlapped:
0683 
0684     ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
0685                   "range in overlapped buffers");
0686 
0687     return NGX_ERROR;
0688 }
0689 
0690 
0691 static ngx_int_t
0692 ngx_http_range_singlepart_body(ngx_http_request_t *r,
0693     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
0694 {
0695     off_t              start, last;
0696     ngx_buf_t         *buf;
0697     ngx_chain_t       *out, *cl, **ll;
0698     ngx_http_range_t  *range;
0699 
0700     out = NULL;
0701     ll = &out;
0702     range = ctx->ranges.elts;
0703 
0704     for (cl = in; cl; cl = cl->next) {
0705 
0706         buf = cl->buf;
0707 
0708         start = ctx->offset;
0709         last = ctx->offset + ngx_buf_size(buf);
0710 
0711         ctx->offset = last;
0712 
0713         ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0714                        "http range body buf: %O-%O", start, last);
0715 
0716         if (ngx_buf_special(buf)) {
0717             *ll = cl;
0718             ll = &cl->next;
0719             continue;
0720         }
0721 
0722         if (range->end <= start || range->start >= last) {
0723 
0724             ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0725                            "http range body skip");
0726 
0727             if (buf->in_file) {
0728                 buf->file_pos = buf->file_last;
0729             }
0730 
0731             buf->pos = buf->last;
0732             buf->sync = 1;
0733 
0734             continue;
0735         }
0736 
0737         if (range->start > start) {
0738 
0739             if (buf->in_file) {
0740                 buf->file_pos += range->start - start;
0741             }
0742 
0743             if (ngx_buf_in_memory(buf)) {
0744                 buf->pos += (size_t) (range->start - start);
0745             }
0746         }
0747 
0748         if (range->end <= last) {
0749 
0750             if (buf->in_file) {
0751                 buf->file_last -= last - range->end;
0752             }
0753 
0754             if (ngx_buf_in_memory(buf)) {
0755                 buf->last -= (size_t) (last - range->end);
0756             }
0757 
0758             buf->last_buf = (r == r->main) ? 1 : 0;
0759             buf->last_in_chain = 1;
0760             *ll = cl;
0761             cl->next = NULL;
0762 
0763             break;
0764         }
0765 
0766         *ll = cl;
0767         ll = &cl->next;
0768     }
0769 
0770     if (out == NULL) {
0771         return NGX_OK;
0772     }
0773 
0774     return ngx_http_next_body_filter(r, out);
0775 }
0776 
0777 
0778 static ngx_int_t
0779 ngx_http_range_multipart_body(ngx_http_request_t *r,
0780     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
0781 {
0782     ngx_buf_t         *b, *buf;
0783     ngx_uint_t         i;
0784     ngx_chain_t       *out, *hcl, *rcl, *dcl, **ll;
0785     ngx_http_range_t  *range;
0786 
0787     ll = &out;
0788     buf = in->buf;
0789     range = ctx->ranges.elts;
0790 
0791     for (i = 0; i < ctx->ranges.nelts; i++) {
0792 
0793         /*
0794          * The boundary header of the range:
0795          * CRLF
0796          * "--0123456789" CRLF
0797          * "Content-Type: image/jpeg" CRLF
0798          * "Content-Range: bytes "
0799          */
0800 
0801         b = ngx_calloc_buf(r->pool);
0802         if (b == NULL) {
0803             return NGX_ERROR;
0804         }
0805 
0806         b->memory = 1;
0807         b->pos = ctx->boundary_header.data;
0808         b->last = ctx->boundary_header.data + ctx->boundary_header.len;
0809 
0810         hcl = ngx_alloc_chain_link(r->pool);
0811         if (hcl == NULL) {
0812             return NGX_ERROR;
0813         }
0814 
0815         hcl->buf = b;
0816 
0817 
0818         /* "SSSS-EEEE/TTTT" CRLF CRLF */
0819 
0820         b = ngx_calloc_buf(r->pool);
0821         if (b == NULL) {
0822             return NGX_ERROR;
0823         }
0824 
0825         b->temporary = 1;
0826         b->pos = range[i].content_range.data;
0827         b->last = range[i].content_range.data + range[i].content_range.len;
0828 
0829         rcl = ngx_alloc_chain_link(r->pool);
0830         if (rcl == NULL) {
0831             return NGX_ERROR;
0832         }
0833 
0834         rcl->buf = b;
0835 
0836 
0837         /* the range data */
0838 
0839         b = ngx_calloc_buf(r->pool);
0840         if (b == NULL) {
0841             return NGX_ERROR;
0842         }
0843 
0844         b->in_file = buf->in_file;
0845         b->temporary = buf->temporary;
0846         b->memory = buf->memory;
0847         b->mmap = buf->mmap;
0848         b->file = buf->file;
0849 
0850         if (buf->in_file) {
0851             b->file_pos = buf->file_pos + range[i].start;
0852             b->file_last = buf->file_pos + range[i].end;
0853         }
0854 
0855         if (ngx_buf_in_memory(buf)) {
0856             b->pos = buf->pos + (size_t) range[i].start;
0857             b->last = buf->pos + (size_t) range[i].end;
0858         }
0859 
0860         dcl = ngx_alloc_chain_link(r->pool);
0861         if (dcl == NULL) {
0862             return NGX_ERROR;
0863         }
0864 
0865         dcl->buf = b;
0866 
0867         *ll = hcl;
0868         hcl->next = rcl;
0869         rcl->next = dcl;
0870         ll = &dcl->next;
0871     }
0872 
0873     /* the last boundary CRLF "--0123456789--" CRLF  */
0874 
0875     b = ngx_calloc_buf(r->pool);
0876     if (b == NULL) {
0877         return NGX_ERROR;
0878     }
0879 
0880     b->temporary = 1;
0881     b->last_buf = 1;
0882 
0883     b->pos = ngx_pnalloc(r->pool, sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
0884                                   + sizeof("--" CRLF) - 1);
0885     if (b->pos == NULL) {
0886         return NGX_ERROR;
0887     }
0888 
0889     b->last = ngx_cpymem(b->pos, ctx->boundary_header.data,
0890                          sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN);
0891     *b->last++ = '-'; *b->last++ = '-';
0892     *b->last++ = CR; *b->last++ = LF;
0893 
0894     hcl = ngx_alloc_chain_link(r->pool);
0895     if (hcl == NULL) {
0896         return NGX_ERROR;
0897     }
0898 
0899     hcl->buf = b;
0900     hcl->next = NULL;
0901 
0902     *ll = hcl;
0903 
0904     return ngx_http_next_body_filter(r, out);
0905 }
0906 
0907 
0908 static ngx_int_t
0909 ngx_http_range_header_filter_init(ngx_conf_t *cf)
0910 {
0911     ngx_http_next_header_filter = ngx_http_top_header_filter;
0912     ngx_http_top_header_filter = ngx_http_range_header_filter;
0913 
0914     return NGX_OK;
0915 }
0916 
0917 
0918 static ngx_int_t
0919 ngx_http_range_body_filter_init(ngx_conf_t *cf)
0920 {
0921     ngx_http_next_body_filter = ngx_http_top_body_filter;
0922     ngx_http_top_body_filter = ngx_http_range_body_filter;
0923 
0924     return NGX_OK;
0925 }