1 /* 2 * test82: test HTTP with a remote server (is $USENETWORK="yes") 3 */ 4 5 #define DEBUG 0 6 7 #if DEBUG 8 #define dbgprintf(...) do { \ 9 fprintf(stderr, "[%s:%s:%d %d] ", \ 10 __FILE__, __FUNCTION__, \ 11 __LINE__, getpid()); \ 12 fprintf(stderr, __VA_ARGS__); \ 13 fflush(stderr); \ 14 } while (0) 15 #else 16 #define dbgprintf(...) 17 #endif 18 19 #include <arpa/inet.h> 20 #include <assert.h> 21 #include <netdb.h> 22 #include <netinet/in.h> 23 #include <stdarg.h> 24 #include <stdio.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <sys/socket.h> 28 #include <sys/wait.h> 29 30 #include "common.h" 31 32 #define CLOSE(fd) do { assert(fd >= 0); if (close((fd)) != 0) efmt("close failed"); } while (0); 33 #define REALLOC(p, size) do { p = realloc(p, size); if (!p) efmt("realloc of %zu bytes failed", size); } while (0); 34 35 #define HOST "test82.minix3.org" 36 #define PORT 80 37 #define PATH1 "/test1.txt" 38 #define PATH1_DATA "Hello world\n" 39 #define PATH2 "/test2.bin" 40 41 static void callback_verify_path1(const void *data, size_t size); 42 static void callback_verify_path2(const void *data, size_t size); 43 44 #define URL_COUNT 2 45 46 struct url { 47 const char *host; 48 int port; 49 const char *path; 50 void (* callback_verify)(const void *data, size_t size); 51 }; 52 53 static const struct url urls[URL_COUNT] = { 54 { HOST, PORT, PATH1, callback_verify_path1 }, 55 { HOST, PORT, PATH2, callback_verify_path2 }, 56 }; 57 58 static int http_connect(const char *host, int port) { 59 struct addrinfo *addr = NULL; 60 int fd = -1; 61 struct addrinfo hints = { 62 .ai_family = PF_INET, 63 .ai_socktype = SOCK_STREAM, 64 }; 65 char serv[12]; 66 67 assert(host); 68 69 snprintf(serv, sizeof(serv), "%d", port); 70 71 errno = 0; 72 if (getaddrinfo(host, serv, &hints, &addr) != 0 || !addr) { 73 efmt("host %s not found", host); 74 goto failure; 75 } 76 77 fd = socket(AF_INET, SOCK_STREAM, 0); 78 if (fd < 0) { 79 efmt("cannot create socket"); 80 goto failure; 81 } 82 83 if (connect(fd, addr->ai_addr, addr->ai_addrlen) != 0) { 84 efmt("cannot connect to %s:%d", host, port); 85 goto failure; 86 } 87 88 freeaddrinfo(addr); 89 return fd; 90 91 failure: 92 if (fd >= 0) CLOSE(fd); 93 if (addr) freeaddrinfo(addr); 94 return -1; 95 } 96 97 static void write_chunked( 98 int fd, 99 const char *data, 100 size_t size, 101 size_t chunksize) { 102 ssize_t r; 103 size_t s; 104 105 assert(fd >= 0); 106 assert(data); 107 assert(chunksize > 0); 108 109 while (size > 0) { 110 s = chunksize; 111 if (s > size) s = size; 112 113 errno = 0; 114 r = write(fd, data, s); 115 if (r <= 0 || (size_t) r > s) { 116 errno = 0; 117 efmt("write of %zu bytes failed with result %zd", s, r); 118 break; 119 } 120 121 data += r; 122 size -= r; 123 } 124 } 125 126 static void http_send_request( 127 int fd, 128 const char *host, 129 const char *path, 130 size_t chunksize, 131 int bigrequest) { 132 char buf[8192]; 133 size_t len; 134 int lineno; 135 136 assert(fd >= 0); 137 assert(host); 138 assert(path); 139 assert(chunksize > 0); 140 141 /* http://tools.ietf.org/html/rfc2616#section-5 */ 142 len = snprintf(buf, sizeof(buf), 143 "GET %s HTTP/1.1\r\n" 144 "Host: %s\r\n", 145 path, host); 146 if (bigrequest) { 147 lineno = 0; 148 while (len + 24 < sizeof(buf)) { 149 len += snprintf(buf + len, sizeof(buf) - len, 150 "X-Padding%d: %d\r\n", 151 lineno, lineno); 152 lineno++; 153 } 154 } 155 len += snprintf(buf + len, sizeof(buf) - len, "\r\n"); 156 157 dbgprintf("sending request:\n%.*s", (int) len, buf); 158 write_chunked(fd, buf, len, chunksize); 159 } 160 161 static int is_whitespace(char c) { 162 return c == ' ' || c == '\t'; 163 } 164 165 static int is_whitespace_or_linebreak(char c) { 166 return is_whitespace(c) || c == '\r' || c == '\n'; 167 } 168 169 static int is_numeric(char c) { 170 return c >= '0' && c <= '9'; 171 } 172 173 static int http_get_header_line( 174 const char *data, 175 size_t len, 176 size_t *index_p, 177 size_t *linelen_p) { 178 int has_cr; 179 size_t index; 180 size_t linelen; 181 182 assert(data); 183 assert(index_p); 184 assert(*index_p <= len); 185 assert(linelen_p); 186 187 /* starting the next line with whitespace means the line is continued */ 188 index = *index_p; 189 do { 190 while (index < len && data[index] != '\n') index++; 191 if (index >= len) goto notfound; 192 index++; 193 } while (index < len && is_whitespace(data[index])); 194 195 /* exclude LF or CR+LF from line length */ 196 assert(index - 1 >= *index_p && data[index - 1] == '\n'); 197 has_cr = (index - 2 >= *index_p) && data[index - 2] == '\r'; 198 linelen = index - *index_p - (has_cr ? 2 : 1); 199 200 /* if LF is the last character in the buffer, the line may be continued 201 * when more data is retrieved unless we reached the end of the headers 202 */ 203 if (index >= len && linelen > 0) goto notfound; 204 205 *linelen_p = linelen; 206 *index_p = index; 207 return 1; 208 209 notfound: 210 *linelen_p = 0; 211 *index_p = index; 212 return 0; 213 } 214 215 static int http_get_status_line( 216 const char *data, 217 size_t len, 218 size_t *index_p, 219 int *error_p, 220 int *code_p) { 221 int code, i; 222 size_t index; 223 224 assert(data); 225 assert(index_p); 226 assert(*index_p <= len); 227 assert(error_p); 228 assert(*error_p == 0); 229 assert(code_p); 230 231 /* skip leading whitespace/blank lines */ 232 index = *index_p; 233 while (index < len && is_whitespace_or_linebreak(data[index])) index++; 234 235 /* parse version */ 236 while (index < len && !is_whitespace(data[index])) index++; 237 238 /* skip separator */ 239 while (index < len && is_whitespace(data[index])) index++; 240 241 /* parse status code */ 242 code = 0; 243 for (i = 0; i < 3; i++) { 244 if (index >= len) goto notfound; 245 if (!is_numeric(data[index])) { 246 errno = 0; 247 efmt("HTTP error: bad status line: \"%.*s\"", 248 (int) (index - *index_p), data + *index_p); 249 *error_p = 1; 250 goto notfound; 251 } 252 code = code * 10 + (data[index++] - '0'); 253 } 254 255 /* skip separator */ 256 while (index < len && is_whitespace(data[index])) index++; 257 258 /* parse reason phrase */ 259 while (index < len && data[index] != '\n') index++; 260 if (index >= len) goto notfound; 261 index++; 262 263 *code_p = code; 264 *index_p = index; 265 return 1; 266 267 notfound: 268 *code_p = 0; 269 *index_p = index; 270 return 0; 271 } 272 273 static int http_header_is( 274 const char *data, 275 size_t len, 276 size_t index, 277 const char *name, 278 size_t *index_value_p) { 279 size_t namelen; 280 281 assert(data); 282 assert(index <= len); 283 assert(name); 284 assert(index_value_p); 285 286 namelen = strlen(name); 287 if (index + namelen > len) goto notfound; 288 if (strncasecmp(data + index, name, namelen) != 0) goto notfound; 289 index += namelen; 290 while (index < len && is_whitespace(data[index])) index++; 291 if (index >= len || data[index] != ':') goto notfound; 292 index++; 293 294 while (index < len && is_whitespace(data[index])) index++; 295 *index_value_p = index; 296 return 1; 297 298 notfound: 299 *index_value_p = 0; 300 return 0; 301 } 302 303 static int http_parse_int_header( 304 const char *data, 305 size_t index, 306 size_t index_end, 307 int *value_p, 308 int *error_p) { 309 int value = 0; 310 311 assert(data); 312 assert(index <= index_end); 313 assert(value_p); 314 assert(error_p); 315 assert(!*error_p); 316 317 while (index < index_end && is_numeric(data[index])) { 318 value = value * 10 + (data[index++] - '0'); 319 } 320 321 while (index < index_end && is_whitespace_or_linebreak(data[index])) { 322 index++; 323 } 324 325 if (index < index_end) { 326 errno = 0; 327 efmt("HTTP error: bad numeric header value: \"%.*s\"", 328 (int) (index_end - index), data + index); 329 *error_p = 1; 330 return 0; 331 } 332 333 *value_p = value; 334 return 1; 335 } 336 337 static int http_response_complete( 338 const char *data, 339 size_t len, 340 int *error_p, 341 int *code_p, 342 size_t *index_body_p) { 343 int content_length = -1; 344 size_t index = 0, index_line; 345 size_t index_value; 346 size_t linelen; 347 348 assert(data); 349 assert(error_p); 350 assert(!*error_p); 351 assert(code_p); 352 assert(index_body_p); 353 354 /* parse status line */ 355 if (!http_get_status_line(data, len, &index, error_p, code_p)) { 356 return 0; 357 } 358 359 /* parse headers */ 360 for (;;) { 361 index_line = index; 362 if (!http_get_header_line(data, len, &index, &linelen)) { 363 return 0; 364 } 365 if (linelen == 0) break; 366 if (http_header_is(data, len, index_line, 367 "Content-Length", &index_value)) { 368 if (!http_parse_int_header(data, index_value, 369 index_line + linelen, &content_length, 370 error_p)) { 371 return 0; 372 } 373 } 374 } 375 376 /* do we know how long the response will be? */ 377 if (content_length < 0) { 378 errno = 0; 379 efmt("HTTP error: missing Content-Length header " 380 "(maybe Transfer-Encoding is specified instead " 381 "but this is currently unsupported)"); 382 goto error; 383 } 384 385 /* check whether the amount of data is correct */ 386 if (len > index + content_length) { 387 errno = 0; 388 efmt("HTTP error: more data received than expected"); 389 goto error; 390 } 391 392 *index_body_p = index; 393 return len == index + content_length; 394 395 error: 396 *error_p = 1; 397 *code_p = 0; 398 *index_body_p = 0; 399 return 0; 400 } 401 402 static void http_recv_response( 403 int fd, 404 void (* callback_verify)(const void *data, size_t size), 405 size_t chunksize) { 406 int code; 407 char *data; 408 size_t datalen = 0, datasize = 0; 409 int error = 0; 410 size_t index_body; 411 ssize_t r; 412 413 assert(fd >= 0); 414 assert(callback_verify); 415 assert(chunksize > 0); 416 417 data = NULL; 418 for (;;) { 419 /* make room for another chunk in the buffer if needed */ 420 if (datasize < datalen + chunksize) { 421 datasize = (datalen + chunksize) * 2; 422 REALLOC(data, datasize); 423 } 424 425 /* read a chunk of data */ 426 errno = 0; 427 r = read(fd, data + datalen, chunksize); 428 if (r < 0 || (size_t) r > chunksize) { 429 efmt("read of %zu bytes failed with result %zd", 430 chunksize, r); 431 goto cleanup; 432 } 433 datalen += r; 434 435 /* if we received all headers+data, we are done */ 436 if (http_response_complete(data, datalen, &error, &code, 437 &index_body)) { 438 break; 439 } 440 if (error) goto cleanup; 441 442 /* check for premature disconnection */ 443 if (r == 0) { 444 errno = 0; 445 efmt("server disconnected even though the response " 446 "seems to be incomplete"); 447 goto cleanup; 448 } 449 } 450 451 dbgprintf("received response:\n%.*s", (int) datalen, data); 452 453 assert(index_body <= datalen); 454 if (code == 200) { 455 callback_verify(data + index_body, datalen - index_body); 456 } else { 457 errno = 0; 458 efmt("unexpected HTTP status code %d", code); 459 } 460 461 cleanup: 462 if (data) free(data); 463 } 464 465 static void http_test( 466 const struct url *url, 467 size_t chunksize, 468 int bigrequest, 469 int delay, 470 int withshutdown) { 471 int fd; 472 473 assert(url); 474 assert(chunksize > 0); 475 476 dbgprintf("attempting download from http://%s:%d%s, " 477 "chunksize=%zu, bigrequest=%d, delay=%d, withshutdown=%d\n", 478 url->host, url->port, url->path, chunksize, bigrequest, 479 delay, withshutdown); 480 481 fd = http_connect(url->host, url->port); 482 if (fd < 0) return; 483 484 http_send_request(fd, url->host, url->path, chunksize, bigrequest); 485 486 errno = 0; 487 if (withshutdown && shutdown(fd, SHUT_WR) != 0) { 488 efmt("shutdown failed"); 489 } 490 491 if (delay) sleep(1); 492 http_recv_response(fd, url->callback_verify, chunksize); 493 494 CLOSE(fd); 495 496 dbgprintf("download attempt completed\n"); 497 } 498 499 static int child_count; 500 501 static void http_test_fork( 502 const struct url *url, 503 size_t chunksize, 504 int bigrequest, 505 int delay, 506 int withshutdown) { 507 int errctold; 508 pid_t pid; 509 510 assert(url); 511 assert(chunksize > 0); 512 513 errno = 0; 514 pid = fork(); 515 if (pid < 0) { 516 efmt("fork failed"); 517 return; 518 } 519 520 if (pid > 0) { 521 child_count++; 522 return; 523 } 524 525 errctold = errct; 526 http_test( 527 url, 528 chunksize, 529 bigrequest, 530 delay, 531 withshutdown); 532 assert(errct >= errctold); 533 exit(errct - errctold); 534 } 535 536 static void wait_all(void) { 537 int exitcode, status; 538 pid_t pid; 539 540 while (child_count > 0) { 541 errno = 0; 542 pid = waitpid(-1, &status, 0); 543 if (pid <= 0) { 544 efmt("waitpid failed"); 545 return; 546 } 547 if (WIFEXITED(status)) { 548 exitcode = WEXITSTATUS(status); 549 dbgprintf("child %d completed with exit code %d\n", 550 (int) pid, exitcode); 551 if (exitcode >= 0) { 552 errct += exitcode; 553 } else { 554 efmt("child has negative exit code %d", 555 exitcode); 556 } 557 } else if (WIFSIGNALED(status)) { 558 dbgprintf("child %d killed by signal %d\n", 559 (int) pid, WTERMSIG(status)); 560 efmt("child killed by signal %d", WTERMSIG(status)); 561 } else { 562 dbgprintf("child %d gone with status 0x%x\n", 563 (int) pid, status); 564 efmt("child gone, but neither exit nor signal"); 565 } 566 child_count--; 567 } 568 569 errno = 0; 570 if (waitpid(-1, &status, 0) != -1 || errno != ECHILD) { 571 efmt("waitpid should have returned ECHILD"); 572 } 573 } 574 575 #define OPTION_BIGREQUEST (1 << 0) 576 #define OPTION_DELAY (1 << 1) 577 #define OPTION_SHUTDOWN (1 << 2) 578 579 static void http_test_all(int multiproc) { 580 static const size_t chunksizes[] = { 1, 1024, 65536 }; 581 static const int optionsets[] = { 582 0, 583 OPTION_BIGREQUEST, 584 OPTION_DELAY, 585 OPTION_SHUTDOWN, 586 OPTION_BIGREQUEST | OPTION_DELAY | OPTION_SHUTDOWN, 587 }; 588 int chunksizeindex; 589 int options; 590 int optionindex; 591 int urlindex; 592 593 for (urlindex = 0; urlindex < URL_COUNT; urlindex++) { 594 for (chunksizeindex = 0; chunksizeindex < 3; chunksizeindex++) { 595 for (optionindex = 0; optionindex < 3; optionindex++) { 596 options = optionsets[optionindex]; 597 (multiproc ? http_test_fork : http_test)( 598 &urls[urlindex], 599 chunksizes[chunksizeindex], 600 options & OPTION_BIGREQUEST, 601 options & OPTION_DELAY, 602 options & OPTION_SHUTDOWN); 603 } 604 } 605 } 606 607 wait_all(); 608 } 609 610 static void verify_data( 611 const void *httpdata, size_t httpsize, 612 const void *refdata, size_t refsize, 613 const char *path) { 614 615 assert(httpdata); 616 assert(refdata); 617 assert(path); 618 619 if (httpsize != refsize) { 620 errno = 0; 621 efmt("download from http://%s:%d%s returned wrong number " 622 "of bytes: %zd (expected %zd)", 623 HOST, PORT, path, httpsize, refsize); 624 } else if (memcmp(httpdata, refdata, refsize) != 0) { 625 errno = 0; 626 efmt("download from http://%s:%d%s returned wrong data", 627 HOST, PORT, path); 628 } 629 } 630 631 static void callback_verify_path1(const void *data, size_t size) { 632 verify_data(data, size, PATH1_DATA, strlen(PATH1_DATA), PATH1); 633 } 634 635 static void callback_verify_path2(const void *data, size_t size) { 636 unsigned short buf[65536]; 637 int i; 638 639 for (i = 0; i < 65536; i++) buf[i] = htons(i); 640 641 verify_data(data, size, buf, sizeof(buf), PATH2); 642 } 643 644 int main(int argc, char **argv) 645 { 646 int use_network; 647 648 start(82); 649 650 use_network = get_setting_use_network(); 651 if (use_network) { 652 http_test_all(0 /* multiproc */); 653 http_test_all(1 /* multiproc */); 654 } else { 655 dbgprintf("test disabled, set USENETWORK=yes to enable\n"); 656 } 657 658 quit(); 659 return 0; 660 } 661