diff options
| author | Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> | 2014-08-01 23:10:10 +0900 | 
|---|---|---|
| committer | Daniel Stenberg <daniel@haxx.se> | 2014-08-02 23:15:46 +0200 | 
| commit | 595f5f0e43b5395881ffb7d3076a9cdd898cfa5d (patch) | |
| tree | 54660683b0ec539505b234dd0f2141a1ed7a6212 /lib | |
| parent | e4f6adb023546d864a1548a28b08112c59d9e85a (diff) | |
HTTP2: Support expect: 100-continue
"Expect: 100-continue", which was once deprecated in HTTP/2, is now
resurrected in HTTP/2 draft 14.  This change adds its support to
HTTP/2 code.  This change also includes stricter header field
checking.
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/http.c | 4 | ||||
| -rw-r--r-- | lib/http.h | 5 | ||||
| -rw-r--r-- | lib/http2.c | 154 | 
3 files changed, 147 insertions, 16 deletions
diff --git a/lib/http.c b/lib/http.c index f50ea5262..a695924a1 100644 --- a/lib/http.c +++ b/lib/http.c @@ -1525,10 +1525,6 @@ static CURLcode expect100(struct SessionHandle *data,    const char *ptr;    data->state.expect100header = FALSE; /* default to false unless it is set                                            to TRUE below */ -  if(conn->httpversion == 20) { -    /* We don't use Expect in HTTP2 */ -    return CURLE_OK; -  }    if(use_http_1_1plus(data, conn)) {      /* if not doing HTTP 1.0 or disabled explicitly, we add a Expect:         100-continue to the headers which actually speeds up post operations diff --git a/lib/http.h b/lib/http.h index 7cf183101..907755a8a 100644 --- a/lib/http.h +++ b/lib/http.h @@ -169,7 +169,9 @@ struct http_conn {    sending send_underlying; /* underlying send Curl_send callback */    recving recv_underlying; /* underlying recv Curl_recv callback */    bool closed; /* TRUE on HTTP2 stream close */ -  Curl_send_buffer *header_recvbuf; /* store response headers */ +  Curl_send_buffer *header_recvbuf; /* store response headers.  We +                                       store non-final and final +                                       response headers into it. */    size_t nread_header_recvbuf; /* number of bytes in header_recvbuf                                    fed into upper layer */    int32_t stream_id; /* stream we are interested in */ @@ -185,6 +187,7 @@ struct http_conn {    const uint8_t *upload_mem; /* points to a buffer to read from */    size_t upload_len; /* size of the buffer 'upload_mem' points to */    size_t upload_left; /* number of bytes left to upload */ +  int status_code; /* HTTP status code */  #else    int unused; /* prevent a compiler warning */  #endif diff --git a/lib/http2.c b/lib/http2.c index 00eb9d9b2..77dd014d3 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -191,23 +191,75 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,    struct connectdata *conn = (struct connectdata *)userp;    struct http_conn *c = &conn->proto.httpc;    int rv; +  size_t left, ncopy; +    (void)session;    (void)frame;    infof(conn->data, "on_frame_recv() was called with header %x\n",          frame->hd.type);    switch(frame->hd.type) { +  case NGHTTP2_DATA: +    /* If body started, then receiving DATA is illegal. */ +    if(!c->bodystarted) { +      rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, +                                     frame->hd.stream_id, +                                     NGHTTP2_PROTOCOL_ERROR); + +      if(nghttp2_is_fatal(rv)) { +        return NGHTTP2_ERR_CALLBACK_FAILURE; +      } +    } +    break;    case NGHTTP2_HEADERS: -    if(frame->headers.cat != NGHTTP2_HCAT_RESPONSE) +    if(frame->headers.cat == NGHTTP2_HCAT_REQUEST)        break; -    c->bodystarted = TRUE; + +    if(c->bodystarted) { +      /* Only valid HEADERS after body started is trailer header, +         which is not fully supported in this code.  If HEADERS is not +         trailer, then it is a PROTOCOL_ERROR. */ +      if((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { +        rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, +                                       frame->hd.stream_id, +                                       NGHTTP2_PROTOCOL_ERROR); + +        if(nghttp2_is_fatal(rv)) { +          return NGHTTP2_ERR_CALLBACK_FAILURE; +        } +      } +      break; +    } + +    if(c->status_code == -1) { +      /* No :status header field means PROTOCOL_ERROR. */ +      rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, +                                     frame->hd.stream_id, +                                     NGHTTP2_PROTOCOL_ERROR); + +      if(nghttp2_is_fatal(rv)) { +        return NGHTTP2_ERR_CALLBACK_FAILURE; +      } + +      break; +    } + +    /* Only final status code signals the end of header */ +    if(c->status_code / 100 != 1) { +      c->bodystarted = TRUE; +    } + +    c->status_code = -1; +      Curl_add_buffer(c->header_recvbuf, "\r\n", 2); -    c->nread_header_recvbuf = c->len < c->header_recvbuf->size_used ? -      c->len : c->header_recvbuf->size_used; -    memcpy(c->mem, c->header_recvbuf->buffer, c->nread_header_recvbuf); +    left = c->header_recvbuf->size_used - c->nread_header_recvbuf; +    ncopy = c->len < left ? c->len : left; + +    memcpy(c->mem, c->header_recvbuf->buffer + c->nread_header_recvbuf, ncopy); +    c->nread_header_recvbuf += ncopy; -    c->mem += c->nread_header_recvbuf; -    c->len -= c->nread_header_recvbuf; +    c->mem += ncopy; +    c->len -= ncopy;      break;    case NGHTTP2_PUSH_PROMISE:      rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, @@ -339,6 +391,33 @@ static int on_begin_headers(nghttp2_session *session,    return 0;  } +/* Decode HTTP status code.  Returns -1 if no valid status code was +   decoded. */ +static int decode_status_code(const uint8_t *value, size_t len) +{ +  int i; +  int res; + +  if(len != 3) { +    return -1; +  } + +  res = 0; + +  for(i = 0; i < 3; ++i) { +    char c = value[i]; + +    if(c < '0' || c > '9') { +      return -1; +    } + +    res *= 10; +    res += c - '0'; +  } + +  return res; +} +  static const char STATUS[] = ":status";  /* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */ @@ -350,6 +429,8 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,  {    struct connectdata *conn = (struct connectdata *)userp;    struct http_conn *c = &conn->proto.httpc; +  int rv; +    (void)session;    (void)frame;    (void)flags; @@ -358,13 +439,64 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,      return 0;    } +  if(c->bodystarted) { +    /* Ignore trailer or HEADERS not mapped to HTTP semantics.  The +       consequence is handled in on_frame_recv(). */ +    return 0; +  } + +  if(!nghttp2_check_header_name(name, namelen) || +     !nghttp2_check_header_value(value, valuelen)) { + +    rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, +                                   frame->hd.stream_id, +                                   NGHTTP2_PROTOCOL_ERROR); + +    if(nghttp2_is_fatal(rv)) { +      return NGHTTP2_ERR_CALLBACK_FAILURE; +    } + +    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; +  } +    if(namelen == sizeof(":status") - 1 &&       memcmp(STATUS, name, namelen) == 0) { -    snprintf(c->header_recvbuf->buffer, 13, "HTTP/2.0 %s", value); -    c->header_recvbuf->buffer[12] = '\r'; + +    /* :status must appear exactly once. */ +    if(c->status_code != -1 || +       (c->status_code = decode_status_code(value, valuelen)) == -1) { + +      rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, +                                     frame->hd.stream_id, +                                     NGHTTP2_PROTOCOL_ERROR); +      if(nghttp2_is_fatal(rv)) { +        return NGHTTP2_ERR_CALLBACK_FAILURE; +      } + +      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; +    } + +    Curl_add_buffer(c->header_recvbuf, "HTTP/2.0 ", 9); +    Curl_add_buffer(c->header_recvbuf, value, valuelen); +    Curl_add_buffer(c->header_recvbuf, "\r\n", 2); +      return 0;    }    else { +    /* Here we are sure that namelen > 0 because of +       nghttp2_check_header_name().  Pseudo header other than :status +       is illegal. */ +    if(c->status_code == -1 || name[0] == ':') { +      rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, +                                     frame->hd.stream_id, +                                     NGHTTP2_PROTOCOL_ERROR); +      if(nghttp2_is_fatal(rv)) { +        return NGHTTP2_ERR_CALLBACK_FAILURE; +      } + +      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; +    } +      /* convert to a HTTP1-style header */      infof(conn->data, "got header\n");      Curl_add_buffer(c->header_recvbuf, name, namelen); @@ -803,11 +935,11 @@ CURLcode Curl_http2_setup(struct connectdata *conn)    httpc->upload_mem = NULL;    httpc->upload_len = 0;    httpc->stream_id = -1; +  httpc->status_code = -1;    conn->httpversion = 20; -  /* Put place holder for status line */ -  return Curl_add_buffer(httpc->header_recvbuf, "HTTP/2.0 200\r\n", 14); +  return 0;  }  CURLcode Curl_http2_switched(struct connectdata *conn)  | 
