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 int verbose = 1;
46
47static
48int my_trace(CURL *handle, curl_infotype type,
49             char *data, size_t size,
50             void *userp)
51{
52  const char *text;
53  (void)handle; /* prevent compiler warning */
54  (void)userp;
55
56  switch(type) {
57  case CURLINFO_TEXT:
58    fprintf(stderr, "== Info: %s", data);
59    return 0;
60  case CURLINFO_HEADER_OUT:
61    text = "=> Send header";
62    break;
63  case CURLINFO_DATA_OUT:
64    if(verbose <= 1)
65      return 0;
66    text = "=> Send data";
67    break;
68  case CURLINFO_HEADER_IN:
69    text = "<= Recv header";
70    break;
71  case CURLINFO_DATA_IN:
72    if(verbose <= 1)
73      return 0;
74    text = "<= Recv data";
75    break;
76  default: /* in case a new one is introduced to shock us */
77    return 0;
78  }
79
80  fprintf(stderr, "%s, %lu bytes (0x%lx)\n",
81          text, (unsigned long)size, (unsigned long)size);
82  return 0;
83}
84
85struct transfer {
86  int idx;
87  CURL *easy;
88  char filename[128];
89  FILE *out;
90  curl_off_t recv_size;
91  curl_off_t pause_at;
92  int started;
93  int paused;
94  int resumed;
95  int done;
96};
97
98static size_t transfer_count = 1;
99static struct transfer *transfers;
100
101static struct transfer *get_transfer_for_easy(CURL *easy)
102{
103  size_t i;
104  for(i = 0; i < transfer_count; ++i) {
105    if(easy == transfers[i].easy)
106      return &transfers[i];
107  }
108  return NULL;
109}
110
111static size_t my_write_cb(char *buf, size_t nitems, size_t buflen,
112                          void *userdata)
113{
114  struct transfer *t = userdata;
115  size_t nwritten;
116
117  if(!t->resumed &&
118     t->recv_size < t->pause_at &&
119     ((t->recv_size + (curl_off_t)(nitems * buflen)) >= t->pause_at)) {
120    fprintf(stderr, "[t-%d] PAUSE\n", t->idx);
121    t->paused = 1;
122    return CURL_WRITEFUNC_PAUSE;
123  }
124
125  if(!t->out) {
126    curl_msnprintf(t->filename, sizeof(t->filename)-1, "download_%u.data",
127                   t->idx);
128    t->out = fopen(t->filename, "wb");
129    if(!t->out)
130      return 0;
131  }
132
133  nwritten = fwrite(buf, nitems, buflen, t->out);
134  if(nwritten < buflen) {
135    fprintf(stderr, "[t-%d] write failure\n", t->idx);
136    return 0;
137  }
138  t->recv_size += (curl_off_t)nwritten;
139  return (size_t)nwritten;
140}
141
142static int setup(CURL *hnd, const char *url, struct transfer *t)
143{
144  curl_easy_setopt(hnd, CURLOPT_URL, url);
145  curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
146  curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
147  curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L);
148
149  curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, my_write_cb);
150  curl_easy_setopt(hnd, CURLOPT_WRITEDATA, t);
151
152  /* please be verbose */
153  if(verbose) {
154    curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);
155    curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, my_trace);
156  }
157
158#if (CURLPIPE_MULTIPLEX > 0)
159  /* wait for pipe connection to confirm */
160  curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L);
161#endif
162  return 0; /* all is good */
163}
164
165static void usage(const char *msg)
166{
167  if(msg)
168    fprintf(stderr, "%s\n", msg);
169  fprintf(stderr,
170    "usage: [options] url\n"
171    "  download a url with following options:\n"
172    "  -m number  max parallel downloads\n"
173    "  -n number  total downloads\n"
174    "  -P number  pause transfer after `number` response bytes\n"
175  );
176}
177
178/*
179 * Download a file over HTTP/2, take care of server push.
180 */
181int main(int argc, char *argv[])
182{
183  CURLM *multi_handle;
184  struct CURLMsg *m;
185  const char *url;
186  size_t i, n, max_parallel = 1;
187  size_t active_transfers;
188  size_t pause_offset = 0;
189  int abort_paused = 0;
190  struct transfer *t;
191  int ch;
192
193  while((ch = getopt(argc, argv, "ahm:n:P:")) != -1) {
194    switch(ch) {
195    case 'h':
196      usage(NULL);
197      return 2;
198    case 'a':
199      abort_paused = 1;
200      break;
201    case 'm':
202      max_parallel = (size_t)strtol(optarg, NULL, 10);
203      break;
204    case 'n':
205      transfer_count = (size_t)strtol(optarg, NULL, 10);
206      break;
207    case 'P':
208      pause_offset = (size_t)strtol(optarg, NULL, 10);
209      break;
210    default:
211     usage("invalid option");
212     return 1;
213    }
214  }
215  argc -= optind;
216  argv += optind;
217
218  if(argc != 1) {
219    usage("not enough arguments");
220    return 2;
221  }
222  url = argv[0];
223
224  transfers = calloc(transfer_count, sizeof(*transfers));
225  if(!transfers) {
226    fprintf(stderr, "error allocating transfer structs\n");
227    return 1;
228  }
229
230  multi_handle = curl_multi_init();
231  curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
232
233  active_transfers = 0;
234  for(i = 0; i < transfer_count; ++i) {
235    t = &transfers[i];
236    t->idx = (int)i;
237    t->pause_at = (curl_off_t)(pause_offset * i);
238  }
239
240  n = (max_parallel < transfer_count)? max_parallel : transfer_count;
241  for(i = 0; i < n; ++i) {
242    t = &transfers[i];
243    t->easy = curl_easy_init();
244    if(!t->easy || setup(t->easy, url, t)) {
245      fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
246      return 1;
247    }
248    curl_multi_add_handle(multi_handle, t->easy);
249    t->started = 1;
250    ++active_transfers;
251    fprintf(stderr, "[t-%d] STARTED\n", t->idx);
252  }
253
254  do {
255    int still_running; /* keep number of running handles */
256    CURLMcode mc = curl_multi_perform(multi_handle, &still_running);
257
258    if(still_running) {
259      /* wait for activity, timeout or "nothing" */
260      mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);
261      fprintf(stderr, "curl_multi_poll() -> %d\n", mc);
262    }
263
264    if(mc)
265      break;
266
267    do {
268      int msgq = 0;
269      m = curl_multi_info_read(multi_handle, &msgq);
270      if(m && (m->msg == CURLMSG_DONE)) {
271        CURL *e = m->easy_handle;
272        active_transfers--;
273        curl_multi_remove_handle(multi_handle, e);
274        t = get_transfer_for_easy(e);
275        if(t) {
276          t->done = 1;
277        }
278        else
279          curl_easy_cleanup(e);
280      }
281      else {
282        /* nothing happening, maintenance */
283        if(abort_paused) {
284          /* abort paused transfers */
285          for(i = 0; i < transfer_count; ++i) {
286            t = &transfers[i];
287            if(!t->done && t->paused && t->easy) {
288              curl_multi_remove_handle(multi_handle, t->easy);
289              t->done = 1;
290              active_transfers--;
291              fprintf(stderr, "[t-%d] ABORTED\n", t->idx);
292            }
293          }
294        }
295        else {
296          /* resume one paused transfer */
297          for(i = 0; i < transfer_count; ++i) {
298            t = &transfers[i];
299            if(!t->done && t->paused) {
300              t->resumed = 1;
301              t->paused = 0;
302              curl_easy_pause(t->easy, CURLPAUSE_CONT);
303              fprintf(stderr, "[t-%d] RESUMED\n", t->idx);
304              break;
305            }
306          }
307        }
308
309        while(active_transfers < max_parallel) {
310          for(i = 0; i < transfer_count; ++i) {
311            t = &transfers[i];
312            if(!t->started) {
313              t->easy = curl_easy_init();
314              if(!t->easy || setup(t->easy, url, t)) {
315                fprintf(stderr, "[t-%d] FAILEED setup\n", (int)i);
316                return 1;
317              }
318              curl_multi_add_handle(multi_handle, t->easy);
319              t->started = 1;
320              ++active_transfers;
321              fprintf(stderr, "[t-%d] STARTED\n", t->idx);
322              break;
323            }
324          }
325          /* all started */
326          if(i == transfer_count)
327            break;
328        }
329      }
330    } while(m);
331
332  } while(active_transfers); /* as long as we have transfers going */
333
334  for(i = 0; i < transfer_count; ++i) {
335    t = &transfers[i];
336    if(t->out) {
337      fclose(t->out);
338      t->out = NULL;
339    }
340    if(t->easy) {
341      curl_easy_cleanup(t->easy);
342      t->easy = NULL;
343    }
344  }
345  free(transfers);
346
347  curl_multi_cleanup(multi_handle);
348
349  return 0;
350}
351