summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/examples/Makefile.inc2
-rw-r--r--docs/examples/http2-serverpush.c313
-rw-r--r--docs/libcurl/opts/CURLMOPT_PUSHDATA.349
-rw-r--r--docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.3132
-rw-r--r--docs/libcurl/opts/Makefile.am8
-rw-r--r--include/curl/multi.h30
-rw-r--r--lib/http.c5
-rw-r--r--lib/http.h4
-rw-r--r--lib/http2.c222
-rw-r--r--lib/http2.h2
-rw-r--r--lib/multi.c30
-rw-r--r--lib/multihandle.h4
-rw-r--r--lib/multiif.h6
-rw-r--r--lib/url.c21
-rw-r--r--lib/url.h3
-rw-r--r--lib/urldata.h2
16 files changed, 798 insertions, 35 deletions
diff --git a/docs/examples/Makefile.inc b/docs/examples/Makefile.inc
index 4b0c28ff4..b2161300b 100644
--- a/docs/examples/Makefile.inc
+++ b/docs/examples/Makefile.inc
@@ -32,7 +32,7 @@ check_PROGRAMS = 10-at-a-time anyauthput cookie_interface debug fileupload \
imap-list imap-lsub imap-fetch imap-store imap-append imap-examine \
imap-search imap-create imap-delete imap-copy imap-noop imap-ssl \
imap-tls imap-multi url2file sftpget ftpsget postinmemory http2-download \
- http2-upload
+ http2-upload http2-serverpush
# These examples require external dependencies that may not be commonly
# available on POSIX systems, so don't bother attempting to compile them here.
diff --git a/docs/examples/http2-serverpush.c b/docs/examples/http2-serverpush.c
new file mode 100644
index 000000000..cc5bf3037
--- /dev/null
+++ b/docs/examples/http2-serverpush.c
@@ -0,0 +1,313 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+#include <stdio.h>
+#include <stdlib.h>
+
+/* somewhat unix-specific */
+#include <sys/time.h>
+#include <unistd.h>
+
+/* curl stuff */
+#include <curl/curl.h>
+
+#ifndef CURLPIPE_MULTIPLEX
+#error "too old libcurl, can't do HTTP/2 server push!"
+#endif
+
+static
+void dump(const char *text, unsigned char *ptr, size_t size,
+ char nohex)
+{
+ size_t i;
+ size_t c;
+
+ unsigned int width=0x10;
+
+ if(nohex)
+ /* without the hex output, we can fit more on screen */
+ width = 0x40;
+
+ fprintf(stderr, "%s, %ld bytes (0x%lx)\n",
+ text, (long)size, (long)size);
+
+ for(i=0; i<size; i+= width) {
+
+ fprintf(stderr, "%4.4lx: ", (long)i);
+
+ if(!nohex) {
+ /* hex not disabled, show it */
+ for(c = 0; c < width; c++)
+ if(i+c < size)
+ fprintf(stderr, "%02x ", ptr[i+c]);
+ else
+ fputs(" ", stderr);
+ }
+
+ for(c = 0; (c < width) && (i+c < size); c++) {
+ /* check for 0D0A; if found, skip past and start a new line of output */
+ if (nohex && (i+c+1 < size) && ptr[i+c]==0x0D && ptr[i+c+1]==0x0A) {
+ i+=(c+2-width);
+ break;
+ }
+ fprintf(stderr, "%c",
+ (ptr[i+c]>=0x20) && (ptr[i+c]<0x80)?ptr[i+c]:'.');
+ /* check again for 0D0A, to avoid an extra \n if it's at width */
+ if (nohex && (i+c+2 < size) && ptr[i+c+1]==0x0D && ptr[i+c+2]==0x0A) {
+ i+=(c+3-width);
+ break;
+ }
+ }
+ fputc('\n', stderr); /* newline */
+ }
+}
+
+static
+int my_trace(CURL *handle, curl_infotype type,
+ char *data, size_t size,
+ void *userp)
+{
+ const char *text;
+ (void)handle; /* prevent compiler warning */
+ (void)userp;
+ switch (type) {
+ case CURLINFO_TEXT:
+ fprintf(stderr, "== Info: %s", data);
+ default: /* in case a new one is introduced to shock us */
+ return 0;
+
+ case CURLINFO_HEADER_OUT:
+ text = "=> Send header";
+ break;
+ case CURLINFO_DATA_OUT:
+ text = "=> Send data";
+ break;
+ case CURLINFO_SSL_DATA_OUT:
+ text = "=> Send SSL data";
+ break;
+ case CURLINFO_HEADER_IN:
+ text = "<= Recv header";
+ break;
+ case CURLINFO_DATA_IN:
+ text = "<= Recv data";
+ break;
+ case CURLINFO_SSL_DATA_IN:
+ text = "<= Recv SSL data";
+ break;
+ }
+
+ dump(text, (unsigned char *)data, size, 1);
+ return 0;
+}
+
+static void setup(CURL *hnd)
+{
+ FILE *out = fopen("dl", "wb");
+
+ /* write to this file */
+ curl_easy_setopt(hnd, CURLOPT_WRITEDATA, out);
+
+ /* set the same URL */
+ curl_easy_setopt(hnd, CURLOPT_URL, "https://localhost:8443/index.html");
+
+ /* send it verbose for max debuggaility */
+ curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);
+ curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, my_trace);
+
+ /* HTTP/2 please */
+ curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
+
+ /* we use a self-signed test server, skip verification during debugging */
+ curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
+ curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L);
+
+#if (CURLPIPE_MULTIPLEX > 0)
+ /* wait for pipe connection to confirm */
+ curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L);
+#endif
+
+}
+
+/* called when there's an incoming push */
+static int server_push_callback(CURL *parent,
+ CURL *easy,
+ size_t num_headers,
+ struct curl_pushheaders *headers,
+ void *userp)
+{
+ char *headp;
+ size_t i;
+ int *transfers = (int *)userp;
+ char filename[128];
+ FILE *out;
+ static unsigned int count = 0;
+
+ (void)parent; /* we have no use for this */
+
+ sprintf(filename, "push%u", count++);
+
+ /* here's a new stream, save it in a new file for each new push */
+ out = fopen(filename, "wb");
+
+ /* write to this file */
+ curl_easy_setopt(easy, CURLOPT_WRITEDATA, out);
+
+ fprintf(stderr, "**** push callback approves stream %u, got %d headers!\n",
+ count, (int)num_headers);
+
+ for(i=0; i<num_headers; i++) {
+ headp = curl_pushheader_bynum(headers, i);
+ fprintf(stderr, "**** header %u: %s\n", (int)i, headp);
+ }
+
+ headp = curl_pushheader_byname(headers, ":path");
+ if(headp) {
+ fprintf(stderr, "**** The PATH is %s\n", headp /* skip :path + colon */ );
+ }
+
+ (*transfers)++; /* one more */
+ return CURL_PUSH_OK;
+}
+
+
+/*
+ * Download a file over HTTP/2, take care of server push.
+ */
+int main(void)
+{
+ CURL *easy;
+ CURLM *multi_handle;
+ int still_running; /* keep number of running handles */
+ int transfers=1; /* we start with one */
+ struct CURLMsg *m;
+
+ /* init a multi stack */
+ multi_handle = curl_multi_init();
+
+ easy = curl_easy_init();
+
+ /* set options */
+ setup(easy);
+
+ /* add the easy transfer */
+ curl_multi_add_handle(multi_handle, easy);
+
+ curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
+ curl_multi_setopt(multi_handle, CURLMOPT_PUSHFUNCTION, server_push_callback);
+ curl_multi_setopt(multi_handle, CURLMOPT_PUSHDATA, &transfers);
+
+ /* we start some action by calling perform right away */
+ curl_multi_perform(multi_handle, &still_running);
+
+ do {
+ struct timeval timeout;
+ int rc; /* select() return code */
+ CURLMcode mc; /* curl_multi_fdset() return code */
+
+ fd_set fdread;
+ fd_set fdwrite;
+ fd_set fdexcep;
+ int maxfd = -1;
+
+ long curl_timeo = -1;
+
+ FD_ZERO(&fdread);
+ FD_ZERO(&fdwrite);
+ FD_ZERO(&fdexcep);
+
+ /* set a suitable timeout to play around with */
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+
+ curl_multi_timeout(multi_handle, &curl_timeo);
+ if(curl_timeo >= 0) {
+ timeout.tv_sec = curl_timeo / 1000;
+ if(timeout.tv_sec > 1)
+ timeout.tv_sec = 1;
+ else
+ timeout.tv_usec = (curl_timeo % 1000) * 1000;
+ }
+
+ /* get file descriptors from the transfers */
+ mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
+
+ if(mc != CURLM_OK) {
+ fprintf(stderr, "curl_multi_fdset() failed, code %d.\n", mc);
+ break;
+ }
+
+ /* On success the value of maxfd is guaranteed to be >= -1. We call
+ select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
+ no fds ready yet so we call select(0, ...) --or Sleep() on Windows--
+ to sleep 100ms, which is the minimum suggested value in the
+ curl_multi_fdset() doc. */
+
+ if(maxfd == -1) {
+#ifdef _WIN32
+ Sleep(100);
+ rc = 0;
+#else
+ /* Portable sleep for platforms other than Windows. */
+ struct timeval wait = { 0, 100 * 1000 }; /* 100ms */
+ rc = select(0, NULL, NULL, NULL, &wait);
+#endif
+ }
+ else {
+ /* Note that on some platforms 'timeout' may be modified by select().
+ If you need access to the original value save a copy beforehand. */
+ rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
+ }
+
+ switch(rc) {
+ case -1:
+ /* select error */
+ break;
+ case 0:
+ default:
+ /* timeout or readable/writable sockets */
+ curl_multi_perform(multi_handle, &still_running);
+ break;
+ }
+
+ /*
+ * A little caution when doing server push is that libcurl itself has
+ * created and added one or more easy handles but we need to clean them up
+ * when we are done.
+ */
+
+ do {
+ int msgq = 0;;
+ m = curl_multi_info_read(multi_handle, &msgq);
+ if(m && (m->msg == CURLMSG_DONE)) {
+ CURL *e = m->easy_handle;
+ transfers--;
+ curl_multi_remove_handle(multi_handle, e);
+ curl_easy_cleanup(e);
+ }
+ } while(m);
+
+ } while(transfers); /* as long as we have transfers going */
+
+ curl_multi_cleanup(multi_handle);
+
+
+ return 0;
+}
diff --git a/docs/libcurl/opts/CURLMOPT_PUSHDATA.3 b/docs/libcurl/opts/CURLMOPT_PUSHDATA.3
new file mode 100644
index 000000000..135e63212
--- /dev/null
+++ b/docs/libcurl/opts/CURLMOPT_PUSHDATA.3
@@ -0,0 +1,49 @@
+.\" **************************************************************************
+.\" * _ _ ____ _
+.\" * Project ___| | | | _ \| |
+.\" * / __| | | | |_) | |
+.\" * | (__| |_| | _ <| |___
+.\" * \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLMOPT_PUSHDATA 3 "1 Jun 2015" "libcurl 7.44.0" "curl_multi_setopt options"
+.SH NAME
+CURLMOPT_PUSHDATA \- pointer to pass to push callback
+.SH SYNOPSIS
+.nf
+#include <curl/curl.h>
+
+CURLMcode curl_multi_setopt(CURLM *handle, CURLMOPT_PUSHDATA, void *pointer);
+.fi
+.SH DESCRIPTION
+Set \fIpointer\fP to pass as the last argument to the
+\fICURLMOPT_PUSHFUNCTION(3)\fP callback. The pointer will not be touched or
+used by libcurl itself, only passed on to the callback function.
+.SH DEFAULT
+NULL
+.SH PROTOCOLS
+HTTP(S)
+.SH EXAMPLE
+TODO
+.SH AVAILABILITY
+Added in 7.44.0
+.SH RETURN VALUE
+Returns CURLM_OK if the option is supported, and CURLM_UNKNOWN_OPTION if not.
+.SH "SEE ALSO"
+.BR CURLMOPT_PUSHFUNCTION "(3), " CURLMOPT_PIPELINING "(3), "
+.BR CURLOPT_PIPEWAIT "(3), "
+.BR RFC 7540
diff --git a/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.3 b/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.3
new file mode 100644
index 000000000..fb5e4e49c
--- /dev/null
+++ b/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.3
@@ -0,0 +1,132 @@
+.\" **************************************************************************
+.\" * _ _ ____ _
+.\" * Project ___| | | | _ \| |
+.\" * / __| | | | |_) | |
+.\" * | (__| |_| | _ <| |___
+.\" * \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at http://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLMOPT_PUSHFUNCTION 3 "1 Jun 2015" "libcurl 7.44.0" "curl_multi_setopt options"
+.SH NAME
+CURLMOPT_PUSHFUNCTION \- callback that approves or denies server pushes
+.SH SYNOPSIS
+.nf
+#include <curl/curl.h>
+
+char *curl_pushheader_bynum(push_headers, int num);
+char *curl_pushheader_byname(push_headers, const char *name);
+
+int curl_push_callback(CURL *parent,
+ CURL *easy,
+ size_t num_headers,
+ struct curl_pushheaders *headers,
+ void *userp);
+
+CURLMcode curl_multi_setopt(CURLM *handle, CURLMOPT_PUSHFUNCTION,
+ curl_push_callback func);
+.fi
+.SH DESCRIPTION
+This callback gets called when a new HTTP/2 stream is being pushed by the
+server (using the PUSH_PROMISE frame). If no push callback is set, all offered
+pushes will be denied automatically.
+.SH CALLBACK DESCRIPTION
+The callback gets its arguments like this:
+
+\fIparent\fP is the handle of the stream on which this push arrives. The new
+handle has been duphandle()d from the parent, meaning that it has gotten all
+its options inherited. It is then up to the application to alter any options
+if desired.
+
+\fIeasy\fP is a newly created handle that represents this upcoming transfer.
+
+\fInum_headers\fP is the number of name+value pairs that was received and can
+be accessed
+
+\fIheaders\fP is a handle used to access push headers using the accessor
+functions described below. This only accesses and provides the PUSH_PROMISE
+headers, the normal response headers will be provided in the header callback
+as usual.
+
+\fIuserp\fP is the pointer set with \fICURLMOPT_PUSHDATA(3)\fP
+
+If the callback returns CURL_PUSH_OK, the 'easy' handle will be added to the
+multi handle, the callback must not do that by itself.
+
+The callback can access PUSH_PROMISE headers with two accessor
+functions. These functions can only be used from within this callback and they
+can only access the PUSH_PROMISE headers. The normal response headers will be
+pased to the header callback for pushed streams just as for normal streams.
+.IP curl_pushheader_bynum
+Returns the header at index 'num' (or NULL). The returned pointer points to a
+"name:value" string that will be freed when this callback returns.
+.IP curl_pushheader_byname
+Returns the value for the given header name (or NULL). This is a shortcut so
+that the application doesn't have to loop through all headers to find the one
+it is interested in. The data pointed will be freed when this callback
+returns.
+.SH CALLBACK RETURN VALUE
+.IP "CURL_PUSH_OK (0)"
+The application has accepted the stream and it can now start receiving data,
+the ownership of the CURL handle has been taken over by the application.
+.IP "CURL_PUSH_DENY (1)"
+The callback denies the stream and no data for this will reach the
+application, the easy handle will be destroyed by libcurl.
+.IP *
+All other return codes are reserved for future use.
+.SH DEFAULT
+NULL, no callback
+.SH PROTOCOLS
+HTTP(S) (HTTP/2 only)
+.SH EXAMPLE
+.nf
+/* only allow pushes for file names starting with "push-" */
+int push_callback(CURL *parent,
+ CURL *easy,
+ size_t num_headers,
+ struct curl_pushheaders *headers,
+ void *userp)
+{
+ char *headp;
+ int *transfers = (int *)userp;
+ FILE *out;
+ headp = curl_pushheader_byname(headers, ":path");
+ if(headp && !strncmp(headp, "/push-", 6)) {
+ fprintf(stderr, "The PATH is %s\n", headp);
+
+ /* save the push here */
+ out = fopen("pushed-stream", "wb");
+
+ /* write to this file */
+ curl_easy_setopt(easy, CURLOPT_WRITEDATA, out);
+
+ (*transfers)++; /* one more */
+
+ return CURL_PUSH_OK;
+ }
+ return CURL_PUSH_DENY;
+}
+
+curl_multi_setopt(multi, CURLMOPT_PUSHFUNCTION, push_callback);
+curl_multi_setopt(multi, CURLMOPT_PUSHDATA, &counter);
+.fi
+.SH AVAILABILITY
+Added in 7.44.0
+.SH RETURN VALUE
+Returns CURLM_OK if the option is supported, and CURLM_UNKNOWN_OPTION if not.
+.SH "SEE ALSO"
+.BR CURLMOPT_PUSHDATA "(3), " CURLMOPT_PIPELINING "(3), " CURLOPT_PIPEWAIT "(3), "
+.BR RFC 7540
diff --git a/docs/libcurl/opts/Makefile.am b/docs/libcurl/opts/Makefile.am
index 5517811d0..4865b41b5 100644
--- a/docs/libcurl/opts/Makefile.am
+++ b/docs/libcurl/opts/Makefile.am
@@ -114,7 +114,8 @@ man_MANS = CURLOPT_ACCEPT_ENCODING.3 CURLOPT_ACCEPTTIMEOUT_MS.3 \
CURLMOPT_SOCKETDATA.3 CURLMOPT_SOCKETFUNCTION.3 CURLMOPT_TIMERDATA.3 \
CURLMOPT_TIMERFUNCTION.3 CURLOPT_UNIX_SOCKET_PATH.3 \
CURLOPT_PATH_AS_IS.3 CURLOPT_PROXY_SERVICE_NAME.3 \
- CURLOPT_SERVICE_NAME.3 CURLOPT_PIPEWAIT.3
+ CURLOPT_SERVICE_NAME.3 CURLOPT_PIPEWAIT.3 CURLMOPT_PUSHDATA.3 \
+ CURLMOPT_PUSHFUNCTION.3
HTMLPAGES = CURLOPT_ACCEPT_ENCODING.html CURLOPT_ACCEPTTIMEOUT_MS.html \
CURLOPT_ADDRESS_SCOPE.html CURLOPT_APPEND.html \
@@ -222,7 +223,8 @@ HTMLPAGES = CURLOPT_ACCEPT_ENCODING.html CURLOPT_ACCEPTTIMEOUT_MS.html \
CURLMOPT_TIMERDATA.html CURLMOPT_TIMERFUNCTION.html \
CURLOPT_UNIX_SOCKET_PATH.html CURLOPT_PATH_AS_IS.html \
CURLOPT_PROXY_SERVICE_NAME.html CURLOPT_SERVICE_NAME.html \
- CURLOPT_PIPEWAIT.html
+ CURLOPT_PIPEWAIT.html CURLMOPT_PUSHDATA.html \
+ CURLMOPT_PUSHFUNCTION.html
PDFPAGES = CURLOPT_ACCEPT_ENCODING.pdf CURLOPT_ACCEPTTIMEOUT_MS.pdf \
CURLOPT_ADDRESS_SCOPE.pdf CURLOPT_APPEND.pdf CURLOPT_AUTOREFERER.pdf \
@@ -328,7 +330,7 @@ PDFPAGES = CURLOPT_ACCEPT_ENCODING.pdf CURLOPT_ACCEPTTIMEOUT_MS.pdf \
CURLMOPT_TIMERDATA.pdf CURLMOPT_TIMERFUNCTION.pdf \
CURLOPT_UNIX_SOCKET_PATH.pdf CURLOPT_PATH_AS_IS.pdf \
CURLOPT_PROXY_SERVICE_NAME.pdf CURLOPT_SERVICE_NAME.pdf \
- CURLOPT_PIPEWAIT.pdf
+ CURLOPT_PIPEWAIT.pdf CURLMOPT_PUSHDATA.pdf CURLMOPT_PUSHFUNCTION.pdf
CLEANFILES = $(HTMLPAGES) $(PDFPAGES)
diff --git a/include/curl/multi.h b/include/curl/multi.h
index 0d859f8fd..d34694c4c 100644
--- a/include/curl/multi.h
+++ b/include/curl/multi.h
@@ -283,6 +283,30 @@ typedef int (*curl_multi_timer_callback)(CURLM *multi, /* multi handle */
void *userp); /* private callback
pointer */
+/*
+ * Name: curl_push_callback
+ *
+ * Desc: This callback gets called when a new stream is being pushed by the
+ * server. It approves or denies the new stream.
+ *
+ * Returns: CURL_PUSH_OK or CURL_PUSH_DENY.
+ */
+#define CURL_PUSH_OK 0
+#define CURL_PUSH_DENY 1
+
+struct curl_pushheaders; /* forward declaration only */
+
+CURL_EXTERN char *curl_pushheader_bynum(struct curl_pushheaders *h,
+ size_t num);
+CURL_EXTERN char *curl_pushheader_byname(struct curl_pushheaders *h,
+ const char *name);
+
+typedef int (*curl_push_callback)(CURL *parent,
+ CURL *easy,
+ size_t num_headers,
+ struct curl_pushheaders *headers,
+ void *userp);
+
CURL_EXTERN CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s,
int *running_handles);
@@ -370,6 +394,12 @@ typedef enum {
/* maximum number of open connections in total */
CINIT(MAX_TOTAL_CONNECTIONS, LONG, 13),
+ /* This is the server push callback function pointer */
+ CINIT(PUSHFUNCTION, FUNCTIONPOINT, 14),
+
+ /* This is the argument passed to the server push callback */
+ CINIT(PUSHDATA, OBJECTPOINT, 15),
+
CURLMOPT_LASTENTRY /* the last unused */
} CURLMoption;
diff --git a/lib/http.c b/lib/http.c
index 8e422f0bf..e3cb8370a 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -164,6 +164,7 @@ CURLcode Curl_http_setup_conn(struct connectdata *conn)
conn->data->req.protop = http;
Curl_http2_setup_conn(conn);
+ Curl_http2_setup_req(conn->data);
return CURLE_OK;
}
@@ -175,6 +176,8 @@ static CURLcode http_disconnect(struct connectdata *conn, bool dead_connection)
if(http) {
Curl_add_buffer_free(http->header_recvbuf);
http->header_recvbuf = NULL; /* clear the pointer */
+ free(http->push_headers);
+ http->push_headers = NULL;
}
#else
(void)conn;
@@ -1491,6 +1494,8 @@ CURLcode Curl_http_done(struct connectdata *conn,
DEBUGF(infof(data, "free header_recvbuf!!\n"));
Curl_add_buffer_free(http->header_recvbuf);
http->header_recvbuf = NULL; /* clear the pointer */
+ free(http->push_headers);
+ http->push_headers = NULL;
}
#endif
diff --git a/lib/http.h b/lib/http.h
index 415be39e1..63ea4ace4 100644
--- a/lib/http.h
+++ b/lib/http.h
@@ -176,6 +176,10 @@ struct HTTP {
const uint8_t *upload_mem; /* points to a buffer to read from */
size_t upload_len; /* size of the buffer 'upload_mem' points to */
curl_off_t upload_left; /* number of bytes left to upload */
+
+ char **push_headers; /* allocated array */
+ size_t push_headers_used; /* number of entries filled in */
+ size_t push_headers_alloc; /* number of entries allocated */
#endif
};
diff --git a/lib/http2.c b/lib/http2.c
index fa47d0ece..fc0d5de91 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"
@@ -94,12 +95,9 @@ static CURLcode http2_disconnect(struct connectdata *conn,
}
/* called from Curl_http_setup_conn */
-void Curl_http2_setup_conn(struct connectdata *conn)
+void Curl_http2_setup_req(struct SessionHandle *data)
{
- struct HTTP *http = conn->data->req.protop;
-
- conn->proto.httpc.settings.max_concurrent_streams =
- DEFAULT_MAX_CONCURRENT_STREAMS;
+ struct HTTP *http = data->req.protop;
http->nread_header_recvbuf = 0;
http->bodystarted = FALSE;
@@ -108,13 +106,18 @@ void Curl_http2_setup_conn(struct connectdata *conn)
http->pauselen = 0;
http->error_code = NGHTTP2_NO_ERROR;
http->closed = FALSE;
-
- /* where to store incoming data for this stream and how big the buffer is */
- http->mem = conn->data->state.buffer;
+ http->mem = data->state.buffer;
http->len = BUFSIZE;
http->memlen = 0;
}
+/* called from Curl_http_setup_conn */
+void Curl_http2_setup_conn(struct connectdata *conn)
+{
+ conn->proto.httpc.settings.max_concurrent_streams =
+ DEFAULT_MAX_CONCURRENT_STREAMS;
+}
+
/*
* HTTP2 handler interface. This isn't added to the general list of protocols
* but will be used at run-time when the protocol is dynamically switched from
@@ -205,6 +208,160 @@ 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
+ */
+char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t 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;
+ else {
+ struct HTTP *stream = h->data->req.protop;
+ if(num < stream->push_headers_used)
+ return stream->push_headers[num];
+ }
+ return NULL;
+}
+
+/*
+ * push header access function. Only to be used from within the push callback
+ */
+char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header)
+{
+ /* Verify that we got a good easy handle in the push header struct,
+ mostly to detect rubbish input fast(er). Also empty header name
+ is just a rubbish too. We have to allow ":" at the beginning of
+ the header, but header == ":" must be rejected. If we have ':' in
+ the middle of header, it could be matched in middle of the value,
+ this is because we do prefix match.*/
+ if(!h || !GOOD_EASY_HANDLE(h->data) || !header || !header[0] ||
+ Curl_raw_equal(header, ":") || strchr(header + 1, ':'))
+ return NULL;
+ else {
+ struct HTTP *stream = h->data->req.protop;
+ size_t len = strlen(header);
+ size_t i;
+ for(i=0; i<stream->push_headers_used; i++) {
+ if(!strncmp(header, stream->push_headers[i], len)) {
+ /* sub-match, make sure that it us followed by a colon */
+ if(stream->push_headers[i][len] != ':')
+ continue;
+ return &stream->push_headers[i][len+1];
+ }
+ }
+ }
+ return NULL;
+}
+
+static CURL *duphandle(struct SessionHandle *data)
+{
+ struct SessionHandle *second = curl_easy_duphandle(data);
+ if(second) {
+ /* setup the request struct */
+ struct HTTP *http = calloc(1, sizeof(struct HTTP));
+ if(!http) {
+ (void)Curl_close(second);
+ second = NULL;
+ }
+ else {
+ second->req.protop = http;
+ http->header_recvbuf = Curl_add_buffer_init();
+ if(!http->header_recvbuf) {
+ free(http);
+ (void)Curl_close(second);
+ second = NULL;
+ }
+ else
+ Curl_http2_setup_req(second);
+ }
+ }
+ return second;
+}
+
+
+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;
+
+ 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;
+ /* put the newhandle in the hash with the stream id as key */
+ if(!Curl_hash_add(&httpc->streamsh,
+ (size_t *)&frame->promised_stream_id,
+ sizeof(frame->promised_stream_id), newhandle)) {
+ failf(conn->data, "Couldn't add stream to hash!");
+ rv = 1;
+ }
+ else
+ rv = 0;
+ }
+ else {
+ DEBUGF(infof(data, "Got PUSH_PROMISE, ignore it!\n"));
+ rv = 1;
+ }
+ fail:
+ return rv;
+}
+
static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
void *userp)
{
@@ -292,12 +449,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, 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:
@@ -523,11 +682,6 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
(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 */
@@ -547,6 +701,36 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
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
diff --git a/lib/http2.h b/lib/http2.h
index 1614736d3..bb7ad9c4c 100644
--- a/lib/http2.h
+++ b/lib/http2.h
@@ -46,6 +46,7 @@ CURLcode Curl_http2_switched(struct connectdata *conn,
const char *data, size_t nread);
/* called from Curl_http_setup_conn */
void Curl_http2_setup_conn(struct connectdata *conn);
+void Curl_http2_setup_req(struct SessionHandle *data);
#else /* USE_NGHTTP2 */
#define Curl_http2_init(x) CURLE_UNSUPPORTED_PROTOCOL
#define Curl_http2_send_request(x) CURLE_UNSUPPORTED_PROTOCOL
@@ -53,6 +54,7 @@ void Curl_http2_setup_conn(struct connectdata *conn);
#define Curl_http2_setup(x) CURLE_UNSUPPORTED_PROTOCOL
#define Curl_http2_switched(x,y,z) CURLE_UNSUPPORTED_PROTOCOL
#define Curl_http2_setup_conn(x)
+#define Curl_http2_setup_req(x)
#endif
#endif /* HEADER_CURL_HTTP2_H */
diff --git a/lib/multi.c b/lib/multi.c
index fcef2e10f..85a563d55 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);
@@ -954,6 +952,28 @@ static bool multi_ischanged(struct Curl_multi *multi, bool clear)
return retval;
}
+CURLMcode Curl_multi_add_perform(struct Curl_multi *multi,
+ struct SessionHandle *data,
+ struct connectdata *conn)
+{
+ CURLMcode rc;
+
+ rc = curl_multi_add_handle(multi, data);
+ if(!rc) {
+ struct SingleRequest *k = &data->req;
+
+ /* pass in NULL for 'conn' here since we don't want to init the
+ connection, only this transfer */
+ Curl_init_do(data, NULL);
+
+ /* take this handle to the perform state right away */
+ multistate(data, CURLM_STATE_PERFORM);
+ data->easy_conn = conn;
+ k->keepon |= KEEP_RECV; /* setup to receive! */
+ }
+ return rc;
+}
+
static CURLMcode multi_runsingle(struct Curl_multi *multi,
struct timeval now,
struct SessionHandle *data)
@@ -2346,6 +2366,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 cad44d1df..6c24f50f1 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/multiif.h b/lib/multiif.h
index 5052f65ae..e6323adf5 100644
--- a/lib/multiif.h
+++ b/lib/multiif.h
@@ -88,4 +88,10 @@ void Curl_multi_connchanged(struct Curl_multi *multi);
void Curl_multi_closed(struct connectdata *conn, curl_socket_t s);
+/*
+ * Add a handle and move it into PERFORM state at once. For pushed streams.
+ */
+CURLMcode Curl_multi_add_perform(struct Curl_multi *multi,
+ struct SessionHandle *data,
+ struct connectdata *conn);
#endif /* HEADER_CURL_MULTIIF_H */
diff --git a/lib/url.c b/lib/url.c
index 17279bbe0..4e9495251 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -142,7 +142,6 @@ find_oldest_idle_connection_in_bundle(struct SessionHandle *data,
struct connectbundle *bundle);
static void conn_free(struct connectdata *conn);
static void signalPipeClose(struct curl_llist *pipeline, bool pipe_broke);
-static CURLcode do_init(struct connectdata *conn);
static CURLcode parse_url_login(struct SessionHandle *data,
struct connectdata *conn,
char **userptr, char **passwdptr,
@@ -5651,7 +5650,7 @@ static CURLcode create_conn(struct SessionHandle *data,
}
/* since we skip do_init() */
- do_init(conn);
+ Curl_init_do(data, conn);
goto out;
}
@@ -5830,7 +5829,7 @@ static CURLcode create_conn(struct SessionHandle *data,
conn->inuse = TRUE;
/* Setup and init stuff before DO starts, in preparing for the transfer. */
- do_init(conn);
+ Curl_init_do(data, conn);
/*
* Setup whatever necessary for a resumed transfer
@@ -6112,20 +6111,24 @@ CURLcode Curl_done(struct connectdata **connp,
}
/*
- * do_init() inits the readwrite session. This is inited each time (in the DO
- * function before the protocol-specific DO functions are invoked) for a
- * transfer, sometimes multiple times on the same SessionHandle. Make sure
+ * Curl_init_do() inits the readwrite session. This is inited each time (in
+ * the DO function before the protocol-specific DO functions are invoked) for
+ * a transfer, sometimes multiple times on the same SessionHandle. Make sure
* nothing in here depends on stuff that are setup dynamically for the
* transfer.
+ *
+ * Allow this function to get called with 'conn' set to NULL.
*/
-static CURLcode do_init(struct connectdata *conn)
+CURLcode Curl_init_do(struct SessionHandle *data, struct connectdata *conn)
{
- struct SessionHandle *data = conn->data;
struct SingleRequest *k = &data->req;
+ if(conn)
+ conn->bits.do_more = FALSE; /* by default there's no curl_do_more() to
+ * use */
+
data->state.done = FALSE; /* Curl_done() is not called yet */
- conn->bits.do_more = FALSE; /* by default there's no curl_do_more() to use */
data->state.expect100header = FALSE;
if(data->set.opt_no_body)
diff --git a/lib/url.h b/lib/url.h
index e49b7724d..f9667cbc3 100644
--- a/lib/url.h
+++ b/lib/url.h
@@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@@ -27,6 +27,7 @@
* Prototypes for library-wide functions provided by url.c
*/
+CURLcode Curl_init_do(struct SessionHandle *data, struct connectdata *conn);
CURLcode Curl_open(struct SessionHandle **curl);
CURLcode Curl_init_userdefined(struct UserDefined *set);
CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
diff --git a/lib/urldata.h b/lib/urldata.h
index 64b855e32..5a2a07d9e 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. */