113498266Sopenharmony_ci/***************************************************************************
213498266Sopenharmony_ci *                                  _   _ ____  _
313498266Sopenharmony_ci *  Project                     ___| | | |  _ \| |
413498266Sopenharmony_ci *                             / __| | | | |_) | |
513498266Sopenharmony_ci *                            | (__| |_| |  _ <| |___
613498266Sopenharmony_ci *                             \___|\___/|_| \_\_____|
713498266Sopenharmony_ci *
813498266Sopenharmony_ci * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
913498266Sopenharmony_ci *
1013498266Sopenharmony_ci * This software is licensed as described in the file COPYING, which
1113498266Sopenharmony_ci * you should have received as part of this distribution. The terms
1213498266Sopenharmony_ci * are also available at https://curl.se/docs/copyright.html.
1313498266Sopenharmony_ci *
1413498266Sopenharmony_ci * You may opt to use, copy, modify, merge, publish, distribute and/or sell
1513498266Sopenharmony_ci * copies of the Software, and permit persons to whom the Software is
1613498266Sopenharmony_ci * furnished to do so, under the terms of the COPYING file.
1713498266Sopenharmony_ci *
1813498266Sopenharmony_ci * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
1913498266Sopenharmony_ci * KIND, either express or implied.
2013498266Sopenharmony_ci *
2113498266Sopenharmony_ci * SPDX-License-Identifier: curl
2213498266Sopenharmony_ci *
2313498266Sopenharmony_ci ***************************************************************************/
2413498266Sopenharmony_ci/* <DESC>
2513498266Sopenharmony_ci * HTTP/2 server push
2613498266Sopenharmony_ci * </DESC>
2713498266Sopenharmony_ci */
2813498266Sopenharmony_ci
2913498266Sopenharmony_ci/* curl stuff */
3013498266Sopenharmony_ci#include <curl/curl.h>
3113498266Sopenharmony_ci#include <curl/mprintf.h>
3213498266Sopenharmony_ci
3313498266Sopenharmony_ci#include <stdio.h>
3413498266Sopenharmony_ci#include <stdlib.h>
3513498266Sopenharmony_ci#include <string.h>
3613498266Sopenharmony_ci
3713498266Sopenharmony_ci/* somewhat unix-specific */
3813498266Sopenharmony_ci#include <sys/time.h>
3913498266Sopenharmony_ci#include <unistd.h>
4013498266Sopenharmony_ci
4113498266Sopenharmony_ci#ifndef CURLPIPE_MULTIPLEX
4213498266Sopenharmony_ci#error "too old libcurl, cannot do HTTP/2 server push!"
4313498266Sopenharmony_ci#endif
4413498266Sopenharmony_ci
4513498266Sopenharmony_cistatic int verbose = 1;
4613498266Sopenharmony_ci
4713498266Sopenharmony_cistatic
4813498266Sopenharmony_ciint my_trace(CURL *handle, curl_infotype type,
4913498266Sopenharmony_ci             char *data, size_t size,
5013498266Sopenharmony_ci             void *userp)
5113498266Sopenharmony_ci{
5213498266Sopenharmony_ci  const char *text;
5313498266Sopenharmony_ci  (void)handle; /* prevent compiler warning */
5413498266Sopenharmony_ci  (void)userp;
5513498266Sopenharmony_ci
5613498266Sopenharmony_ci  switch(type) {
5713498266Sopenharmony_ci  case CURLINFO_TEXT:
5813498266Sopenharmony_ci    fprintf(stderr, "== Info: %s", data);
5913498266Sopenharmony_ci    return 0;
6013498266Sopenharmony_ci  case CURLINFO_HEADER_OUT:
6113498266Sopenharmony_ci    text = "=> Send header";
6213498266Sopenharmony_ci    break;
6313498266Sopenharmony_ci  case CURLINFO_DATA_OUT:
6413498266Sopenharmony_ci    if(verbose <= 1)
6513498266Sopenharmony_ci      return 0;
6613498266Sopenharmony_ci    text = "=> Send data";
6713498266Sopenharmony_ci    break;
6813498266Sopenharmony_ci  case CURLINFO_HEADER_IN:
6913498266Sopenharmony_ci    text = "<= Recv header";
7013498266Sopenharmony_ci    break;
7113498266Sopenharmony_ci  case CURLINFO_DATA_IN:
7213498266Sopenharmony_ci    if(verbose <= 1)
7313498266Sopenharmony_ci      return 0;
7413498266Sopenharmony_ci    text = "<= Recv data";
7513498266Sopenharmony_ci    break;
7613498266Sopenharmony_ci  default: /* in case a new one is introduced to shock us */
7713498266Sopenharmony_ci    return 0;
7813498266Sopenharmony_ci  }
7913498266Sopenharmony_ci
8013498266Sopenharmony_ci  fprintf(stderr, "%s, %lu bytes (0x%lx)\n",
8113498266Sopenharmony_ci          text, (unsigned long)size, (unsigned long)size);
8213498266Sopenharmony_ci  return 0;
8313498266Sopenharmony_ci}
8413498266Sopenharmony_ci
8513498266Sopenharmony_cistruct transfer {
8613498266Sopenharmony_ci  int idx;
8713498266Sopenharmony_ci  CURL *easy;
8813498266Sopenharmony_ci  char filename[128];
8913498266Sopenharmony_ci  FILE *out;
9013498266Sopenharmony_ci  curl_off_t recv_size;
9113498266Sopenharmony_ci  curl_off_t pause_at;
9213498266Sopenharmony_ci  int started;
9313498266Sopenharmony_ci  int paused;
9413498266Sopenharmony_ci  int resumed;
9513498266Sopenharmony_ci  int done;
9613498266Sopenharmony_ci};
9713498266Sopenharmony_ci
9813498266Sopenharmony_cistatic size_t transfer_count = 1;
9913498266Sopenharmony_cistatic struct transfer *transfers;
10013498266Sopenharmony_ci
10113498266Sopenharmony_cistatic struct transfer *get_transfer_for_easy(CURL *easy)
10213498266Sopenharmony_ci{
10313498266Sopenharmony_ci  size_t i;
10413498266Sopenharmony_ci  for(i = 0; i < transfer_count; ++i) {
10513498266Sopenharmony_ci    if(easy == transfers[i].easy)
10613498266Sopenharmony_ci      return &transfers[i];
10713498266Sopenharmony_ci  }
10813498266Sopenharmony_ci  return NULL;
10913498266Sopenharmony_ci}
11013498266Sopenharmony_ci
11113498266Sopenharmony_cistatic size_t my_write_cb(char *buf, size_t nitems, size_t buflen,
11213498266Sopenharmony_ci                          void *userdata)
11313498266Sopenharmony_ci{
11413498266Sopenharmony_ci  struct transfer *t = userdata;
11513498266Sopenharmony_ci  size_t nwritten;
11613498266Sopenharmony_ci
11713498266Sopenharmony_ci  if(!t->resumed &&
11813498266Sopenharmony_ci     t->recv_size < t->pause_at &&
11913498266Sopenharmony_ci     ((t->recv_size + (curl_off_t)(nitems * buflen)) >= t->pause_at)) {
12013498266Sopenharmony_ci    fprintf(stderr, "[t-%d] PAUSE\n", t->idx);
12113498266Sopenharmony_ci    t->paused = 1;
12213498266Sopenharmony_ci    return CURL_WRITEFUNC_PAUSE;
12313498266Sopenharmony_ci  }
12413498266Sopenharmony_ci
12513498266Sopenharmony_ci  if(!t->out) {
12613498266Sopenharmony_ci    curl_msnprintf(t->filename, sizeof(t->filename)-1, "download_%u.data",
12713498266Sopenharmony_ci                   t->idx);
12813498266Sopenharmony_ci    t->out = fopen(t->filename, "wb");
12913498266Sopenharmony_ci    if(!t->out)
13013498266Sopenharmony_ci      return 0;
13113498266Sopenharmony_ci  }
13213498266Sopenharmony_ci
13313498266Sopenharmony_ci  nwritten = fwrite(buf, nitems, buflen, t->out);
13413498266Sopenharmony_ci  if(nwritten < buflen) {
13513498266Sopenharmony_ci    fprintf(stderr, "[t-%d] write failure\n", t->idx);
13613498266Sopenharmony_ci    return 0;
13713498266Sopenharmony_ci  }
13813498266Sopenharmony_ci  t->recv_size += (curl_off_t)nwritten;
13913498266Sopenharmony_ci  return (size_t)nwritten;
14013498266Sopenharmony_ci}
14113498266Sopenharmony_ci
14213498266Sopenharmony_cistatic int setup(CURL *hnd, const char *url, struct transfer *t)
14313498266Sopenharmony_ci{
14413498266Sopenharmony_ci  curl_easy_setopt(hnd, CURLOPT_URL, url);
14513498266Sopenharmony_ci  curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
14613498266Sopenharmony_ci  curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
14713498266Sopenharmony_ci  curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L);
14813498266Sopenharmony_ci
14913498266Sopenharmony_ci  curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, my_write_cb);
15013498266Sopenharmony_ci  curl_easy_setopt(hnd, CURLOPT_WRITEDATA, t);
15113498266Sopenharmony_ci
15213498266Sopenharmony_ci  /* please be verbose */
15313498266Sopenharmony_ci  if(verbose) {
15413498266Sopenharmony_ci    curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);
15513498266Sopenharmony_ci    curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, my_trace);
15613498266Sopenharmony_ci  }
15713498266Sopenharmony_ci
15813498266Sopenharmony_ci#if (CURLPIPE_MULTIPLEX > 0)
15913498266Sopenharmony_ci  /* wait for pipe connection to confirm */
16013498266Sopenharmony_ci  curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L);
16113498266Sopenharmony_ci#endif
16213498266Sopenharmony_ci  return 0; /* all is good */
16313498266Sopenharmony_ci}
16413498266Sopenharmony_ci
16513498266Sopenharmony_cistatic void usage(const char *msg)
16613498266Sopenharmony_ci{
16713498266Sopenharmony_ci  if(msg)
16813498266Sopenharmony_ci    fprintf(stderr, "%s\n", msg);
16913498266Sopenharmony_ci  fprintf(stderr,
17013498266Sopenharmony_ci    "usage: [options] url\n"
17113498266Sopenharmony_ci    "  download a url with following options:\n"
17213498266Sopenharmony_ci    "  -m number  max parallel downloads\n"
17313498266Sopenharmony_ci    "  -n number  total downloads\n"
17413498266Sopenharmony_ci    "  -P number  pause transfer after `number` response bytes\n"
17513498266Sopenharmony_ci  );
17613498266Sopenharmony_ci}
17713498266Sopenharmony_ci
17813498266Sopenharmony_ci/*
17913498266Sopenharmony_ci * Download a file over HTTP/2, take care of server push.
18013498266Sopenharmony_ci */
18113498266Sopenharmony_ciint main(int argc, char *argv[])
18213498266Sopenharmony_ci{
18313498266Sopenharmony_ci  CURLM *multi_handle;
18413498266Sopenharmony_ci  struct CURLMsg *m;
18513498266Sopenharmony_ci  const char *url;
18613498266Sopenharmony_ci  size_t i, n, max_parallel = 1;
18713498266Sopenharmony_ci  size_t active_transfers;
18813498266Sopenharmony_ci  size_t pause_offset = 0;
18913498266Sopenharmony_ci  int abort_paused = 0;
19013498266Sopenharmony_ci  struct transfer *t;
19113498266Sopenharmony_ci  int ch;
19213498266Sopenharmony_ci
19313498266Sopenharmony_ci  while((ch = getopt(argc, argv, "ahm:n:P:")) != -1) {
19413498266Sopenharmony_ci    switch(ch) {
19513498266Sopenharmony_ci    case 'h':
19613498266Sopenharmony_ci      usage(NULL);
19713498266Sopenharmony_ci      return 2;
19813498266Sopenharmony_ci    case 'a':
19913498266Sopenharmony_ci      abort_paused = 1;
20013498266Sopenharmony_ci      break;
20113498266Sopenharmony_ci    case 'm':
20213498266Sopenharmony_ci      max_parallel = (size_t)strtol(optarg, NULL, 10);
20313498266Sopenharmony_ci      break;
20413498266Sopenharmony_ci    case 'n':
20513498266Sopenharmony_ci      transfer_count = (size_t)strtol(optarg, NULL, 10);
20613498266Sopenharmony_ci      break;
20713498266Sopenharmony_ci    case 'P':
20813498266Sopenharmony_ci      pause_offset = (size_t)strtol(optarg, NULL, 10);
20913498266Sopenharmony_ci      break;
21013498266Sopenharmony_ci    default:
21113498266Sopenharmony_ci     usage("invalid option");
21213498266Sopenharmony_ci     return 1;
21313498266Sopenharmony_ci    }
21413498266Sopenharmony_ci  }
21513498266Sopenharmony_ci  argc -= optind;
21613498266Sopenharmony_ci  argv += optind;
21713498266Sopenharmony_ci
21813498266Sopenharmony_ci  if(argc != 1) {
21913498266Sopenharmony_ci    usage("not enough arguments");
22013498266Sopenharmony_ci    return 2;
22113498266Sopenharmony_ci  }
22213498266Sopenharmony_ci  url = argv[0];
22313498266Sopenharmony_ci
22413498266Sopenharmony_ci  transfers = calloc(transfer_count, sizeof(*transfers));
22513498266Sopenharmony_ci  if(!transfers) {
22613498266Sopenharmony_ci    fprintf(stderr, "error allocating transfer structs\n");
22713498266Sopenharmony_ci    return 1;
22813498266Sopenharmony_ci  }
22913498266Sopenharmony_ci
23013498266Sopenharmony_ci  multi_handle = curl_multi_init();
23113498266Sopenharmony_ci  curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
23213498266Sopenharmony_ci
23313498266Sopenharmony_ci  active_transfers = 0;
23413498266Sopenharmony_ci  for(i = 0; i < transfer_count; ++i) {
23513498266Sopenharmony_ci    t = &transfers[i];
23613498266Sopenharmony_ci    t->idx = (int)i;
23713498266Sopenharmony_ci    t->pause_at = (curl_off_t)(pause_offset * i);
23813498266Sopenharmony_ci  }
23913498266Sopenharmony_ci
24013498266Sopenharmony_ci  n = (max_parallel < transfer_count)? max_parallel : transfer_count;
24113498266Sopenharmony_ci  for(i = 0; i < n; ++i) {
24213498266Sopenharmony_ci    t = &transfers[i];
24313498266Sopenharmony_ci    t->easy = curl_easy_init();
24413498266Sopenharmony_ci    if(!t->easy || setup(t->easy, url, t)) {
24513498266Sopenharmony_ci      fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
24613498266Sopenharmony_ci      return 1;
24713498266Sopenharmony_ci    }
24813498266Sopenharmony_ci    curl_multi_add_handle(multi_handle, t->easy);
24913498266Sopenharmony_ci    t->started = 1;
25013498266Sopenharmony_ci    ++active_transfers;
25113498266Sopenharmony_ci    fprintf(stderr, "[t-%d] STARTED\n", t->idx);
25213498266Sopenharmony_ci  }
25313498266Sopenharmony_ci
25413498266Sopenharmony_ci  do {
25513498266Sopenharmony_ci    int still_running; /* keep number of running handles */
25613498266Sopenharmony_ci    CURLMcode mc = curl_multi_perform(multi_handle, &still_running);
25713498266Sopenharmony_ci
25813498266Sopenharmony_ci    if(still_running) {
25913498266Sopenharmony_ci      /* wait for activity, timeout or "nothing" */
26013498266Sopenharmony_ci      mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);
26113498266Sopenharmony_ci      fprintf(stderr, "curl_multi_poll() -> %d\n", mc);
26213498266Sopenharmony_ci    }
26313498266Sopenharmony_ci
26413498266Sopenharmony_ci    if(mc)
26513498266Sopenharmony_ci      break;
26613498266Sopenharmony_ci
26713498266Sopenharmony_ci    do {
26813498266Sopenharmony_ci      int msgq = 0;
26913498266Sopenharmony_ci      m = curl_multi_info_read(multi_handle, &msgq);
27013498266Sopenharmony_ci      if(m && (m->msg == CURLMSG_DONE)) {
27113498266Sopenharmony_ci        CURL *e = m->easy_handle;
27213498266Sopenharmony_ci        active_transfers--;
27313498266Sopenharmony_ci        curl_multi_remove_handle(multi_handle, e);
27413498266Sopenharmony_ci        t = get_transfer_for_easy(e);
27513498266Sopenharmony_ci        if(t) {
27613498266Sopenharmony_ci          t->done = 1;
27713498266Sopenharmony_ci        }
27813498266Sopenharmony_ci        else
27913498266Sopenharmony_ci          curl_easy_cleanup(e);
28013498266Sopenharmony_ci      }
28113498266Sopenharmony_ci      else {
28213498266Sopenharmony_ci        /* nothing happening, maintenance */
28313498266Sopenharmony_ci        if(abort_paused) {
28413498266Sopenharmony_ci          /* abort paused transfers */
28513498266Sopenharmony_ci          for(i = 0; i < transfer_count; ++i) {
28613498266Sopenharmony_ci            t = &transfers[i];
28713498266Sopenharmony_ci            if(!t->done && t->paused && t->easy) {
28813498266Sopenharmony_ci              curl_multi_remove_handle(multi_handle, t->easy);
28913498266Sopenharmony_ci              t->done = 1;
29013498266Sopenharmony_ci              active_transfers--;
29113498266Sopenharmony_ci              fprintf(stderr, "[t-%d] ABORTED\n", t->idx);
29213498266Sopenharmony_ci            }
29313498266Sopenharmony_ci          }
29413498266Sopenharmony_ci        }
29513498266Sopenharmony_ci        else {
29613498266Sopenharmony_ci          /* resume one paused transfer */
29713498266Sopenharmony_ci          for(i = 0; i < transfer_count; ++i) {
29813498266Sopenharmony_ci            t = &transfers[i];
29913498266Sopenharmony_ci            if(!t->done && t->paused) {
30013498266Sopenharmony_ci              t->resumed = 1;
30113498266Sopenharmony_ci              t->paused = 0;
30213498266Sopenharmony_ci              curl_easy_pause(t->easy, CURLPAUSE_CONT);
30313498266Sopenharmony_ci              fprintf(stderr, "[t-%d] RESUMED\n", t->idx);
30413498266Sopenharmony_ci              break;
30513498266Sopenharmony_ci            }
30613498266Sopenharmony_ci          }
30713498266Sopenharmony_ci        }
30813498266Sopenharmony_ci
30913498266Sopenharmony_ci        while(active_transfers < max_parallel) {
31013498266Sopenharmony_ci          for(i = 0; i < transfer_count; ++i) {
31113498266Sopenharmony_ci            t = &transfers[i];
31213498266Sopenharmony_ci            if(!t->started) {
31313498266Sopenharmony_ci              t->easy = curl_easy_init();
31413498266Sopenharmony_ci              if(!t->easy || setup(t->easy, url, t)) {
31513498266Sopenharmony_ci                fprintf(stderr, "[t-%d] FAILEED setup\n", (int)i);
31613498266Sopenharmony_ci                return 1;
31713498266Sopenharmony_ci              }
31813498266Sopenharmony_ci              curl_multi_add_handle(multi_handle, t->easy);
31913498266Sopenharmony_ci              t->started = 1;
32013498266Sopenharmony_ci              ++active_transfers;
32113498266Sopenharmony_ci              fprintf(stderr, "[t-%d] STARTED\n", t->idx);
32213498266Sopenharmony_ci              break;
32313498266Sopenharmony_ci            }
32413498266Sopenharmony_ci          }
32513498266Sopenharmony_ci          /* all started */
32613498266Sopenharmony_ci          if(i == transfer_count)
32713498266Sopenharmony_ci            break;
32813498266Sopenharmony_ci        }
32913498266Sopenharmony_ci      }
33013498266Sopenharmony_ci    } while(m);
33113498266Sopenharmony_ci
33213498266Sopenharmony_ci  } while(active_transfers); /* as long as we have transfers going */
33313498266Sopenharmony_ci
33413498266Sopenharmony_ci  for(i = 0; i < transfer_count; ++i) {
33513498266Sopenharmony_ci    t = &transfers[i];
33613498266Sopenharmony_ci    if(t->out) {
33713498266Sopenharmony_ci      fclose(t->out);
33813498266Sopenharmony_ci      t->out = NULL;
33913498266Sopenharmony_ci    }
34013498266Sopenharmony_ci    if(t->easy) {
34113498266Sopenharmony_ci      curl_easy_cleanup(t->easy);
34213498266Sopenharmony_ci      t->easy = NULL;
34313498266Sopenharmony_ci    }
34413498266Sopenharmony_ci  }
34513498266Sopenharmony_ci  free(transfers);
34613498266Sopenharmony_ci
34713498266Sopenharmony_ci  curl_multi_cleanup(multi_handle);
34813498266Sopenharmony_ci
34913498266Sopenharmony_ci  return 0;
35013498266Sopenharmony_ci}
351