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
26 * </DESC>
27 */
28
29/* curl stuff */
30#include <curl/curl.h>
31#include <curl/mprintf.h>
32
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36
37/* somewhat unix-specific */
38#include <sys/time.h>
39#include <unistd.h>
40
41#ifndef CURLPIPE_MULTIPLEX
42#error "too old libcurl, cannot do HTTP/2 server push!"
43#endif
44
45static
46void dump(const char *text, unsigned char *ptr, size_t size,
47          char nohex)
48{
49  size_t i;
50  size_t c;
51
52  unsigned int width = 0x10;
53
54  if(nohex)
55    /* without the hex output, we can fit more on screen */
56    width = 0x40;
57
58  fprintf(stderr, "%s, %lu bytes (0x%lx)\n",
59          text, (unsigned long)size, (unsigned long)size);
60
61  for(i = 0; i<size; i += width) {
62
63    fprintf(stderr, "%4.4lx: ", (unsigned long)i);
64
65    if(!nohex) {
66      /* hex not disabled, show it */
67      for(c = 0; c < width; c++)
68        if(i + c < size)
69          fprintf(stderr, "%02x ", ptr[i + c]);
70        else
71          fputs("   ", stderr);
72    }
73
74    for(c = 0; (c < width) && (i + c < size); c++) {
75      /* check for 0D0A; if found, skip past and start a new line of output */
76      if(nohex && (i + c + 1 < size) && ptr[i + c] == 0x0D &&
77         ptr[i + c + 1] == 0x0A) {
78        i += (c + 2 - width);
79        break;
80      }
81      fprintf(stderr, "%c",
82              (ptr[i + c] >= 0x20) && (ptr[i + c]<0x80)?ptr[i + c]:'.');
83      /* check again for 0D0A, to avoid an extra \n if it's at width */
84      if(nohex && (i + c + 2 < size) && ptr[i + c + 1] == 0x0D &&
85         ptr[i + c + 2] == 0x0A) {
86        i += (c + 3 - width);
87        break;
88      }
89    }
90    fputc('\n', stderr); /* newline */
91  }
92}
93
94static
95int my_trace(CURL *handle, curl_infotype type,
96             char *data, size_t size,
97             void *userp)
98{
99  const char *text;
100  (void)handle; /* prevent compiler warning */
101  (void)userp;
102  switch(type) {
103  case CURLINFO_TEXT:
104    fprintf(stderr, "== Info: %s", data);
105    return 0;
106  case CURLINFO_HEADER_OUT:
107    text = "=> Send header";
108    break;
109  case CURLINFO_DATA_OUT:
110    text = "=> Send data";
111    break;
112  case CURLINFO_SSL_DATA_OUT:
113    text = "=> Send SSL data";
114    break;
115  case CURLINFO_HEADER_IN:
116    text = "<= Recv header";
117    break;
118  case CURLINFO_DATA_IN:
119    text = "<= Recv data";
120    break;
121  case CURLINFO_SSL_DATA_IN:
122    text = "<= Recv SSL data";
123    break;
124  default: /* in case a new one is introduced to shock us */
125    return 0;
126  }
127
128  dump(text, (unsigned char *)data, size, 1);
129  return 0;
130}
131
132#define OUTPUTFILE "download_0.data"
133
134static int setup(CURL *hnd, const char *url)
135{
136  FILE *out = fopen(OUTPUTFILE, "wb");
137  if(!out)
138    /* failed */
139    return 1;
140
141  curl_easy_setopt(hnd, CURLOPT_URL, url);
142  curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
143  curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
144  curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L);
145
146  curl_easy_setopt(hnd, CURLOPT_WRITEDATA, out);
147
148  /* please be verbose */
149  curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);
150  curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, my_trace);
151
152#if (CURLPIPE_MULTIPLEX > 0)
153  /* wait for pipe connection to confirm */
154  curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L);
155#endif
156  return 0; /* all is good */
157}
158
159/* called when there's an incoming push */
160static int server_push_callback(CURL *parent,
161                                CURL *easy,
162                                size_t num_headers,
163                                struct curl_pushheaders *headers,
164                                void *userp)
165{
166  char *headp;
167  size_t i;
168  int *transfers = (int *)userp;
169  char filename[128];
170  FILE *out;
171  static unsigned int count = 0;
172  int rv;
173
174  (void)parent; /* we have no use for this */
175  curl_msnprintf(filename, sizeof(filename)-1, "push%u", count++);
176
177  /* here's a new stream, save it in a new file for each new push */
178  out = fopen(filename, "wb");
179  if(!out) {
180    /* if we cannot save it, deny it */
181    fprintf(stderr, "Failed to create output file for push\n");
182    rv = CURL_PUSH_DENY;
183    goto out;
184  }
185
186  /* write to this file */
187  curl_easy_setopt(easy, CURLOPT_WRITEDATA, out);
188
189  fprintf(stderr, "**** push callback approves stream %u, got %lu headers!\n",
190          count, (unsigned long)num_headers);
191
192  for(i = 0; i<num_headers; i++) {
193    headp = curl_pushheader_bynum(headers, i);
194    fprintf(stderr, "**** header %lu: %s\n", (unsigned long)i, headp);
195  }
196
197  headp = curl_pushheader_byname(headers, ":path");
198  if(headp) {
199    fprintf(stderr, "**** The PATH is %s\n", headp /* skip :path + colon */);
200  }
201
202  (*transfers)++; /* one more */
203  rv = CURL_PUSH_OK;
204
205out:
206  return rv;
207}
208
209
210/*
211 * Download a file over HTTP/2, take care of server push.
212 */
213int main(int argc, char *argv[])
214{
215  CURL *easy;
216  CURLM *multi_handle;
217  int transfers = 1; /* we start with one */
218  struct CURLMsg *m;
219  const char *url;
220
221  if(argc != 2) {
222    fprintf(stderr, "need URL as argument\n");
223    return 2;
224  }
225  url = argv[1];
226
227  multi_handle = curl_multi_init();
228  curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
229  curl_multi_setopt(multi_handle, CURLMOPT_PUSHFUNCTION, server_push_callback);
230  curl_multi_setopt(multi_handle, CURLMOPT_PUSHDATA, &transfers);
231
232  easy = curl_easy_init();
233  if(setup(easy, url)) {
234    fprintf(stderr, "failed\n");
235    return 1;
236  }
237
238  curl_multi_add_handle(multi_handle, easy);
239  do {
240    int still_running; /* keep number of running handles */
241    CURLMcode mc = curl_multi_perform(multi_handle, &still_running);
242
243    if(still_running)
244      /* wait for activity, timeout or "nothing" */
245      mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);
246
247    if(mc)
248      break;
249
250    /*
251     * A little caution when doing server push is that libcurl itself has
252     * created and added one or more easy handles but we need to clean them up
253     * when we are done.
254     */
255    do {
256      int msgq = 0;
257      m = curl_multi_info_read(multi_handle, &msgq);
258      if(m && (m->msg == CURLMSG_DONE)) {
259        CURL *e = m->easy_handle;
260        transfers--;
261        curl_multi_remove_handle(multi_handle, e);
262        curl_easy_cleanup(e);
263      }
264    } while(m);
265
266  } while(transfers); /* as long as we have transfers going */
267
268  curl_multi_cleanup(multi_handle);
269
270  return 0;
271}
272