static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { http2_session_data *session_data = (http2_session_data*)user_data; http2_stream_data *stream_data; size_t i; const char PATH[] = ":path"; switch(frame->hd.type) { case NGHTTP2_HEADERS: if(frame->headers.cat != NGHTTP2_HCAT_REQUEST) { break; } stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, stream_data); for(i = 0; i < frame->headers.nvlen; ++i) { nghttp2_nv *nv = &frame->headers.nva[i]; if(nv->namelen == sizeof(PATH) - 1 && memcmp(PATH, nv->name, nv->namelen) == 0) { size_t j; for(j = 0; j < nv->valuelen && nv->value[j] != '?'; ++j); stream_data->request_path = percent_decode(nv->value, j); break; } } break; default: break; } return 0; }
static int on_stream_close(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *userp) { struct SessionHandle *data_s; struct HTTP *stream; (void)session; (void)stream_id; (void)userp; if(stream_id) { /* get the stream from the hash based on Stream ID, stream ID zero is for connection-oriented stuff */ data_s = nghttp2_session_get_stream_user_data(session, stream_id); if(!data_s) { /* We could get stream ID not in the hash. For example, if we decided to reject stream (e.g., PUSH_PROMISE). */ return 0; } DEBUGF(infof(data_s, "on_stream_close(), error_code = %d, stream %u\n", error_code, stream_id)); stream = data_s->req.protop; if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; stream->error_code = error_code; stream->closed = TRUE; /* remove the entry from the hash as the stream is now gone */ nghttp2_session_set_stream_user_data(session, stream_id, 0); DEBUGF(infof(data_s, "Removed stream %u hash!\n", stream_id)); } return 0; }
static int on_begin_headers_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { http2_session_data *session_data = (http2_session_data *)user_data; http2_stream_data *stream_data; if (frame->hd.type != NGHTTP2_HEADERS || frame->headers.cat != NGHTTP2_HCAT_REQUEST) { return 0; } stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, stream_data); return 0; }
static int push_promise(struct SessionHandle *data, struct connectdata *conn, const nghttp2_push_promise *frame) { int rv; DEBUGF(infof(data, "PUSH_PROMISE received, stream %u!\n", frame->promised_stream_id)); if(data->multi->push_cb) { struct HTTP *stream; struct curl_pushheaders heads; CURLMcode rc; struct http_conn *httpc; size_t i; /* clone the parent */ CURL *newhandle = duphandle(data); if(!newhandle) { infof(data, "failed to duplicate handle\n"); rv = 1; /* FAIL HARD */ goto fail; } heads.data = data; heads.frame = frame; /* ask the application */ DEBUGF(infof(data, "Got PUSH_PROMISE, ask application!\n")); stream = data->req.protop; if(!stream) { failf(data, "Internal NULL stream!\n"); rv = 1; goto fail; } rv = data->multi->push_cb(data, newhandle, stream->push_headers_used, &heads, data->multi->push_userp); /* free the headers again */ for(i=0; i<stream->push_headers_used; i++) free(stream->push_headers[i]); free(stream->push_headers); stream->push_headers = NULL; if(rv) { /* denied, kill off the new handle again */ (void)Curl_close(newhandle); goto fail; } /* approved, add to the multi handle and immediately switch to PERFORM state with the given connection !*/ rc = Curl_multi_add_perform(data->multi, newhandle, conn); if(rc) { infof(data, "failed to add handle to multi\n"); Curl_close(newhandle); rv = 1; goto fail; } httpc = &conn->proto.httpc; nghttp2_session_set_stream_user_data(httpc->h2, frame->promised_stream_id, newhandle); } else { DEBUGF(infof(data, "Got PUSH_PROMISE, ignore it!\n")); rv = 1; } fail: return rv; }
CURLcode Curl_http2_switched(struct connectdata *conn, const char *mem, size_t nread) { CURLcode result; struct http_conn *httpc = &conn->proto.httpc; int rv; ssize_t nproc; struct SessionHandle *data = conn->data; struct HTTP *stream = conn->data->req.protop; result = Curl_http2_setup(conn); if(result) return result; httpc->recv_underlying = (recving)conn->recv[FIRSTSOCKET]; httpc->send_underlying = (sending)conn->send[FIRSTSOCKET]; conn->recv[FIRSTSOCKET] = http2_recv; conn->send[FIRSTSOCKET] = http2_send; if(conn->data->req.upgr101 == UPGR101_RECEIVED) { /* stream 1 is opened implicitly on upgrade */ stream->stream_id = 1; /* queue SETTINGS frame (again) */ rv = nghttp2_session_upgrade(httpc->h2, httpc->binsettings, httpc->binlen, NULL); if(rv != 0) { failf(data, "nghttp2_session_upgrade() failed: %s(%d)", nghttp2_strerror(rv), rv); return CURLE_HTTP2; } nghttp2_session_set_stream_user_data(httpc->h2, stream->stream_id, conn->data); } else { /* stream ID is unknown at this point */ stream->stream_id = -1; rv = nghttp2_submit_settings(httpc->h2, NGHTTP2_FLAG_NONE, NULL, 0); if(rv != 0) { failf(data, "nghttp2_submit_settings() failed: %s(%d)", nghttp2_strerror(rv), rv); return CURLE_HTTP2; } } /* we are going to copy mem to httpc->inbuf. This is required since mem is part of buffer pointed by stream->mem, and callbacks called by nghttp2_session_mem_recv() will write stream specific data into stream->mem, overwriting data already there. */ if(H2_BUFSIZE < nread) { failf(data, "connection buffer size is too small to store data following " "HTTP Upgrade response header: buflen=%zu, datalen=%zu", H2_BUFSIZE, nread); return CURLE_HTTP2; } infof(conn->data, "Copying HTTP/2 data in stream buffer to connection buffer" " after upgrade: len=%zu\n", nread); memcpy(httpc->inbuf, mem, nread); httpc->inbuflen = nread; nproc = nghttp2_session_mem_recv(httpc->h2, (const uint8_t *)httpc->inbuf, httpc->inbuflen); if(nghttp2_is_fatal((int)nproc)) { failf(data, "nghttp2_session_mem_recv() failed: %s(%d)", nghttp2_strerror((int)nproc), (int)nproc); return CURLE_HTTP2; } DEBUGF(infof(data, "nghttp2_session_mem_recv() returns %zd\n", nproc)); if((ssize_t)nread == nproc) { httpc->inbuflen = 0; httpc->nread_inbuf = 0; } else { httpc->nread_inbuf += nproc; } /* Try to send some frames since we may read SETTINGS already. */ rv = nghttp2_session_send(httpc->h2); if(rv != 0) { failf(data, "nghttp2_session_send() failed: %s(%d)", nghttp2_strerror(rv), rv); return CURLE_HTTP2; } return CURLE_OK; }