comparison src/http/v2/ngx_http_v2_filter_module.c @ 6277:b930e598a199

HTTP/2: fixed splitting of response headers on CONTINUATION frames. Previous code has been based on assumption that the header block can only be splitted at the borders of individual headers. That wasn't the case and might result in emitting frames bigger than the frame size limit. The current approach is to split header blocks by the frame size limit.
author Valentin Bartenev <vbart@nginx.com>
date Mon, 28 Sep 2015 02:32:44 +0300
parents 0efc16d55adb
children c72eaf694d99
comparison
equal deleted inserted replaced
6276:0efc16d55adb 6277:b930e598a199
41 #define NGX_HTTP_V2_VARY_INDEX 59 41 #define NGX_HTTP_V2_VARY_INDEX 59
42 42
43 43
44 static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, 44 static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix,
45 ngx_uint_t value); 45 ngx_uint_t value);
46 static void ngx_http_v2_write_headers_head(u_char *pos, size_t length, 46 static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame(
47 ngx_uint_t sid, ngx_uint_t end_headers, ngx_uint_t end_stream); 47 ngx_http_request_t *r, u_char *pos, u_char *end);
48 static void ngx_http_v2_write_continuation_head(u_char *pos, size_t length,
49 ngx_uint_t sid, ngx_uint_t end_headers);
50 48
51 static ngx_chain_t *ngx_http_v2_send_chain(ngx_connection_t *fc, 49 static ngx_chain_t *ngx_http_v2_send_chain(ngx_connection_t *fc,
52 ngx_chain_t *in, off_t limit); 50 ngx_chain_t *in, off_t limit);
53 51
54 static ngx_chain_t *ngx_http_v2_filter_get_shadow( 52 static ngx_chain_t *ngx_http_v2_filter_get_shadow(
114 112
115 113
116 static ngx_int_t 114 static ngx_int_t
117 ngx_http_v2_header_filter(ngx_http_request_t *r) 115 ngx_http_v2_header_filter(ngx_http_request_t *r)
118 { 116 {
119 u_char status, *p, *head; 117 u_char status, *pos, *start, *p;
120 size_t len, rest, frame_size; 118 size_t len;
121 ngx_buf_t *b;
122 ngx_str_t host, location; 119 ngx_str_t host, location;
123 ngx_uint_t i, port, continuation; 120 ngx_uint_t i, port;
124 ngx_chain_t *cl;
125 ngx_list_part_t *part; 121 ngx_list_part_t *part;
126 ngx_table_elt_t *header; 122 ngx_table_elt_t *header;
127 ngx_connection_t *fc; 123 ngx_connection_t *fc;
128 ngx_http_cleanup_t *cln; 124 ngx_http_cleanup_t *cln;
129 ngx_http_v2_stream_t *stream;
130 ngx_http_v2_out_frame_t *frame; 125 ngx_http_v2_out_frame_t *frame;
131 ngx_http_core_loc_conf_t *clcf; 126 ngx_http_core_loc_conf_t *clcf;
132 ngx_http_core_srv_conf_t *cscf; 127 ngx_http_core_srv_conf_t *cscf;
133 struct sockaddr_in *sin; 128 struct sockaddr_in *sin;
134 #if (NGX_HAVE_INET6) 129 #if (NGX_HAVE_INET6)
386 381
387 len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len 382 len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len
388 + NGX_HTTP_V2_INT_OCTETS + header[i].value.len; 383 + NGX_HTTP_V2_INT_OCTETS + header[i].value.len;
389 } 384 }
390 385
391 stream = r->stream; 386 pos = ngx_palloc(r->pool, len);
392 frame_size = stream->connection->frame_size; 387 if (pos == NULL) {
393
394 len += NGX_HTTP_V2_FRAME_HEADER_SIZE
395 * ((len + frame_size - 1) / frame_size);
396
397 b = ngx_create_temp_buf(r->pool, len);
398 if (b == NULL) {
399 return NGX_ERROR; 388 return NGX_ERROR;
400 } 389 }
401 390
402 b->last_buf = r->header_only; 391 start = pos;
403
404 b->last += NGX_HTTP_V2_FRAME_HEADER_SIZE;
405 392
406 if (status) { 393 if (status) {
407 *b->last++ = status; 394 *pos++ = status;
408 395
409 } else { 396 } else {
410 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); 397 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX);
411 *b->last++ = NGX_HTTP_V2_ENCODE_RAW | 3; 398 *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3;
412 b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); 399 pos = ngx_sprintf(pos, "%03ui", r->headers_out.status);
413 } 400 }
414 401
415 if (r->headers_out.server == NULL) { 402 if (r->headers_out.server == NULL) {
416 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); 403 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX);
417 404
418 if (clcf->server_tokens) { 405 if (clcf->server_tokens) {
419 *b->last++ = NGX_HTTP_V2_ENCODE_RAW | (sizeof(NGINX_VER) - 1); 406 *pos++ = NGX_HTTP_V2_ENCODE_RAW | (sizeof(NGINX_VER) - 1);
420 b->last = ngx_cpymem(b->last, NGINX_VER, sizeof(NGINX_VER) - 1); 407 pos = ngx_cpymem(pos, NGINX_VER, sizeof(NGINX_VER) - 1);
421 408
422 } else { 409 } else {
423 *b->last++ = NGX_HTTP_V2_ENCODE_RAW | (sizeof("nginx") - 1); 410 *pos++ = NGX_HTTP_V2_ENCODE_RAW | (sizeof("nginx") - 1);
424 b->last = ngx_cpymem(b->last, "nginx", sizeof("nginx") - 1); 411 pos = ngx_cpymem(pos, "nginx", sizeof("nginx") - 1);
425 } 412 }
426 } 413 }
427 414
428 if (r->headers_out.date == NULL) { 415 if (r->headers_out.date == NULL) {
429 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX); 416 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX);
430 *b->last++ = (u_char) ngx_cached_http_time.len; 417 *pos++ = (u_char) ngx_cached_http_time.len;
431 418
432 b->last = ngx_cpymem(b->last, ngx_cached_http_time.data, 419 pos = ngx_cpymem(pos, ngx_cached_http_time.data,
433 ngx_cached_http_time.len); 420 ngx_cached_http_time.len);
434 } 421 }
435 422
436 if (r->headers_out.content_type.len) { 423 if (r->headers_out.content_type.len) {
437 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX); 424 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX);
438 425
439 if (r->headers_out.content_type_len == r->headers_out.content_type.len 426 if (r->headers_out.content_type_len == r->headers_out.content_type.len
440 && r->headers_out.charset.len) 427 && r->headers_out.charset.len)
441 { 428 {
442 *b->last = NGX_HTTP_V2_ENCODE_RAW; 429 *pos = NGX_HTTP_V2_ENCODE_RAW;
443 b->last = ngx_http_v2_write_int(b->last, ngx_http_v2_prefix(7), 430 pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7),
444 r->headers_out.content_type.len 431 r->headers_out.content_type.len
445 + sizeof("; charset=") - 1 432 + sizeof("; charset=") - 1
446 + r->headers_out.charset.len); 433 + r->headers_out.charset.len);
447 434
448 p = b->last; 435 p = pos;
449 436
450 b->last = ngx_cpymem(p, r->headers_out.content_type.data, 437 pos = ngx_cpymem(pos, r->headers_out.content_type.data,
451 r->headers_out.content_type.len); 438 r->headers_out.content_type.len);
452 439
453 b->last = ngx_cpymem(b->last, "; charset=", 440 pos = ngx_cpymem(pos, "; charset=", sizeof("; charset=") - 1);
454 sizeof("; charset=") - 1); 441
455 442 pos = ngx_cpymem(pos, r->headers_out.charset.data,
456 b->last = ngx_cpymem(b->last, r->headers_out.charset.data, 443 r->headers_out.charset.len);
457 r->headers_out.charset.len);
458 444
459 /* update r->headers_out.content_type for possible logging */ 445 /* update r->headers_out.content_type for possible logging */
460 446
461 r->headers_out.content_type.len = b->last - p; 447 r->headers_out.content_type.len = pos - p;
462 r->headers_out.content_type.data = p; 448 r->headers_out.content_type.data = p;
463 449
464 } else { 450 } else {
465 *b->last = NGX_HTTP_V2_ENCODE_RAW; 451 *pos = NGX_HTTP_V2_ENCODE_RAW;
466 b->last = ngx_http_v2_write_int(b->last, ngx_http_v2_prefix(7), 452 pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7),
467 r->headers_out.content_type.len); 453 r->headers_out.content_type.len);
468 b->last = ngx_cpymem(b->last, r->headers_out.content_type.data, 454 pos = ngx_cpymem(pos, r->headers_out.content_type.data,
469 r->headers_out.content_type.len); 455 r->headers_out.content_type.len);
470 } 456 }
471 } 457 }
472 458
473 if (r->headers_out.content_length == NULL 459 if (r->headers_out.content_length == NULL
474 && r->headers_out.content_length_n >= 0) 460 && r->headers_out.content_length_n >= 0)
475 { 461 {
476 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX); 462 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX);
477 463
478 p = b->last; 464 p = pos;
479 b->last = ngx_sprintf(b->last + 1, "%O", 465 pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n);
480 r->headers_out.content_length_n); 466 *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1);
481 *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (b->last - p - 1);
482 } 467 }
483 468
484 if (r->headers_out.last_modified == NULL 469 if (r->headers_out.last_modified == NULL
485 && r->headers_out.last_modified_time != -1) 470 && r->headers_out.last_modified_time != -1)
486 { 471 {
487 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX); 472 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX);
488 473
489 *b->last++ = NGX_HTTP_V2_ENCODE_RAW 474 *pos++ = NGX_HTTP_V2_ENCODE_RAW
490 | (sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1); 475 | (sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1);
491 b->last = ngx_http_time(b->last, r->headers_out.last_modified_time); 476 pos = ngx_http_time(pos, r->headers_out.last_modified_time);
492 } 477 }
493 478
494 if (r->headers_out.location && r->headers_out.location->value.len) { 479 if (r->headers_out.location && r->headers_out.location->value.len) {
495 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX); 480 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX);
496 481
497 *b->last = NGX_HTTP_V2_ENCODE_RAW; 482 *pos = NGX_HTTP_V2_ENCODE_RAW;
498 b->last = ngx_http_v2_write_int(b->last, ngx_http_v2_prefix(7), 483 pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7),
499 r->headers_out.location->value.len); 484 r->headers_out.location->value.len);
500 b->last = ngx_cpymem(b->last, r->headers_out.location->value.data, 485 pos = ngx_cpymem(pos, r->headers_out.location->value.data,
501 r->headers_out.location->value.len); 486 r->headers_out.location->value.len);
502 } 487 }
503 488
504 #if (NGX_HTTP_GZIP) 489 #if (NGX_HTTP_GZIP)
505 if (r->gzip_vary) { 490 if (r->gzip_vary) {
506 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX); 491 *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX);
507 *b->last++ = NGX_HTTP_V2_ENCODE_RAW | (sizeof("Accept-Encoding") - 1); 492 *pos++ = NGX_HTTP_V2_ENCODE_RAW | (sizeof("Accept-Encoding") - 1);
508 b->last = ngx_cpymem(b->last, "Accept-Encoding", 493 pos = ngx_cpymem(pos, "Accept-Encoding", sizeof("Accept-Encoding") - 1);
509 sizeof("Accept-Encoding") - 1);
510 } 494 }
511 #endif 495 #endif
512
513 continuation = 0;
514 head = b->pos;
515
516 len = b->last - head - NGX_HTTP_V2_FRAME_HEADER_SIZE;
517 rest = frame_size - len;
518 496
519 part = &r->headers_out.headers.part; 497 part = &r->headers_out.headers.part;
520 header = part->elts; 498 header = part->elts;
521 499
522 for (i = 0; /* void */; i++) { 500 for (i = 0; /* void */; i++) {
533 511
534 if (header[i].hash == 0) { 512 if (header[i].hash == 0) {
535 continue; 513 continue;
536 } 514 }
537 515
538 len = 1 + NGX_HTTP_V2_INT_OCTETS * 2 516 *pos++ = 0;
539 + header[i].key.len 517
540 + header[i].value.len; 518 *pos = NGX_HTTP_V2_ENCODE_RAW;
541 519 pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7),
542 if (len > rest) { 520 header[i].key.len);
543 len = b->last - head - NGX_HTTP_V2_FRAME_HEADER_SIZE; 521 ngx_strlow(pos, header[i].key.data, header[i].key.len);
544 522 pos += header[i].key.len;
545 if (continuation) { 523
546 ngx_http_v2_write_continuation_head(head, len, 524 *pos = NGX_HTTP_V2_ENCODE_RAW;
547 stream->node->id, 0); 525 pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7),
548 } else { 526 header[i].value.len);
549 continuation = 1; 527 pos = ngx_cpymem(pos, header[i].value.data, header[i].value.len);
550 ngx_http_v2_write_headers_head(head, len, stream->node->id, 0, 528 }
551 r->header_only); 529
552 } 530 frame = ngx_http_v2_create_headers_frame(r, start, pos);
553
554 rest = frame_size;
555 head = b->last;
556
557 b->last += NGX_HTTP_V2_FRAME_HEADER_SIZE;
558 }
559
560 p = b->last;
561
562 *p++ = 0;
563
564 *p = NGX_HTTP_V2_ENCODE_RAW;
565 p = ngx_http_v2_write_int(p, ngx_http_v2_prefix(7), header[i].key.len);
566 ngx_strlow(p, header[i].key.data, header[i].key.len);
567 p += header[i].key.len;
568
569 *p = NGX_HTTP_V2_ENCODE_RAW;
570 p = ngx_http_v2_write_int(p, ngx_http_v2_prefix(7),
571 header[i].value.len);
572 p = ngx_cpymem(p, header[i].value.data, header[i].value.len);
573
574 rest -= p - b->last;
575 b->last = p;
576 }
577
578 len = b->last - head - NGX_HTTP_V2_FRAME_HEADER_SIZE;
579
580 if (continuation) {
581 ngx_http_v2_write_continuation_head(head, len, stream->node->id, 1);
582
583 } else {
584 ngx_http_v2_write_headers_head(head, len, stream->node->id, 1,
585 r->header_only);
586 }
587
588 cl = ngx_alloc_chain_link(r->pool);
589 if (cl == NULL) {
590 return NGX_ERROR;
591 }
592
593 cl->buf = b;
594 cl->next = NULL;
595
596 frame = ngx_palloc(r->pool, sizeof(ngx_http_v2_out_frame_t));
597 if (frame == NULL) { 531 if (frame == NULL) {
598 return NGX_ERROR; 532 return NGX_ERROR;
599 } 533 }
600 534
601 frame->first = cl; 535 ngx_http_v2_queue_blocked_frame(r->stream->connection, frame);
602 frame->last = cl;
603 frame->handler = ngx_http_v2_headers_frame_handler;
604 frame->stream = stream;
605 frame->length = b->last - b->pos - NGX_HTTP_V2_FRAME_HEADER_SIZE;
606 frame->blocked = 1;
607 frame->fin = r->header_only;
608
609 ngx_log_debug3(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0,
610 "http2:%ui create HEADERS frame %p: len:%uz",
611 stream->node->id, frame, frame->length);
612
613 ngx_http_v2_queue_blocked_frame(stream->connection, frame);
614 536
615 cln = ngx_http_cleanup_add(r, 0); 537 cln = ngx_http_cleanup_add(r, 0);
616 if (cln == NULL) { 538 if (cln == NULL) {
617 return NGX_ERROR; 539 return NGX_ERROR;
618 } 540 }
619 541
620 cln->handler = ngx_http_v2_filter_cleanup; 542 cln->handler = ngx_http_v2_filter_cleanup;
621 cln->data = stream; 543 cln->data = r->stream;
622 544
623 stream->queued = 1; 545 r->stream->queued = 1;
624 546
625 fc->send_chain = ngx_http_v2_send_chain; 547 fc->send_chain = ngx_http_v2_send_chain;
626 fc->need_last_buf = 1; 548 fc->need_last_buf = 1;
627 549
628 return ngx_http_v2_filter_send(fc, stream); 550 return ngx_http_v2_filter_send(fc, r->stream);
629 } 551 }
630 552
631 553
632 static u_char * 554 static u_char *
633 ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) 555 ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value)
649 571
650 return pos; 572 return pos;
651 } 573 }
652 574
653 575
654 static void 576 static ngx_http_v2_out_frame_t *
655 ngx_http_v2_write_headers_head(u_char *pos, size_t length, ngx_uint_t sid, 577 ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos,
656 ngx_uint_t end_headers, ngx_uint_t end_stream) 578 u_char *end)
657 { 579 {
658 u_char flags; 580 u_char type, flags;
659 581 size_t rest, frame_size;
660 pos = ngx_http_v2_write_len_and_type(pos, length, 582 ngx_buf_t *b;
661 NGX_HTTP_V2_HEADERS_FRAME); 583 ngx_chain_t *cl, **ll;
662 584 ngx_http_v2_stream_t *stream;
663 flags = NGX_HTTP_V2_NO_FLAG; 585 ngx_http_v2_out_frame_t *frame;
664 586
665 if (end_headers) { 587 stream = r->stream;
666 flags |= NGX_HTTP_V2_END_HEADERS_FLAG; 588 rest = end - pos;
667 } 589
668 590 frame = ngx_palloc(r->pool, sizeof(ngx_http_v2_out_frame_t));
669 if (end_stream) { 591 if (frame == NULL) {
670 flags |= NGX_HTTP_V2_END_STREAM_FLAG; 592 return NULL;
671 } 593 }
672 594
673 *pos++ = flags; 595 frame->handler = ngx_http_v2_headers_frame_handler;
674 596 frame->stream = stream;
675 (void) ngx_http_v2_write_sid(pos, sid); 597 frame->length = rest;
676 } 598 frame->blocked = 1;
677 599 frame->fin = r->header_only;
678 600
679 static void 601 ll = &frame->first;
680 ngx_http_v2_write_continuation_head(u_char *pos, size_t length, ngx_uint_t sid, 602
681 ngx_uint_t end_headers) 603 type = NGX_HTTP_V2_HEADERS_FRAME;
682 { 604 flags = r->header_only ? NGX_HTTP_V2_END_STREAM_FLAG : NGX_HTTP_V2_NO_FLAG;
683 pos = ngx_http_v2_write_len_and_type(pos, length, 605 frame_size = stream->connection->frame_size;
684 NGX_HTTP_V2_CONTINUATION_FRAME); 606
685 607 for ( ;; ) {
686 *pos++ = end_headers ? NGX_HTTP_V2_END_HEADERS_FLAG : NGX_HTTP_V2_NO_FLAG; 608 if (rest <= frame_size) {
687 609 frame_size = rest;
688 (void) ngx_http_v2_write_sid(pos, sid); 610 flags |= NGX_HTTP_V2_END_HEADERS_FLAG;
611 }
612
613 b = ngx_create_temp_buf(r->pool, NGX_HTTP_V2_FRAME_HEADER_SIZE);
614 if (b == NULL) {
615 return NULL;
616 }
617
618 b->last = ngx_http_v2_write_len_and_type(b->last, frame_size, type);
619 *b->last++ = flags;
620 b->last = ngx_http_v2_write_sid(b->last, stream->node->id);
621
622 cl = ngx_alloc_chain_link(r->pool);
623 if (cl == NULL) {
624 return NULL;
625 }
626
627 cl->buf = b;
628
629 *ll = cl;
630 ll = &cl->next;
631
632 b = ngx_calloc_buf(r->pool);
633 if (b == NULL) {
634 return NULL;
635 }
636
637 b->pos = pos;
638
639 pos += frame_size;
640
641 b->last = pos;
642 b->start = b->pos;
643 b->end = b->last;
644 b->temporary = 1;
645
646 cl = ngx_alloc_chain_link(r->pool);
647 if (cl == NULL) {
648 return NULL;
649 }
650
651 cl->buf = b;
652
653 *ll = cl;
654 ll = &cl->next;
655
656 rest -= frame_size;
657
658 if (rest) {
659 type = NGX_HTTP_V2_CONTINUATION_FRAME;
660 flags = NGX_HTTP_V2_NO_FLAG;
661 continue;
662 }
663
664 b->last_buf = r->header_only;
665 cl->next = NULL;
666 frame->last = cl;
667
668 ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
669 "http2:%ui create HEADERS frame %p: len:%uz",
670 stream->node->id, frame, frame->length);
671
672 return frame;
673 }
689 } 674 }
690 675
691 676
692 static ngx_chain_t * 677 static ngx_chain_t *
693 ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit) 678 ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit)