xref: /curl/tests/http/clients/hx-upload.c (revision fbf5d507)
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 upload tests and tweaks
26  * </DESC>
27  */
28 /* curl stuff */
29 #include <curl/curl.h>
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 
35 #ifndef _MSC_VER
36 /* somewhat Unix-specific */
37 #include <unistd.h>  /* getopt() */
38 #endif
39 
40 #ifndef CURLPIPE_MULTIPLEX
41 #error "too old libcurl"
42 #endif
43 
44 #ifndef _MSC_VER
45 static int verbose = 1;
46 
log_line_start(FILE * log,const char * idsbuf,curl_infotype type)47 static void log_line_start(FILE *log, const char *idsbuf, curl_infotype type)
48 {
49   /*
50    * This is the trace look that is similar to what libcurl makes on its
51    * own.
52    */
53   static const char * const s_infotype[] = {
54     "* ", "< ", "> ", "{ ", "} ", "{ ", "} "
55   };
56   if(idsbuf && *idsbuf)
57     fprintf(log, "%s%s", idsbuf, s_infotype[type]);
58   else
59     fputs(s_infotype[type], log);
60 }
61 
62 #define TRC_IDS_FORMAT_IDS_1  "[%" CURL_FORMAT_CURL_OFF_T "-x] "
63 #define TRC_IDS_FORMAT_IDS_2  "[%" CURL_FORMAT_CURL_OFF_T "-%" \
64                                    CURL_FORMAT_CURL_OFF_T "] "
65 /*
66 ** callback for CURLOPT_DEBUGFUNCTION
67 */
debug_cb(CURL * handle,curl_infotype type,char * data,size_t size,void * userdata)68 static int debug_cb(CURL *handle, curl_infotype type,
69                     char *data, size_t size,
70                     void *userdata)
71 {
72   FILE *output = stderr;
73   static int newl = 0;
74   static int traced_data = 0;
75   char idsbuf[60];
76   curl_off_t xfer_id, conn_id;
77 
78   (void)handle; /* not used */
79   (void)userdata;
80 
81   if(!curl_easy_getinfo(handle, CURLINFO_XFER_ID, &xfer_id) && xfer_id >= 0) {
82     if(!curl_easy_getinfo(handle, CURLINFO_CONN_ID, &conn_id) &&
83        conn_id >= 0) {
84       curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_2, xfer_id,
85                      conn_id);
86     }
87     else {
88       curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_1, xfer_id);
89     }
90   }
91   else
92     idsbuf[0] = 0;
93 
94   switch(type) {
95   case CURLINFO_HEADER_OUT:
96     if(size > 0) {
97       size_t st = 0;
98       size_t i;
99       for(i = 0; i < size - 1; i++) {
100         if(data[i] == '\n') { /* LF */
101           if(!newl) {
102             log_line_start(output, idsbuf, type);
103           }
104           (void)fwrite(data + st, i - st + 1, 1, output);
105           st = i + 1;
106           newl = 0;
107         }
108       }
109       if(!newl)
110         log_line_start(output, idsbuf, type);
111       (void)fwrite(data + st, i - st + 1, 1, output);
112     }
113     newl = (size && (data[size - 1] != '\n')) ? 1 : 0;
114     traced_data = 0;
115     break;
116   case CURLINFO_TEXT:
117   case CURLINFO_HEADER_IN:
118     if(!newl)
119       log_line_start(output, idsbuf, type);
120     (void)fwrite(data, size, 1, output);
121     newl = (size && (data[size - 1] != '\n')) ? 1 : 0;
122     traced_data = 0;
123     break;
124   case CURLINFO_DATA_OUT:
125   case CURLINFO_DATA_IN:
126   case CURLINFO_SSL_DATA_IN:
127   case CURLINFO_SSL_DATA_OUT:
128     if(!traced_data) {
129       if(!newl)
130         log_line_start(output, idsbuf, type);
131       fprintf(output, "[%ld bytes data]\n", (long)size);
132       newl = 0;
133       traced_data = 1;
134     }
135     break;
136   default: /* nada */
137     newl = 0;
138     traced_data = 1;
139     break;
140   }
141 
142   return 0;
143 }
144 
145 struct transfer {
146   int idx;
147   CURL *easy;
148   const char *method;
149   char filename[128];
150   FILE *out;
151   curl_off_t send_total;
152   curl_off_t recv_size;
153   curl_off_t send_size;
154   curl_off_t fail_at;
155   curl_off_t pause_at;
156   curl_off_t abort_at;
157   int started;
158   int paused;
159   int resumed;
160   int done;
161 };
162 
163 static size_t transfer_count = 1;
164 static struct transfer *transfers;
165 static int forbid_reuse = 0;
166 
get_transfer_for_easy(CURL * easy)167 static struct transfer *get_transfer_for_easy(CURL *easy)
168 {
169   size_t i;
170   for(i = 0; i < transfer_count; ++i) {
171     if(easy == transfers[i].easy)
172       return &transfers[i];
173   }
174   return NULL;
175 }
176 
my_write_cb(char * buf,size_t nitems,size_t buflen,void * userdata)177 static size_t my_write_cb(char *buf, size_t nitems, size_t buflen,
178                           void *userdata)
179 {
180   struct transfer *t = userdata;
181   size_t blen = (nitems * buflen);
182   size_t nwritten;
183 
184   fprintf(stderr, "[t-%d] RECV %ld bytes, total=%ld, pause_at=%ld\n",
185           t->idx, (long)blen, (long)t->recv_size, (long)t->pause_at);
186   if(!t->out) {
187     curl_msnprintf(t->filename, sizeof(t->filename)-1, "download_%u.data",
188                    t->idx);
189     t->out = fopen(t->filename, "wb");
190     if(!t->out)
191       return 0;
192   }
193 
194   nwritten = fwrite(buf, nitems, buflen, t->out);
195   if(nwritten < blen) {
196     fprintf(stderr, "[t-%d] write failure\n", t->idx);
197     return 0;
198   }
199   t->recv_size += (curl_off_t)nwritten;
200   return (size_t)nwritten;
201 }
202 
my_read_cb(char * buf,size_t nitems,size_t buflen,void * userdata)203 static size_t my_read_cb(char *buf, size_t nitems, size_t buflen,
204                          void *userdata)
205 {
206   struct transfer *t = userdata;
207   size_t blen = (nitems * buflen);
208   size_t nread;
209 
210   if(t->send_total <= t->send_size)
211     nread = 0;
212   else if((t->send_total - t->send_size) < (curl_off_t)blen)
213     nread = (size_t)(t->send_total - t->send_size);
214   else
215     nread = blen;
216 
217   fprintf(stderr, "[t-%d] SEND %ld bytes, total=%ld, pause_at=%ld\n",
218           t->idx, (long)nread, (long)t->send_total, (long)t->pause_at);
219 
220   if(!t->resumed &&
221      t->send_size < t->pause_at &&
222      ((t->send_size + (curl_off_t)blen) >= t->pause_at)) {
223     fprintf(stderr, "[t-%d] PAUSE\n", t->idx);
224     t->paused = 1;
225     return CURL_READFUNC_PAUSE;
226   }
227 
228   memset(buf, 'x', nread);
229   t->send_size += (curl_off_t)nread;
230   if(t->fail_at > 0 && t->send_size >= t->fail_at) {
231     fprintf(stderr, "[t-%d] ABORT by read callback at %ld bytes\n",
232             t->idx, (long)t->send_size);
233     return CURL_READFUNC_ABORT;
234   }
235   return (size_t)nread;
236 }
237 
my_progress_cb(void * userdata,curl_off_t dltotal,curl_off_t dlnow,curl_off_t ultotal,curl_off_t ulnow)238 static int my_progress_cb(void *userdata,
239                           curl_off_t dltotal, curl_off_t dlnow,
240                           curl_off_t ultotal, curl_off_t ulnow)
241 {
242   struct transfer *t = userdata;
243   (void)ultotal;
244   (void)dlnow;
245   (void)dltotal;
246   if(t->abort_at > 0 && ulnow >= t->abort_at) {
247     fprintf(stderr, "[t-%d] ABORT by progress_cb at %ld bytes sent\n",
248             t->idx, (long)ulnow);
249     return 1;
250   }
251   return 0;
252 }
253 
setup(CURL * hnd,const char * url,struct transfer * t,int http_version)254 static int setup(CURL *hnd, const char *url, struct transfer *t,
255                  int http_version)
256 {
257   curl_easy_setopt(hnd, CURLOPT_URL, url);
258   curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, http_version);
259   curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
260   curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L);
261   curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, (long)(128 * 1024));
262   curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, my_write_cb);
263   curl_easy_setopt(hnd, CURLOPT_WRITEDATA, t);
264 
265   if(!t->method || !strcmp("PUT", t->method))
266     curl_easy_setopt(hnd, CURLOPT_UPLOAD, 1L);
267   else if(!strcmp("POST", t->method))
268     curl_easy_setopt(hnd, CURLOPT_POST, 1L);
269   else {
270     fprintf(stderr, "unsupported method '%s'\n", t->method);
271     return 1;
272   }
273   curl_easy_setopt(hnd, CURLOPT_READFUNCTION, my_read_cb);
274   curl_easy_setopt(hnd, CURLOPT_READDATA, t);
275   curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 0L);
276   curl_easy_setopt(hnd, CURLOPT_XFERINFOFUNCTION, my_progress_cb);
277   curl_easy_setopt(hnd, CURLOPT_XFERINFODATA, t);
278   if(forbid_reuse)
279     curl_easy_setopt(hnd, CURLOPT_FORBID_REUSE, 1L);
280 
281   /* please be verbose */
282   if(verbose) {
283     curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);
284     curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, debug_cb);
285   }
286 
287 #if (CURLPIPE_MULTIPLEX > 0)
288   /* wait for pipe connection to confirm */
289   curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L);
290 #endif
291   return 0; /* all is good */
292 }
293 
usage(const char * msg)294 static void usage(const char *msg)
295 {
296   if(msg)
297     fprintf(stderr, "%s\n", msg);
298   fprintf(stderr,
299     "usage: [options] url\n"
300     "  upload to a url with following options:\n"
301     "  -a         abort paused transfer\n"
302     "  -m number  max parallel uploads\n"
303     "  -n number  total uploads\n"
304     "  -A number  abort transfer after `number` request body bytes\n"
305     "  -F number  fail reading request body after `number` of bytes\n"
306     "  -P number  pause transfer after `number` request body bytes\n"
307     "  -S number  size to upload\n"
308     "  -V http_version (http/1.1, h2, h3) http version to use\n"
309   );
310 }
311 #endif /* !_MSC_VER */
312 
313 /*
314  * Download a file over HTTP/2, take care of server push.
315  */
main(int argc,char * argv[])316 int main(int argc, char *argv[])
317 {
318 #ifndef _MSC_VER
319   CURLM *multi_handle;
320   struct CURLMsg *m;
321   const char *url;
322   const char *method = "PUT";
323   size_t i, n, max_parallel = 1;
324   size_t active_transfers;
325   size_t pause_offset = 0;
326   size_t abort_offset = 0;
327   size_t fail_offset = 0;
328   size_t send_total = (128 * 1024);
329   int abort_paused = 0;
330   int reuse_easy = 0;
331   struct transfer *t;
332   int http_version = CURL_HTTP_VERSION_2_0;
333   int ch;
334 
335   while((ch = getopt(argc, argv, "afhm:n:A:F:M:P:RS:V:")) != -1) {
336     switch(ch) {
337     case 'h':
338       usage(NULL);
339       return 2;
340     case 'a':
341       abort_paused = 1;
342       break;
343     case 'f':
344       forbid_reuse = 1;
345       break;
346     case 'm':
347       max_parallel = (size_t)strtol(optarg, NULL, 10);
348       break;
349     case 'n':
350       transfer_count = (size_t)strtol(optarg, NULL, 10);
351       break;
352     case 'A':
353       abort_offset = (size_t)strtol(optarg, NULL, 10);
354       break;
355     case 'F':
356       fail_offset = (size_t)strtol(optarg, NULL, 10);
357       break;
358     case 'M':
359       method = optarg;
360       break;
361     case 'P':
362       pause_offset = (size_t)strtol(optarg, NULL, 10);
363       break;
364     case 'R':
365       reuse_easy = 1;
366       break;
367     case 'S':
368       send_total = (size_t)strtol(optarg, NULL, 10);
369       break;
370     case 'V': {
371       if(!strcmp("http/1.1", optarg))
372         http_version = CURL_HTTP_VERSION_1_1;
373       else if(!strcmp("h2", optarg))
374         http_version = CURL_HTTP_VERSION_2_0;
375       else if(!strcmp("h3", optarg))
376         http_version = CURL_HTTP_VERSION_3ONLY;
377       else {
378         usage("invalid http version");
379         return 1;
380       }
381       break;
382     }
383     default:
384      usage("invalid option");
385      return 1;
386     }
387   }
388   argc -= optind;
389   argv += optind;
390 
391   if(max_parallel > 1 && reuse_easy) {
392     usage("cannot mix -R and -P");
393     return 2;
394   }
395 
396   curl_global_init(CURL_GLOBAL_DEFAULT);
397   curl_global_trace("ids,time,http/2,http/3");
398 
399   if(argc != 1) {
400     usage("not enough arguments");
401     return 2;
402   }
403   url = argv[0];
404 
405   transfers = calloc(transfer_count, sizeof(*transfers));
406   if(!transfers) {
407     fprintf(stderr, "error allocating transfer structs\n");
408     return 1;
409   }
410 
411   active_transfers = 0;
412   for(i = 0; i < transfer_count; ++i) {
413     t = &transfers[i];
414     t->idx = (int)i;
415     t->method = method;
416     t->send_total = (curl_off_t)send_total;
417     t->abort_at = (curl_off_t)abort_offset;
418     t->fail_at = (curl_off_t)fail_offset;
419     t->pause_at = (curl_off_t)pause_offset;
420   }
421 
422   if(reuse_easy) {
423     CURL *easy = curl_easy_init();
424     CURLcode rc = CURLE_OK;
425     if(!easy) {
426       fprintf(stderr, "failed to init easy handle\n");
427       return 1;
428     }
429     for(i = 0; i < transfer_count; ++i) {
430       t = &transfers[i];
431       t->easy = easy;
432       if(setup(t->easy, url, t, http_version)) {
433         fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
434         return 1;
435       }
436 
437       fprintf(stderr, "[t-%d] STARTING\n", t->idx);
438       rc = curl_easy_perform(easy);
439       fprintf(stderr, "[t-%d] DONE -> %d\n", t->idx, rc);
440       t->easy = NULL;
441       curl_easy_reset(easy);
442     }
443     curl_easy_cleanup(easy);
444   }
445   else {
446     multi_handle = curl_multi_init();
447     curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
448 
449     n = (max_parallel < transfer_count) ? max_parallel : transfer_count;
450     for(i = 0; i < n; ++i) {
451       t = &transfers[i];
452       t->easy = curl_easy_init();
453       if(!t->easy || setup(t->easy, url, t, http_version)) {
454         fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
455         return 1;
456       }
457       curl_multi_add_handle(multi_handle, t->easy);
458       t->started = 1;
459       ++active_transfers;
460       fprintf(stderr, "[t-%d] STARTED\n", t->idx);
461     }
462 
463     do {
464       int still_running; /* keep number of running handles */
465       CURLMcode mc = curl_multi_perform(multi_handle, &still_running);
466 
467       if(still_running) {
468         /* wait for activity, timeout or "nothing" */
469         mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);
470       }
471 
472       if(mc)
473         break;
474 
475       do {
476         int msgq = 0;
477         m = curl_multi_info_read(multi_handle, &msgq);
478         if(m && (m->msg == CURLMSG_DONE)) {
479           CURL *e = m->easy_handle;
480           --active_transfers;
481           curl_multi_remove_handle(multi_handle, e);
482           t = get_transfer_for_easy(e);
483           if(t) {
484             t->done = 1;
485             fprintf(stderr, "[t-%d] FINISHED\n", t->idx);
486           }
487           else {
488             curl_easy_cleanup(e);
489             fprintf(stderr, "unknown FINISHED???\n");
490           }
491         }
492 
493 
494         /* nothing happening, maintenance */
495         if(abort_paused) {
496           /* abort paused transfers */
497           for(i = 0; i < transfer_count; ++i) {
498             t = &transfers[i];
499             if(!t->done && t->paused && t->easy) {
500               curl_multi_remove_handle(multi_handle, t->easy);
501               t->done = 1;
502               active_transfers--;
503               fprintf(stderr, "[t-%d] ABORTED\n", t->idx);
504             }
505           }
506         }
507         else {
508           /* resume one paused transfer */
509           for(i = 0; i < transfer_count; ++i) {
510             t = &transfers[i];
511             if(!t->done && t->paused) {
512               t->resumed = 1;
513               t->paused = 0;
514               curl_easy_pause(t->easy, CURLPAUSE_CONT);
515               fprintf(stderr, "[t-%d] RESUMED\n", t->idx);
516               break;
517             }
518           }
519         }
520 
521         while(active_transfers < max_parallel) {
522           for(i = 0; i < transfer_count; ++i) {
523             t = &transfers[i];
524             if(!t->started) {
525               t->easy = curl_easy_init();
526               if(!t->easy || setup(t->easy, url, t, http_version)) {
527                 fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
528                 return 1;
529               }
530               curl_multi_add_handle(multi_handle, t->easy);
531               t->started = 1;
532               ++active_transfers;
533               fprintf(stderr, "[t-%d] STARTED\n", t->idx);
534               break;
535             }
536           }
537           /* all started */
538           if(i == transfer_count)
539             break;
540         }
541       } while(m);
542 
543     } while(active_transfers); /* as long as we have transfers going */
544 
545     curl_multi_cleanup(multi_handle);
546   }
547 
548   for(i = 0; i < transfer_count; ++i) {
549     t = &transfers[i];
550     if(t->out) {
551       fclose(t->out);
552       t->out = NULL;
553     }
554     if(t->easy) {
555       curl_easy_cleanup(t->easy);
556       t->easy = NULL;
557     }
558   }
559   free(transfers);
560 
561   return 0;
562 #else
563   (void)argc;
564   (void)argv;
565   fprintf(stderr, "Not supported with this compiler.\n");
566   return 1;
567 #endif /* !_MSC_VER */
568 }
569