1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2007 Christian Grothoff 4 5 libmicrohttpd is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 2, or (at your 8 option) any later version. 9 10 libmicrohttpd is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with libmicrohttpd; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18 Boston, MA 02111-1307, USA. 19 */ 20 21 /** 22 * @file test_post.c 23 * @brief Testcase for libmicrohttpd POST operations using URL-encoding 24 * @author Christian Grothoff 25 */ 26 27 #include "MHD_config.h" 28 #include "platform.h" 29 #include <curl/curl.h> 30 #include <microhttpd.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <time.h> 34 35 #ifndef WINDOWS 36 #include <unistd.h> 37 #endif 38 39 #ifdef _WIN32 40 #ifndef WIN32_LEAN_AND_MEAN 41 #define WIN32_LEAN_AND_MEAN 1 42 #endif /* !WIN32_LEAN_AND_MEAN */ 43 #include <windows.h> 44 #endif 45 46 #if defined(CPU_COUNT) && (CPU_COUNT+0) < 2 47 #undef CPU_COUNT 48 #endif 49 #if !defined(CPU_COUNT) 50 #define CPU_COUNT 2 51 #endif 52 53 #define POST_DATA "name=daniel&project=curl" 54 55 static int oneone; 56 57 struct CBC 58 { 59 char *buf; 60 size_t pos; 61 size_t size; 62 }; 63 64 65 static void 66 completed_cb (void *cls, 67 struct MHD_Connection *connection, 68 void **con_cls, 69 enum MHD_RequestTerminationCode toe) 70 { 71 struct MHD_PostProcessor *pp = *con_cls; 72 73 if (NULL != pp) 74 MHD_destroy_post_processor (pp); 75 *con_cls = NULL; 76 } 77 78 79 static size_t 80 copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx) 81 { 82 struct CBC *cbc = ctx; 83 84 if (cbc->pos + size * nmemb > cbc->size) 85 return 0; /* overflow */ 86 memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb); 87 cbc->pos += size * nmemb; 88 return size * nmemb; 89 } 90 91 92 /** 93 * Note that this post_iterator is not perfect 94 * in that it fails to support incremental processing. 95 * (to be fixed in the future) 96 */ 97 static int 98 post_iterator (void *cls, 99 enum MHD_ValueKind kind, 100 const char *key, 101 const char *filename, 102 const char *content_type, 103 const char *transfer_encoding, 104 const char *value, uint64_t off, size_t size) 105 { 106 int *eok = cls; 107 108 if ((0 == strcmp (key, "name")) && 109 (size == strlen ("daniel")) && (0 == strncmp (value, "daniel", size))) 110 (*eok) |= 1; 111 if ((0 == strcmp (key, "project")) && 112 (size == strlen ("curl")) && (0 == strncmp (value, "curl", size))) 113 (*eok) |= 2; 114 return MHD_YES; 115 } 116 117 118 static int 119 ahc_echo (void *cls, 120 struct MHD_Connection *connection, 121 const char *url, 122 const char *method, 123 const char *version, 124 const char *upload_data, size_t *upload_data_size, 125 void **unused) 126 { 127 static int eok; 128 struct MHD_Response *response; 129 struct MHD_PostProcessor *pp; 130 int ret; 131 132 if (0 != strcmp ("POST", method)) 133 { 134 printf ("METHOD: %s\n", method); 135 return MHD_NO; /* unexpected method */ 136 } 137 pp = *unused; 138 if (pp == NULL) 139 { 140 eok = 0; 141 pp = MHD_create_post_processor (connection, 1024, &post_iterator, &eok); 142 *unused = pp; 143 } 144 MHD_post_process (pp, upload_data, *upload_data_size); 145 if ((eok == 3) && (0 == *upload_data_size)) 146 { 147 response = MHD_create_response_from_buffer (strlen (url), 148 (void *) url, 149 MHD_RESPMEM_MUST_COPY); 150 ret = MHD_queue_response (connection, MHD_HTTP_OK, response); 151 MHD_destroy_response (response); 152 MHD_destroy_post_processor (pp); 153 *unused = NULL; 154 return ret; 155 } 156 *upload_data_size = 0; 157 return MHD_YES; 158 } 159 160 161 static int 162 testInternalPost () 163 { 164 struct MHD_Daemon *d; 165 CURL *c; 166 char buf[2048]; 167 struct CBC cbc; 168 CURLcode errornum; 169 170 cbc.buf = buf; 171 cbc.size = 2048; 172 cbc.pos = 0; 173 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, 174 1080, NULL, NULL, &ahc_echo, NULL, 175 MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL, 176 MHD_OPTION_END); 177 if (d == NULL) 178 return 1; 179 c = curl_easy_init (); 180 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1080/hello_world"); 181 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 182 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 183 curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA); 184 curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA)); 185 curl_easy_setopt (c, CURLOPT_POST, 1L); 186 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 187 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 188 if (oneone) 189 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 190 else 191 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 192 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 193 // NOTE: use of CONNECTTIMEOUT without also 194 // setting NOSIGNAL results in really weird 195 // crashes on my system! 196 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 197 if (CURLE_OK != (errornum = curl_easy_perform (c))) 198 { 199 fprintf (stderr, 200 "curl_easy_perform failed: `%s'\n", 201 curl_easy_strerror (errornum)); 202 curl_easy_cleanup (c); 203 MHD_stop_daemon (d); 204 return 2; 205 } 206 curl_easy_cleanup (c); 207 MHD_stop_daemon (d); 208 if (cbc.pos != strlen ("/hello_world")) 209 return 4; 210 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 211 return 8; 212 return 0; 213 } 214 215 static int 216 testMultithreadedPost () 217 { 218 struct MHD_Daemon *d; 219 CURL *c; 220 char buf[2048]; 221 struct CBC cbc; 222 CURLcode errornum; 223 224 cbc.buf = buf; 225 cbc.size = 2048; 226 cbc.pos = 0; 227 d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG, 228 1081, NULL, NULL, &ahc_echo, NULL, 229 MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL, 230 MHD_OPTION_END); 231 if (d == NULL) 232 return 16; 233 c = curl_easy_init (); 234 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world"); 235 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 236 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 237 curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA); 238 curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA)); 239 curl_easy_setopt (c, CURLOPT_POST, 1L); 240 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 241 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 242 if (oneone) 243 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 244 else 245 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 246 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 247 // NOTE: use of CONNECTTIMEOUT without also 248 // setting NOSIGNAL results in really weird 249 // crashes on my system! 250 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 251 if (CURLE_OK != (errornum = curl_easy_perform (c))) 252 { 253 fprintf (stderr, 254 "curl_easy_perform failed: `%s'\n", 255 curl_easy_strerror (errornum)); 256 curl_easy_cleanup (c); 257 MHD_stop_daemon (d); 258 return 32; 259 } 260 curl_easy_cleanup (c); 261 MHD_stop_daemon (d); 262 if (cbc.pos != strlen ("/hello_world")) 263 return 64; 264 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 265 return 128; 266 return 0; 267 } 268 269 static int 270 testMultithreadedPoolPost () 271 { 272 struct MHD_Daemon *d; 273 CURL *c; 274 char buf[2048]; 275 struct CBC cbc; 276 CURLcode errornum; 277 278 cbc.buf = buf; 279 cbc.size = 2048; 280 cbc.pos = 0; 281 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, 282 1081, NULL, NULL, &ahc_echo, NULL, 283 MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, 284 MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL, 285 MHD_OPTION_END); 286 if (d == NULL) 287 return 16; 288 c = curl_easy_init (); 289 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world"); 290 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 291 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 292 curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA); 293 curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA)); 294 curl_easy_setopt (c, CURLOPT_POST, 1L); 295 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 296 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 297 if (oneone) 298 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 299 else 300 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 301 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 302 // NOTE: use of CONNECTTIMEOUT without also 303 // setting NOSIGNAL results in really weird 304 // crashes on my system! 305 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 306 if (CURLE_OK != (errornum = curl_easy_perform (c))) 307 { 308 fprintf (stderr, 309 "curl_easy_perform failed: `%s'\n", 310 curl_easy_strerror (errornum)); 311 curl_easy_cleanup (c); 312 MHD_stop_daemon (d); 313 return 32; 314 } 315 curl_easy_cleanup (c); 316 MHD_stop_daemon (d); 317 if (cbc.pos != strlen ("/hello_world")) 318 return 64; 319 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 320 return 128; 321 return 0; 322 } 323 324 static int 325 testExternalPost () 326 { 327 struct MHD_Daemon *d; 328 CURL *c; 329 char buf[2048]; 330 struct CBC cbc; 331 CURLM *multi; 332 CURLMcode mret; 333 fd_set rs; 334 fd_set ws; 335 fd_set es; 336 MHD_socket max; 337 int running; 338 struct CURLMsg *msg; 339 time_t start; 340 struct timeval tv; 341 342 multi = NULL; 343 cbc.buf = buf; 344 cbc.size = 2048; 345 cbc.pos = 0; 346 d = MHD_start_daemon (MHD_USE_DEBUG, 347 1082, NULL, NULL, &ahc_echo, NULL, 348 MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL, 349 MHD_OPTION_END); 350 if (d == NULL) 351 return 256; 352 c = curl_easy_init (); 353 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1082/hello_world"); 354 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 355 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 356 curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA); 357 curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA)); 358 curl_easy_setopt (c, CURLOPT_POST, 1L); 359 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 360 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 361 if (oneone) 362 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 363 else 364 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 365 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 366 // NOTE: use of CONNECTTIMEOUT without also 367 // setting NOSIGNAL results in really weird 368 // crashes on my system! 369 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 370 371 372 multi = curl_multi_init (); 373 if (multi == NULL) 374 { 375 curl_easy_cleanup (c); 376 MHD_stop_daemon (d); 377 return 512; 378 } 379 mret = curl_multi_add_handle (multi, c); 380 if (mret != CURLM_OK) 381 { 382 curl_multi_cleanup (multi); 383 curl_easy_cleanup (c); 384 MHD_stop_daemon (d); 385 return 1024; 386 } 387 start = time (NULL); 388 while ((time (NULL) - start < 5) && (multi != NULL)) 389 { 390 max = 0; 391 FD_ZERO (&rs); 392 FD_ZERO (&ws); 393 FD_ZERO (&es); 394 curl_multi_perform (multi, &running); 395 mret = curl_multi_fdset (multi, &rs, &ws, &es, &max); 396 if (mret != CURLM_OK) 397 { 398 curl_multi_remove_handle (multi, c); 399 curl_multi_cleanup (multi); 400 curl_easy_cleanup (c); 401 MHD_stop_daemon (d); 402 return 2048; 403 } 404 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) 405 { 406 curl_multi_remove_handle (multi, c); 407 curl_multi_cleanup (multi); 408 curl_easy_cleanup (c); 409 MHD_stop_daemon (d); 410 return 4096; 411 } 412 tv.tv_sec = 0; 413 tv.tv_usec = 1000; 414 select (max + 1, &rs, &ws, &es, &tv); 415 curl_multi_perform (multi, &running); 416 if (running == 0) 417 { 418 msg = curl_multi_info_read (multi, &running); 419 if (msg == NULL) 420 break; 421 if (msg->msg == CURLMSG_DONE) 422 { 423 if (msg->data.result != CURLE_OK) 424 printf ("%s failed at %s:%d: `%s'\n", 425 "curl_multi_perform", 426 __FILE__, 427 __LINE__, curl_easy_strerror (msg->data.result)); 428 curl_multi_remove_handle (multi, c); 429 curl_multi_cleanup (multi); 430 curl_easy_cleanup (c); 431 c = NULL; 432 multi = NULL; 433 } 434 } 435 MHD_run (d); 436 } 437 if (multi != NULL) 438 { 439 curl_multi_remove_handle (multi, c); 440 curl_easy_cleanup (c); 441 curl_multi_cleanup (multi); 442 } 443 MHD_stop_daemon (d); 444 if (cbc.pos != strlen ("/hello_world")) 445 return 8192; 446 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 447 return 16384; 448 return 0; 449 } 450 451 452 static int 453 ahc_cancel (void *cls, 454 struct MHD_Connection *connection, 455 const char *url, 456 const char *method, 457 const char *version, 458 const char *upload_data, size_t *upload_data_size, 459 void **unused) 460 { 461 struct MHD_Response *response; 462 int ret; 463 464 if (0 != strcmp ("POST", method)) 465 { 466 fprintf (stderr, 467 "Unexpected method `%s'\n", method); 468 return MHD_NO; 469 } 470 471 if (*unused == NULL) 472 { 473 *unused = "wibble"; 474 /* We don't want the body. Send a 500. */ 475 response = MHD_create_response_from_buffer (0, NULL, 476 MHD_RESPMEM_PERSISTENT); 477 ret = MHD_queue_response(connection, 500, response); 478 if (ret != MHD_YES) 479 fprintf(stderr, "Failed to queue response\n"); 480 MHD_destroy_response(response); 481 return ret; 482 } 483 else 484 { 485 fprintf(stderr, 486 "In ahc_cancel again. This should not happen.\n"); 487 return MHD_NO; 488 } 489 } 490 491 struct CRBC 492 { 493 const char *buffer; 494 size_t size; 495 size_t pos; 496 }; 497 498 499 static size_t 500 readBuffer(void *p, size_t size, size_t nmemb, void *opaque) 501 { 502 struct CRBC *data = opaque; 503 size_t required = size * nmemb; 504 size_t left = data->size - data->pos; 505 506 if (required > left) 507 required = left; 508 509 memcpy(p, data->buffer + data->pos, required); 510 data->pos += required; 511 512 return required/size; 513 } 514 515 516 static size_t 517 slowReadBuffer(void *p, size_t size, size_t nmemb, void *opaque) 518 { 519 sleep(1); 520 return readBuffer(p, size, nmemb, opaque); 521 } 522 523 524 #define FLAG_EXPECT_CONTINUE 1 525 #define FLAG_CHUNKED 2 526 #define FLAG_FORM_DATA 4 527 #define FLAG_SLOW_READ 8 528 #define FLAG_COUNT 16 529 530 531 static int 532 testMultithreadedPostCancelPart(int flags) 533 { 534 struct MHD_Daemon *d; 535 CURL *c; 536 char buf[2048]; 537 struct CBC cbc; 538 CURLcode errornum; 539 struct curl_slist *headers = NULL; 540 long response_code; 541 CURLcode cc; 542 int result = 0; 543 struct CRBC crbc; 544 545 /* Don't test features that aren't available with HTTP/1.0 in 546 * HTTP/1.0 mode. */ 547 if (!oneone && (flags & (FLAG_EXPECT_CONTINUE | FLAG_CHUNKED))) 548 return 0; 549 550 cbc.buf = buf; 551 cbc.size = 2048; 552 cbc.pos = 0; 553 d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG, 554 1081, NULL, NULL, &ahc_cancel, NULL, 555 MHD_OPTION_END); 556 if (d == NULL) 557 return 32768; 558 559 crbc.buffer = "Test content"; 560 crbc.size = strlen(crbc.buffer); 561 crbc.pos = 0; 562 563 c = curl_easy_init (); 564 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world"); 565 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 566 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 567 curl_easy_setopt (c, CURLOPT_READFUNCTION, (flags & FLAG_SLOW_READ) ? &slowReadBuffer : &readBuffer); 568 curl_easy_setopt (c, CURLOPT_READDATA, &crbc); 569 curl_easy_setopt (c, CURLOPT_POSTFIELDS, NULL); 570 curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, crbc.size); 571 curl_easy_setopt (c, CURLOPT_POST, 1L); 572 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 573 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 574 if (oneone) 575 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 576 else 577 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 578 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 579 // NOTE: use of CONNECTTIMEOUT without also 580 // setting NOSIGNAL results in really weird 581 // crashes on my system! 582 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 583 584 if (flags & FLAG_CHUNKED) 585 headers = curl_slist_append(headers, "Transfer-Encoding: chunked"); 586 if (!(flags & FLAG_FORM_DATA)) 587 headers = curl_slist_append(headers, "Content-Type: application/octet-stream"); 588 if (flags & FLAG_EXPECT_CONTINUE) 589 headers = curl_slist_append(headers, "Expect: 100-Continue"); 590 curl_easy_setopt(c, CURLOPT_HTTPHEADER, headers); 591 592 if (CURLE_HTTP_RETURNED_ERROR != (errornum = curl_easy_perform (c))) 593 { 594 fprintf (stderr, 595 "flibbet curl_easy_perform didn't fail as expected: `%s' %d\n", 596 curl_easy_strerror (errornum), errornum); 597 curl_easy_cleanup (c); 598 MHD_stop_daemon (d); 599 curl_slist_free_all(headers); 600 return 65536; 601 } 602 603 if (CURLE_OK != (cc = curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &response_code))) 604 { 605 fprintf(stderr, "curl_easy_getinfo failed: '%s'\n", curl_easy_strerror(errornum)); 606 result = 65536; 607 } 608 609 if (!result && (response_code != 500)) 610 { 611 fprintf(stderr, "Unexpected response code: %ld\n", response_code); 612 result = 131072; 613 } 614 615 if (!result && (cbc.pos != 0)) 616 result = 262144; 617 618 curl_easy_cleanup (c); 619 MHD_stop_daemon (d); 620 curl_slist_free_all(headers); 621 return result; 622 } 623 624 625 static int 626 testMultithreadedPostCancel() 627 { 628 int result = 0; 629 int flags; 630 for(flags = 0; flags < FLAG_COUNT; ++flags) 631 result |= testMultithreadedPostCancelPart(flags); 632 return result; 633 } 634 635 636 int 637 main (int argc, char *const *argv) 638 { 639 unsigned int errorCount = 0; 640 641 oneone = (NULL != strrchr (argv[0], (int) '/')) ? 642 (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0; 643 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 644 return 2; 645 errorCount += testMultithreadedPostCancel (); 646 errorCount += testInternalPost (); 647 errorCount += testMultithreadedPost (); 648 errorCount += testMultithreadedPoolPost (); 649 errorCount += testExternalPost (); 650 if (errorCount != 0) 651 fprintf (stderr, "Error (code: %u)\n", errorCount); 652 curl_global_cleanup (); 653 return errorCount != 0; /* 0 == pass */ 654 } 655