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 40static 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*/ 61static 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 138static int err(void) 139{ 140 fprintf(stderr, "something unexpected went wrong - bailing out!\n"); 141 exit(2); 142} 143 144struct handle 145{ 146 int idx; 147 int paused; 148 int resumed; 149 CURL *h; 150}; 151 152static 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 175int 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 324out: 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