Mercurial > hg > nginx
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, |