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