test_put_chunked.c (16149B)
1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2007 Christian Grothoff 4 Copyright (C) 2014-2022 Evgeny Grin (Karlson2k) 5 6 libmicrohttpd is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published 8 by the Free Software Foundation; either version 2, or (at your 9 option) any later version. 10 11 libmicrohttpd is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with libmicrohttpd; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 Boston, MA 02110-1301, USA. 20 */ 21 22 /** 23 * @file daemontest_put_chunked.c 24 * @brief Testcase for libmicrohttpd PUT operations with chunked encoding 25 * for the upload data 26 * @author Christian Grothoff 27 * @author Karlson2k (Evgeny Grin) 28 */ 29 30 #include "MHD_config.h" 31 #include "platform.h" 32 #include <curl/curl.h> 33 #include <microhttpd.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <time.h> 37 #include <errno.h> 38 39 /* Turn any MHD_PANIC() or failing mhd_assert() reached from this 40 test into a marked, classifiable test error (TESTING.md, P5). */ 41 #include "mhd_panic_tripwire.h" 42 43 #ifndef WINDOWS 44 #include <unistd.h> 45 #endif 46 47 #if defined(MHD_CPU_COUNT) && (MHD_CPU_COUNT + 0) < 2 48 #undef MHD_CPU_COUNT 49 #endif 50 #if ! defined(MHD_CPU_COUNT) 51 #define MHD_CPU_COUNT 2 52 #endif 53 54 struct CBC 55 { 56 char *buf; 57 size_t pos; 58 size_t size; 59 }; 60 61 static size_t 62 putBuffer (void *stream, size_t size, size_t nmemb, void *ptr) 63 { 64 size_t *pos = ptr; 65 size_t wrt; 66 67 wrt = size * nmemb; 68 if (wrt > 8 - (*pos)) 69 wrt = 8 - (*pos); 70 if (wrt > 4) 71 wrt = 4; /* only send half at first => force multiple chunks! */ 72 memcpy (stream, &("Hello123"[*pos]), wrt); 73 (*pos) += wrt; 74 return wrt; 75 } 76 77 78 static size_t 79 copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx) 80 { 81 struct CBC *cbc = ctx; 82 83 if (cbc->pos + size * nmemb > cbc->size) 84 return 0; /* overflow */ 85 memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb); 86 cbc->pos += size * nmemb; 87 return size * nmemb; 88 } 89 90 91 static enum MHD_Result 92 ahc_echo (void *cls, 93 struct MHD_Connection *connection, 94 const char *url, 95 const char *method, 96 const char *version, 97 const char *upload_data, size_t *upload_data_size, 98 void **req_cls) 99 { 100 size_t *done = cls; 101 struct MHD_Response *response; 102 enum MHD_Result ret; 103 size_t have; 104 (void) version; (void) req_cls; /* Unused. Silent compiler warning. */ 105 106 if (0 != strcmp ("PUT", method)) 107 return MHD_NO; /* unexpected method */ 108 if ((*done) < 8) 109 { 110 have = *upload_data_size; 111 if (have + *done > 8) 112 { 113 printf ("Invalid upload data `%8s'!\n", upload_data); 114 return MHD_NO; 115 } 116 if (0 == have) 117 return MHD_YES; 118 if (0 == memcmp (upload_data, &"Hello123"[*done], have)) 119 { 120 *done += have; 121 *upload_data_size = 0; 122 } 123 else 124 { 125 printf ("Invalid upload data `%8s'!\n", upload_data); 126 return MHD_NO; 127 } 128 #if 0 129 fprintf (stderr, "Not ready for response: %u/%u\n", *done, 8); 130 #endif 131 return MHD_YES; 132 } 133 response = MHD_create_response_from_buffer_copy (strlen (url), 134 (const void *) url); 135 ret = MHD_queue_response (connection, MHD_HTTP_OK, response); 136 MHD_destroy_response (response); 137 return ret; 138 } 139 140 141 static unsigned int 142 testInternalPut (void) 143 { 144 struct MHD_Daemon *d; 145 CURL *c; 146 char buf[2048]; 147 struct CBC cbc; 148 size_t pos = 0; 149 size_t done_flag = 0; 150 CURLcode errornum; 151 uint16_t port; 152 153 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) 154 port = 0; 155 else 156 port = 1440; 157 158 cbc.buf = buf; 159 cbc.size = 2048; 160 cbc.pos = 0; 161 d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, 162 port, 163 NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END); 164 if (d == NULL) 165 return 1; 166 if (0 == port) 167 { 168 const union MHD_DaemonInfo *dinfo; 169 dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); 170 if ((NULL == dinfo) || (0 == dinfo->port) ) 171 { 172 MHD_stop_daemon (d); return 32; 173 } 174 port = dinfo->port; 175 } 176 c = curl_easy_init (); 177 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world"); 178 curl_easy_setopt (c, CURLOPT_PORT, (long) port); 179 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 180 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 181 curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer); 182 curl_easy_setopt (c, CURLOPT_READDATA, &pos); 183 curl_easy_setopt (c, CURLOPT_UPLOAD, 1L); 184 /* by not giving the file size, we force chunking! */ 185 /* 186 curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L); 187 */ 188 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); 189 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 190 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 191 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 192 /* NOTE: use of CONNECTTIMEOUT without also 193 * setting NOSIGNAL results in really weird 194 * crashes on my system! */ 195 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); 196 if (CURLE_OK != (errornum = curl_easy_perform (c))) 197 { 198 fprintf (stderr, 199 "curl_easy_perform failed: `%s'\n", 200 curl_easy_strerror (errornum)); 201 curl_easy_cleanup (c); 202 MHD_stop_daemon (d); 203 return 2; 204 } 205 curl_easy_cleanup (c); 206 MHD_stop_daemon (d); 207 if (cbc.pos != strlen ("/hello_world")) 208 return 4; 209 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 210 return 8; 211 return 0; 212 } 213 214 215 static unsigned int 216 testMultithreadedPut (void) 217 { 218 struct MHD_Daemon *d; 219 CURL *c; 220 char buf[2048]; 221 struct CBC cbc; 222 size_t pos = 0; 223 size_t done_flag = 0; 224 CURLcode errornum; 225 uint16_t port; 226 227 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) 228 port = 0; 229 else 230 port = 1441; 231 232 cbc.buf = buf; 233 cbc.size = 2048; 234 cbc.pos = 0; 235 d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION 236 | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, 237 port, 238 NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END); 239 if (d == NULL) 240 return 16; 241 if (0 == port) 242 { 243 const union MHD_DaemonInfo *dinfo; 244 dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); 245 if ((NULL == dinfo) || (0 == dinfo->port) ) 246 { 247 MHD_stop_daemon (d); return 32; 248 } 249 port = dinfo->port; 250 } 251 c = curl_easy_init (); 252 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world"); 253 curl_easy_setopt (c, CURLOPT_PORT, (long) port); 254 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 255 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 256 curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer); 257 curl_easy_setopt (c, CURLOPT_READDATA, &pos); 258 curl_easy_setopt (c, CURLOPT_UPLOAD, 1L); 259 /* by not giving the file size, we force chunking! */ 260 /* 261 curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L); 262 */ 263 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); 264 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 265 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 266 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 267 /* NOTE: use of CONNECTTIMEOUT without also 268 * setting NOSIGNAL results in really weird 269 * crashes on my system! */ 270 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); 271 if (CURLE_OK != (errornum = curl_easy_perform (c))) 272 { 273 fprintf (stderr, 274 "curl_easy_perform failed: `%s'\n", 275 curl_easy_strerror (errornum)); 276 curl_easy_cleanup (c); 277 MHD_stop_daemon (d); 278 return 32; 279 } 280 curl_easy_cleanup (c); 281 MHD_stop_daemon (d); 282 if (cbc.pos != strlen ("/hello_world")) 283 return 64; 284 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 285 return 128; 286 287 return 0; 288 } 289 290 291 static unsigned int 292 testMultithreadedPoolPut (void) 293 { 294 struct MHD_Daemon *d; 295 CURL *c; 296 char buf[2048]; 297 struct CBC cbc; 298 size_t pos = 0; 299 size_t done_flag = 0; 300 CURLcode errornum; 301 uint16_t port; 302 303 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) 304 port = 0; 305 else 306 port = 1442; 307 308 cbc.buf = buf; 309 cbc.size = 2048; 310 cbc.pos = 0; 311 d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, 312 port, 313 NULL, NULL, &ahc_echo, &done_flag, 314 MHD_OPTION_THREAD_POOL_SIZE, MHD_CPU_COUNT, 315 MHD_OPTION_END); 316 if (d == NULL) 317 return 16; 318 if (0 == port) 319 { 320 const union MHD_DaemonInfo *dinfo; 321 dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); 322 if ((NULL == dinfo) || (0 == dinfo->port) ) 323 { 324 MHD_stop_daemon (d); return 32; 325 } 326 port = dinfo->port; 327 } 328 c = curl_easy_init (); 329 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world"); 330 curl_easy_setopt (c, CURLOPT_PORT, (long) port); 331 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 332 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 333 curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer); 334 curl_easy_setopt (c, CURLOPT_READDATA, &pos); 335 curl_easy_setopt (c, CURLOPT_UPLOAD, 1L); 336 /* by not giving the file size, we force chunking! */ 337 /* 338 curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L); 339 */ 340 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); 341 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 342 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 343 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 344 /* NOTE: use of CONNECTTIMEOUT without also 345 * setting NOSIGNAL results in really weird 346 * crashes on my system! */ 347 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); 348 if (CURLE_OK != (errornum = curl_easy_perform (c))) 349 { 350 fprintf (stderr, 351 "curl_easy_perform failed: `%s'\n", 352 curl_easy_strerror (errornum)); 353 curl_easy_cleanup (c); 354 MHD_stop_daemon (d); 355 return 32; 356 } 357 curl_easy_cleanup (c); 358 MHD_stop_daemon (d); 359 if (cbc.pos != strlen ("/hello_world")) 360 return 64; 361 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 362 return 128; 363 364 return 0; 365 } 366 367 368 static unsigned int 369 testExternalPut (void) 370 { 371 struct MHD_Daemon *d; 372 CURL *c; 373 char buf[2048]; 374 struct CBC cbc; 375 CURLM *multi; 376 CURLMcode mret; 377 fd_set rs; 378 fd_set ws; 379 fd_set es; 380 MHD_socket maxsock; 381 #ifdef MHD_WINSOCK_SOCKETS 382 int maxposixs; /* Max socket number unused on W32 */ 383 #else /* MHD_POSIX_SOCKETS */ 384 #define maxposixs maxsock 385 #endif /* MHD_POSIX_SOCKETS */ 386 int running; 387 struct CURLMsg *msg; 388 time_t start; 389 struct timeval tv; 390 size_t pos = 0; 391 size_t done_flag = 0; 392 uint16_t port; 393 394 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) 395 port = 0; 396 else 397 port = 1443; 398 399 multi = NULL; 400 cbc.buf = buf; 401 cbc.size = 2048; 402 cbc.pos = 0; 403 d = MHD_start_daemon (MHD_USE_ERROR_LOG, 404 port, 405 NULL, NULL, &ahc_echo, &done_flag, 406 MHD_OPTION_APP_FD_SETSIZE, (int) FD_SETSIZE, 407 MHD_OPTION_END); 408 if (d == NULL) 409 return 256; 410 if (0 == port) 411 { 412 const union MHD_DaemonInfo *dinfo; 413 dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); 414 if ((NULL == dinfo) || (0 == dinfo->port) ) 415 { 416 MHD_stop_daemon (d); return 32; 417 } 418 port = dinfo->port; 419 } 420 c = curl_easy_init (); 421 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world"); 422 curl_easy_setopt (c, CURLOPT_PORT, (long) port); 423 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 424 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 425 curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer); 426 curl_easy_setopt (c, CURLOPT_READDATA, &pos); 427 curl_easy_setopt (c, CURLOPT_UPLOAD, 1L); 428 /* by not giving the file size, we force chunking! */ 429 /* 430 curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L); 431 */ 432 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); 433 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 434 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 435 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 436 /* NOTE: use of CONNECTTIMEOUT without also 437 * setting NOSIGNAL results in really weird 438 * crashes on my system! */ 439 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); 440 441 442 multi = curl_multi_init (); 443 if (multi == NULL) 444 { 445 curl_easy_cleanup (c); 446 MHD_stop_daemon (d); 447 return 512; 448 } 449 mret = curl_multi_add_handle (multi, c); 450 if (mret != CURLM_OK) 451 { 452 curl_multi_cleanup (multi); 453 curl_easy_cleanup (c); 454 MHD_stop_daemon (d); 455 return 1024; 456 } 457 start = time (NULL); 458 while ((time (NULL) - start < 5) && (multi != NULL)) 459 { 460 maxsock = MHD_INVALID_SOCKET; 461 maxposixs = -1; 462 FD_ZERO (&rs); 463 FD_ZERO (&ws); 464 FD_ZERO (&es); 465 curl_multi_perform (multi, &running); 466 mret = curl_multi_fdset (multi, &rs, &ws, &es, &maxposixs); 467 if (mret != CURLM_OK) 468 { 469 curl_multi_remove_handle (multi, c); 470 curl_multi_cleanup (multi); 471 curl_easy_cleanup (c); 472 MHD_stop_daemon (d); 473 return 2048; 474 } 475 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &maxsock)) 476 { 477 curl_multi_remove_handle (multi, c); 478 curl_multi_cleanup (multi); 479 curl_easy_cleanup (c); 480 MHD_stop_daemon (d); 481 return 4096; 482 } 483 tv.tv_sec = 0; 484 tv.tv_usec = 1000; 485 if (-1 == select (maxposixs + 1, &rs, &ws, &es, &tv)) 486 { 487 #ifdef MHD_POSIX_SOCKETS 488 if (EINTR != errno) 489 { 490 fprintf (stderr, "Unexpected select() error: %d. Line: %d\n", 491 (int) errno, __LINE__); 492 fflush (stderr); 493 exit (99); 494 } 495 #else 496 if ((WSAEINVAL != WSAGetLastError ()) || 497 (0 != rs.fd_count) || (0 != ws.fd_count) || (0 != es.fd_count) ) 498 { 499 fprintf (stderr, "Unexpected select() error: %d. Line: %d\n", 500 (int) WSAGetLastError (), __LINE__); 501 fflush (stderr); 502 exit (99); 503 } 504 Sleep (1); 505 #endif 506 } 507 curl_multi_perform (multi, &running); 508 if (0 == running) 509 { 510 int pending; 511 int curl_fine = 0; 512 while (NULL != (msg = curl_multi_info_read (multi, &pending))) 513 { 514 if (msg->msg == CURLMSG_DONE) 515 { 516 if (msg->data.result == CURLE_OK) 517 curl_fine = 1; 518 else 519 { 520 fprintf (stderr, 521 "%s failed at %s:%d: `%s'\n", 522 "curl_multi_perform", 523 __FILE__, 524 __LINE__, curl_easy_strerror (msg->data.result)); 525 abort (); 526 } 527 } 528 } 529 if (! curl_fine) 530 { 531 fprintf (stderr, "libcurl haven't returned OK code\n"); 532 abort (); 533 } 534 curl_multi_remove_handle (multi, c); 535 curl_multi_cleanup (multi); 536 curl_easy_cleanup (c); 537 c = NULL; 538 multi = NULL; 539 } 540 MHD_run (d); 541 } 542 if (multi != NULL) 543 { 544 curl_multi_remove_handle (multi, c); 545 curl_easy_cleanup (c); 546 curl_multi_cleanup (multi); 547 } 548 MHD_stop_daemon (d); 549 if (cbc.pos != strlen ("/hello_world")) 550 return 8192; 551 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 552 return 16384; 553 return 0; 554 } 555 556 557 int 558 main (int argc, char *const *argv) 559 { 560 unsigned int errorCount = 0; 561 (void) argc; (void) argv; /* Unused. Silent compiler warning. */ 562 563 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 564 return 2; 565 if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_THREADS)) 566 { 567 errorCount += testInternalPut (); 568 errorCount += testMultithreadedPut (); 569 errorCount += testMultithreadedPoolPut (); 570 } 571 errorCount += testExternalPut (); 572 if (errorCount != 0) 573 fprintf (stderr, "Error (code: %u)\n", errorCount); 574 curl_global_cleanup (); 575 return (0 == errorCount) ? 0 : 1; /* 0 == pass */ 576 }