comparison src/http/modules/ngx_http_range_filter_module.c @ 4162:fb1375e8b68c stable-1.0

Merging r4036, r4055, r4056, r4057, r4058, r4059, r4060, r4061, r4062, r4063, r4064: Ranges related fixes: The "max_ranges" directive. "max_ranges 0" disables ranges support at all, "max_ranges 1" allows the single range, etc. By default number of ranges is unlimited, to be precise, 2^31-1. If client requests more ranges than "max_ranges" permits, nginx disables ranges and returns just the source response. If total size of all ranges is greater than source response size, then nginx disables ranges and returns just the source response. This fix should not affect well-behaving applications but will defeat DoS attempts exploiting malicious byte ranges. Now unsatisfiable ranges are processed according to RFC 2616.
author Igor Sysoev <igor@sysoev.ru>
date Fri, 30 Sep 2011 14:06:08 +0000
parents dd1570b6f237
children 4919fb357a5d
comparison
equal deleted inserted replaced
4161:010a0907bc95 4162:fb1375e8b68c
56 ngx_str_t boundary_header; 56 ngx_str_t boundary_header;
57 ngx_array_t ranges; 57 ngx_array_t ranges;
58 } ngx_http_range_filter_ctx_t; 58 } ngx_http_range_filter_ctx_t;
59 59
60 60
61 ngx_int_t ngx_http_range_parse(ngx_http_request_t *r, 61 static ngx_int_t ngx_http_range_parse(ngx_http_request_t *r,
62 ngx_http_range_filter_ctx_t *ctx); 62 ngx_http_range_filter_ctx_t *ctx, ngx_uint_t ranges);
63 static ngx_int_t ngx_http_range_singlepart_header(ngx_http_request_t *r, 63 static ngx_int_t ngx_http_range_singlepart_header(ngx_http_request_t *r,
64 ngx_http_range_filter_ctx_t *ctx); 64 ngx_http_range_filter_ctx_t *ctx);
65 static ngx_int_t ngx_http_range_multipart_header(ngx_http_request_t *r, 65 static ngx_int_t ngx_http_range_multipart_header(ngx_http_request_t *r,
66 ngx_http_range_filter_ctx_t *ctx); 66 ngx_http_range_filter_ctx_t *ctx);
67 static ngx_int_t ngx_http_range_not_satisfiable(ngx_http_request_t *r); 67 static ngx_int_t ngx_http_range_not_satisfiable(ngx_http_request_t *r);
144 144
145 static ngx_int_t 145 static ngx_int_t
146 ngx_http_range_header_filter(ngx_http_request_t *r) 146 ngx_http_range_header_filter(ngx_http_request_t *r)
147 { 147 {
148 time_t if_range; 148 time_t if_range;
149 ngx_int_t rc; 149 ngx_http_core_loc_conf_t *clcf;
150 ngx_http_range_filter_ctx_t *ctx; 150 ngx_http_range_filter_ctx_t *ctx;
151 151
152 if (r->http_version < NGX_HTTP_VERSION_10 152 if (r->http_version < NGX_HTTP_VERSION_10
153 || r->headers_out.status != NGX_HTTP_OK 153 || r->headers_out.status != NGX_HTTP_OK
154 || r != r->main 154 || r != r->main
156 || !r->allow_ranges) 156 || !r->allow_ranges)
157 { 157 {
158 return ngx_http_next_header_filter(r); 158 return ngx_http_next_header_filter(r);
159 } 159 }
160 160
161 clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
162
163 if (clcf->max_ranges == 0) {
164 return ngx_http_next_header_filter(r);
165 }
166
161 if (r->headers_in.range == NULL 167 if (r->headers_in.range == NULL
162 || r->headers_in.range->value.len < 7 168 || r->headers_in.range->value.len < 7
163 || ngx_strncasecmp(r->headers_in.range->value.data, 169 || ngx_strncasecmp(r->headers_in.range->value.data,
164 (u_char *) "bytes=", 6) 170 (u_char *) "bytes=", 6)
165 != 0) 171 != 0)
190 != NGX_OK) 196 != NGX_OK)
191 { 197 {
192 return NGX_ERROR; 198 return NGX_ERROR;
193 } 199 }
194 200
195 rc = ngx_http_range_parse(r, ctx); 201 switch (ngx_http_range_parse(r, ctx, clcf->max_ranges)) {
196 202
197 if (rc == NGX_OK) { 203 case NGX_OK:
198
199 ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module); 204 ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
200 205
201 r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT; 206 r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
202 r->headers_out.status_line.len = 0; 207 r->headers_out.status_line.len = 0;
203 208
204 if (ctx->ranges.nelts == 1) { 209 if (ctx->ranges.nelts == 1) {
205 return ngx_http_range_singlepart_header(r, ctx); 210 return ngx_http_range_singlepart_header(r, ctx);
206 } 211 }
207 212
208 return ngx_http_range_multipart_header(r, ctx); 213 return ngx_http_range_multipart_header(r, ctx);
209 } 214
210 215 case NGX_HTTP_RANGE_NOT_SATISFIABLE:
211 if (rc == NGX_HTTP_RANGE_NOT_SATISFIABLE) {
212 return ngx_http_range_not_satisfiable(r); 216 return ngx_http_range_not_satisfiable(r);
213 } 217
214 218 case NGX_ERROR:
215 /* rc == NGX_ERROR */ 219 return NGX_ERROR;
216 220
217 return rc; 221 default: /* NGX_DECLINED */
222 break;
223 }
218 224
219 next_filter: 225 next_filter:
220 226
221 r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers); 227 r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers);
222 if (r->headers_out.accept_ranges == NULL) { 228 if (r->headers_out.accept_ranges == NULL) {
229 235
230 return ngx_http_next_header_filter(r); 236 return ngx_http_next_header_filter(r);
231 } 237 }
232 238
233 239
234 ngx_int_t 240 static ngx_int_t
235 ngx_http_range_parse(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx) 241 ngx_http_range_parse(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx,
242 ngx_uint_t ranges)
236 { 243 {
237 u_char *p; 244 u_char *p;
238 off_t start, end; 245 off_t start, end, size, content_length;
239 ngx_uint_t suffix; 246 ngx_uint_t suffix;
240 ngx_http_range_t *range; 247 ngx_http_range_t *range;
241 248
242 p = r->headers_in.range->value.data + 6; 249 p = r->headers_in.range->value.data + 6;
250 size = 0;
251 content_length = r->headers_out.content_length_n;
243 252
244 for ( ;; ) { 253 for ( ;; ) {
245 start = 0; 254 start = 0;
246 end = 0; 255 end = 0;
247 suffix = 0; 256 suffix = 0;
261 270
262 if (*p++ != '-') { 271 if (*p++ != '-') {
263 return NGX_HTTP_RANGE_NOT_SATISFIABLE; 272 return NGX_HTTP_RANGE_NOT_SATISFIABLE;
264 } 273 }
265 274
266 if (start >= r->headers_out.content_length_n) {
267 return NGX_HTTP_RANGE_NOT_SATISFIABLE;
268 }
269
270 while (*p == ' ') { p++; } 275 while (*p == ' ') { p++; }
271 276
272 if (*p == ',' || *p == '\0') { 277 if (*p == ',' || *p == '\0') {
273 range = ngx_array_push(&ctx->ranges); 278 end = content_length;
274 if (range == NULL) { 279 goto found;
275 return NGX_ERROR;
276 }
277
278 range->start = start;
279 range->end = r->headers_out.content_length_n;
280
281 if (*p++ != ',') {
282 return NGX_OK;
283 }
284
285 continue;
286 } 280 }
287 281
288 } else { 282 } else {
289 suffix = 1; 283 suffix = 1;
290 p++; 284 p++;
303 if (*p != ',' && *p != '\0') { 297 if (*p != ',' && *p != '\0') {
304 return NGX_HTTP_RANGE_NOT_SATISFIABLE; 298 return NGX_HTTP_RANGE_NOT_SATISFIABLE;
305 } 299 }
306 300
307 if (suffix) { 301 if (suffix) {
308 start = r->headers_out.content_length_n - end; 302 start = content_length - end;
309 end = r->headers_out.content_length_n - 1; 303 end = content_length - 1;
310 } 304 }
311 305
312 if (start > end) { 306 if (end >= content_length) {
313 return NGX_HTTP_RANGE_NOT_SATISFIABLE; 307 end = content_length;
314 }
315
316 range = ngx_array_push(&ctx->ranges);
317 if (range == NULL) {
318 return NGX_ERROR;
319 }
320
321 range->start = start;
322
323 if (end >= r->headers_out.content_length_n) {
324 /*
325 * Download Accelerator sends the last byte position
326 * that equals to the file length
327 */
328 range->end = r->headers_out.content_length_n;
329 308
330 } else { 309 } else {
331 range->end = end + 1; 310 end++;
311 }
312
313 found:
314
315 if (start < end) {
316 range = ngx_array_push(&ctx->ranges);
317 if (range == NULL) {
318 return NGX_ERROR;
319 }
320
321 range->start = start;
322 range->end = end;
323
324 size += end - start;
325
326 if (ranges-- == 0) {
327 return NGX_DECLINED;
328 }
332 } 329 }
333 330
334 if (*p++ != ',') { 331 if (*p++ != ',') {
335 return NGX_OK; 332 break;
336 } 333 }
337 } 334 }
335
336 if (ctx->ranges.nelts == 0) {
337 return NGX_HTTP_RANGE_NOT_SATISFIABLE;
338 }
339
340 if (size > content_length) {
341 return NGX_DECLINED;
342 }
343
344 return NGX_OK;
338 } 345 }
339 346
340 347
341 static ngx_int_t 348 static ngx_int_t
342 ngx_http_range_singlepart_header(ngx_http_request_t *r, 349 ngx_http_range_singlepart_header(ngx_http_request_t *r,