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 * TLS session reuse
26 * </DESC>
27 */
28#include <stdio.h>
29#include <stdlib.h>
30#include <stdint.h>
31#include <string.h>
32#include <inttypes.h>
33/* #include <error.h> */
34#include <errno.h>
35#include <curl/curl.h>
36#include <curl/mprintf.h>
37
38
39static void log_line_start(FILE *log, const char *idsbuf, curl_infotype type)
40{
41  /*
42   * This is the trace look that is similar to what libcurl makes on its
43   * own.
44   */
45  static const char * const s_infotype[] = {
46    "* ", "< ", "> ", "{ ", "} ", "{ ", "} "
47  };
48  if(idsbuf && *idsbuf)
49    fprintf(log, "%s%s", idsbuf, s_infotype[type]);
50  else
51    fputs(s_infotype[type], log);
52}
53
54#define TRC_IDS_FORMAT_IDS_1  "[%" CURL_FORMAT_CURL_OFF_T "-x] "
55#define TRC_IDS_FORMAT_IDS_2  "[%" CURL_FORMAT_CURL_OFF_T "-%" \
56                                   CURL_FORMAT_CURL_OFF_T "] "
57/*
58** callback for CURLOPT_DEBUGFUNCTION
59*/
60static int debug_cb(CURL *handle, curl_infotype type,
61                    char *data, size_t size,
62                    void *userdata)
63{
64  FILE *output = stderr;
65  static int newl = 0;
66  static int traced_data = 0;
67  char idsbuf[60];
68  curl_off_t xfer_id, conn_id;
69
70  (void)handle; /* not used */
71  (void)userdata;
72
73  if(!curl_easy_getinfo(handle, CURLINFO_XFER_ID, &xfer_id) && xfer_id >= 0) {
74    if(!curl_easy_getinfo(handle, CURLINFO_CONN_ID, &conn_id) &&
75        conn_id >= 0) {
76      curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_2,
77                     xfer_id, conn_id);
78    }
79    else {
80      curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_1, xfer_id);
81    }
82  }
83  else
84    idsbuf[0] = 0;
85
86  switch(type) {
87  case CURLINFO_HEADER_OUT:
88    if(size > 0) {
89      size_t st = 0;
90      size_t i;
91      for(i = 0; i < size - 1; i++) {
92        if(data[i] == '\n') { /* LF */
93          if(!newl) {
94            log_line_start(output, idsbuf, type);
95          }
96          (void)fwrite(data + st, i - st + 1, 1, output);
97          st = i + 1;
98          newl = 0;
99        }
100      }
101      if(!newl)
102        log_line_start(output, idsbuf, type);
103      (void)fwrite(data + st, i - st + 1, 1, output);
104    }
105    newl = (size && (data[size - 1] != '\n')) ? 1 : 0;
106    traced_data = 0;
107    break;
108  case CURLINFO_TEXT:
109  case CURLINFO_HEADER_IN:
110    if(!newl)
111      log_line_start(output, idsbuf, type);
112    (void)fwrite(data, size, 1, output);
113    newl = (size && (data[size - 1] != '\n')) ? 1 : 0;
114    traced_data = 0;
115    break;
116  case CURLINFO_DATA_OUT:
117  case CURLINFO_DATA_IN:
118  case CURLINFO_SSL_DATA_IN:
119  case CURLINFO_SSL_DATA_OUT:
120    if(!traced_data) {
121      if(!newl)
122        log_line_start(output, idsbuf, type);
123      fprintf(output, "[%ld bytes data]\n", (long)size);
124      newl = 0;
125      traced_data = 1;
126    }
127    break;
128  default: /* nada */
129    newl = 0;
130    traced_data = 1;
131    break;
132  }
133
134  return 0;
135}
136
137static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *opaque)
138{
139  (void)ptr;
140  (void)opaque;
141  return size * nmemb;
142}
143
144static void add_transfer(CURLM *multi, CURLSH *share,
145                         struct curl_slist *resolve, const char *url)
146{
147  CURL *easy;
148  CURLMcode mc;
149
150  easy = curl_easy_init();
151  if(!easy) {
152    fprintf(stderr, "curl_easy_init failed\n");
153    exit(1);
154  }
155  curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L);
156  curl_easy_setopt(easy, CURLOPT_DEBUGFUNCTION, debug_cb);
157  curl_easy_setopt(easy, CURLOPT_URL, url);
158  curl_easy_setopt(easy, CURLOPT_SHARE, share);
159  curl_easy_setopt(easy, CURLOPT_NOSIGNAL, 1L);
160  curl_easy_setopt(easy, CURLOPT_AUTOREFERER, 1L);
161  curl_easy_setopt(easy, CURLOPT_FAILONERROR, 1L);
162  curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
163  curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, write_cb);
164  curl_easy_setopt(easy, CURLOPT_WRITEDATA, NULL);
165  curl_easy_setopt(easy, CURLOPT_HTTPGET, 1L);
166  curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 0L);
167  if(resolve)
168    curl_easy_setopt(easy, CURLOPT_RESOLVE, resolve);
169
170
171  mc = curl_multi_add_handle(multi, easy);
172  if(mc != CURLM_OK) {
173    fprintf(stderr, "curl_multi_add_handle: %s\n",
174           curl_multi_strerror(mc));
175    exit(1);
176  }
177}
178
179int main(int argc, char *argv[])
180{
181  const char *url;
182  CURLM *multi;
183  CURLMcode mc;
184  int running_handles = 0, numfds;
185  CURLMsg *msg;
186  CURLSH *share;
187  CURLU *cu;
188  struct curl_slist resolve;
189  char resolve_buf[1024];
190  int msgs_in_queue;
191  int add_more, waits, ongoing = 0;
192  char *host, *port;
193
194  if(argc != 2) {
195    fprintf(stderr, "%s URL\n", argv[0]);
196    exit(2);
197  }
198
199  url = argv[1];
200  cu = curl_url();
201  if(!cu) {
202    fprintf(stderr, "out of memory\n");
203    exit(1);
204  }
205  if(curl_url_set(cu, CURLUPART_URL, url, 0)) {
206    fprintf(stderr, "not a URL: '%s'\n", url);
207    exit(1);
208  }
209  if(curl_url_get(cu, CURLUPART_HOST, &host, 0)) {
210    fprintf(stderr, "could not get host of '%s'\n", url);
211    exit(1);
212  }
213  if(curl_url_get(cu, CURLUPART_PORT, &port, 0)) {
214    fprintf(stderr, "could not get port of '%s'\n", url);
215    exit(1);
216  }
217
218   memset(&resolve, 0, sizeof(resolve));
219   curl_msnprintf(resolve_buf, sizeof(resolve_buf)-1,
220                  "%s:%s:127.0.0.1", host, port);
221  curl_slist_append(&resolve, resolve_buf);
222
223  multi = curl_multi_init();
224  if(!multi) {
225    fprintf(stderr, "curl_multi_init failed\n");
226    exit(1);
227  }
228
229  share = curl_share_init();
230  if(!share) {
231    fprintf(stderr, "curl_share_init failed\n");
232    exit(1);
233  }
234  curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
235
236
237  add_transfer(multi, share, &resolve, url);
238  ++ongoing;
239  add_more = 6;
240  waits = 3;
241  do {
242    mc = curl_multi_perform(multi, &running_handles);
243    if(mc != CURLM_OK) {
244      fprintf(stderr, "curl_multi_perform: %s\n",
245             curl_multi_strerror(mc));
246      exit(1);
247    }
248
249    if(running_handles) {
250      mc = curl_multi_poll(multi, NULL, 0, 1000000, &numfds);
251      if(mc != CURLM_OK) {
252        fprintf(stderr, "curl_multi_poll: %s\n",
253               curl_multi_strerror(mc));
254        exit(1);
255      }
256    }
257
258    if(waits) {
259      --waits;
260    }
261    else {
262      while(add_more) {
263        add_transfer(multi, share, &resolve, url);
264        ++ongoing;
265        --add_more;
266      }
267    }
268
269    /* Check for finished handles and remove. */
270    while((msg = curl_multi_info_read(multi, &msgs_in_queue))) {
271      if(msg->msg == CURLMSG_DONE) {
272        long status = 0;
273        curl_off_t xfer_id;
274        curl_easy_getinfo(msg->easy_handle, CURLINFO_XFER_ID, &xfer_id);
275        curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &status);
276        if(msg->data.result == CURLE_SEND_ERROR ||
277            msg->data.result == CURLE_RECV_ERROR) {
278          /* We get these if the server had a GOAWAY in transit on
279           * re-using a connection */
280        }
281        else if(msg->data.result) {
282          fprintf(stderr, "transfer #%" CURL_FORMAT_CURL_OFF_T
283                  ": failed with %d\n", xfer_id, msg->data.result);
284          exit(1);
285        }
286        else if(status != 200) {
287          fprintf(stderr, "transfer #%" CURL_FORMAT_CURL_OFF_T
288                  ": wrong http status %ld (expected 200)\n", xfer_id, status);
289          exit(1);
290        }
291        curl_multi_remove_handle(multi, msg->easy_handle);
292        curl_easy_cleanup(msg->easy_handle);
293        --ongoing;
294        fprintf(stderr, "transfer #%" CURL_FORMAT_CURL_OFF_T" retiring "
295                "(%d now running)\n", xfer_id, running_handles);
296      }
297    }
298
299    fprintf(stderr, "running_handles=%d, yet_to_start=%d\n",
300            running_handles, add_more);
301
302  } while(ongoing || add_more);
303
304  fprintf(stderr, "exiting\n");
305  exit(EXIT_SUCCESS);
306}
307