/* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, void *userp) { struct connectdata *conn = (struct connectdata *)userp; struct http_conn *c = &conn->proto.httpc; (void)session; (void)frame; if(frame->hd.stream_id != c->stream_id) { return 0; } 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'; return 0; } else { /* convert to a HTTP1-style header */ infof(conn->data, "got header\n"); Curl_add_buffer(c->header_recvbuf, name, namelen); Curl_add_buffer(c->header_recvbuf, ":", 1); Curl_add_buffer(c->header_recvbuf, value, valuelen); Curl_add_buffer(c->header_recvbuf, "\r\n", 2); } return 0; /* 0 is successful */ }
CURLcode Curl_http2_setup(struct connectdata *conn) { struct http_conn *httpc = &conn->proto.httpc; if(conn->handler->flags & PROTOPT_SSL) conn->handler = &Curl_handler_http2_ssl; else conn->handler = &Curl_handler_http2; infof(conn->data, "Using HTTP2\n"); httpc->bodystarted = FALSE; httpc->closed = FALSE; httpc->header_recvbuf = Curl_add_buffer_init(); httpc->nread_header_recvbuf = 0; httpc->data = NULL; httpc->datalen = 0; httpc->upload_left = 0; httpc->upload_mem = NULL; httpc->upload_len = 0; httpc->stream_id = -1; conn->httpversion = 20; /* Put place holder for status line */ return Curl_add_buffer(httpc->header_recvbuf, "HTTP/2.0 200\r\n", 14); }
static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct connectdata *conn = (struct connectdata *)userp; struct http_conn *c = &conn->proto.httpc; int rv; (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_HEADERS: if(frame->headers.cat != NGHTTP2_HCAT_RESPONSE) break; c->bodystarted = TRUE; 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); c->mem += c->nread_header_recvbuf; c->len -= c->nread_header_recvbuf; break; case NGHTTP2_PUSH_PROMISE: rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_CANCEL); if(nghttp2_is_fatal(rv)) { return rv; } break; } return 0; }
/* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *userp) { struct connectdata *conn = (struct connectdata *)userp; struct http_conn *c = &conn->proto.httpc; int rv; (void)session; (void)frame; (void)flags; if(frame->hd.stream_id != c->stream_id) { 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) { /* :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); Curl_add_buffer(c->header_recvbuf, ":", 1); Curl_add_buffer(c->header_recvbuf, value, valuelen); Curl_add_buffer(c->header_recvbuf, "\r\n", 2); } return 0; /* 0 is successful */ }
static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { 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_REQUEST) break; 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); 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 += ncopy; c->len -= ncopy; break; case NGHTTP2_PUSH_PROMISE: rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_CANCEL); if(nghttp2_is_fatal(rv)) { return rv; } break; } return 0; }
int Curl_http2_switched(struct connectdata *conn) { int rv; CURLcode rc; struct http_conn *httpc = &conn->proto.httpc; /* we are switched! */ /* Don't know this is needed here at this moment. Original handler->flags is still useful. */ if(conn->handler->flags & PROTOPT_SSL) conn->handler = &Curl_handler_http2_ssl; else conn->handler = &Curl_handler_http2; httpc->recv_underlying = (recving)conn->recv[FIRSTSOCKET]; httpc->send_underlying = (sending)conn->send[FIRSTSOCKET]; conn->recv[FIRSTSOCKET] = http2_recv; conn->send[FIRSTSOCKET] = http2_send; infof(conn->data, "We have switched to HTTP2\n"); httpc->bodystarted = FALSE; httpc->closed = FALSE; httpc->header_recvbuf = Curl_add_buffer_init(); httpc->nread_header_recvbuf = 0; httpc->data = NULL; httpc->datalen = 0; httpc->upload_left = 0; httpc->upload_mem = NULL; httpc->upload_len = 0; conn->httpversion = 20; /* Put place holder for status line */ Curl_add_buffer(httpc->header_recvbuf, "HTTP/2.0 200\r\n", 14); /* TODO: May get CURLE_AGAIN */ rv = (int) ((Curl_send*)httpc->send_underlying) (conn, FIRSTSOCKET, NGHTTP2_CLIENT_CONNECTION_HEADER, NGHTTP2_CLIENT_CONNECTION_HEADER_LEN, &rc); assert(rv == 24); if(conn->data->req.upgr101 == UPGR101_RECEIVED) { /* stream 1 is opened implicitly on upgrade */ httpc->stream_id = 1; /* queue SETTINGS frame (again) */ rv = nghttp2_session_upgrade(httpc->h2, httpc->binsettings, httpc->binlen, NULL); if(rv != 0) { failf(conn->data, "nghttp2_session_upgrade() failed: %s(%d)", nghttp2_strerror(rv), rv); return -1; } } else { /* stream ID is unknown at this point */ httpc->stream_id = -1; rv = nghttp2_submit_settings(httpc->h2, NGHTTP2_FLAG_NONE, NULL, 0); if(rv != 0) { failf(conn->data, "nghttp2_submit_settings() failed: %s(%d)", nghttp2_strerror(rv), rv); return -1; } } return 0; }
static CURLcode rtsp_do(struct connectdata *conn, bool *done) { struct Curl_easy *data = conn->data; CURLcode result = CURLE_OK; Curl_RtspReq rtspreq = data->set.rtspreq; struct RTSP *rtsp = data->req.protop; struct HTTP *http; Curl_send_buffer *req_buffer; curl_off_t postsize = 0; /* for ANNOUNCE and SET_PARAMETER */ curl_off_t putsize = 0; /* for ANNOUNCE and SET_PARAMETER */ const char *p_request = NULL; const char *p_session_id = NULL; const char *p_accept = NULL; const char *p_accept_encoding = NULL; const char *p_range = NULL; const char *p_referrer = NULL; const char *p_stream_uri = NULL; const char *p_transport = NULL; const char *p_uagent = NULL; const char *p_proxyuserpwd = NULL; const char *p_userpwd = NULL; *done = TRUE; http = &(rtsp->http_wrapper); /* Assert that no one has changed the RTSP struct in an evil way */ DEBUGASSERT((void *)http == (void *)rtsp); rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq; rtsp->CSeq_recv = 0; /* Setup the 'p_request' pointer to the proper p_request string * Since all RTSP requests are included here, there is no need to * support custom requests like HTTP. **/ data->set.opt_no_body = TRUE; /* most requests don't contain a body */ switch(rtspreq) { default: failf(data, "Got invalid RTSP request"); return CURLE_BAD_FUNCTION_ARGUMENT; case RTSPREQ_OPTIONS: p_request = "OPTIONS"; break; case RTSPREQ_DESCRIBE: p_request = "DESCRIBE"; data->set.opt_no_body = FALSE; break; case RTSPREQ_ANNOUNCE: p_request = "ANNOUNCE"; break; case RTSPREQ_SETUP: p_request = "SETUP"; break; case RTSPREQ_PLAY: p_request = "PLAY"; break; case RTSPREQ_PAUSE: p_request = "PAUSE"; break; case RTSPREQ_TEARDOWN: p_request = "TEARDOWN"; break; case RTSPREQ_GET_PARAMETER: /* GET_PARAMETER's no_body status is determined later */ p_request = "GET_PARAMETER"; data->set.opt_no_body = FALSE; break; case RTSPREQ_SET_PARAMETER: p_request = "SET_PARAMETER"; break; case RTSPREQ_RECORD: p_request = "RECORD"; break; case RTSPREQ_RECEIVE: p_request = ""; /* Treat interleaved RTP as body*/ data->set.opt_no_body = FALSE; break; case RTSPREQ_LAST: failf(data, "Got invalid RTSP request: RTSPREQ_LAST"); return CURLE_BAD_FUNCTION_ARGUMENT; } if(rtspreq == RTSPREQ_RECEIVE) { Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, -1, NULL); return result; } p_session_id = data->set.str[STRING_RTSP_SESSION_ID]; if(!p_session_id && (rtspreq & ~(RTSPREQ_OPTIONS | RTSPREQ_DESCRIBE | RTSPREQ_SETUP))) { failf(data, "Refusing to issue an RTSP request [%s] without a session ID.", p_request); return CURLE_BAD_FUNCTION_ARGUMENT; } /* TODO: proxy? */ /* Stream URI. Default to server '*' if not specified */ if(data->set.str[STRING_RTSP_STREAM_URI]) { p_stream_uri = data->set.str[STRING_RTSP_STREAM_URI]; } else { p_stream_uri = "*"; } /* Transport Header for SETUP requests */ p_transport = Curl_checkheaders(conn, "Transport:"); if(rtspreq == RTSPREQ_SETUP && !p_transport) { /* New Transport: setting? */ if(data->set.str[STRING_RTSP_TRANSPORT]) { Curl_safefree(conn->allocptr.rtsp_transport); conn->allocptr.rtsp_transport = aprintf("Transport: %s\r\n", data->set.str[STRING_RTSP_TRANSPORT]); if(!conn->allocptr.rtsp_transport) return CURLE_OUT_OF_MEMORY; } else { failf(data, "Refusing to issue an RTSP SETUP without a Transport: header."); return CURLE_BAD_FUNCTION_ARGUMENT; } p_transport = conn->allocptr.rtsp_transport; } /* Accept Headers for DESCRIBE requests */ if(rtspreq == RTSPREQ_DESCRIBE) { /* Accept Header */ p_accept = Curl_checkheaders(conn, "Accept:")? NULL:"Accept: application/sdp\r\n"; /* Accept-Encoding header */ if(!Curl_checkheaders(conn, "Accept-Encoding:") && data->set.str[STRING_ENCODING]) { Curl_safefree(conn->allocptr.accept_encoding); conn->allocptr.accept_encoding = aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); if(!conn->allocptr.accept_encoding) return CURLE_OUT_OF_MEMORY; p_accept_encoding = conn->allocptr.accept_encoding; } } /* The User-Agent string might have been allocated in url.c already, because it might have been used in the proxy connect, but if we have got a header with the user-agent string specified, we erase the previously made string here. */ if(Curl_checkheaders(conn, "User-Agent:") && conn->allocptr.uagent) { Curl_safefree(conn->allocptr.uagent); conn->allocptr.uagent = NULL; } else if(!Curl_checkheaders(conn, "User-Agent:") && data->set.str[STRING_USERAGENT]) { p_uagent = conn->allocptr.uagent; } /* setup the authentication headers */ result = Curl_http_output_auth(conn, p_request, p_stream_uri, FALSE); if(result) return result; p_proxyuserpwd = conn->allocptr.proxyuserpwd; p_userpwd = conn->allocptr.userpwd; /* Referrer */ Curl_safefree(conn->allocptr.ref); if(data->change.referer && !Curl_checkheaders(conn, "Referer:")) conn->allocptr.ref = aprintf("Referer: %s\r\n", data->change.referer); else conn->allocptr.ref = NULL; p_referrer = conn->allocptr.ref; /* * Range Header * Only applies to PLAY, PAUSE, RECORD * * Go ahead and use the Range stuff supplied for HTTP */ if(data->state.use_range && (rtspreq & (RTSPREQ_PLAY | RTSPREQ_PAUSE | RTSPREQ_RECORD))) { /* Check to see if there is a range set in the custom headers */ if(!Curl_checkheaders(conn, "Range:") && data->state.range) { Curl_safefree(conn->allocptr.rangeline); conn->allocptr.rangeline = aprintf("Range: %s\r\n", data->state.range); p_range = conn->allocptr.rangeline; } } /* * Sanity check the custom headers */ if(Curl_checkheaders(conn, "CSeq:")) { failf(data, "CSeq cannot be set as a custom header."); return CURLE_RTSP_CSEQ_ERROR; } if(Curl_checkheaders(conn, "Session:")) { failf(data, "Session ID cannot be set as a custom header."); return CURLE_BAD_FUNCTION_ARGUMENT; } /* Initialize a dynamic send buffer */ req_buffer = Curl_add_buffer_init(); if(!req_buffer) return CURLE_OUT_OF_MEMORY; result = Curl_add_bufferf(req_buffer, "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */ "CSeq: %ld\r\n", /* CSeq */ p_request, p_stream_uri, rtsp->CSeq_sent); if(result) return result; /* * Rather than do a normal alloc line, keep the session_id unformatted * to make comparison easier */ if(p_session_id) { result = Curl_add_bufferf(req_buffer, "Session: %s\r\n", p_session_id); if(result) return result; } /* * Shared HTTP-like options */ result = Curl_add_bufferf(req_buffer, "%s" /* transport */ "%s" /* accept */ "%s" /* accept-encoding */ "%s" /* range */ "%s" /* referrer */ "%s" /* user-agent */ "%s" /* proxyuserpwd */ "%s" /* userpwd */ , p_transport ? p_transport : "", p_accept ? p_accept : "", p_accept_encoding ? p_accept_encoding : "", p_range ? p_range : "", p_referrer ? p_referrer : "", p_uagent ? p_uagent : "", p_proxyuserpwd ? p_proxyuserpwd : "", p_userpwd ? p_userpwd : ""); /* * Free userpwd now --- cannot reuse this for Negotiate and possibly NTLM * with basic and digest, it will be freed anyway by the next request */ Curl_safefree(conn->allocptr.userpwd); conn->allocptr.userpwd = NULL; if(result) return result; if((rtspreq == RTSPREQ_SETUP) || (rtspreq == RTSPREQ_DESCRIBE)) { result = Curl_add_timecondition(data, req_buffer); if(result) return result; } result = Curl_add_custom_headers(conn, FALSE, req_buffer); if(result) return result; if(rtspreq == RTSPREQ_ANNOUNCE || rtspreq == RTSPREQ_SET_PARAMETER || rtspreq == RTSPREQ_GET_PARAMETER) { if(data->set.upload) { putsize = data->state.infilesize; data->set.httpreq = HTTPREQ_PUT; } else { postsize = (data->state.infilesize != -1)? data->state.infilesize: (data->set.postfields? (curl_off_t)strlen(data->set.postfields):0); data->set.httpreq = HTTPREQ_POST; } if(putsize > 0 || postsize > 0) { /* As stated in the http comments, it is probably not wise to * actually set a custom Content-Length in the headers */ if(!Curl_checkheaders(conn, "Content-Length:")) { result = Curl_add_bufferf(req_buffer, "Content-Length: %" CURL_FORMAT_CURL_OFF_T"\r\n", (data->set.upload ? putsize : postsize)); if(result) return result; } if(rtspreq == RTSPREQ_SET_PARAMETER || rtspreq == RTSPREQ_GET_PARAMETER) { if(!Curl_checkheaders(conn, "Content-Type:")) { result = Curl_add_bufferf(req_buffer, "Content-Type: text/parameters\r\n"); if(result) return result; } } if(rtspreq == RTSPREQ_ANNOUNCE) { if(!Curl_checkheaders(conn, "Content-Type:")) { result = Curl_add_bufferf(req_buffer, "Content-Type: application/sdp\r\n"); if(result) return result; } } data->state.expect100header = FALSE; /* RTSP posts are simple/small */ } else if(rtspreq == RTSPREQ_GET_PARAMETER) { /* Check for an empty GET_PARAMETER (heartbeat) request */ data->set.httpreq = HTTPREQ_HEAD; data->set.opt_no_body = TRUE; } } /* RTSP never allows chunked transfer */ data->req.forbidchunk = TRUE; /* Finish the request buffer */ result = Curl_add_buffer(req_buffer, "\r\n", 2); if(result) return result; if(postsize > 0) { result = Curl_add_buffer(req_buffer, data->set.postfields, (size_t)postsize); if(result) return result; } /* issue the request */ result = Curl_add_buffer_send(req_buffer, conn, &data->info.request_size, 0, FIRSTSOCKET); if(result) { failf(data, "Failed sending RTSP request"); return result; } Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, putsize?FIRSTSOCKET:-1, putsize?&http->writebytecount:NULL); /* Increment the CSeq on success */ data->state.rtsp_next_client_CSeq++; if(http->writebytecount) { /* if a request-body has been sent off, we make sure this progress is noted properly */ Curl_pgrsSetUploadCounter(data, http->writebytecount); if(Curl_pgrsUpdate(conn)) result = CURLE_ABORTED_BY_CALLBACK; } return result; }
/* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *userp) { struct connectdata *conn = (struct connectdata *)userp; struct HTTP *stream; struct SessionHandle *data_s; int32_t stream_id = frame->hd.stream_id; (void)session; (void)frame; (void)flags; /* Ignore PUSH_PROMISE for now */ if(frame->hd.type != NGHTTP2_HEADERS) { return 0; } DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ /* get the stream from the hash based on Stream ID */ data_s = Curl_hash_pick(&conn->proto.httpc.streamsh, &stream_id, sizeof(stream_id)); if(!data_s) { /* Receiving a Stream ID not in the hash should not happen, this is an internal error more than anything else! */ failf(conn->data, "Received frame on Stream ID: %x not in stream hash!", stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } stream = data_s->req.protop; if(stream->bodystarted) /* Ignore trailer or HEADERS not mapped to HTTP semantics. The consequence is handled in on_frame_recv(). */ return 0; if(namelen == sizeof(":status") - 1 && memcmp(":status", name, namelen) == 0) { /* nghttp2 guarantees :status is received first and only once, and value is 3 digits status code, and decode_status_code always succeeds. */ stream->status_code = decode_status_code(value, valuelen); DEBUGASSERT(stream->status_code != -1); Curl_add_buffer(stream->header_recvbuf, "HTTP/2.0 ", 9); Curl_add_buffer(stream->header_recvbuf, value, valuelen); Curl_add_buffer(stream->header_recvbuf, "\r\n", 2); data_s->state.drain++; Curl_expire(data_s, 1); DEBUGF(infof(data_s, "h2 status: HTTP/2 %03d\n", stream->status_code)); return 0; } /* nghttp2 guarantees that namelen > 0, and :status was already received, and this is not pseudo-header field . */ /* convert to a HTTP1-style header */ Curl_add_buffer(stream->header_recvbuf, name, namelen); Curl_add_buffer(stream->header_recvbuf, ":", 1); Curl_add_buffer(stream->header_recvbuf, value, valuelen); Curl_add_buffer(stream->header_recvbuf, "\r\n", 2); data_s->state.drain++; Curl_expire(data_s, 1); DEBUGF(infof(data_s, "h2 header: %.*s: %.*s\n", namelen, name, valuelen, value)); return 0; /* 0 is successful */ }
static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct connectdata *conn = (struct connectdata *)userp; struct http_conn *httpc = &conn->proto.httpc; struct SessionHandle *data_s = NULL; struct HTTP *stream = NULL; int rv; size_t left, ncopy; int32_t stream_id = frame->hd.stream_id; (void)session; (void)frame; DEBUGF(infof(conn->data, "on_frame_recv() header %x stream %x\n", frame->hd.type, stream_id)); if(stream_id) { /* get the stream from the hash based on Stream ID, stream ID zero is for connection-oriented stuff */ data_s = Curl_hash_pick(&httpc->streamsh, &stream_id, sizeof(stream_id)); if(!data_s) { /* Receiving a Stream ID not in the hash should not happen, this is an internal error more than anything else! */ failf(conn->data, "Received frame on Stream ID: %x not in stream hash!", stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; } stream = data_s->req.protop; } switch(frame->hd.type) { case NGHTTP2_DATA: /* If body started on this stream, then receiving DATA is illegal. */ if(!stream->bodystarted) { rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 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_REQUEST) break; if(stream->bodystarted) { /* Only valid HEADERS after body started is trailer HEADERS. We ignores trailer HEADERS for now. nghttp2 guarantees that it has END_STREAM flag set. */ break; } /* nghttp2 guarantees that :status is received, and we store it to stream->status_code */ DEBUGASSERT(stream->status_code != -1); /* Only final status code signals the end of header */ if(stream->status_code / 100 != 1) { stream->bodystarted = TRUE; stream->status_code = -1; } Curl_add_buffer(stream->header_recvbuf, "\r\n", 2); left = stream->header_recvbuf->size_used - stream->nread_header_recvbuf; ncopy = MIN(stream->len, left); memcpy(&stream->mem[stream->memlen], stream->header_recvbuf->buffer + stream->nread_header_recvbuf, ncopy); stream->nread_header_recvbuf += ncopy; DEBUGF(infof(data_s, "Store %zu bytes headers from stream %u at %p\n", ncopy, stream_id, stream->mem)); stream->len -= ncopy; stream->memlen += ncopy; data_s->state.drain++; Curl_expire(data_s, 1); break; case NGHTTP2_PUSH_PROMISE: DEBUGF(infof(data_s, "Got PUSH_PROMISE, RST_STREAM it!\n")); rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->push_promise.promised_stream_id, NGHTTP2_CANCEL); if(nghttp2_is_fatal(rv)) { return rv; } break; case NGHTTP2_SETTINGS: { uint32_t max_conn = httpc->settings.max_concurrent_streams; DEBUGF(infof(conn->data, "Got SETTINGS for stream %u!\n", stream_id)); httpc->settings.max_concurrent_streams = nghttp2_session_get_remote_settings( session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); httpc->settings.enable_push = nghttp2_session_get_remote_settings( session, NGHTTP2_SETTINGS_ENABLE_PUSH); DEBUGF(infof(conn->data, "MAX_CONCURRENT_STREAMS == %d\n", httpc->settings.max_concurrent_streams)); DEBUGF(infof(conn->data, "ENABLE_PUSH == %s\n", httpc->settings.enable_push?"TRUE":"false")); if(max_conn != httpc->settings.max_concurrent_streams) { /* only signal change if the value actually changed */ infof(conn->data, "Connection state changed (MAX_CONCURRENT_STREAMS updated)!\n"); Curl_multi_connchanged(conn->data->multi); } } break; default: DEBUGF(infof(conn->data, "Got frame type %x for stream %u!\n", frame->hd.type, stream_id)); break; } return 0; }
/* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *userp) { struct HTTP *stream; struct SessionHandle *data_s; int32_t stream_id = frame->hd.stream_id; (void)flags; (void)userp; DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ /* get the stream from the hash based on Stream ID */ data_s = nghttp2_session_get_stream_user_data(session, stream_id); if(!data_s) /* Receiving a Stream ID not in the hash should not happen, this is an internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; stream = data_s->req.protop; if(!stream) { failf(data_s, "Internal NULL stream! 5\n"); return NGHTTP2_ERR_CALLBACK_FAILURE; } if(stream->bodystarted) /* Ignore trailer or HEADERS not mapped to HTTP semantics. The consequence is handled in on_frame_recv(). */ return 0; /* Store received PUSH_PROMISE headers to be used when the subsequent PUSH_PROMISE callback comes */ if(frame->hd.type == NGHTTP2_PUSH_PROMISE) { char *h; if(!stream->push_headers) { stream->push_headers_alloc = 10; stream->push_headers = malloc(stream->push_headers_alloc * sizeof(char *)); stream->push_headers_used = 0; } else if(stream->push_headers_used == stream->push_headers_alloc) { char **headp; stream->push_headers_alloc *= 2; headp = realloc(stream->push_headers, stream->push_headers_alloc * sizeof(char *)); if(!headp) { free(stream->push_headers); stream->push_headers = NULL; return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } stream->push_headers = headp; } h = aprintf("%s:%s", name, value); if(h) stream->push_headers[stream->push_headers_used++] = h; return 0; } if(namelen == sizeof(":status") - 1 && memcmp(":status", name, namelen) == 0) { /* nghttp2 guarantees :status is received first and only once, and value is 3 digits status code, and decode_status_code always succeeds. */ stream->status_code = decode_status_code(value, valuelen); DEBUGASSERT(stream->status_code != -1); Curl_add_buffer(stream->header_recvbuf, "HTTP/2.0 ", 9); Curl_add_buffer(stream->header_recvbuf, value, valuelen); Curl_add_buffer(stream->header_recvbuf, "\r\n", 2); data_s->state.drain++; Curl_expire(data_s, 1); DEBUGF(infof(data_s, "h2 status: HTTP/2 %03d\n", stream->status_code)); return 0; } /* nghttp2 guarantees that namelen > 0, and :status was already received, and this is not pseudo-header field . */ /* convert to a HTTP1-style header */ Curl_add_buffer(stream->header_recvbuf, name, namelen); Curl_add_buffer(stream->header_recvbuf, ":", 1); Curl_add_buffer(stream->header_recvbuf, value, valuelen); Curl_add_buffer(stream->header_recvbuf, "\r\n", 2); data_s->state.drain++; Curl_expire(data_s, 1); DEBUGF(infof(data_s, "h2 header: %.*s: %.*s\n", namelen, name, valuelen, value)); return 0; /* 0 is successful */ }
static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct connectdata *conn = (struct connectdata *)userp; struct http_conn *httpc = NULL; struct SessionHandle *data_s = NULL; struct HTTP *stream = NULL; static int lastStream = -1; int rv; size_t left, ncopy; int32_t stream_id = frame->hd.stream_id; if(!stream_id) { /* stream ID zero is for connection-oriented stuff */ return 0; } data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); if(lastStream != frame->hd.stream_id) { lastStream = frame->hd.stream_id; } if(!data_s) { DEBUGF(infof(conn->data, "No SessionHandle associated with stream: %x\n", stream_id)); return 0; } stream = data_s->req.protop; if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; DEBUGF(infof(data_s, "on_frame_recv() header %x stream %x\n", frame->hd.type, stream_id)); conn = data_s->easy_conn; assert(conn); assert(conn->data == data_s); httpc = &conn->proto.httpc; switch(frame->hd.type) { case NGHTTP2_DATA: /* If body started on this stream, then receiving DATA is illegal. */ if(!stream->bodystarted) { rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 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_REQUEST) break; if(stream->bodystarted) { /* Only valid HEADERS after body started is trailer HEADERS. We ignores trailer HEADERS for now. nghttp2 guarantees that it has END_STREAM flag set. */ break; } /* nghttp2 guarantees that :status is received, and we store it to stream->status_code */ DEBUGASSERT(stream->status_code != -1); /* Only final status code signals the end of header */ if(stream->status_code / 100 != 1) { stream->bodystarted = TRUE; stream->status_code = -1; } Curl_add_buffer(stream->header_recvbuf, "\r\n", 2); left = stream->header_recvbuf->size_used - stream->nread_header_recvbuf; ncopy = MIN(stream->len, left); memcpy(&stream->mem[stream->memlen], stream->header_recvbuf->buffer + stream->nread_header_recvbuf, ncopy); stream->nread_header_recvbuf += ncopy; DEBUGF(infof(data_s, "Store %zu bytes headers from stream %u at %p\n", ncopy, stream_id, stream->mem)); stream->len -= ncopy; stream->memlen += ncopy; data_s->state.drain++; { /* get the pointer from userp again since it was re-assigned above */ struct connectdata *conn_s = (struct connectdata *)userp; /* if we receive data for another handle, wake that up */ if(conn_s->data != data_s) Curl_expire(data_s, 1); } break; case NGHTTP2_PUSH_PROMISE: rv = push_promise(data_s, conn, &frame->push_promise); if(rv) { /* deny! */ rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->push_promise.promised_stream_id, NGHTTP2_CANCEL); if(nghttp2_is_fatal(rv)) { return rv; } } break; case NGHTTP2_SETTINGS: { uint32_t max_conn = httpc->settings.max_concurrent_streams; DEBUGF(infof(conn->data, "Got SETTINGS for stream %u!\n", stream_id)); httpc->settings.max_concurrent_streams = nghttp2_session_get_remote_settings( session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); httpc->settings.enable_push = nghttp2_session_get_remote_settings( session, NGHTTP2_SETTINGS_ENABLE_PUSH); DEBUGF(infof(conn->data, "MAX_CONCURRENT_STREAMS == %d\n", httpc->settings.max_concurrent_streams)); DEBUGF(infof(conn->data, "ENABLE_PUSH == %s\n", httpc->settings.enable_push?"TRUE":"false")); if(max_conn != httpc->settings.max_concurrent_streams) { /* only signal change if the value actually changed */ infof(conn->data, "Connection state changed (MAX_CONCURRENT_STREAMS updated)!\n"); Curl_multi_connchanged(conn->data->multi); } } break; default: DEBUGF(infof(conn->data, "Got frame type %x for stream %u!\n", frame->hd.type, stream_id)); break; } return 0; }