text: 
  /*
   * Copyright (C) Nginx, Inc.
   * Copyright (C) Valentin V. Bartenev
   * Copyright (C) Ruslan Ermilov
   */
  
  
  #include <ngx_config.h>
  #include <ngx_core.h>
  #include <ngx_http.h>
  #include <nginx.h>
  #include <ngx_http_v2_module.h>
  
  
  /*
   * This returns precise number of octets for values in range 0..253
   * and estimate number for the rest, but not smaller than required.
   */
  
  #define ngx_http_v2_integer_octets(v)  (1 + (v) / 127)
  
  #define ngx_http_v2_literal_size(h)                                           \
      (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1)
  
  #define ngx_http_v2_indexed(i)      (128 + (i))
  #define ngx_http_v2_inc_indexed(i)  (64 + (i))
  
  #define ngx_http_v2_write_name(dst, src, len, tmp)                            \
      ngx_http_v2_string_encode(dst, src, len, tmp, 1)
  #define ngx_http_v2_write_value(dst, src, len, tmp)                           \
      ngx_http_v2_string_encode(dst, src, len, tmp, 0)
  
  #define NGX_HTTP_V2_ENCODE_RAW            0
  #define NGX_HTTP_V2_ENCODE_HUFF           0x80
  
  #define NGX_HTTP_V2_AUTHORITY_INDEX       1
  #define NGX_HTTP_V2_METHOD_GET_INDEX      2
  #define NGX_HTTP_V2_PATH_INDEX            4
  
  #define NGX_HTTP_V2_SCHEME_HTTP_INDEX     6
  #define NGX_HTTP_V2_SCHEME_HTTPS_INDEX    7
  
  #define NGX_HTTP_V2_STATUS_INDEX          8
  #define NGX_HTTP_V2_STATUS_200_INDEX      8
  #define NGX_HTTP_V2_STATUS_204_INDEX      9
  #define NGX_HTTP_V2_STATUS_206_INDEX      10
  #define NGX_HTTP_V2_STATUS_304_INDEX      11
  #define NGX_HTTP_V2_STATUS_400_INDEX      12
  #define NGX_HTTP_V2_STATUS_404_INDEX      13
  #define NGX_HTTP_V2_STATUS_500_INDEX      14
  
  #define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16
  #define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17
  #define NGX_HTTP_V2_CONTENT_LENGTH_INDEX  28
  #define NGX_HTTP_V2_CONTENT_TYPE_INDEX    31
  #define NGX_HTTP_V2_DATE_INDEX            33
  #define NGX_HTTP_V2_LAST_MODIFIED_INDEX   44
  #define NGX_HTTP_V2_LOCATION_INDEX        46
  #define NGX_HTTP_V2_SERVER_INDEX          54
  #define NGX_HTTP_V2_USER_AGENT_INDEX      58
  #define NGX_HTTP_V2_VARY_INDEX            59
  
  #define NGX_HTTP_V2_NO_TRAILERS           (ngx_http_v2_out_frame_t *) -1
  
  
  typedef struct {
      ngx_str_t      name;
      u_char         index;
      ngx_uint_t     offset;
  } ngx_http_v2_push_header_t;
  
  
  static ngx_http_v2_push_header_t  ngx_http_v2_push_headers[] = {
      { ngx_string(":authority"), NGX_HTTP_V2_AUTHORITY_INDEX,
        offsetof(ngx_http_headers_in_t, host) },
  
      { ngx_string("accept-encoding"), NGX_HTTP_V2_ACCEPT_ENCODING_INDEX,
        offsetof(ngx_http_headers_in_t, accept_encoding) },
  
      { ngx_string("accept-language"), NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX,
        offsetof(ngx_http_headers_in_t, accept_language) },
  
      { ngx_string("user-agent"), NGX_HTTP_V2_USER_AGENT_INDEX,
        offsetof(ngx_http_headers_in_t, user_agent) },
  };
  
  #define NGX_HTTP_V2_PUSH_HEADERS                                              \
      (sizeof(ngx_http_v2_push_headers) / sizeof(ngx_http_v2_push_header_t))
  
  
  static ngx_int_t ngx_http_v2_push_resources(ngx_http_request_t *r);
  static ngx_int_t ngx_http_v2_push_resource(ngx_http_request_t *r,
      ngx_str_t *path, ngx_str_t *binary);
  
  static u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len,
      u_char *tmp, ngx_uint_t lower);
  static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix,
      ngx_uint_t value);
  static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame(
      ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin);
  static ngx_http_v2_out_frame_t *ngx_http_v2_create_push_frame(
      ngx_http_request_t *r, u_char *pos, u_char *end);
  static ngx_http_v2_out_frame_t *ngx_http_v2_create_trailers_frame(
      ngx_http_request_t *r);
  
  static ngx_chain_t *ngx_http_v2_send_chain(ngx_connection_t *fc,
      ngx_chain_t *in, off_t limit);
  
  static ngx_chain_t *ngx_http_v2_filter_get_shadow(
      ngx_http_v2_stream_t *stream, ngx_buf_t *buf, off_t offset, off_t size);
  static ngx_http_v2_out_frame_t *ngx_http_v2_filter_get_data_frame(
      ngx_http_v2_stream_t *stream, size_t len, ngx_chain_t *first,
      ngx_chain_t *last);
  
  static ngx_inline ngx_int_t ngx_http_v2_flow_control(
      ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream);
  static void ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c,
      ngx_http_v2_stream_t *stream);
  
  static ngx_inline ngx_int_t ngx_http_v2_filter_send(
      ngx_connection_t *fc, ngx_http_v2_stream_t *stream);
  
  static ngx_int_t ngx_http_v2_headers_frame_handler(
      ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame);
  static ngx_int_t ngx_http_v2_push_frame_handler(
      ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame);
  static ngx_int_t ngx_http_v2_data_frame_handler(
      ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame);
  static ngx_inline void ngx_http_v2_handle_frame(
      ngx_http_v2_stream_t *stream, ngx_http_v2_out_frame_t *frame);
  static ngx_inline void ngx_http_v2_handle_stream(
      ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream);
  
  static void ngx_http_v2_filter_cleanup(void *data);
  
  static ngx_int_t ngx_http_v2_filter_init(ngx_conf_t *cf);
  
  
  static ngx_http_module_t  ngx_http_v2_filter_module_ctx = {
      NULL,                                  /* preconfiguration */
      ngx_http_v2_filter_init,               /* postconfiguration */
  
      NULL,                                  /* create main configuration */
      NULL,                                  /* init main configuration */
  
      NULL,                                  /* create server configuration */
      NULL,                                  /* merge server configuration */
  
      NULL,                                  /* create location configuration */
      NULL                                   /* merge location configuration */
  };
  
  
  ngx_module_t  ngx_http_v2_filter_module = {
      NGX_MODULE_V1,
      &ngx_http_v2_filter_module_ctx,        /* module context */
      NULL,                                  /* module directives */
      NGX_HTTP_MODULE,                       /* module type */
      NULL,                                  /* init master */
      NULL,                                  /* init module */
      NULL,                                  /* init process */
      NULL,                                  /* init thread */
      NULL,                                  /* exit thread */
      NULL,                                  /* exit process */
      NULL,                                  /* exit master */
      NGX_MODULE_V1_PADDING
  };
  
  
  static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
  
  
  static ngx_int_t
  ngx_http_v2_header_filter(ngx_http_request_t *r)
  {
      u_char                     status, *pos, *start, *p, *tmp;
      size_t                     len, tmp_len;
      ngx_str_t                  host, location;
      ngx_uint_t                 i, port;
      ngx_list_part_t           *part;
      ngx_table_elt_t           *header;
      ngx_connection_t          *fc;
      ngx_http_cleanup_t        *cln;
      ngx_http_v2_stream_t      *stream;
      ngx_http_v2_out_frame_t   *frame;
      ngx_http_v2_connection_t  *h2c;
      ngx_http_core_loc_conf_t  *clcf;
      ngx_http_core_srv_conf_t  *cscf;
      u_char                     addr[NGX_SOCKADDR_STRLEN];
  
      static const u_char nginx[5] = "\x84\xaa\x63\x55\xe7";
  #if (NGX_HTTP_GZIP)
      static const u_char accept_encoding[12] =
          "\x8b\x84\x84\x2d\x69\x5b\x05\x44\x3c\x86\xaa\x6f";
  #endif
  
      static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER);
      static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)];
  
      static size_t nginx_ver_build_len =
                                    ngx_http_v2_literal_size(NGINX_VER_BUILD);
      static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)];
  
      stream = r->stream;
  
      if (!stream) {
          return ngx_http_next_header_filter(r);
      }
  
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "http2 header filter");
  
      if (r->header_sent) {
          return NGX_OK;
      }
  
      r->header_sent = 1;
  
      if (r != r->main) {
          return NGX_OK;
      }
  
      fc = r->connection;
  
      if (fc->error) {
          return NGX_ERROR;
      }
  
      if (r->method == NGX_HTTP_HEAD) {
          r->header_only = 1;
      }
  
      switch (r->headers_out.status) {
  
      case NGX_HTTP_OK:
          status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_200_INDEX);
          break;
  
      case NGX_HTTP_NO_CONTENT:
          r->header_only = 1;
  
          ngx_str_null(&r->headers_out.content_type);
  
          r->headers_out.content_length = NULL;
          r->headers_out.content_length_n = -1;
  
          r->headers_out.last_modified_time = -1;
          r->headers_out.last_modified = NULL;
  
          status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_204_INDEX);
          break;
  
      case NGX_HTTP_PARTIAL_CONTENT:
          status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_206_INDEX);
          break;
  
      case NGX_HTTP_NOT_MODIFIED:
          r->header_only = 1;
          status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_304_INDEX);
          break;
  
      default:
          r->headers_out.last_modified_time = -1;
          r->headers_out.last_modified = NULL;
  
          switch (r->headers_out.status) {
  
          case NGX_HTTP_BAD_REQUEST:
              status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_400_INDEX);
              break;
  
          case NGX_HTTP_NOT_FOUND:
              status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_404_INDEX);
              break;
  
          case NGX_HTTP_INTERNAL_SERVER_ERROR:
              status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_500_INDEX);
              break;
  
          default:
              status = 0;
          }
      }
  
      h2c = stream->connection;
  
      if (!h2c->push_disabled && !h2c->goaway
          && stream->node->id % 2 == 1
          && r->method != NGX_HTTP_HEAD)
      {
          if (ngx_http_v2_push_resources(r) != NGX_OK) {
              return NGX_ERROR;
          }
      }
  
      len = h2c->table_update ? 1 : 0;
  
      len += status ? 1 : 1 + ngx_http_v2_literal_size("418");
  
      clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
  
      if (r->headers_out.server == NULL) {
  
          if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
              len += 1 + nginx_ver_len;
  
          } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
              len += 1 + nginx_ver_build_len;
  
          } else {
              len += 1 + sizeof(nginx);
          }
      }
  
      if (r->headers_out.date == NULL) {
          len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT");
      }
  
      if (r->headers_out.content_type.len) {
          len += 1 + NGX_HTTP_V2_INT_OCTETS + r->headers_out.content_type.len;
  
          if (r->headers_out.content_type_len == r->headers_out.content_type.len
              && r->headers_out.charset.len)
          {
              len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
          }
      }
  
      if (r->headers_out.content_length == NULL
          && r->headers_out.content_length_n >= 0)
      {
          len += 1 + ngx_http_v2_integer_octets(NGX_OFF_T_LEN) + NGX_OFF_T_LEN;
      }
  
      if (r->headers_out.last_modified == NULL
          && r->headers_out.last_modified_time != -1)
      {
          len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT");
      }
  
      if (r->headers_out.location && r->headers_out.location->value.len) {
  
          if (r->headers_out.location->value.data[0] == '/'
              && clcf->absolute_redirect)
          {
              if (clcf->server_name_in_redirect) {
                  cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
                  host = cscf->server_name;
  
              } else if (r->headers_in.server.len) {
                  host = r->headers_in.server;
  
              } else {
                  host.len = NGX_SOCKADDR_STRLEN;
                  host.data = addr;
  
                  if (ngx_connection_local_sockaddr(fc, &host, 0) != NGX_OK) {
                      return NGX_ERROR;
                  }
              }
  
              port = ngx_inet_get_port(fc->local_sockaddr);
  
              location.len = sizeof("https://") - 1 + host.len
                             + r->headers_out.location->value.len;
  
              if (clcf->port_in_redirect) {
  
  #if (NGX_HTTP_SSL)
                  if (fc->ssl)
                      port = (port == 443) ? 0 : port;
                  else
  #endif
                      port = (port == 80) ? 0 : port;
  
              } else {
                  port = 0;
              }
  
              if (port) {
                  location.len += sizeof(":65535") - 1;
              }
  
              location.data = ngx_pnalloc(r->pool, location.len);
              if (location.data == NULL) {
                  return NGX_ERROR;
              }
  
              p = ngx_cpymem(location.data, "http", sizeof("http") - 1);
  
  #if (NGX_HTTP_SSL)
              if (fc->ssl) {
                  *p++ = 's';
              }
  #endif
  
              *p++ = ':'; *p++ = '/'; *p++ = '/';
              p = ngx_cpymem(p, host.data, host.len);
  
              if (port) {
                  p = ngx_sprintf(p, ":%ui", port);
              }
  
              p = ngx_cpymem(p, r->headers_out.location->value.data,
                                r->headers_out.location->value.len);
  
              /* update r->headers_out.location->value for possible logging */
  
              r->headers_out.location->value.len = p - location.data;
              r->headers_out.location->value.data = location.data;
              ngx_str_set(&r->headers_out.location->key, "Location");
          }
  
          r->headers_out.location->hash = 0;
  
          len += 1 + NGX_HTTP_V2_INT_OCTETS + r->headers_out.location->value.len;
      }
  
      tmp_len = len;
  
  #if (NGX_HTTP_GZIP)
      if (r->gzip_vary) {
          if (clcf->gzip_vary) {
              len += 1 + sizeof(accept_encoding);
  
          } else {
              r->gzip_vary = 0;
          }
      }
  #endif
  
      part = &r->headers_out.headers.part;
      header = part->elts;
  
      for (i = 0; /* void */; i++) {
  
          if (i >= part->nelts) {
              if (part->next == NULL) {
                  break;
              }
  
              part = part->next;
              header = part->elts;
              i = 0;
          }
  
          if (header[i].hash == 0) {
              continue;
          }
  
          if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) {
              ngx_log_error(NGX_LOG_CRIT, fc->log, 0,
                            "too long response header name: \"%V\"",
                            &header[i].key);
              return NGX_ERROR;
          }
  
          if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) {
              ngx_log_error(NGX_LOG_CRIT, fc->log, 0,
                            "too long response header value: \"%V: %V\"",
                            &header[i].key, &header[i].value);
              return NGX_ERROR;
          }
  
          len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len
                   + NGX_HTTP_V2_INT_OCTETS + header[i].value.len;
  
          if (header[i].key.len > tmp_len) {
              tmp_len = header[i].key.len;
          }
  
          if (header[i].value.len > tmp_len) {
              tmp_len = header[i].value.len;
          }
      }
  
      tmp = ngx_palloc(r->pool, tmp_len);
      pos = ngx_pnalloc(r->pool, len);
  
      if (pos == NULL || tmp == NULL) {
          return NGX_ERROR;
      }
  
      start = pos;
  
      if (h2c->table_update) {
          ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                         "http2 table size update: 0");
          *pos++ = (1 << 5) | 0;
          h2c->table_update = 0;
      }
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                     "http2 output header: \":status: %03ui\"",
                     r->headers_out.status);
  
      if (status) {
          *pos++ = status;
  
      } else {
          *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX);
          *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3;
          pos = ngx_sprintf(pos, "%03ui", r->headers_out.status);
      }
  
      if (r->headers_out.server == NULL) {
  
          if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
              ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                             "http2 output header: \"server: %s\"",
                             NGINX_VER);
  
          } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
              ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                             "http2 output header: \"server: %s\"",
                             NGINX_VER_BUILD);
  
          } else {
              ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                             "http2 output header: \"server: nginx\"");
          }
  
          *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX);
  
          if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
              if (nginx_ver[0] == '\0') {
                  p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER,
                                              sizeof(NGINX_VER) - 1, tmp);
                  nginx_ver_len = p - nginx_ver;
              }
  
              pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len);
  
          } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
              if (nginx_ver_build[0] == '\0') {
                  p = ngx_http_v2_write_value(nginx_ver_build,
                                              (u_char *) NGINX_VER_BUILD,
                                              sizeof(NGINX_VER_BUILD) - 1, tmp);
                  nginx_ver_build_len = p - nginx_ver_build;
              }
  
              pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len);
  
          } else {
              pos = ngx_cpymem(pos, nginx, sizeof(nginx));
          }
      }
  
      if (r->headers_out.date == NULL) {
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                         "http2 output header: \"date: %V\"",
                         &ngx_cached_http_time);
  
          *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX);
          pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data,
                                        ngx_cached_http_time.len, tmp);
      }
  
      if (r->headers_out.content_type.len) {
          *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX);
  
          if (r->headers_out.content_type_len == r->headers_out.content_type.len
              && r->headers_out.charset.len)
          {
              len = r->headers_out.content_type.len + sizeof("; charset=") - 1
                    + r->headers_out.charset.len;
  
              p = ngx_pnalloc(r->pool, len);
              if (p == NULL) {
                  return NGX_ERROR;
              }
  
              p = ngx_cpymem(p, r->headers_out.content_type.data,
                             r->headers_out.content_type.len);
  
              p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1);
  
              p = ngx_cpymem(p, r->headers_out.charset.data,
                             r->headers_out.charset.len);
  
              /* updated r->headers_out.content_type is also needed for logging */
  
              r->headers_out.content_type.len = len;
              r->headers_out.content_type.data = p - len;
          }
  
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                         "http2 output header: \"content-type: %V\"",
                         &r->headers_out.content_type);
  
          pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data,
                                        r->headers_out.content_type.len, tmp);
      }
  
      if (r->headers_out.content_length == NULL
          && r->headers_out.content_length_n >= 0)
      {
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                         "http2 output header: \"content-length: %O\"",
                         r->headers_out.content_length_n);
  
          *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX);
  
          p = pos;
          pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n);
          *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1);
      }
  
      if (r->headers_out.last_modified == NULL
          && r->headers_out.last_modified_time != -1)
      {
          *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX);
  
          ngx_http_time(pos, r->headers_out.last_modified_time);
          len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1;
  
          ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                         "http2 output header: \"last-modified: %*s\"",
                         len, pos);
  
          /*
           * Date will always be encoded using huffman in the temporary buffer,
           * so it's safe here to use src and dst pointing to the same address.
           */
          pos = ngx_http_v2_write_value(pos, pos, len, tmp);
      }
  
      if (r->headers_out.location && r->headers_out.location->value.len) {
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                         "http2 output header: \"location: %V\"",
                         &r->headers_out.location->value);
  
          *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX);
          pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data,
                                        r->headers_out.location->value.len, tmp);
      }
  
  #if (NGX_HTTP_GZIP)
      if (r->gzip_vary) {
          ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                         "http2 output header: \"vary: Accept-Encoding\"");
  
          *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX);
          pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding));
      }
  #endif
  
      part = &r->headers_out.headers.part;
      header = part->elts;
  
      for (i = 0; /* void */; i++) {
  
          if (i >= part->nelts) {
              if (part->next == NULL) {
                  break;
              }
  
              part = part->next;
              header = part->elts;
              i = 0;
          }
  
          if (header[i].hash == 0) {
              continue;
          }
  
  #if (NGX_DEBUG)
          if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) {
              ngx_strlow(tmp, header[i].key.data, header[i].key.len);
  
              ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                             "http2 output header: \"%*s: %V\"",
                             header[i].key.len, tmp, &header[i].value);
          }
  #endif
  
          *pos++ = 0;
  
          pos = ngx_http_v2_write_name(pos, header[i].key.data,
                                       header[i].key.len, tmp);
  
          pos = ngx_http_v2_write_value(pos, header[i].value.data,
                                        header[i].value.len, tmp);
      }
  
      frame = ngx_http_v2_create_headers_frame(r, start, pos, r->header_only);
      if (frame == NULL) {
          return NGX_ERROR;
      }
  
      ngx_http_v2_queue_blocked_frame(h2c, frame);
  
      stream->queued++;
  
      cln = ngx_http_cleanup_add(r, 0);
      if (cln == NULL) {
          return NGX_ERROR;
      }
  
      cln->handler = ngx_http_v2_filter_cleanup;
      cln->data = stream;
  
      fc->send_chain = ngx_http_v2_send_chain;
      fc->need_last_buf = 1;
  
      return ngx_http_v2_filter_send(fc, stream);
  }
  
  
  static ngx_int_t
  ngx_http_v2_push_resources(ngx_http_request_t *r)
  {
      u_char                     *start, *end, *last;
      ngx_int_t                   rc;
      ngx_str_t                   path;
      ngx_uint_t                  i, push;
      ngx_table_elt_t           **h;
      ngx_http_v2_loc_conf_t     *h2lcf;
      ngx_http_complex_value_t   *pushes;
      ngx_str_t                   binary[NGX_HTTP_V2_PUSH_HEADERS];
  
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "http2 push resources");
  
      ngx_memzero(binary, NGX_HTTP_V2_PUSH_HEADERS * sizeof(ngx_str_t));
  
      h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module);
  
      if (h2lcf->pushes) {
          pushes = h2lcf->pushes->elts;
  
          for (i = 0; i < h2lcf->pushes->nelts; i++) {
  
              if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) {
                  return NGX_ERROR;
              }
  
              if (path.len == 0) {
                  continue;
              }
  
              if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) {
                  continue;
              }
  
              rc = ngx_http_v2_push_resource(r, &path, binary);
  
              if (rc == NGX_ERROR) {
                  return NGX_ERROR;
              }
  
              if (rc == NGX_ABORT) {
                  return NGX_OK;
              }
  
              /* NGX_OK, NGX_DECLINED */
          }
      }
  
      if (!h2lcf->push_preload) {
          return NGX_OK;
      }
  
      h = r->headers_out.link.elts;
  
      for (i = 0; i < r->headers_out.link.nelts; i++) {
  
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                         "http2 parse link: \"%V\"", &h[i]->value);
  
          start = h[i]->value.data;
          end = h[i]->value.data + h[i]->value.len;
  
      next_link:
  
          while (start < end && *start == ' ') { start++; }
  
          if (start == end || *start++ != '<') {
              continue;
          }
  
          while (start < end && *start == ' ') { start++; }
  
          for (last = start; last < end && *last != '>'; last++) {
              /* void */
          }
  
          if (last == start || last == end) {
              continue;
          }
  
          path.len = last - start;
          path.data = start;
  
          start = last + 1;
  
          while (start < end && *start == ' ') { start++; }
  
          if (start == end) {
              continue;
          }
  
          if (*start == ',') {
              start++;
              goto next_link;
          }
  
          if (*start++ != ';') {
              continue;
          }
  
          last = ngx_strlchr(start, end, ',');
  
          if (last == NULL) {
              last = end;
          }
  
          push = 0;
  
          for ( ;; ) {
  
              while (start < last && *start == ' ') { start++; }
  
              if (last - start >= 6
                  && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0)
              {
                  start += 6;
  
                  if (start == last || *start == ' ' || *start == ';') {
                      push = 0;
                      break;
                  }
  
                  goto next_param;
              }
  
              if (last - start >= 11
                  && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0)
              {
                  start += 11;
  
                  if (start == last || *start == ' ' || *start == ';') {
                      push = 1;
                  }
  
                  goto next_param;
              }
  
              if (last - start >= 4
                  && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0)
              {
                  start += 4;
  
                  while (start < last && *start == ' ') { start++; }
  
                  if (start == last || *start++ != '"') {
                      goto next_param;
                  }
  
                  for ( ;; ) {
  
                      while (start < last && *start == ' ') { start++; }
  
                      if (last - start >= 7
                          && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0)
                      {
                          start += 7;
  
                          if (start < last && (*start == ' ' || *start == '"')) {
                              push = 1;
                              break;
                          }
                      }
  
                      while (start < last && *start != ' ' && *start != '"') {
                          start++;
                      }
  
                      if (start == last) {
                          break;
                      }
  
                      if (*start == '"') {
                          break;
                      }
  
                      start++;
                  }
              }
  
          next_param:
  
              start = ngx_strlchr(start, last, ';');
  
              if (start == NULL) {
                  break;
              }
  
              start++;
          }
  
          if (push) {
              while (path.len && path.data[path.len - 1] == ' ') {
                  path.len--;
              }
          }
  
          if (push && path.len
              && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/'))
          {
              rc = ngx_http_v2_push_resource(r, &path, binary);
  
              if (rc == NGX_ERROR) {
                  return NGX_ERROR;
              }
  
              if (rc == NGX_ABORT) {
                  return NGX_OK;
              }
  
              /* NGX_OK, NGX_DECLINED */
          }
  
          if (last < end) {
              start = last + 1;
              goto next_link;
          }
      }
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
      ngx_str_t *binary)
  {
      u_char                      *start, *pos, *tmp;
      size_t                       len;
      ngx_str_t                   *value;
      ngx_uint_t                   i;
      ngx_table_elt_t            **h;
      ngx_connection_t            *fc;
      ngx_http_v2_stream_t        *stream;
      ngx_http_v2_out_frame_t     *frame;
      ngx_http_v2_connection_t    *h2c;
      ngx_http_v2_push_header_t   *ph;
  
      fc = r->connection;
  
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push resource");
  
      stream = r->stream;
      h2c = stream->connection;
  
      if (!ngx_path_separator(path->data[0])) {
          ngx_log_error(NGX_LOG_WARN, fc->log, 0,
                        "non-absolute path \"%V\" not pushed", path);
          return NGX_DECLINED;
      }
  
      ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
                     "http2 pushing:%ui limit:%ui",
                     h2c->pushing, h2c->concurrent_pushes);
  
      if (h2c->pushing >= h2c->concurrent_pushes) {
          return NGX_ABORT;
      }
  
      if (h2c->last_push == 0x7ffffffe) {
          return NGX_ABORT;
      }
  
      if (path->len > NGX_HTTP_V2_MAX_FIELD) {
          return NGX_DECLINED;
      }
  
      if (r->headers_in.host == NULL) {
          return NGX_ABORT;
      }
  
      ph = ngx_http_v2_push_headers;
  
      if (binary[0].len) {
          tmp = ngx_palloc(r->pool, path->len);
          if (tmp == NULL) {
              return NGX_ERROR;
          }
  
      } else {
          len = path->len;
  
          for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
              h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset);
  
              if (*h) {
                  len = ngx_max(len, (*h)->value.len);
              }
          }
  
          tmp = ngx_palloc(r->pool, len);
          if (tmp == NULL) {
              return NGX_ERROR;
          }
  
          for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
              h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset);
  
              if (*h == NULL) {
                  continue;
              }
  
              value = &(*h)->value;
  
              len = 1 + NGX_HTTP_V2_INT_OCTETS + value->len;
  
              pos = ngx_pnalloc(r->pool, len);
              if (pos == NULL) {
                  return NGX_ERROR;
              }
  
              binary[i].data = pos;
  
              *pos++ = ngx_http_v2_inc_indexed(ph[i].index);
              pos = ngx_http_v2_write_value(pos, value->data, value->len, tmp);
  
              binary[i].len = pos - binary[i].data;
          }
      }
  
      len = (h2c->table_update ? 1 : 0)
            + 1
            + 1 + NGX_HTTP_V2_INT_OCTETS + path->len
            + 1;
  
      for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
          len += binary[i].len;
      }
  
      pos = ngx_pnalloc(r->pool, len);
      if (pos == NULL) {
          return NGX_ERROR;
      }
  
      start = pos;
  
      if (h2c->table_update) {
          ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                         "http2 table size update: 0");
          *pos++ = (1 << 5) | 0;
          h2c->table_update = 0;
      }
  
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                     "http2 push header: \":method: GET\"");
  
      *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_METHOD_GET_INDEX);
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                     "http2 push header: \":path: %V\"", path);
  
      *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX);
      pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp);
  
  #if (NGX_HTTP_SSL)
      if (fc->ssl) {
          ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                         "http2 push header: \":scheme: https\"");
          *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTPS_INDEX);
  
      } else
  #endif
      {
          ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                         "http2 push header: \":scheme: http\"");
          *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX);
      }
  
      for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
          h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset);
  
          if (*h == NULL) {
              continue;
          }
  
          ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                         "http2 push header: \"%V: %V\"",
                         &ph[i].name, &(*h)->value);
  
          pos = ngx_cpymem(pos, binary[i].data, binary[i].len);
      }
  
      frame = ngx_http_v2_create_push_frame(r, start, pos);
      if (frame == NULL) {
          return NGX_ERROR;
      }
  
      ngx_http_v2_queue_blocked_frame(h2c, frame);
  
      stream->queued++;
  
      stream = ngx_http_v2_push_stream(stream, path);
  
      if (stream) {
          stream->request->request_length = pos - start;
          return NGX_OK;
      }
  
      return NGX_ERROR;
  }
  
  
  static u_char *
  ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp,
      ngx_uint_t lower)
  {
      size_t  hlen;
  
      hlen = ngx_http_v2_huff_encode(src, len, tmp, lower);
  
      if (hlen > 0) {
          *dst = NGX_HTTP_V2_ENCODE_HUFF;
          dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), hlen);
          return ngx_cpymem(dst, tmp, hlen);
      }
  
      *dst = NGX_HTTP_V2_ENCODE_RAW;
      dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), len);
  
      if (lower) {
          ngx_strlow(dst, src, len);
          return dst + len;
      }
  
      return ngx_cpymem(dst, src, len);
  }
  
  
  static u_char *
  ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value)
  {
      if (value < prefix) {
          *pos++ |= value;
          return pos;
      }
  
      *pos++ |= prefix;
      value -= prefix;
  
      while (value >= 128) {
          *pos++ = value % 128 + 128;
          value /= 128;
      }
  
      *pos++ = (u_char) value;
  
      return pos;
  }
  
  
  static ngx_http_v2_out_frame_t *
  ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos,
      u_char *end, ngx_uint_t fin)
  {
      u_char                    type, flags;
      size_t                    rest, frame_size;
      ngx_buf_t                *b;
      ngx_chain_t              *cl, **ll;
      ngx_http_v2_stream_t     *stream;
      ngx_http_v2_out_frame_t  *frame;
  
      stream = r->stream;
      rest = end - pos;
  
      frame = ngx_palloc(r->pool, sizeof(ngx_http_v2_out_frame_t));
      if (frame == NULL) {
          return NULL;
      }
  
      frame->handler = ngx_http_v2_headers_frame_handler;
      frame->stream = stream;
      frame->length = rest;
      frame->blocked = 1;
      frame->fin = fin;
  
      ll = &frame->first;
  
      type = NGX_HTTP_V2_HEADERS_FRAME;
      flags = fin ? NGX_HTTP_V2_END_STREAM_FLAG : NGX_HTTP_V2_NO_FLAG;
      frame_size = stream->connection->frame_size;
  
      for ( ;; ) {
          if (rest <= frame_size) {
              frame_size = rest;
              flags |= NGX_HTTP_V2_END_HEADERS_FLAG;
          }
  
          b = ngx_create_temp_buf(r->pool, NGX_HTTP_V2_FRAME_HEADER_SIZE);
          if (b == NULL) {
              return NULL;
          }
  
          b->last = ngx_http_v2_write_len_and_type(b->last, frame_size, type);
          *b->last++ = flags;
          b->last = ngx_http_v2_write_sid(b->last, stream->node->id);
  
          b->tag = (ngx_buf_tag_t) &ngx_http_v2_module;
  
          cl = ngx_alloc_chain_link(r->pool);
          if (cl == NULL) {
              return NULL;
          }
  
          cl->buf = b;
  
          *ll = cl;
          ll = &cl->next;
  
          b = ngx_calloc_buf(r->pool);
          if (b == NULL) {
              return NULL;
          }
  
          b->pos = pos;
  
          pos += frame_size;
  
          b->last = pos;
          b->start = b->pos;
          b->end = b->last;
          b->temporary = 1;
  
          cl = ngx_alloc_chain_link(r->pool);
          if (cl == NULL) {
              return NULL;
          }
  
          cl->buf = b;
  
          *ll = cl;
          ll = &cl->next;
  
          rest -= frame_size;
  
          if (rest) {
              frame->length += NGX_HTTP_V2_FRAME_HEADER_SIZE;
  
              type = NGX_HTTP_V2_CONTINUATION_FRAME;
              flags = NGX_HTTP_V2_NO_FLAG;
              continue;
          }
  
          b->last_buf = fin;
          cl->next = NULL;
          frame->last = cl;
  
          ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                         "http2:%ui create HEADERS frame %p: len:%uz",
                         stream->node->id, frame, frame->length);
  
          return frame;
      }
  }
  
  
  static ngx_http_v2_out_frame_t *
  ngx_http_v2_create_push_frame(ngx_http_request_t *r, u_char *pos, u_char *end)
  {
      u_char                     type, flags;
      size_t                     rest, frame_size, len;
      ngx_buf_t                 *b;
      ngx_chain_t               *cl, **ll;
      ngx_http_v2_stream_t      *stream;
      ngx_http_v2_out_frame_t   *frame;
      ngx_http_v2_connection_t  *h2c;
  
      stream = r->stream;
      h2c = stream->connection;
      rest = NGX_HTTP_V2_STREAM_ID_SIZE + (end - pos);
  
      frame = ngx_palloc(r->pool, sizeof(ngx_http_v2_out_frame_t));
      if (frame == NULL) {
          return NULL;
      }
  
      frame->handler = ngx_http_v2_push_frame_handler;
      frame->stream = stream;
      frame->length = rest;
      frame->blocked = 1;
      frame->fin = 0;
  
      ll = &frame->first;
  
      type = NGX_HTTP_V2_PUSH_PROMISE_FRAME;
      flags = NGX_HTTP_V2_NO_FLAG;
      frame_size = h2c->frame_size;
  
      for ( ;; ) {
          if (rest <= frame_size) {
              frame_size = rest;
              flags |= NGX_HTTP_V2_END_HEADERS_FLAG;
          }
  
          b = ngx_create_temp_buf(r->pool,
                                  NGX_HTTP_V2_FRAME_HEADER_SIZE
                                  + ((type == NGX_HTTP_V2_PUSH_PROMISE_FRAME)
                                     ? NGX_HTTP_V2_STREAM_ID_SIZE : 0));
          if (b == NULL) {
              return NULL;
          }
  
          b->last = ngx_http_v2_write_len_and_type(b->last, frame_size, type);
          *b->last++ = flags;
          b->last = ngx_http_v2_write_sid(b->last, stream->node->id);
  
          b->tag = (ngx_buf_tag_t) &ngx_http_v2_module;
  
          if (type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) {
              h2c->last_push += 2;
  
              b->last = ngx_http_v2_write_sid(b->last, h2c->last_push);
              len = frame_size - NGX_HTTP_V2_STREAM_ID_SIZE;
  
          } else {
              len = frame_size;
          }
  
          cl = ngx_alloc_chain_link(r->pool);
          if (cl == NULL) {
              return NULL;
          }
  
          cl->buf = b;
  
          *ll = cl;
          ll = &cl->next;
  
          b = ngx_calloc_buf(r->pool);
          if (b == NULL) {
              return NULL;
          }
  
          b->pos = pos;
  
          pos += len;
  
          b->last = pos;
          b->start = b->pos;
          b->end = b->last;
          b->temporary = 1;
  
          cl = ngx_alloc_chain_link(r->pool);
          if (cl == NULL) {
              return NULL;
          }
  
          cl->buf = b;
  
          *ll = cl;
          ll = &cl->next;
  
          rest -= frame_size;
  
          if (rest) {
              frame->length += NGX_HTTP_V2_FRAME_HEADER_SIZE;
  
              type = NGX_HTTP_V2_CONTINUATION_FRAME;
              continue;
          }
  
          cl->next = NULL;
          frame->last = cl;
  
          ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                         "http2:%ui create PUSH_PROMISE frame %p: "
                         "sid:%ui len:%uz",
                         stream->node->id, frame, h2c->last_push,
                         frame->length);
  
          return frame;
      }
  }
  
  
  static ngx_http_v2_out_frame_t *
  ngx_http_v2_create_trailers_frame(ngx_http_request_t *r)
  {
      u_char            *pos, *start, *tmp;
      size_t             len, tmp_len;
      ngx_uint_t         i;
      ngx_list_part_t   *part;
      ngx_table_elt_t   *header;
      ngx_connection_t  *fc;
  
      fc = r->connection;
      len = 0;
      tmp_len = 0;
  
      part = &r->headers_out.trailers.part;
      header = part->elts;
  
      for (i = 0; /* void */; i++) {
  
          if (i >= part->nelts) {
              if (part->next == NULL) {
                  break;
              }
  
              part = part->next;
              header = part->elts;
              i = 0;
          }
  
          if (header[i].hash == 0) {
              continue;
          }
  
          if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) {
              ngx_log_error(NGX_LOG_CRIT, fc->log, 0,
                            "too long response trailer name: \"%V\"",
                            &header[i].key);
              return NULL;
          }
  
          if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) {
              ngx_log_error(NGX_LOG_CRIT, fc->log, 0,
                            "too long response trailer value: \"%V: %V\"",
                            &header[i].key, &header[i].value);
              return NULL;
          }
  
          len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len
                   + NGX_HTTP_V2_INT_OCTETS + header[i].value.len;
  
          if (header[i].key.len > tmp_len) {
              tmp_len = header[i].key.len;
          }
  
          if (header[i].value.len > tmp_len) {
              tmp_len = header[i].value.len;
          }
      }
  
      if (len == 0) {
          return NGX_HTTP_V2_NO_TRAILERS;
      }
  
      tmp = ngx_palloc(r->pool, tmp_len);
      pos = ngx_pnalloc(r->pool, len);
  
      if (pos == NULL || tmp == NULL) {
          return NULL;
      }
  
      start = pos;
  
      part = &r->headers_out.trailers.part;
      header = part->elts;
  
      for (i = 0; /* void */; i++) {
  
          if (i >= part->nelts) {
              if (part->next == NULL) {
                  break;
              }
  
              part = part->next;
              header = part->elts;
              i = 0;
          }
  
          if (header[i].hash == 0) {
              continue;
          }
  
  #if (NGX_DEBUG)
          if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) {
              ngx_strlow(tmp, header[i].key.data, header[i].key.len);
  
              ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0,
                             "http2 output trailer: \"%*s: %V\"",
                             header[i].key.len, tmp, &header[i].value);
          }
  #endif
  
          *pos++ = 0;
  
          pos = ngx_http_v2_write_name(pos, header[i].key.data,
                                       header[i].key.len, tmp);
  
          pos = ngx_http_v2_write_value(pos, header[i].value.data,
                                        header[i].value.len, tmp);
      }
  
      return ngx_http_v2_create_headers_frame(r, start, pos, 1);
  }
  
  
  static ngx_chain_t *
  ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit)
  {
      off_t                      size, offset;
      size_t                     rest, frame_size;
      ngx_chain_t               *cl, *out, **ln;
      ngx_http_request_t        *r;
      ngx_http_v2_stream_t      *stream;
      ngx_http_v2_loc_conf_t    *h2lcf;
      ngx_http_v2_out_frame_t   *frame, *trailers;
      ngx_http_v2_connection_t  *h2c;
  
      r = fc->data;
      stream = r->stream;
  
  #if (NGX_SUPPRESS_WARN)
      size = 0;
  #endif
  
      while (in) {
          size = ngx_buf_size(in->buf);
  
          if (size || in->buf->last_buf) {
              break;
          }
  
          in = in->next;
      }
  
      if (in == NULL) {
  
          if (stream->queued) {
              fc->write->active = 1;
              fc->write->ready = 0;
  
          } else {
              fc->buffered &= ~NGX_HTTP_V2_BUFFERED;
          }
  
          return NULL;
      }
  
      h2c = stream->connection;
  
      if (size && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) {
          fc->write->active = 1;
          fc->write->ready = 0;
          return in;
      }
  
      if (in->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) {
          cl = ngx_alloc_chain_link(r->pool);
          if (cl == NULL) {
              return NGX_CHAIN_ERROR;
          }
  
          cl->buf = in->buf;
          in->buf = cl->buf->shadow;
  
          offset = ngx_buf_in_memory(in->buf)
                   ? (cl->buf->pos - in->buf->pos)
                   : (cl->buf->file_pos - in->buf->file_pos);
  
          cl->next = stream->free_bufs;
          stream->free_bufs = cl;
  
      } else {
          offset = 0;
      }
  
      if (limit == 0 || limit > (off_t) h2c->send_window) {
          limit = h2c->send_window;
      }
  
      if (limit > stream->send_window) {
          limit = (stream->send_window > 0) ? stream->send_window : 0;
      }
  
      h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module);
  
      frame_size = (h2lcf->chunk_size < h2c->frame_size)
                   ? h2lcf->chunk_size : h2c->frame_size;
  
      trailers = NGX_HTTP_V2_NO_TRAILERS;
  
  #if (NGX_SUPPRESS_WARN)
      cl = NULL;
  #endif
  
      for ( ;; ) {
          if ((off_t) frame_size > limit) {
              frame_size = (size_t) limit;
          }
  
          ln = &out;
          rest = frame_size;
  
          while ((off_t) rest >= size) {
  
              if (offset) {
                  cl = ngx_http_v2_filter_get_shadow(stream, in->buf,
                                                     offset, size);
                  if (cl == NULL) {
                      return NGX_CHAIN_ERROR;
                  }
  
                  offset = 0;
  
              } else {
                  cl = ngx_alloc_chain_link(r->pool);
                  if (cl == NULL) {
                      return NGX_CHAIN_ERROR;
                  }
  
                  cl->buf = in->buf;
              }
  
              *ln = cl;
              ln = &cl->next;
  
              rest -= (size_t) size;
              in = in->next;
  
              if (in == NULL) {
                  frame_size -= rest;
                  rest = 0;
                  break;
              }
  
              size = ngx_buf_size(in->buf);
          }
  
          if (rest) {
              cl = ngx_http_v2_filter_get_shadow(stream, in->buf, offset, rest);
              if (cl == NULL) {
                  return NGX_CHAIN_ERROR;
              }
  
              cl->buf->flush = 0;
              cl->buf->last_buf = 0;
  
              *ln = cl;
  
              offset += rest;
              size -= rest;
          }
  
          if (cl->buf->last_buf) {
              trailers = ngx_http_v2_create_trailers_frame(r);
              if (trailers == NULL) {
                  return NGX_CHAIN_ERROR;
              }
  
              if (trailers != NGX_HTTP_V2_NO_TRAILERS) {
                  cl->buf->last_buf = 0;
              }
          }
  
          if (frame_size || cl->buf->last_buf) {
              frame = ngx_http_v2_filter_get_data_frame(stream, frame_size,
                                                        out, cl);
              if (frame == NULL) {
                  return NGX_CHAIN_ERROR;
              }
  
              ngx_http_v2_queue_frame(h2c, frame);
  
              h2c->send_window -= frame_size;
  
              stream->send_window -= frame_size;
              stream->queued++;
          }
  
          if (in == NULL) {
  
              if (trailers != NGX_HTTP_V2_NO_TRAILERS) {
                  ngx_http_v2_queue_frame(h2c, trailers);
                  stream->queued++;
              }
  
              break;
          }
  
          limit -= frame_size;
  
          if (limit == 0) {
              break;
          }
      }
  
      if (offset) {
          cl = ngx_http_v2_filter_get_shadow(stream, in->buf, offset, size);
          if (cl == NULL) {
              return NGX_CHAIN_ERROR;
          }
  
          in->buf = cl->buf;
          ngx_free_chain(r->pool, cl);
      }
  
      if (ngx_http_v2_filter_send(fc, stream) == NGX_ERROR) {
          return NGX_CHAIN_ERROR;
      }
  
      if (in && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) {
          fc->write->active = 1;
          fc->write->ready = 0;
      }
  
      return in;
  }
  
  
  static ngx_chain_t *
  ngx_http_v2_filter_get_shadow(ngx_http_v2_stream_t *stream, ngx_buf_t *buf,
      off_t offset, off_t size)
  {
      ngx_buf_t    *chunk;
      ngx_chain_t  *cl;
  
      cl = ngx_chain_get_free_buf(stream->request->pool, &stream->free_bufs);
      if (cl == NULL) {
          return NULL;
      }
  
      chunk = cl->buf;
  
      ngx_memcpy(chunk, buf, sizeof(ngx_buf_t));
  
      chunk->tag = (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow;
      chunk->shadow = buf;
  
      if (ngx_buf_in_memory(chunk)) {
          chunk->pos += offset;
          chunk->last = chunk->pos + size;
      }
  
      if (chunk->in_file) {
          chunk->file_pos += offset;
          chunk->file_last = chunk->file_pos + size;
      }
  
      return cl;
  }
  
  
  static ngx_http_v2_out_frame_t *
  ngx_http_v2_filter_get_data_frame(ngx_http_v2_stream_t *stream,
      size_t len, ngx_chain_t *first, ngx_chain_t *last)
  {
      u_char                    flags;
      ngx_buf_t                *buf;
      ngx_chain_t              *cl;
      ngx_http_v2_out_frame_t  *frame;
  
      frame = stream->free_frames;
  
      if (frame) {
          stream->free_frames = frame->next;
  
      } else {
          frame = ngx_palloc(stream->request->pool,
                             sizeof(ngx_http_v2_out_frame_t));
          if (frame == NULL) {
              return NULL;
          }
      }
  
      flags = last->buf->last_buf ? NGX_HTTP_V2_END_STREAM_FLAG : 0;
  
      ngx_log_debug4(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0,
                     "http2:%ui create DATA frame %p: len:%uz flags:%ui",
                     stream->node->id, frame, len, (ngx_uint_t) flags);
  
      cl = ngx_chain_get_free_buf(stream->request->pool,
                                  &stream->free_frame_headers);
      if (cl == NULL) {
          return NULL;
      }
  
      buf = cl->buf;
  
      if (buf->start == NULL) {
          buf->start = ngx_palloc(stream->request->pool,
                                  NGX_HTTP_V2_FRAME_HEADER_SIZE);
          if (buf->start == NULL) {
              return NULL;
          }
  
          buf->end = buf->start + NGX_HTTP_V2_FRAME_HEADER_SIZE;
          buf->last = buf->end;
  
          buf->tag = (ngx_buf_tag_t) &ngx_http_v2_module;
          buf->memory = 1;
      }
  
      buf->pos = buf->start;
      buf->last = buf->pos;
  
      buf->last = ngx_http_v2_write_len_and_type(buf->last, len,
                                                 NGX_HTTP_V2_DATA_FRAME);
      *buf->last++ = flags;
  
      buf->last = ngx_http_v2_write_sid(buf->last, stream->node->id);
  
      cl->next = first;
      first = cl;
  
      last->buf->flush = 1;
  
      frame->first = first;
      frame->last = last;
      frame->handler = ngx_http_v2_data_frame_handler;
      frame->stream = stream;
      frame->length = len;
      frame->blocked = 0;
      frame->fin = last->buf->last_buf;
  
      return frame;
  }
  
  
  static ngx_inline ngx_int_t
  ngx_http_v2_flow_control(ngx_http_v2_connection_t *h2c,
      ngx_http_v2_stream_t *stream)
  {
      ngx_log_debug3(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
                     "http2:%ui windows: conn:%uz stream:%z",
                     stream->node->id, h2c->send_window, stream->send_window);
  
      if (stream->send_window <= 0) {
          stream->exhausted = 1;
          return NGX_DECLINED;
      }
  
      if (h2c->send_window == 0) {
          ngx_http_v2_waiting_queue(h2c, stream);
          return NGX_DECLINED;
      }
  
      return NGX_OK;
  }
  
  
  static void
  ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c,
      ngx_http_v2_stream_t *stream)
  {
      ngx_queue_t           *q;
      ngx_http_v2_stream_t  *s;
  
      if (stream->waiting) {
          return;
      }
  
      stream->waiting = 1;
  
      for (q = ngx_queue_last(&h2c->waiting);
           q != ngx_queue_sentinel(&h2c->waiting);
           q = ngx_queue_prev(q))
      {
          s = ngx_queue_data(q, ngx_http_v2_stream_t, queue);
  
          if (s->node->rank < stream->node->rank
              || (s->node->rank == stream->node->rank
                  && s->node->rel_weight >= stream->node->rel_weight))
          {
              break;
          }
      }
  
      ngx_queue_insert_after(q, &stream->queue);
  }
  
  
  static ngx_inline ngx_int_t
  ngx_http_v2_filter_send(ngx_connection_t *fc, ngx_http_v2_stream_t *stream)
  {
      stream->blocked = 1;
  
      if (ngx_http_v2_send_output_queue(stream->connection) == NGX_ERROR) {
          fc->error = 1;
          return NGX_ERROR;
      }
  
      stream->blocked = 0;
  
      if (stream->queued) {
          fc->buffered |= NGX_HTTP_V2_BUFFERED;
          fc->write->active = 1;
          fc->write->ready = 0;
          return NGX_AGAIN;
      }
  
      fc->buffered &= ~NGX_HTTP_V2_BUFFERED;
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_v2_headers_frame_handler(ngx_http_v2_connection_t *h2c,
      ngx_http_v2_out_frame_t *frame)
  {
      ngx_chain_t           *cl, *ln;
      ngx_http_v2_stream_t  *stream;
  
      stream = frame->stream;
      cl = frame->first;
  
      for ( ;; ) {
          if (cl->buf->pos != cl->buf->last) {
              frame->first = cl;
  
              ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
                             "http2:%ui HEADERS frame %p was sent partially",
                             stream->node->id, frame);
  
              return NGX_AGAIN;
          }
  
          ln = cl->next;
  
          if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) {
              cl->next = stream->free_frame_headers;
              stream->free_frame_headers = cl;
  
          } else {
              cl->next = stream->free_bufs;
              stream->free_bufs = cl;
          }
  
          if (cl == frame->last) {
              break;
          }
  
          cl = ln;
      }
  
      ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
                     "http2:%ui HEADERS frame %p was sent",
                     stream->node->id, frame);
  
      stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE
                                      + frame->length;
  
      ngx_http_v2_handle_frame(stream, frame);
  
      ngx_http_v2_handle_stream(h2c, stream);
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_v2_push_frame_handler(ngx_http_v2_connection_t *h2c,
      ngx_http_v2_out_frame_t *frame)
  {
      ngx_chain_t           *cl, *ln;
      ngx_http_v2_stream_t  *stream;
  
      stream = frame->stream;
      cl = frame->first;
  
      for ( ;; ) {
          if (cl->buf->pos != cl->buf->last) {
              frame->first = cl;
  
              ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
                             "http2:%ui PUSH_PROMISE frame %p was sent partially",
                             stream->node->id, frame);
  
              return NGX_AGAIN;
          }
  
          ln = cl->next;
  
          if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) {
              cl->next = stream->free_frame_headers;
              stream->free_frame_headers = cl;
  
          } else {
              cl->next = stream->free_bufs;
              stream->free_bufs = cl;
          }
  
          if (cl == frame->last) {
              break;
          }
  
          cl = ln;
      }
  
      ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
                     "http2:%ui PUSH_PROMISE frame %p was sent",
                     stream->node->id, frame);
  
      stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE
                                      + frame->length;
  
      ngx_http_v2_handle_frame(stream, frame);
  
      ngx_http_v2_handle_stream(h2c, stream);
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_v2_data_frame_handler(ngx_http_v2_connection_t *h2c,
      ngx_http_v2_out_frame_t *frame)
  {
      ngx_buf_t             *buf;
      ngx_chain_t           *cl, *ln;
      ngx_http_v2_stream_t  *stream;
  
      stream = frame->stream;
      cl = frame->first;
  
      if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) {
  
          if (cl->buf->pos != cl->buf->last) {
              ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
                             "http2:%ui DATA frame %p was sent partially",
                             stream->node->id, frame);
  
              return NGX_AGAIN;
          }
  
          ln = cl->next;
  
          cl->next = stream->free_frame_headers;
          stream->free_frame_headers = cl;
  
          if (cl == frame->last) {
              goto done;
          }
  
          cl = ln;
      }
  
      for ( ;; ) {
          if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) {
              buf = cl->buf->shadow;
  
              if (ngx_buf_in_memory(buf)) {
                  buf->pos = cl->buf->pos;
              }
  
              if (buf->in_file) {
                  buf->file_pos = cl->buf->file_pos;
              }
          }
  
          if (ngx_buf_size(cl->buf) != 0) {
  
              if (cl != frame->first) {
                  frame->first = cl;
                  ngx_http_v2_handle_stream(h2c, stream);
              }
  
              ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
                             "http2:%ui DATA frame %p was sent partially",
                             stream->node->id, frame);
  
              return NGX_AGAIN;
          }
  
          ln = cl->next;
  
          if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) {
              cl->next = stream->free_bufs;
              stream->free_bufs = cl;
  
          } else {
              ngx_free_chain(stream->request->pool, cl);
          }
  
          if (cl == frame->last) {
              goto done;
          }
  
          cl = ln;
      }
  
  done:
  
      ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
                     "http2:%ui DATA frame %p was sent",
                     stream->node->id, frame);
  
      stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE;
  
      ngx_http_v2_handle_frame(stream, frame);
  
      ngx_http_v2_handle_stream(h2c, stream);
  
      return NGX_OK;
  }
  
  
  static ngx_inline void
  ngx_http_v2_handle_frame(ngx_http_v2_stream_t *stream,
      ngx_http_v2_out_frame_t *frame)
  {
      ngx_http_request_t  *r;
  
      r = stream->request;
  
      r->connection->sent += NGX_HTTP_V2_FRAME_HEADER_SIZE + frame->length;
  
      if (frame->fin) {
          stream->out_closed = 1;
      }
  
      frame->next = stream->free_frames;
      stream->free_frames = frame;
  
      stream->queued--;
  }
  
  
  static ngx_inline void
  ngx_http_v2_handle_stream(ngx_http_v2_connection_t *h2c,
      ngx_http_v2_stream_t *stream)
  {
      ngx_event_t       *wev;
      ngx_connection_t  *fc;
  
      if (stream->waiting || stream->blocked) {
          return;
      }
  
      fc = stream->request->connection;
  
      if (!fc->error && stream->exhausted) {
          return;
      }
  
      wev = fc->write;
  
      wev->active = 0;
      wev->ready = 1;
  
      if (!fc->error && wev->delayed) {
          return;
      }
  
      ngx_post_event(wev, &ngx_posted_events);
  }
  
  
  static void
  ngx_http_v2_filter_cleanup(void *data)
  {
      ngx_http_v2_stream_t *stream = data;
  
      size_t                     window;
      ngx_event_t               *wev;
      ngx_queue_t               *q;
      ngx_http_v2_out_frame_t   *frame, **fn;
      ngx_http_v2_connection_t  *h2c;
  
      if (stream->waiting) {
          stream->waiting = 0;
          ngx_queue_remove(&stream->queue);
      }
  
      if (stream->queued == 0) {
          return;
      }
  
      window = 0;
      h2c = stream->connection;
      fn = &h2c->last_out;
  
      for ( ;; ) {
          frame = *fn;
  
          if (frame == NULL) {
              break;
          }
  
          if (frame->stream == stream && !frame->blocked) {
              *fn = frame->next;
  
              window += frame->length;
  
              if (--stream->queued == 0) {
                  break;
              }
  
              continue;
          }
  
          fn = &frame->next;
      }
  
      if (h2c->send_window == 0 && window) {
  
          while (!ngx_queue_empty(&h2c->waiting)) {
              q = ngx_queue_head(&h2c->waiting);
  
              ngx_queue_remove(q);
  
              stream = ngx_queue_data(q, ngx_http_v2_stream_t, queue);
  
              stream->waiting = 0;
  
              wev = stream->request->connection->write;
  
              wev->active = 0;
              wev->ready = 1;
  
              if (!wev->delayed) {
                  ngx_post_event(wev, &ngx_posted_events);
              }
          }
      }
  
      h2c->send_window += window;
  }
  
  
  static ngx_int_t
  ngx_http_v2_filter_init(ngx_conf_t *cf)
  {
      ngx_http_next_header_filter = ngx_http_top_header_filter;
      ngx_http_top_header_filter = ngx_http_v2_header_filter;
  
      return NGX_OK;
  }
  