1/* $Id: minihttptestserver.c,v 1.16 2014/04/01 15:08:28 nanard Exp $ */ 2/* Project : miniUPnP 3 * Author : Thomas Bernard 4 * Copyright (c) 2011-2014 Thomas Bernard 5 * This software is subject to the conditions detailed in the 6 * LICENCE file provided in this distribution. 7 * */ 8#include <stdio.h> 9#include <stdlib.h> 10#include <string.h> 11#include <unistd.h> 12#include <sys/types.h> 13#include <sys/socket.h> 14#include <sys/wait.h> 15#include <arpa/inet.h> 16#include <netinet/in.h> 17#include <signal.h> 18#include <time.h> 19#include <errno.h> 20 21#define CRAP_LENGTH (2048) 22 23volatile sig_atomic_t quit = 0; 24volatile sig_atomic_t child_to_wait_for = 0; 25 26/** 27 * signal handler for SIGCHLD (child status has changed) 28 */ 29void handle_signal_chld(int sig) 30{ 31 (void)sig; 32 /* printf("handle_signal_chld(%d)\n", sig); */ 33 ++child_to_wait_for; 34} 35 36/** 37 * signal handler for SIGINT (CRTL C) 38 */ 39void handle_signal_int(int sig) 40{ 41 (void)sig; 42 /* printf("handle_signal_int(%d)\n", sig); */ 43 quit = 1; 44} 45 46/** 47 * build a text/plain content of the specified length 48 */ 49void build_content(char * p, int n) 50{ 51 char line_buffer[80]; 52 int k; 53 int i = 0; 54 55 while(n > 0) { 56 k = snprintf(line_buffer, sizeof(line_buffer), 57 "%04d_ABCDEFGHIJKL_This_line_is_64_bytes_long_ABCDEFGHIJKL_%04d\r\n", 58 i, i); 59 if(k != 64) { 60 fprintf(stderr, "snprintf() returned %d in build_content()\n", k); 61 } 62 ++i; 63 if(n >= 64) { 64 memcpy(p, line_buffer, 64); 65 p += 64; 66 n -= 64; 67 } else { 68 memcpy(p, line_buffer, n); 69 p += n; 70 n = 0; 71 } 72 } 73} 74 75/** 76 * build crappy content 77 */ 78void build_crap(char * p, int n) 79{ 80 static const char crap[] = "_CRAP_\r\n"; 81 int i; 82 83 while(n > 0) { 84 i = sizeof(crap) - 1; 85 if(i > n) 86 i = n; 87 memcpy(p, crap, i); 88 p += i; 89 n -= i; 90 } 91} 92 93/** 94 * build chunked response. 95 * return a malloc'ed buffer 96 */ 97char * build_chunked_response(int content_length, int * response_len) 98{ 99 char * response_buffer; 100 char * content_buffer; 101 int buffer_length; 102 int i, n; 103 104 /* allocate to have some margin */ 105 buffer_length = 256 + content_length + (content_length >> 4); 106 response_buffer = malloc(buffer_length); 107 *response_len = snprintf(response_buffer, buffer_length, 108 "HTTP/1.1 200 OK\r\n" 109 "Content-Type: text/plain\r\n" 110 "Transfer-Encoding: chunked\r\n" 111 "\r\n"); 112 113 /* build the content */ 114 content_buffer = malloc(content_length); 115 build_content(content_buffer, content_length); 116 117 /* chunk it */ 118 i = 0; 119 while(i < content_length) { 120 n = (rand() % 199) + 1; 121 if(i + n > content_length) { 122 n = content_length - i; 123 } 124 /* TODO : check buffer size ! */ 125 *response_len += snprintf(response_buffer + *response_len, 126 buffer_length - *response_len, 127 "%x\r\n", n); 128 memcpy(response_buffer + *response_len, content_buffer + i, n); 129 *response_len += n; 130 i += n; 131 response_buffer[(*response_len)++] = '\r'; 132 response_buffer[(*response_len)++] = '\n'; 133 } 134 /* the last chunk : "0\r\n" a empty body and then 135 * the final "\r\n" */ 136 memcpy(response_buffer + *response_len, "0\r\n\r\n", 5); 137 *response_len += 5; 138 free(content_buffer); 139 140 printf("resp_length=%d buffer_length=%d content_length=%d\n", 141 *response_len, buffer_length, content_length); 142 return response_buffer; 143} 144 145/* favicon.ico generator */ 146#ifdef OLD_HEADER 147#define FAVICON_LENGTH (6 + 16 + 12 + 8 + 32 * 4) 148#else 149#define FAVICON_LENGTH (6 + 16 + 40 + 8 + 32 * 4) 150#endif 151void build_favicon_content(char * p, int n) 152{ 153 int i; 154 if(n < FAVICON_LENGTH) 155 return; 156 /* header : 6 bytes */ 157 *p++ = 0; 158 *p++ = 0; 159 *p++ = 1; /* type : ICO */ 160 *p++ = 0; 161 *p++ = 1; /* number of images in file */ 162 *p++ = 0; 163 /* image directory (1 entry) : 16 bytes */ 164 *p++ = 16; /* width */ 165 *p++ = 16; /* height */ 166 *p++ = 2; /* number of colors in the palette. 0 = no palette */ 167 *p++ = 0; /* reserved */ 168 *p++ = 1; /* color planes */ 169 *p++ = 0; /* " */ 170 *p++ = 1; /* bpp */ 171 *p++ = 0; /* " */ 172#ifdef OLD_HEADER 173 *p++ = 12 + 8 + 32 * 4; /* bmp size */ 174#else 175 *p++ = 40 + 8 + 32 * 4; /* bmp size */ 176#endif 177 *p++ = 0; /* " */ 178 *p++ = 0; /* " */ 179 *p++ = 0; /* " */ 180 *p++ = 6 + 16; /* bmp offset */ 181 *p++ = 0; /* " */ 182 *p++ = 0; /* " */ 183 *p++ = 0; /* " */ 184 /* BMP */ 185#ifdef OLD_HEADER 186 /* BITMAPCOREHEADER */ 187 *p++ = 12; /* size of this header */ 188 *p++ = 0; /* " */ 189 *p++ = 0; /* " */ 190 *p++ = 0; /* " */ 191 *p++ = 16; /* width */ 192 *p++ = 0; /* " */ 193 *p++ = 16 * 2; /* height x 2 ! */ 194 *p++ = 0; /* " */ 195 *p++ = 1; /* color planes */ 196 *p++ = 0; /* " */ 197 *p++ = 1; /* bpp */ 198 *p++ = 0; /* " */ 199#else 200 /* BITMAPINFOHEADER */ 201 *p++ = 40; /* size of this header */ 202 *p++ = 0; /* " */ 203 *p++ = 0; /* " */ 204 *p++ = 0; /* " */ 205 *p++ = 16; /* width */ 206 *p++ = 0; /* " */ 207 *p++ = 0; /* " */ 208 *p++ = 0; /* " */ 209 *p++ = 16 * 2; /* height x 2 ! */ 210 *p++ = 0; /* " */ 211 *p++ = 0; /* " */ 212 *p++ = 0; /* " */ 213 *p++ = 1; /* color planes */ 214 *p++ = 0; /* " */ 215 *p++ = 1; /* bpp */ 216 *p++ = 0; /* " */ 217 /* compression method, image size, ppm x, ppm y */ 218 /* colors in the palette ? */ 219 /* important colors */ 220 for(i = 4 * 6; i > 0; --i) 221 *p++ = 0; 222#endif 223 /* palette */ 224 *p++ = 0; /* b */ 225 *p++ = 0; /* g */ 226 *p++ = 0; /* r */ 227 *p++ = 0; /* reserved */ 228 *p++ = 255; /* b */ 229 *p++ = 255; /* g */ 230 *p++ = 255; /* r */ 231 *p++ = 0; /* reserved */ 232 /* pixel data */ 233 for(i = 16; i > 0; --i) { 234 if(i & 1) { 235 *p++ = 0125; 236 *p++ = 0125; 237 } else { 238 *p++ = 0252; 239 *p++ = 0252; 240 } 241 *p++ = 0; 242 *p++ = 0; 243 } 244 /* Opacity MASK */ 245 for(i = 16 * 4; i > 0; --i) { 246 *p++ = 0; 247 } 248} 249 250enum modes { 251 MODE_INVALID, MODE_CHUNKED, MODE_ADDCRAP, MODE_NORMAL, MODE_FAVICON 252}; 253 254const struct { 255 const enum modes mode; 256 const char * text; 257} modes_array[] = { 258 {MODE_CHUNKED, "chunked"}, 259 {MODE_ADDCRAP, "addcrap"}, 260 {MODE_NORMAL, "normal"}, 261 {MODE_FAVICON, "favicon.ico"}, 262 {MODE_INVALID, NULL} 263}; 264 265/** 266 * write the response with random behaviour ! 267 */ 268void send_response(int c, const char * buffer, int len) 269{ 270 int n; 271 while(len > 0) { 272 n = (rand() % 99) + 1; 273 if(n > len) 274 n = len; 275 n = write(c, buffer, n); 276 if(n < 0) { 277 if(errno != EINTR) { 278 perror("write"); 279 return; 280 } 281 /* if errno == EINTR, try again */ 282 } else { 283 len -= n; 284 buffer += n; 285 } 286 usleep(10000); /* 10ms */ 287 } 288} 289 290/** 291 * handle the HTTP connection 292 */ 293void handle_http_connection(int c) 294{ 295 char request_buffer[2048]; 296 int request_len = 0; 297 int headers_found = 0; 298 int n, i; 299 char request_method[16]; 300 char request_uri[256]; 301 char http_version[16]; 302 char * p; 303 char * response_buffer; 304 int response_len; 305 enum modes mode; 306 int content_length = 16*1024; 307 308 /* read the request */ 309 while(request_len < (int)sizeof(request_buffer) && !headers_found) { 310 n = read(c, 311 request_buffer + request_len, 312 sizeof(request_buffer) - request_len); 313 if(n < 0) { 314 if(errno == EINTR) 315 continue; 316 perror("read"); 317 return; 318 } else if(n==0) { 319 /* remote host closed the connection */ 320 break; 321 } else { 322 request_len += n; 323 for(i = 0; i < request_len - 3; i++) { 324 if(0 == memcmp(request_buffer + i, "\r\n\r\n", 4)) { 325 /* found the end of headers */ 326 headers_found = 1; 327 break; 328 } 329 } 330 } 331 } 332 if(!headers_found) { 333 /* error */ 334 printf("no HTTP header found in the request\n"); 335 return; 336 } 337 printf("headers :\n%.*s", request_len, request_buffer); 338 /* the request have been received, now parse the request line */ 339 p = request_buffer; 340 for(i = 0; i < (int)sizeof(request_method) - 1; i++) { 341 if(*p == ' ' || *p == '\r') 342 break; 343 request_method[i] = *p; 344 ++p; 345 } 346 request_method[i] = '\0'; 347 while(*p == ' ') 348 p++; 349 for(i = 0; i < (int)sizeof(request_uri) - 1; i++) { 350 if(*p == ' ' || *p == '\r') 351 break; 352 request_uri[i] = *p; 353 ++p; 354 } 355 request_uri[i] = '\0'; 356 while(*p == ' ') 357 p++; 358 for(i = 0; i < (int)sizeof(http_version) - 1; i++) { 359 if(*p == ' ' || *p == '\r') 360 break; 361 http_version[i] = *p; 362 ++p; 363 } 364 http_version[i] = '\0'; 365 printf("Method = %s, URI = %s, %s\n", 366 request_method, request_uri, http_version); 367 /* check if the request method is allowed */ 368 if(0 != strcmp(request_method, "GET")) { 369 const char response405[] = "HTTP/1.1 405 Method Not Allowed\r\n" 370 "Allow: GET\r\n\r\n"; 371 const char * pc; 372 /* 405 Method Not Allowed */ 373 /* The response MUST include an Allow header containing a list 374 * of valid methods for the requested resource. */ 375 n = sizeof(response405) - 1; 376 pc = response405; 377 while(n > 0) { 378 i = write(c, pc, n); 379 if(i<0) { 380 if(errno != EINTR) { 381 perror("write"); 382 return; 383 } 384 } else { 385 n -= i; 386 pc += i; 387 } 388 } 389 return; 390 } 391 392 mode = MODE_INVALID; 393 /* use the request URI to know what to do */ 394 for(i = 0; modes_array[i].mode != MODE_INVALID; i++) { 395 if(strstr(request_uri, modes_array[i].text)) { 396 mode = modes_array[i].mode; /* found */ 397 break; 398 } 399 } 400 401 switch(mode) { 402 case MODE_CHUNKED: 403 response_buffer = build_chunked_response(content_length, &response_len); 404 break; 405 case MODE_ADDCRAP: 406 response_len = content_length+256; 407 response_buffer = malloc(response_len); 408 if(!response_buffer) 409 break; 410 n = snprintf(response_buffer, response_len, 411 "HTTP/1.1 200 OK\r\n" 412 "Server: minihttptestserver\r\n" 413 "Content-Type: text/plain\r\n" 414 "Content-Length: %d\r\n" 415 "\r\n", content_length); 416 response_len = content_length+n+CRAP_LENGTH; 417 response_buffer = realloc(response_buffer, response_len); 418 build_content(response_buffer + n, content_length); 419 build_crap(response_buffer + n + content_length, CRAP_LENGTH); 420 break; 421 case MODE_FAVICON: 422 content_length = FAVICON_LENGTH; 423 response_len = content_length + 256; 424 response_buffer = malloc(response_len); 425 if(!response_buffer) 426 break; 427 n = snprintf(response_buffer, response_len, 428 "HTTP/1.1 200 OK\r\n" 429 "Server: minihttptestserver\r\n" 430 "Content-Type: image/vnd.microsoft.icon\r\n" 431 "Content-Length: %d\r\n" 432 "\r\n", content_length); 433 /* image/x-icon */ 434 build_favicon_content(response_buffer + n, content_length); 435 response_len = content_length + n; 436 break; 437 default: 438 response_len = content_length+256; 439 response_buffer = malloc(response_len); 440 if(!response_buffer) 441 break; 442 n = snprintf(response_buffer, response_len, 443 "HTTP/1.1 200 OK\r\n" 444 "Server: minihttptestserver\r\n" 445 "Content-Type: text/plain\r\n" 446 "\r\n"); 447 response_len = content_length+n; 448 response_buffer = realloc(response_buffer, response_len); 449 build_content(response_buffer + n, response_len - n); 450 } 451 452 if(response_buffer) { 453 send_response(c, response_buffer, response_len); 454 free(response_buffer); 455 } else { 456 /* Error 500 */ 457 } 458} 459 460/** 461 */ 462int main(int argc, char * * argv) { 463 int ipv6 = 0; 464 int s, c, i; 465 unsigned short port = 0; 466 struct sockaddr_storage server_addr; 467 socklen_t server_addrlen; 468 struct sockaddr_storage client_addr; 469 socklen_t client_addrlen; 470 pid_t pid; 471 int child = 0; 472 int status; 473 const char * expected_file_name = NULL; 474 struct sigaction sa; 475 476 for(i = 1; i < argc; i++) { 477 if(argv[i][0] == '-') { 478 switch(argv[i][1]) { 479 case '6': 480 ipv6 = 1; 481 break; 482 case 'e': 483 /* write expected file ! */ 484 expected_file_name = argv[++i]; 485 break; 486 case 'p': 487 /* port */ 488 if(++i < argc) { 489 port = (unsigned short)atoi(argv[i]); 490 } 491 break; 492 default: 493 fprintf(stderr, "unknown command line switch '%s'\n", argv[i]); 494 } 495 } else { 496 fprintf(stderr, "unkown command line argument '%s'\n", argv[i]); 497 } 498 } 499 500 srand(time(NULL)); 501 502 memset(&sa, 0, sizeof(struct sigaction)); 503 504 /*signal(SIGCHLD, handle_signal_chld);*/ 505 sa.sa_handler = handle_signal_chld; 506 if(sigaction(SIGCHLD, &sa, NULL) < 0) { 507 perror("sigaction"); 508 return 1; 509 } 510 /*signal(SIGINT, handle_signal_int);*/ 511 sa.sa_handler = handle_signal_int; 512 if(sigaction(SIGINT, &sa, NULL) < 0) { 513 perror("sigaction"); 514 return 1; 515 } 516 517 s = socket(ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0); 518 if(s < 0) { 519 perror("socket"); 520 return 1; 521 } 522 memset(&server_addr, 0, sizeof(struct sockaddr_storage)); 523 memset(&client_addr, 0, sizeof(struct sockaddr_storage)); 524 if(ipv6) { 525 struct sockaddr_in6 * addr = (struct sockaddr_in6 *)&server_addr; 526 addr->sin6_family = AF_INET6; 527 addr->sin6_port = htons(port); 528 addr->sin6_addr = in6addr_loopback; 529 } else { 530 struct sockaddr_in * addr = (struct sockaddr_in *)&server_addr; 531 addr->sin_family = AF_INET; 532 addr->sin_port = htons(port); 533 addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); 534 } 535 if(bind(s, (struct sockaddr *)&server_addr, 536 ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) < 0) { 537 perror("bind"); 538 return 1; 539 } 540 if(listen(s, 5) < 0) { 541 perror("listen"); 542 } 543 if(port == 0) { 544 server_addrlen = sizeof(struct sockaddr_storage); 545 if(getsockname(s, (struct sockaddr *)&server_addr, &server_addrlen) < 0) { 546 perror("getsockname"); 547 return 1; 548 } 549 if(ipv6) { 550 struct sockaddr_in6 * addr = (struct sockaddr_in6 *)&server_addr; 551 port = ntohs(addr->sin6_port); 552 } else { 553 struct sockaddr_in * addr = (struct sockaddr_in *)&server_addr; 554 port = ntohs(addr->sin_port); 555 } 556 printf("Listening on port %hu\n", port); 557 fflush(stdout); 558 } 559 560 /* write expected file */ 561 if(expected_file_name) { 562 FILE * f; 563 f = fopen(expected_file_name, "wb"); 564 if(f) { 565 char * buffer; 566 buffer = malloc(16*1024); 567 build_content(buffer, 16*1024); 568 i = fwrite(buffer, 1, 16*1024, f); 569 if(i != 16*1024) { 570 fprintf(stderr, "error writing to file %s : %dbytes written (out of %d)\n", expected_file_name, i, 16*1024); 571 } 572 free(buffer); 573 fclose(f); 574 } else { 575 fprintf(stderr, "error opening file %s for writing\n", expected_file_name); 576 } 577 } 578 579 /* fork() loop */ 580 while(!child && !quit) { 581 while(child_to_wait_for > 0) { 582 pid = wait(&status); 583 if(pid < 0) { 584 perror("wait"); 585 } else { 586 printf("child(%d) terminated with status %d\n", pid, status); 587 } 588 --child_to_wait_for; 589 } 590 client_addrlen = sizeof(struct sockaddr_storage); 591 c = accept(s, (struct sockaddr *)&client_addr, 592 &client_addrlen); 593 if(c < 0) { 594 if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) 595 continue; 596 perror("accept"); 597 return 1; 598 } 599 printf("accept...\n"); 600 pid = fork(); 601 if(pid < 0) { 602 perror("fork"); 603 return 1; 604 } else if(pid == 0) { 605 /* child */ 606 child = 1; 607 close(s); 608 s = -1; 609 handle_http_connection(c); 610 } 611 close(c); 612 } 613 if(s >= 0) { 614 close(s); 615 s = -1; 616 } 617 if(!child) { 618 while(child_to_wait_for > 0) { 619 pid = wait(&status); 620 if(pid < 0) { 621 perror("wait"); 622 } else { 623 printf("child(%d) terminated with status %d\n", pid, status); 624 } 625 --child_to_wait_for; 626 } 627 printf("Bye...\n"); 628 } 629 return 0; 630} 631 632