diff --git a/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.3 b/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.3 index 28fa2e76c071bd..a46150ab71009e 100644 --- a/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.3 +++ b/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.3 @@ -39,7 +39,7 @@ struct curl_headerpair *curl_pushheader_byname(push_headers, char *name); int curl_push_callback(CURL *parent, CURL *easy, - int num_headers, + size_t num_headers, struct curl_pushheaders *headers, void *userp); diff --git a/include/curl/multi.h b/include/curl/multi.h index ed3a3a793ebb0e..5b462adc8105b7 100644 --- a/include/curl/multi.h +++ b/include/curl/multi.h @@ -302,10 +302,14 @@ struct curl_headerpair { }; struct curl_pushheaders; /* forward declaration only */ +struct curl_headerpair *curl_pushheader_bynum(struct curl_pushheaders *h, + int num); +struct curl_headerpair *curl_pushheader_byname(struct curl_pushheaders *h, + char *name); typedef int (*curl_push_callback)(CURL *parent, CURL *easy, - int num_headers, + size_t num_headers, struct curl_pushheaders *headers, void *userp); diff --git a/lib/http2.c b/lib/http2.c index fa47d0ece16eae..ae8afa480447fe 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -33,6 +33,7 @@ #include "rawstr.h" #include "multiif.h" #include "conncache.h" +#include "url.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -205,6 +206,71 @@ static ssize_t send_callback(nghttp2_session *h2, return written; } + +/* We pass a pointer to this struct in the push callback, but the contents of + the struct are hidden from the user. */ +struct curl_pushheaders { + struct SessionHandle *data; + const nghttp2_push_promise *frame; +}; + +/* + * push header access function. Only to be used from within the push callback + */ +struct curl_headerpair *curl_pushheader_bynum(struct curl_pushheaders *h, + int num) +{ + /* Verify that we got a good easy handle in the push header struct, mostly to + detect rubbish input fast(er). */ + if(!h || !GOOD_EASY_HANDLE(h->data)) + return NULL; + (void)num; + return NULL; +} + +static int push_promise(struct SessionHandle *data, + const nghttp2_push_promise *frame) +{ + int rv; + if(data->multi->push_cb) { + /* clone the parent */ + CURL *newhandle = curl_easy_duphandle(data); + if(!newhandle) { + infof(data, "failed to duplicate handle\n"); + rv = 1; /* FAIL HARD */ + } + else { + struct curl_pushheaders heads; + heads.data = data; + heads.frame = frame; + /* ask the application */ + DEBUGF(infof(data, "Got PUSH_PROMISE, ask application!\n")); + rv = data->multi->push_cb(data, newhandle, + frame->nvlen, &heads, + data->multi->push_userp); + if(rv) + /* denied, kill off the new handle again */ + (void)Curl_close(newhandle); + else { + /* approved, add to the multi handle */ + CURLMcode rc = curl_multi_add_handle(data->multi, newhandle); + if(rc) { + infof(data, "failed to add handle to multi\n"); + Curl_close(newhandle); + rv = 1; + } + else + rv = 0; + } + } + } + else { + DEBUGF(infof(data, "Got PUSH_PROMISE, ignore it!\n")); + rv = 1; + } + return rv; +} + static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { @@ -292,12 +358,14 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, 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; + rv = push_promise(data_s, &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: diff --git a/lib/multi.c b/lib/multi.c index a8d3e38b59c013..33c03f299fda67 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -62,8 +62,6 @@ #define GOOD_MULTI_HANDLE(x) \ ((x) && (((struct Curl_multi *)(x))->type == CURL_MULTI_HANDLE)) -#define GOOD_EASY_HANDLE(x) \ - ((x) && (((struct SessionHandle *)(x))->magic == CURLEASY_MAGIC_NUMBER)) static void singlesocket(struct Curl_multi *multi, struct SessionHandle *data); @@ -2341,6 +2339,12 @@ CURLMcode curl_multi_setopt(CURLM *multi_handle, case CURLMOPT_SOCKETDATA: multi->socket_userp = va_arg(param, void *); break; + case CURLMOPT_PUSHFUNCTION: + multi->push_cb = va_arg(param, curl_push_callback); + break; + case CURLMOPT_PUSHDATA: + multi->push_userp = va_arg(param, void *); + break; case CURLMOPT_PIPELINING: multi->pipelining = va_arg(param, long); break; diff --git a/lib/multihandle.h b/lib/multihandle.h index cad44d1df5af9a..6c24f50f1e1f74 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -87,6 +87,10 @@ struct Curl_multi { curl_socket_callback socket_cb; void *socket_userp; + /* callback function and user data pointer for server push */ + curl_push_callback push_cb; + void *push_userp; + /* Hostname cache */ struct curl_hash hostcache; diff --git a/lib/urldata.h b/lib/urldata.h index 05bda794b744b1..59c704e0da6f5c 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -198,6 +198,8 @@ #define HEADERSIZE 256 #define CURLEASY_MAGIC_NUMBER 0xc0dedbadU +#define GOOD_EASY_HANDLE(x) \ + ((x) && (((struct SessionHandle *)(x))->magic == CURLEASY_MAGIC_NUMBER)) /* Some convenience macros to get the larger/smaller value out of two given. We prefix with CURL to prevent name collisions. */