ngx_http_slice_filter_module.c revision e18a033b
1
2/*
3 * Copyright (C) Roman Arutyunyan
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
13typedef struct {
14    size_t               size;
15} ngx_http_slice_loc_conf_t;
16
17
18typedef struct {
19    off_t                start;
20    off_t                end;
21    ngx_str_t            range;
22    ngx_str_t            etag;
23    unsigned             last:1;
24    unsigned             active:1;
25    ngx_http_request_t  *sr;
26} ngx_http_slice_ctx_t;
27
28
29typedef struct {
30    off_t                start;
31    off_t                end;
32    off_t                complete_length;
33} ngx_http_slice_content_range_t;
34
35
36static ngx_int_t ngx_http_slice_header_filter(ngx_http_request_t *r);
37static ngx_int_t ngx_http_slice_body_filter(ngx_http_request_t *r,
38    ngx_chain_t *in);
39static ngx_int_t ngx_http_slice_parse_content_range(ngx_http_request_t *r,
40    ngx_http_slice_content_range_t *cr);
41static ngx_int_t ngx_http_slice_range_variable(ngx_http_request_t *r,
42    ngx_http_variable_value_t *v, uintptr_t data);
43static off_t ngx_http_slice_get_start(ngx_http_request_t *r);
44static void *ngx_http_slice_create_loc_conf(ngx_conf_t *cf);
45static char *ngx_http_slice_merge_loc_conf(ngx_conf_t *cf, void *parent,
46    void *child);
47static ngx_int_t ngx_http_slice_add_variables(ngx_conf_t *cf);
48static ngx_int_t ngx_http_slice_init(ngx_conf_t *cf);
49
50
51static ngx_command_t  ngx_http_slice_filter_commands[] = {
52
53    { ngx_string("slice"),
54      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
55      ngx_conf_set_size_slot,
56      NGX_HTTP_LOC_CONF_OFFSET,
57      offsetof(ngx_http_slice_loc_conf_t, size),
58      NULL },
59
60      ngx_null_command
61};
62
63
64static ngx_http_module_t  ngx_http_slice_filter_module_ctx = {
65    ngx_http_slice_add_variables,          /* preconfiguration */
66    ngx_http_slice_init,                   /* postconfiguration */
67
68    NULL,                                  /* create main configuration */
69    NULL,                                  /* init main configuration */
70
71    NULL,                                  /* create server configuration */
72    NULL,                                  /* merge server configuration */
73
74    ngx_http_slice_create_loc_conf,        /* create location configuration */
75    ngx_http_slice_merge_loc_conf          /* merge location configuration */
76};
77
78
79ngx_module_t  ngx_http_slice_filter_module = {
80    NGX_MODULE_V1,
81    &ngx_http_slice_filter_module_ctx,     /* module context */
82    ngx_http_slice_filter_commands,        /* module directives */
83    NGX_HTTP_MODULE,                       /* module type */
84    NULL,                                  /* init master */
85    NULL,                                  /* init module */
86    NULL,                                  /* init process */
87    NULL,                                  /* init thread */
88    NULL,                                  /* exit thread */
89    NULL,                                  /* exit process */
90    NULL,                                  /* exit master */
91    NGX_MODULE_V1_PADDING
92};
93
94
95static ngx_str_t  ngx_http_slice_range_name = ngx_string("slice_range");
96
97static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
98static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
99
100
101static ngx_int_t
102ngx_http_slice_header_filter(ngx_http_request_t *r)
103{
104    off_t                            end;
105    ngx_int_t                        rc;
106    ngx_table_elt_t                 *h;
107    ngx_http_slice_ctx_t            *ctx;
108    ngx_http_slice_loc_conf_t       *slcf;
109    ngx_http_slice_content_range_t   cr;
110
111    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
112    if (ctx == NULL) {
113        return ngx_http_next_header_filter(r);
114    }
115
116    if (r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT) {
117        if (r == r->main) {
118            ngx_http_set_ctx(r, NULL, ngx_http_slice_filter_module);
119            return ngx_http_next_header_filter(r);
120        }
121
122        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
123                      "unexpected status code %ui in slice response",
124                      r->headers_out.status);
125        return NGX_ERROR;
126    }
127
128    h = r->headers_out.etag;
129
130    if (ctx->etag.len) {
131        if (h == NULL
132            || h->value.len != ctx->etag.len
133            || ngx_strncmp(h->value.data, ctx->etag.data, ctx->etag.len)
134               != 0)
135        {
136            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
137                          "etag mismatch in slice response");
138            return NGX_ERROR;
139        }
140    }
141
142    if (h) {
143        ctx->etag = h->value;
144    }
145
146    if (ngx_http_slice_parse_content_range(r, &cr) != NGX_OK) {
147        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
148                      "invalid range in slice response");
149        return NGX_ERROR;
150    }
151
152    if (cr.complete_length == -1) {
153        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
154                      "no complete length in slice response");
155        return NGX_ERROR;
156    }
157
158    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
159                   "http slice response range: %O-%O/%O",
160                   cr.start, cr.end, cr.complete_length);
161
162    slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
163
164    end = ngx_min(cr.start + (off_t) slcf->size, cr.complete_length);
165
166    if (cr.start != ctx->start || cr.end != end) {
167        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
168                      "unexpected range in slice response: %O-%O",
169                      cr.start, cr.end);
170        return NGX_ERROR;
171    }
172
173    ctx->start = end;
174    ctx->active = 1;
175
176    r->headers_out.status = NGX_HTTP_OK;
177    r->headers_out.status_line.len = 0;
178    r->headers_out.content_length_n = cr.complete_length;
179    r->headers_out.content_offset = cr.start;
180    r->headers_out.content_range->hash = 0;
181    r->headers_out.content_range = NULL;
182
183    r->allow_ranges = 1;
184    r->subrequest_ranges = 1;
185    r->single_range = 1;
186
187    rc = ngx_http_next_header_filter(r);
188
189    if (r != r->main) {
190        return rc;
191    }
192
193    if (r->headers_out.status == NGX_HTTP_PARTIAL_CONTENT) {
194        if (ctx->start + (off_t) slcf->size <= r->headers_out.content_offset) {
195            ctx->start = slcf->size
196                         * (r->headers_out.content_offset / slcf->size);
197        }
198
199        ctx->end = r->headers_out.content_offset
200                   + r->headers_out.content_length_n;
201
202    } else {
203        ctx->end = cr.complete_length;
204    }
205
206    return rc;
207}
208
209
210static ngx_int_t
211ngx_http_slice_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
212{
213    ngx_int_t                   rc;
214    ngx_chain_t                *cl;
215    ngx_http_slice_ctx_t       *ctx;
216    ngx_http_slice_loc_conf_t  *slcf;
217
218    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
219
220    if (ctx == NULL || r != r->main) {
221        return ngx_http_next_body_filter(r, in);
222    }
223
224    for (cl = in; cl; cl = cl->next) {
225        if (cl->buf->last_buf) {
226            cl->buf->last_buf = 0;
227            cl->buf->last_in_chain = 1;
228            cl->buf->sync = 1;
229            ctx->last = 1;
230        }
231    }
232
233    rc = ngx_http_next_body_filter(r, in);
234
235    if (rc == NGX_ERROR || !ctx->last) {
236        return rc;
237    }
238
239    if (ctx->sr && !ctx->sr->done) {
240        return rc;
241    }
242
243    if (!ctx->active) {
244        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
245                      "missing slice response");
246        return NGX_ERROR;
247    }
248
249    if (ctx->start >= ctx->end) {
250        ngx_http_set_ctx(r, NULL, ngx_http_slice_filter_module);
251        ngx_http_send_special(r, NGX_HTTP_LAST);
252        return rc;
253    }
254
255    if (r->buffered) {
256        return rc;
257    }
258
259    if (ngx_http_subrequest(r, &r->uri, &r->args, &ctx->sr, NULL,
260                            NGX_HTTP_SUBREQUEST_CLONE)
261        != NGX_OK)
262    {
263        return NGX_ERROR;
264    }
265
266    ngx_http_set_ctx(ctx->sr, ctx, ngx_http_slice_filter_module);
267
268    slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
269
270    ctx->range.len = ngx_sprintf(ctx->range.data, "bytes=%O-%O", ctx->start,
271                                 ctx->start + (off_t) slcf->size - 1)
272                     - ctx->range.data;
273
274    ctx->active = 0;
275
276    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
277                   "http slice subrequest: \"%V\"", &ctx->range);
278
279    return rc;
280}
281
282
283static ngx_int_t
284ngx_http_slice_parse_content_range(ngx_http_request_t *r,
285    ngx_http_slice_content_range_t *cr)
286{
287    off_t             start, end, complete_length, cutoff, cutlim;
288    u_char           *p;
289    ngx_table_elt_t  *h;
290
291    h = r->headers_out.content_range;
292
293    if (h == NULL
294        || h->value.len < 7
295        || ngx_strncmp(h->value.data, "bytes ", 6) != 0)
296    {
297        return NGX_ERROR;
298    }
299
300    p = h->value.data + 6;
301
302    cutoff = NGX_MAX_OFF_T_VALUE / 10;
303    cutlim = NGX_MAX_OFF_T_VALUE % 10;
304
305    start = 0;
306    end = 0;
307    complete_length = 0;
308
309    while (*p == ' ') { p++; }
310
311    if (*p < '0' || *p > '9') {
312        return NGX_ERROR;
313    }
314
315    while (*p >= '0' && *p <= '9') {
316        if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) {
317            return NGX_ERROR;
318        }
319
320        start = start * 10 + *p++ - '0';
321    }
322
323    while (*p == ' ') { p++; }
324
325    if (*p++ != '-') {
326        return NGX_ERROR;
327    }
328
329    while (*p == ' ') { p++; }
330
331    if (*p < '0' || *p > '9') {
332        return NGX_ERROR;
333    }
334
335    while (*p >= '0' && *p <= '9') {
336        if (end >= cutoff && (end > cutoff || *p - '0' > cutlim)) {
337            return NGX_ERROR;
338        }
339
340        end = end * 10 + *p++ - '0';
341    }
342
343    end++;
344
345    while (*p == ' ') { p++; }
346
347    if (*p++ != '/') {
348        return NGX_ERROR;
349    }
350
351    while (*p == ' ') { p++; }
352
353    if (*p != '*') {
354        if (*p < '0' || *p > '9') {
355            return NGX_ERROR;
356        }
357
358        while (*p >= '0' && *p <= '9') {
359            if (complete_length >= cutoff
360                && (complete_length > cutoff || *p - '0' > cutlim))
361            {
362                return NGX_ERROR;
363            }
364
365            complete_length = complete_length * 10 + *p++ - '0';
366        }
367
368    } else {
369        complete_length = -1;
370        p++;
371    }
372
373    while (*p == ' ') { p++; }
374
375    if (*p != '\0') {
376        return NGX_ERROR;
377    }
378
379    cr->start = start;
380    cr->end = end;
381    cr->complete_length = complete_length;
382
383    return NGX_OK;
384}
385
386
387static ngx_int_t
388ngx_http_slice_range_variable(ngx_http_request_t *r,
389    ngx_http_variable_value_t *v, uintptr_t data)
390{
391    u_char                     *p;
392    ngx_http_slice_ctx_t       *ctx;
393    ngx_http_slice_loc_conf_t  *slcf;
394
395    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
396
397    if (ctx == NULL) {
398        if (r != r->main || r->headers_out.status) {
399            v->not_found = 1;
400            return NGX_OK;
401        }
402
403        slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
404
405        if (slcf->size == 0) {
406            v->not_found = 1;
407            return NGX_OK;
408        }
409
410        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_slice_ctx_t));
411        if (ctx == NULL) {
412            return NGX_ERROR;
413        }
414
415        ngx_http_set_ctx(r, ctx, ngx_http_slice_filter_module);
416
417        p = ngx_pnalloc(r->pool, sizeof("bytes=-") - 1 + 2 * NGX_OFF_T_LEN);
418        if (p == NULL) {
419            return NGX_ERROR;
420        }
421
422        ctx->start = slcf->size * (ngx_http_slice_get_start(r) / slcf->size);
423
424        ctx->range.data = p;
425        ctx->range.len = ngx_sprintf(p, "bytes=%O-%O", ctx->start,
426                                     ctx->start + (off_t) slcf->size - 1)
427                         - p;
428    }
429
430    v->data = ctx->range.data;
431    v->valid = 1;
432    v->not_found = 0;
433    v->no_cacheable = 1;
434    v->len = ctx->range.len;
435
436    return NGX_OK;
437}
438
439
440static off_t
441ngx_http_slice_get_start(ngx_http_request_t *r)
442{
443    off_t             start, cutoff, cutlim;
444    u_char           *p;
445    ngx_table_elt_t  *h;
446
447    if (r->headers_in.if_range) {
448        return 0;
449    }
450
451    h = r->headers_in.range;
452
453    if (h == NULL
454        || h->value.len < 7
455        || ngx_strncasecmp(h->value.data, (u_char *) "bytes=", 6) != 0)
456    {
457        return 0;
458    }
459
460    p = h->value.data + 6;
461
462    if (ngx_strchr(p, ',')) {
463        return 0;
464    }
465
466    while (*p == ' ') { p++; }
467
468    if (*p == '-') {
469        return 0;
470    }
471
472    cutoff = NGX_MAX_OFF_T_VALUE / 10;
473    cutlim = NGX_MAX_OFF_T_VALUE % 10;
474
475    start = 0;
476
477    while (*p >= '0' && *p <= '9') {
478        if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) {
479            return 0;
480        }
481
482        start = start * 10 + *p++ - '0';
483    }
484
485    return start;
486}
487
488
489static void *
490ngx_http_slice_create_loc_conf(ngx_conf_t *cf)
491{
492    ngx_http_slice_loc_conf_t  *slcf;
493
494    slcf = ngx_palloc(cf->pool, sizeof(ngx_http_slice_loc_conf_t));
495    if (slcf == NULL) {
496        return NULL;
497    }
498
499    slcf->size = NGX_CONF_UNSET_SIZE;
500
501    return slcf;
502}
503
504
505static char *
506ngx_http_slice_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
507{
508    ngx_http_slice_loc_conf_t *prev = parent;
509    ngx_http_slice_loc_conf_t *conf = child;
510
511    ngx_conf_merge_size_value(conf->size, prev->size, 0);
512
513    return NGX_CONF_OK;
514}
515
516
517static ngx_int_t
518ngx_http_slice_add_variables(ngx_conf_t *cf)
519{
520    ngx_http_variable_t  *var;
521
522    var = ngx_http_add_variable(cf, &ngx_http_slice_range_name, 0);
523    if (var == NULL) {
524        return NGX_ERROR;
525    }
526
527    var->get_handler = ngx_http_slice_range_variable;
528
529    return NGX_OK;
530}
531
532
533static ngx_int_t
534ngx_http_slice_init(ngx_conf_t *cf)
535{
536    ngx_http_next_header_filter = ngx_http_top_header_filter;
537    ngx_http_top_header_filter = ngx_http_slice_header_filter;
538
539    ngx_http_next_body_filter = ngx_http_top_body_filter;
540    ngx_http_top_body_filter = ngx_http_slice_body_filter;
541
542    return NGX_OK;
543}
544