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