1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24/* <DESC>
25 * HTTP/2 server push. Receive all data in memory.
26 * </DESC>
27 */
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31
32/* somewhat unix-specific */
33#include <sys/time.h>
34#include <unistd.h>
35
36/* curl stuff */
37#include <curl/curl.h>
38
39struct Memory {
40  char *memory;
41  size_t size;
42};
43
44static size_t
45write_cb(void *contents, size_t size, size_t nmemb, void *userp)
46{
47  size_t realsize = size * nmemb;
48  struct Memory *mem = (struct Memory *)userp;
49  char *ptr = realloc(mem->memory, mem->size + realsize + 1);
50  if(!ptr) {
51    /* out of memory! */
52    printf("not enough memory (realloc returned NULL)\n");
53    return 0;
54  }
55
56  mem->memory = ptr;
57  memcpy(&(mem->memory[mem->size]), contents, realsize);
58  mem->size += realsize;
59  mem->memory[mem->size] = 0;
60
61  return realsize;
62}
63
64#define MAX_FILES 10
65static struct Memory files[MAX_FILES];
66static int pushindex = 1;
67
68static void init_memory(struct Memory *chunk)
69{
70  chunk->memory = malloc(1);  /* grown as needed with realloc */
71  chunk->size = 0;            /* no data at this point */
72}
73
74static void setup(CURL *hnd)
75{
76  /* set the same URL */
77  curl_easy_setopt(hnd, CURLOPT_URL, "https://localhost:8443/index.html");
78
79  /* HTTP/2 please */
80  curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
81
82  /* we use a self-signed test server, skip verification during debugging */
83  curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
84  curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L);
85
86  /* write data to a struct  */
87  curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, write_cb);
88  init_memory(&files[0]);
89  curl_easy_setopt(hnd, CURLOPT_WRITEDATA, &files[0]);
90
91  /* wait for pipe connection to confirm */
92  curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L);
93}
94
95/* called when there is an incoming push */
96static int server_push_callback(CURL *parent,
97                                CURL *easy,
98                                size_t num_headers,
99                                struct curl_pushheaders *headers,
100                                void *userp)
101{
102  char *headp;
103  int *transfers = (int *)userp;
104  (void)parent; /* we have no use for this */
105  (void)num_headers; /* unused */
106
107  if(pushindex == MAX_FILES)
108    /* cannot fit anymore */
109    return CURL_PUSH_DENY;
110
111  /* write to this buffer */
112  init_memory(&files[pushindex]);
113  curl_easy_setopt(easy, CURLOPT_WRITEDATA, &files[pushindex]);
114  pushindex++;
115
116  headp = curl_pushheader_byname(headers, ":path");
117  if(headp)
118    fprintf(stderr, "* Pushed :path '%s'\n", headp /* skip :path + colon */);
119
120  (*transfers)++; /* one more */
121  return CURL_PUSH_OK;
122}
123
124
125/*
126 * Download a file over HTTP/2, take care of server push.
127 */
128int main(void)
129{
130  CURL *easy;
131  CURLM *multi;
132  int still_running; /* keep number of running handles */
133  int transfers = 1; /* we start with one */
134  int i;
135  struct CURLMsg *m;
136
137  /* init a multi stack */
138  multi = curl_multi_init();
139
140  easy = curl_easy_init();
141
142  /* set options */
143  setup(easy);
144
145  /* add the easy transfer */
146  curl_multi_add_handle(multi, easy);
147
148  curl_multi_setopt(multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
149  curl_multi_setopt(multi, CURLMOPT_PUSHFUNCTION, server_push_callback);
150  curl_multi_setopt(multi, CURLMOPT_PUSHDATA, &transfers);
151
152  while(transfers) {
153    int rc;
154    CURLMcode mcode = curl_multi_perform(multi, &still_running);
155    if(mcode)
156      break;
157
158    mcode = curl_multi_wait(multi, NULL, 0, 1000, &rc);
159    if(mcode)
160      break;
161
162
163    /*
164     * When doing server push, libcurl itself created and added one or more
165     * easy handles but *we* need to clean them up when they are done.
166     */
167    do {
168      int msgq = 0;
169      m = curl_multi_info_read(multi, &msgq);
170      if(m && (m->msg == CURLMSG_DONE)) {
171        CURL *e = m->easy_handle;
172        transfers--;
173        curl_multi_remove_handle(multi, e);
174        curl_easy_cleanup(e);
175      }
176    } while(m);
177
178  }
179
180
181  curl_multi_cleanup(multi);
182
183  /* 'pushindex' is now the number of received transfers */
184  for(i = 0; i < pushindex; i++) {
185    /* do something fun with the data, and then free it when done */
186    free(files[i].memory);
187  }
188
189  return 0;
190}
191