• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 download pausing
26  * </DESC>
27  */
28 /* This is based on the poc client of issue #11982
29  */
30 #include <stdio.h>
31 #include <string.h>
32 #include <sys/time.h>
33 #include <unistd.h>
34 #include <stdlib.h>
35 #include <curl/curl.h>
36 #include <curl/mprintf.h>
37 
38 #define HANDLECOUNT 2
39 
log_line_start(FILE * log,const char * idsbuf,curl_infotype type)40 static void log_line_start(FILE *log, const char *idsbuf, curl_infotype type)
41 {
42   /*
43    * This is the trace look that is similar to what libcurl makes on its
44    * own.
45    */
46   static const char * const s_infotype[] = {
47     "* ", "< ", "> ", "{ ", "} ", "{ ", "} "
48   };
49   if(idsbuf && *idsbuf)
50     fprintf(log, "%s%s", idsbuf, s_infotype[type]);
51   else
52     fputs(s_infotype[type], log);
53 }
54 
55 #define TRC_IDS_FORMAT_IDS_1  "[%" CURL_FORMAT_CURL_OFF_T "-x] "
56 #define TRC_IDS_FORMAT_IDS_2  "[%" CURL_FORMAT_CURL_OFF_T "-%" \
57                                    CURL_FORMAT_CURL_OFF_T "] "
58 /*
59 ** callback for CURLOPT_DEBUGFUNCTION
60 */
debug_cb(CURL * handle,curl_infotype type,char * data,size_t size,void * userdata)61 static int debug_cb(CURL *handle, curl_infotype type,
62                     char *data, size_t size,
63                     void *userdata)
64 {
65   FILE *output = stderr;
66   static int newl = 0;
67   static int traced_data = 0;
68   char idsbuf[60];
69   curl_off_t xfer_id, conn_id;
70 
71   (void)handle; /* not used */
72   (void)userdata;
73 
74   if(!curl_easy_getinfo(handle, CURLINFO_XFER_ID, &xfer_id) && xfer_id >= 0) {
75     if(!curl_easy_getinfo(handle, CURLINFO_CONN_ID, &conn_id) &&
76         conn_id >= 0) {
77       curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_2,
78                      xfer_id, conn_id);
79     }
80     else {
81       curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_1, xfer_id);
82     }
83   }
84   else
85     idsbuf[0] = 0;
86 
87   switch(type) {
88   case CURLINFO_HEADER_OUT:
89     if(size > 0) {
90       size_t st = 0;
91       size_t i;
92       for(i = 0; i < size - 1; i++) {
93         if(data[i] == '\n') { /* LF */
94           if(!newl) {
95             log_line_start(output, idsbuf, type);
96           }
97           (void)fwrite(data + st, i - st + 1, 1, output);
98           st = i + 1;
99           newl = 0;
100         }
101       }
102       if(!newl)
103         log_line_start(output, idsbuf, type);
104       (void)fwrite(data + st, i - st + 1, 1, output);
105     }
106     newl = (size && (data[size - 1] != '\n')) ? 1 : 0;
107     traced_data = 0;
108     break;
109   case CURLINFO_TEXT:
110   case CURLINFO_HEADER_IN:
111     if(!newl)
112       log_line_start(output, idsbuf, type);
113     (void)fwrite(data, size, 1, output);
114     newl = (size && (data[size - 1] != '\n')) ? 1 : 0;
115     traced_data = 0;
116     break;
117   case CURLINFO_DATA_OUT:
118   case CURLINFO_DATA_IN:
119   case CURLINFO_SSL_DATA_IN:
120   case CURLINFO_SSL_DATA_OUT:
121     if(!traced_data) {
122       if(!newl)
123         log_line_start(output, idsbuf, type);
124       fprintf(output, "[%ld bytes data]\n", (long)size);
125       newl = 0;
126       traced_data = 1;
127     }
128     break;
129   default: /* nada */
130     newl = 0;
131     traced_data = 1;
132     break;
133   }
134 
135   return 0;
136 }
137 
err(void)138 static int err(void)
139 {
140   fprintf(stderr, "something unexpected went wrong - bailing out!\n");
141   exit(2);
142 }
143 
144 struct handle
145 {
146   int idx;
147   int paused;
148   int resumed;
149   CURL *h;
150 };
151 
cb(void * data,size_t size,size_t nmemb,void * clientp)152 static size_t cb(void *data, size_t size, size_t nmemb, void *clientp)
153 {
154   size_t realsize = size * nmemb;
155   struct handle *handle = (struct handle *) clientp;
156   curl_off_t totalsize;
157 
158   (void)data;
159   if(curl_easy_getinfo(handle->h, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
160                        &totalsize) == CURLE_OK)
161     fprintf(stderr, "INFO: [%d] write, Content-Length %"CURL_FORMAT_CURL_OFF_T
162             "\n", handle->idx, totalsize);
163 
164   if(!handle->resumed) {
165     ++handle->paused;
166     fprintf(stderr, "INFO: [%d] write, PAUSING %d time on %lu bytes\n",
167             handle->idx, handle->paused, (long)realsize);
168     return CURL_WRITEFUNC_PAUSE;
169   }
170   fprintf(stderr, "INFO: [%d] write, accepting %lu bytes\n",
171           handle->idx, (long)realsize);
172   return realsize;
173 }
174 
main(int argc,char * argv[])175 int main(int argc, char *argv[])
176 {
177   struct handle handles[HANDLECOUNT];
178   CURLM *multi_handle;
179   int i, still_running = 1, msgs_left, numfds;
180   CURLMsg *msg;
181   int rounds = 0;
182   int rc = 0;
183   CURLU *cu;
184   struct curl_slist *resolve = NULL;
185   char resolve_buf[1024];
186   char *url, *host = NULL, *port = NULL;
187   int all_paused = 0;
188   int resume_round = -1;
189 
190   if(argc != 2) {
191     fprintf(stderr, "ERROR: need URL as argument\n");
192     return 2;
193   }
194   url = argv[1];
195 
196   curl_global_init(CURL_GLOBAL_DEFAULT);
197   curl_global_trace("ids,time,http/2");
198 
199   cu = curl_url();
200   if(!cu) {
201     fprintf(stderr, "out of memory\n");
202     exit(1);
203   }
204   if(curl_url_set(cu, CURLUPART_URL, url, 0)) {
205     fprintf(stderr, "not a URL: '%s'\n", url);
206     exit(1);
207   }
208   if(curl_url_get(cu, CURLUPART_HOST, &host, 0)) {
209     fprintf(stderr, "could not get host of '%s'\n", url);
210     exit(1);
211   }
212   if(curl_url_get(cu, CURLUPART_PORT, &port, 0)) {
213     fprintf(stderr, "could not get port of '%s'\n", url);
214     exit(1);
215   }
216   memset(&resolve, 0, sizeof(resolve));
217   curl_msnprintf(resolve_buf, sizeof(resolve_buf)-1,
218                  "%s:%s:127.0.0.1", host, port);
219   resolve = curl_slist_append(resolve, resolve_buf);
220 
221   for(i = 0; i<HANDLECOUNT; i++) {
222     handles[i].idx = i;
223     handles[i].paused = 0;
224     handles[i].resumed = 0;
225     handles[i].h = curl_easy_init();
226     if(!handles[i].h ||
227       curl_easy_setopt(handles[i].h, CURLOPT_WRITEFUNCTION, cb) != CURLE_OK ||
228       curl_easy_setopt(handles[i].h, CURLOPT_WRITEDATA, &handles[i])
229         != CURLE_OK ||
230       curl_easy_setopt(handles[i].h, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK ||
231       curl_easy_setopt(handles[i].h, CURLOPT_VERBOSE, 1L) != CURLE_OK ||
232       curl_easy_setopt(handles[i].h, CURLOPT_DEBUGFUNCTION, debug_cb)
233         != CURLE_OK ||
234       curl_easy_setopt(handles[i].h, CURLOPT_SSL_VERIFYPEER, 0L) != CURLE_OK ||
235       curl_easy_setopt(handles[i].h, CURLOPT_RESOLVE, resolve) != CURLE_OK ||
236       curl_easy_setopt(handles[i].h, CURLOPT_URL, url) != CURLE_OK) {
237       err();
238     }
239   }
240 
241   multi_handle = curl_multi_init();
242   if(!multi_handle)
243     err();
244 
245   for(i = 0; i<HANDLECOUNT; i++) {
246     if(curl_multi_add_handle(multi_handle, handles[i].h) != CURLM_OK)
247       err();
248   }
249 
250   for(rounds = 0;; rounds++) {
251     fprintf(stderr, "INFO: multi_perform round %d\n", rounds);
252     if(curl_multi_perform(multi_handle, &still_running) != CURLM_OK)
253       err();
254 
255     if(!still_running) {
256       int as_expected = 1;
257       fprintf(stderr, "INFO: no more handles running\n");
258       for(i = 0; i<HANDLECOUNT; i++) {
259         if(!handles[i].paused) {
260           fprintf(stderr, "ERROR: [%d] NOT PAUSED\n", i);
261           as_expected = 0;
262         }
263         else if(handles[i].paused != 1) {
264           fprintf(stderr, "ERROR: [%d] PAUSED %d times!\n",
265                   i, handles[i].paused);
266           as_expected = 0;
267         }
268         else if(!handles[i].resumed) {
269           fprintf(stderr, "ERROR: [%d] NOT resumed!\n", i);
270           as_expected = 0;
271         }
272       }
273       if(!as_expected) {
274         fprintf(stderr, "ERROR: handles not in expected state "
275                 "after %d rounds\n", rounds);
276         rc = 1;
277       }
278       break;
279     }
280 
281     if(curl_multi_poll(multi_handle, NULL, 0, 100, &numfds) != CURLM_OK)
282       err();
283 
284     while((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
285       if(msg->msg == CURLMSG_DONE) {
286         for(i = 0; i<HANDLECOUNT; i++) {
287           if(msg->easy_handle == handles[i].h) {
288             if(handles[i].paused != 1 || !handles[i].resumed) {
289               fprintf(stderr, "ERROR: [%d] done, pauses=%d, resumed=%d, "
290                       "result %d - wtf?\n", i, handles[i].paused,
291                       handles[i].resumed, msg->data.result);
292               rc = 1;
293               goto out;
294             }
295           }
296         }
297       }
298     }
299 
300     /* Successfully paused? */
301     if(!all_paused) {
302       for(i = 0; i<HANDLECOUNT; i++) {
303         if(!handles[i].paused) {
304           break;
305         }
306       }
307       all_paused = (i == HANDLECOUNT);
308       if(all_paused) {
309         fprintf(stderr, "INFO: all transfers paused\n");
310         /* give transfer some rounds to mess things up */
311         resume_round = rounds + 3;
312       }
313     }
314     if(resume_round > 0 && rounds == resume_round) {
315       /* time to resume */
316       for(i = 0; i<HANDLECOUNT; i++) {
317         fprintf(stderr, "INFO: [%d] resumed\n", i);
318         handles[i].resumed = 1;
319         curl_easy_pause(handles[i].h, CURLPAUSE_CONT);
320       }
321     }
322   }
323 
324 out:
325   for(i = 0; i<HANDLECOUNT; i++) {
326     curl_multi_remove_handle(multi_handle, handles[i].h);
327     curl_easy_cleanup(handles[i].h);
328   }
329 
330 
331   curl_slist_free_all(resolve);
332   curl_free(host);
333   curl_free(port);
334   curl_url_cleanup(cu);
335   curl_multi_cleanup(multi_handle);
336   curl_global_cleanup();
337 
338   return rc;
339 }
340