diff src/http/modules/ngx_http_range_filter_module.c @ 396:77df96611112

Merge with current.
author Maxim Dounin <mdounin@mdounin.ru>
date Tue, 05 Aug 2008 02:12:51 +0400
parents 1d9bef53cd8e 0b6053502c55
children 44a61c599bb2
line wrap: on
line diff
--- a/src/http/modules/ngx_http_range_filter_module.c
+++ b/src/http/modules/ngx_http_range_filter_module.c
@@ -58,6 +58,22 @@ typedef struct {
 } ngx_http_range_filter_ctx_t;
 
 
+ngx_int_t ngx_http_range_parse(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx);
+static ngx_int_t ngx_http_range_singlepart_header(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx);
+static ngx_int_t ngx_http_range_multipart_header(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx);
+static ngx_int_t ngx_http_range_not_satisfiable(ngx_http_request_t *r);
+static ngx_int_t ngx_http_range_test_overlapped(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
+static ngx_int_t ngx_http_range_singlepart_body(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in,
+    ngx_http_output_body_filter_pt ngx_http_next_body_filter);
+static ngx_int_t ngx_http_range_multipart_body(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in,
+    ngx_http_output_body_filter_pt ngx_http_next_body_filter);
+
 static ngx_int_t ngx_http_range_header_filter_init(ngx_conf_t *cf);
 static ngx_int_t ngx_http_range_body_filter_init(ngx_conf_t *cf);
 static ngx_int_t ngx_http_range_late_filter_init(ngx_conf_t *cf);
@@ -164,15 +180,8 @@ static ngx_http_output_body_filter_pt   
 static ngx_int_t
 ngx_http_range_header_filter(ngx_http_request_t *r)
 {
-    u_char                       *p;
-    size_t                        len;
-    off_t                         start, end;
     time_t                        if_range;
     ngx_int_t                     rc;
-    ngx_uint_t                    suffix, i;
-    ngx_atomic_uint_t             boundary;
-    ngx_table_elt_t              *content_range;
-    ngx_http_range_t             *range;
     ngx_http_range_filter_ctx_t  *ctx;
 
     if (r->http_version < NGX_HTTP_VERSION_10
@@ -218,8 +227,54 @@ ngx_http_range_header_filter(ngx_http_re
         return NGX_ERROR;
     }
 
-    rc = 0;
-    range = NULL;
+    rc = ngx_http_range_parse(r, ctx);
+
+    if (rc == NGX_OK) {
+
+        ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
+
+        r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
+
+        if (ctx->ranges.nelts == 1) {
+            return ngx_http_range_singlepart_header(r, ctx);
+        }
+
+        return ngx_http_range_multipart_header(r, ctx);
+    }
+
+    if (rc == NGX_HTTP_RANGE_NOT_SATISFIABLE) {
+        return ngx_http_range_not_satisfiable(r);
+    }
+
+    /* rc == NGX_ERROR */
+
+    return rc;
+
+next_filter:
+
+    r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers);
+    if (r->headers_out.accept_ranges == NULL) {
+        return NGX_ERROR;
+    }
+
+    r->headers_out.accept_ranges->hash = 1;
+    r->headers_out.accept_ranges->key.len = sizeof("Accept-Ranges") - 1;
+    r->headers_out.accept_ranges->key.data = (u_char *) "Accept-Ranges";
+    r->headers_out.accept_ranges->value.len = sizeof("bytes") - 1;
+    r->headers_out.accept_ranges->value.data = (u_char *) "bytes";
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+ngx_int_t
+ngx_http_range_parse(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx)
+{
+    u_char            *p;
+    off_t              start, end;
+    ngx_uint_t         suffix;
+    ngx_http_range_t  *range;
+
     p = r->headers_in.range->value.data + 6;
 
     for ( ;; ) {
@@ -231,8 +286,7 @@ ngx_http_range_header_filter(ngx_http_re
 
         if (*p != '-') {
             if (*p < '0' || *p > '9') {
-                rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
-                break;
+                return NGX_HTTP_RANGE_NOT_SATISFIABLE;
             }
 
             while (*p >= '0' && *p <= '9') {
@@ -242,13 +296,11 @@ ngx_http_range_header_filter(ngx_http_re
             while (*p == ' ') { p++; }
 
             if (*p++ != '-') {
-                rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
-                break;
+                return NGX_HTTP_RANGE_NOT_SATISFIABLE;
             }
 
             if (start >= r->headers_out.content_length_n) {
-                rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
-                break;
+                return NGX_HTTP_RANGE_NOT_SATISFIABLE;
             }
 
             while (*p == ' ') { p++; }
@@ -263,7 +315,7 @@ ngx_http_range_header_filter(ngx_http_re
                 range->end = r->headers_out.content_length_n;
 
                 if (*p++ != ',') {
-                    break;
+                    return NGX_OK;
                 }
 
                 continue;
@@ -275,8 +327,7 @@ ngx_http_range_header_filter(ngx_http_re
         }
 
         if (*p < '0' || *p > '9') {
-            rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
-            break;
+            return NGX_HTTP_RANGE_NOT_SATISFIABLE;
         }
 
         while (*p >= '0' && *p <= '9') {
@@ -286,8 +337,7 @@ ngx_http_range_header_filter(ngx_http_re
         while (*p == ' ') { p++; }
 
         if (*p != ',' && *p != '\0') {
-            rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
-            break;
+            return NGX_HTTP_RANGE_NOT_SATISFIABLE;
         }
 
         if (suffix) {
@@ -296,8 +346,7 @@ ngx_http_range_header_filter(ngx_http_re
         }
 
         if (start > end) {
-            rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
-            break;
+            return NGX_HTTP_RANGE_NOT_SATISFIABLE;
         }
 
         range = ngx_array_push(&ctx->ranges);
@@ -319,86 +368,65 @@ ngx_http_range_header_filter(ngx_http_re
         }
 
         if (*p++ != ',') {
-            break;
+            return NGX_OK;
         }
     }
+}
+
+
+static ngx_int_t
+ngx_http_range_singlepart_header(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx)
+{
+    ngx_table_elt_t   *content_range;
+    ngx_http_range_t  *range;
+
+    content_range = ngx_list_push(&r->headers_out.headers);
+    if (content_range == NULL) {
+        return NGX_ERROR;
+    }
 
-    if (rc) {
-
-        /* rc == NGX_HTTP_RANGE_NOT_SATISFIABLE */
-
-        r->headers_out.status = rc;
-
-        content_range = ngx_list_push(&r->headers_out.headers);
-        if (content_range == NULL) {
-            return NGX_ERROR;
-        }
-
-        r->headers_out.content_range = content_range;
+    r->headers_out.content_range = content_range;
 
-        content_range->hash = 1;
-        content_range->key.len = sizeof("Content-Range") - 1;
-        content_range->key.data = (u_char *) "Content-Range";
+    content_range->hash = 1;
+    content_range->key.len = sizeof("Content-Range") - 1;
+    content_range->key.data = (u_char *) "Content-Range";
 
-        content_range->value.data = ngx_pnalloc(r->pool,
-                                       sizeof("bytes */") - 1 + NGX_OFF_T_LEN);
-        if (content_range->value.data == NULL) {
-            return NGX_ERROR;
-        }
-
-        content_range->value.len = ngx_sprintf(content_range->value.data,
-                                               "bytes */%O",
-                                               r->headers_out.content_length_n)
-                                   - content_range->value.data;
-
-        ngx_http_clear_content_length(r);
-
-        return rc;
+    content_range->value.data = ngx_pnalloc(r->pool,
+                                    sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN);
+    if (content_range->value.data == NULL) {
+        return NGX_ERROR;
     }
 
-    ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
-
-    r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
-
-    if (ctx->ranges.nelts == 1) {
+    /* "Content-Range: bytes SSSS-EEEE/TTTT" header */
 
-        content_range = ngx_list_push(&r->headers_out.headers);
-        if (content_range == NULL) {
-            return NGX_ERROR;
-        }
-
-        r->headers_out.content_range = content_range;
-
-        content_range->hash = 1;
-        content_range->key.len = sizeof("Content-Range") - 1;
-        content_range->key.data = (u_char *) "Content-Range";
+    range = ctx->ranges.elts;
 
-        content_range->value.data =
-              ngx_pnalloc(r->pool, sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN);
-        if (content_range->value.data == NULL) {
-            return NGX_ERROR;
-        }
-
-        /* "Content-Range: bytes SSSS-EEEE/TTTT" header */
+    content_range->value.len = ngx_sprintf(content_range->value.data,
+                                           "bytes %O-%O/%O",
+                                           range->start, range->end - 1,
+                                           r->headers_out.content_length_n)
+                               - content_range->value.data;
 
-        content_range->value.len = ngx_sprintf(content_range->value.data,
-                                               "bytes %O-%O/%O",
-                                               range->start, range->end - 1,
-                                               r->headers_out.content_length_n)
-                                   - content_range->value.data;
+    r->headers_out.content_length_n = range->end - range->start;
 
-        r->headers_out.content_length_n = range->end - range->start;
-
-        if (r->headers_out.content_length) {
-            r->headers_out.content_length->hash = 0;
-            r->headers_out.content_length = NULL;
-        }
-
-        return ngx_http_next_header_filter(r);
+    if (r->headers_out.content_length) {
+        r->headers_out.content_length->hash = 0;
+        r->headers_out.content_length = NULL;
     }
 
+    return ngx_http_next_header_filter(r);
+}
 
-    /* TODO: what if no content_type ?? */
+
+static ngx_int_t
+ngx_http_range_multipart_header(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx)
+{
+    size_t              len;
+    ngx_uint_t          i;
+    ngx_http_range_t   *range;
+    ngx_atomic_uint_t   boundary;
 
     len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
           + sizeof(CRLF "Content-Type: ") - 1
@@ -436,7 +464,7 @@ ngx_http_range_header_filter(ngx_http_re
 
         r->headers_out.charset.len = 0;
 
-    } else {
+    } else if (r->headers_out.content_type.len) {
         ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
                                            CRLF "--%0muA" CRLF
                                            "Content-Type: %V" CRLF
@@ -444,6 +472,13 @@ ngx_http_range_header_filter(ngx_http_re
                                            boundary,
                                            &r->headers_out.content_type)
                                    - ctx->boundary_header.data;
+
+    } else {
+        ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
+                                           CRLF "--%0muA" CRLF
+                                           "Content-Range: bytes ",
+                                           boundary)
+                                   - ctx->boundary_header.data;
     }
 
     r->headers_out.content_type.data =
@@ -498,21 +533,41 @@ ngx_http_range_header_filter(ngx_http_re
     }
 
     return ngx_http_next_header_filter(r);
+}
 
-next_filter:
 
-    r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers);
-    if (r->headers_out.accept_ranges == NULL) {
+static ngx_int_t
+ngx_http_range_not_satisfiable(ngx_http_request_t *r)
+{
+    ngx_table_elt_t  *content_range;
+
+    r->headers_out.status = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+
+    content_range = ngx_list_push(&r->headers_out.headers);
+    if (content_range == NULL) {
         return NGX_ERROR;
     }
 
-    r->headers_out.accept_ranges->hash = 1;
-    r->headers_out.accept_ranges->key.len = sizeof("Accept-Ranges") - 1;
-    r->headers_out.accept_ranges->key.data = (u_char *) "Accept-Ranges";
-    r->headers_out.accept_ranges->value.len = sizeof("bytes") - 1;
-    r->headers_out.accept_ranges->value.data = (u_char *) "bytes";
+    r->headers_out.content_range = content_range;
+
+    content_range->hash = 1;
+    content_range->key.len = sizeof("Content-Range") - 1;
+    content_range->key.data = (u_char *) "Content-Range";
 
-    return ngx_http_next_header_filter(r);
+    content_range->value.data = ngx_pnalloc(r->pool,
+                                       sizeof("bytes */") - 1 + NGX_OFF_T_LEN);
+    if (content_range->value.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    content_range->value.len = ngx_sprintf(content_range->value.data,
+                                           "bytes */%O",
+                                           r->headers_out.content_length_n)
+                               - content_range->value.data;
+
+    ngx_http_clear_content_length(r);
+
+    return NGX_HTTP_RANGE_NOT_SATISFIABLE;
 }
 
 
@@ -520,11 +575,6 @@ static ngx_int_t
 ngx_http_range_body_generic_filter(ngx_http_request_t *r, ngx_chain_t *in,
     ngx_http_output_body_filter_pt ngx_http_next_body_filter)
 {
-    off_t                         start, last;
-    ngx_buf_t                    *b, *buf;
-    ngx_uint_t                    i;
-    ngx_chain_t                  *out, *cl, *hcl, *rcl, *dcl, **ll;
-    ngx_http_range_t             *range;
     ngx_http_range_filter_ctx_t  *ctx;
 
     if (in == NULL) {
@@ -537,25 +587,88 @@ ngx_http_range_body_generic_filter(ngx_h
         return ngx_http_next_body_filter(r, in);
     }
 
-    buf = in->buf;
+    if (ctx->ranges.nelts == 1) {
+        return ngx_http_range_singlepart_body(r, ctx, in,
+                                              ngx_http_next_body_filter);
+    }
+
+    /*
+     * multipart ranges are supported only if whole body is in a single buffer
+     */
 
     if (ngx_buf_special(in->buf)) {
         return ngx_http_next_body_filter(r, in);
     }
 
-    range = ctx->ranges.elts;
+    if (ngx_http_range_test_overlapped(r, ctx, in) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return ngx_http_range_multipart_body(r, ctx, in,
+                                         ngx_http_next_body_filter);
+}
+
 
-    if (ctx->ranges.nelts > 1) {
-        goto multipart;
+static ngx_int_t
+ngx_http_range_test_overlapped(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
+{
+    off_t              start, last;
+    ngx_buf_t         *buf;
+    ngx_uint_t         i;
+    ngx_http_range_t  *range;
+
+    if (ctx->offset) {
+        goto overlapped;
     }
 
-    /*
-     * the optimized version for the responses
-     * that are passed in the single buffer
-     */
+    buf = in->buf;
+
+    if (!buf->last_buf) {
+
+        if (buf->in_file) {
+            start = buf->file_pos + ctx->offset;
+            last = buf->file_last + ctx->offset;
+
+        } else {
+            start = buf->pos - buf->start + ctx->offset;
+            last = buf->last - buf->start + ctx->offset;
+        }
+
+        range = ctx->ranges.elts;
+        for (i = 0; i < ctx->ranges.nelts; i++) {
+            if (start > range[i].start || last < range[i].end) {
+                 goto overlapped;
+            }
+        }
+    }
+
+    ctx->offset = ngx_buf_size(buf);
+
+    return NGX_OK;
+
+overlapped:
+
+    ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                  "range in overlapped buffers");
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_range_singlepart_body(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in,
+    ngx_http_output_body_filter_pt ngx_http_next_body_filter)
+{
+    off_t              start, last;
+    ngx_buf_t         *buf;
+    ngx_chain_t       *out, *cl, **ll;
+    ngx_http_range_t  *range;
 
     out = NULL;
     ll = &out;
+    range = ctx->ranges.elts;
 
     for (cl = in; cl; cl = cl->next) {
 
@@ -567,44 +680,44 @@ ngx_http_range_body_generic_filter(ngx_h
         ctx->offset = last;
 
         ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                       "range body filter: %O-%O", start, last);
+                       "http range body buf: %O-%O", start, last);
 
         if (ngx_buf_special(buf)) {
-            /* pass anyway */
-            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                           "range body filter: pass special");
             *ll = cl;
             ll = &cl->next;
             continue;
         }
 
         if (range->end <= start || range->start >= last) {
-            /* skip buffer */
+
             ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                           "range body filter: skip");
+                           "http range body skip");
+
             buf->pos = buf->last;
             continue;
         }
 
         if (range->start > start) {
+
             if (buf->in_file) {
                 buf->file_pos += range->start - start;
             }
+
             if (ngx_buf_in_memory(buf)) {
                 buf->pos += (size_t) (range->start - start);
             }
         }
 
         if (range->end <= last) {
+
             if (buf->in_file) {
                 buf->file_last -= last - range->end;
             }
+
             if (ngx_buf_in_memory(buf)) {
                 buf->last -= (size_t) (last - range->end);
             }
 
-            /* we are done */
-
             buf->last_buf = 1;
             *ll = cl;
             cl->next = NULL;
@@ -621,34 +734,22 @@ ngx_http_range_body_generic_filter(ngx_h
     }
 
     return ngx_http_next_body_filter(r, out);
-
-multipart:
+}
 
-    if (ctx->offset) {
-        goto overlapped;
-    }
-
-    if (!buf->last_buf) {
-
-        if (buf->in_file) {
-            start = buf->file_pos + ctx->offset;
-            last = buf->file_last + ctx->offset;
 
-        } else {
-            start = buf->pos - buf->start + ctx->offset;
-            last = buf->last - buf->start + ctx->offset;
-        }
-
-        for (i = 0; i < ctx->ranges.nelts; i++) {
-            if (start > range[i].start || last < range[i].end) {
-                 goto overlapped;
-            }
-        }
-    }
-
-    ctx->offset = ngx_buf_size(buf);
+static ngx_int_t
+ngx_http_range_multipart_body(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in,
+    ngx_http_output_body_filter_pt ngx_http_next_body_filter)
+{
+    ngx_buf_t         *b, *buf;
+    ngx_uint_t         i;
+    ngx_chain_t       *out, *hcl, *rcl, *dcl, **ll;
+    ngx_http_range_t  *range;
 
     ll = &out;
+    buf = in->buf;
+    range = ctx->ranges.elts;
 
     for (i = 0; i < ctx->ranges.nelts; i++) {
 
@@ -764,13 +865,6 @@ multipart:
     *ll = hcl;
 
     return ngx_http_next_body_filter(r, out);
-
-overlapped:
-
-    ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
-                  "range in overlapped buffers");
-
-    return NGX_ERROR;
 }