/* $Id: minihttptestserver.c,v 1.24 2018/02/03 22:11:04 nanard Exp $ */ /* Project : miniUPnP * Author : Thomas Bernard * Copyright (c) 2011-2018 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. * */ #include #include #include #include #include #include #include #include #include #include #include #include #ifndef INADDR_LOOPBACK #define INADDR_LOOPBACK 0x7f000001 #endif #define CRAP_LENGTH (2048) static int server(unsigned short port, const char * expected_file_name, int ipv6); volatile sig_atomic_t quit = 0; volatile sig_atomic_t child_to_wait_for = 0; /** * signal handler for SIGCHLD (child status has changed) */ void handle_signal_chld(int sig) { (void)sig; /* printf("handle_signal_chld(%d)\n", sig); */ ++child_to_wait_for; } /** * signal handler for SIGINT (CRTL C) */ void handle_signal_int(int sig) { (void)sig; /* printf("handle_signal_int(%d)\n", sig); */ quit = 1; } /** * build a text/plain content of the specified length */ void build_content(char * p, size_t n) { char line_buffer[80]; int k; int i = 0; while(n > 0) { k = snprintf(line_buffer, sizeof(line_buffer), "%04d_ABCDEFGHIJKL_This_line_is_64_bytes_long_ABCDEFGHIJKL_%04d\r\n", i, i); if(k != 64) { fprintf(stderr, "snprintf() returned %d in build_content()\n", k); } ++i; if(n >= 64) { memcpy(p, line_buffer, 64); p += 64; n -= 64; } else { memcpy(p, line_buffer, n); p += n; n = 0; } } } /** * build crappy content */ void build_crap(char * p, size_t n) { static const char crap[] = "_CRAP_\r\n"; size_t i; while(n > 0) { i = sizeof(crap) - 1; if(i > n) i = n; memcpy(p, crap, i); p += i; n -= i; } } /** * build chunked response. * return a malloc'ed buffer */ char * build_chunked_response(size_t content_length, size_t * response_len) { char * response_buffer; char * content_buffer; size_t buffer_length; size_t i; unsigned int n; /* allocate to have some margin */ buffer_length = 256 + content_length + (content_length >> 4); response_buffer = malloc(buffer_length); if(response_buffer == NULL) return NULL; *response_len = snprintf(response_buffer, buffer_length, "HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" "Transfer-Encoding: chunked\r\n" "\r\n"); /* build the content */ content_buffer = malloc(content_length); if(content_buffer == NULL) { free(response_buffer); return NULL; } build_content(content_buffer, content_length); /* chunk it */ i = 0; while(i < content_length) { n = (rand() % 199) + 1; if(i + n > content_length) { n = content_length - i; } /* TODO : check buffer size ! */ *response_len += snprintf(response_buffer + *response_len, buffer_length - *response_len, "%x\r\n", n); memcpy(response_buffer + *response_len, content_buffer + i, n); *response_len += n; i += n; response_buffer[(*response_len)++] = '\r'; response_buffer[(*response_len)++] = '\n'; } /* the last chunk : "0\r\n" a empty body and then * the final "\r\n" */ memcpy(response_buffer + *response_len, "0\r\n\r\n", 5); *response_len += 5; free(content_buffer); printf("resp_length=%lu buffer_length=%lu content_length=%lu\n", *response_len, buffer_length, content_length); return response_buffer; } /* favicon.ico generator */ #ifdef OLD_HEADER #define FAVICON_LENGTH (6 + 16 + 12 + 8 + 32 * 4) #else #define FAVICON_LENGTH (6 + 16 + 40 + 8 + 32 * 4) #endif void build_favicon_content(unsigned char * p, size_t n) { int i; if(n < FAVICON_LENGTH) return; /* header : 6 bytes */ *p++ = 0; *p++ = 0; *p++ = 1; /* type : ICO */ *p++ = 0; *p++ = 1; /* number of images in file */ *p++ = 0; /* image directory (1 entry) : 16 bytes */ *p++ = 16; /* width */ *p++ = 16; /* height */ *p++ = 2; /* number of colors in the palette. 0 = no palette */ *p++ = 0; /* reserved */ *p++ = 1; /* color planes */ *p++ = 0; /* " */ *p++ = 1; /* bpp */ *p++ = 0; /* " */ #ifdef OLD_HEADER *p++ = 12 + 8 + 32 * 4; /* bmp size */ #else *p++ = 40 + 8 + 32 * 4; /* bmp size */ #endif *p++ = 0; /* " */ *p++ = 0; /* " */ *p++ = 0; /* " */ *p++ = 6 + 16; /* bmp offset */ *p++ = 0; /* " */ *p++ = 0; /* " */ *p++ = 0; /* " */ /* BMP */ #ifdef OLD_HEADER /* BITMAPCOREHEADER */ *p++ = 12; /* size of this header */ *p++ = 0; /* " */ *p++ = 0; /* " */ *p++ = 0; /* " */ *p++ = 16; /* width */ *p++ = 0; /* " */ *p++ = 16 * 2; /* height x 2 ! */ *p++ = 0; /* " */ *p++ = 1; /* color planes */ *p++ = 0; /* " */ *p++ = 1; /* bpp */ *p++ = 0; /* " */ #else /* BITMAPINFOHEADER */ *p++ = 40; /* size of this header */ *p++ = 0; /* " */ *p++ = 0; /* " */ *p++ = 0; /* " */ *p++ = 16; /* width */ *p++ = 0; /* " */ *p++ = 0; /* " */ *p++ = 0; /* " */ *p++ = 16 * 2; /* height x 2 ! */ *p++ = 0; /* " */ *p++ = 0; /* " */ *p++ = 0; /* " */ *p++ = 1; /* color planes */ *p++ = 0; /* " */ *p++ = 1; /* bpp */ *p++ = 0; /* " */ /* compression method, image size, ppm x, ppm y */ /* colors in the palette ? */ /* important colors */ for(i = 4 * 6; i > 0; --i) *p++ = 0; #endif /* palette */ *p++ = 0; /* b */ *p++ = 0; /* g */ *p++ = 0; /* r */ *p++ = 0; /* reserved */ *p++ = 255; /* b */ *p++ = 255; /* g */ *p++ = 255; /* r */ *p++ = 0; /* reserved */ /* pixel data */ for(i = 16; i > 0; --i) { if(i & 1) { *p++ = 0125; *p++ = 0125; } else { *p++ = 0252; *p++ = 0252; } *p++ = 0; *p++ = 0; } /* Opacity MASK */ for(i = 16 * 4; i > 0; --i) { *p++ = 0; } } enum modes { MODE_INVALID, MODE_CHUNKED, MODE_ADDCRAP, MODE_NORMAL, MODE_FAVICON }; const struct { const enum modes mode; const char * text; } modes_array[] = { {MODE_CHUNKED, "chunked"}, {MODE_ADDCRAP, "addcrap"}, {MODE_NORMAL, "normal"}, {MODE_FAVICON, "favicon.ico"}, {MODE_INVALID, NULL} }; /** * write the response with random behaviour ! */ void send_response(int c, const char * buffer, size_t len) { ssize_t n; while(len > 0) { n = (rand() % 99) + 1; if((size_t)n > len) n = len; n = write(c, buffer, n); if(n < 0) { if(errno != EINTR) { perror("write"); return; } /* if errno == EINTR, try again */ } else { len -= n; buffer += n; usleep(10000); /* 10ms */ } } } /** * handle the HTTP connection */ void handle_http_connection(int c) { char request_buffer[2048]; size_t request_len = 0; int headers_found = 0; ssize_t n, m; size_t i; char request_method[16]; char request_uri[256]; char http_version[16]; char * p; char * response_buffer; size_t response_len; enum modes mode; size_t content_length = 16*1024; /* read the request */ while(request_len < sizeof(request_buffer) && !headers_found) { n = read(c, request_buffer + request_len, sizeof(request_buffer) - request_len); if(n < 0) { if(errno == EINTR) continue; perror("read"); return; } else if(n==0) { /* remote host closed the connection */ break; } else { request_len += n; for(i = 0; i < request_len - 3; i++) { if(0 == memcmp(request_buffer + i, "\r\n\r\n", 4)) { /* found the end of headers */ headers_found = 1; break; } } } } if(!headers_found) { /* error */ printf("no HTTP header found in the request\n"); return; } printf("headers :\n%.*s", (int)request_len, request_buffer); /* the request have been received, now parse the request line */ p = request_buffer; for(i = 0; i < sizeof(request_method) - 1; i++) { if(*p == ' ' || *p == '\r') break; request_method[i] = *p; ++p; } request_method[i] = '\0'; while(*p == ' ') p++; for(i = 0; i < (int)sizeof(request_uri) - 1; i++) { if(*p == ' ' || *p == '\r') break; request_uri[i] = *p; ++p; } request_uri[i] = '\0'; while(*p == ' ') p++; for(i = 0; i < (int)sizeof(http_version) - 1; i++) { if(*p == ' ' || *p == '\r') break; http_version[i] = *p; ++p; } http_version[i] = '\0'; printf("Method = %s, URI = %s, %s\n", request_method, request_uri, http_version); /* check if the request method is allowed */ if(0 != strcmp(request_method, "GET")) { const char response405[] = "HTTP/1.1 405 Method Not Allowed\r\n" "Allow: GET\r\n\r\n"; const char * pc; /* 405 Method Not Allowed */ /* The response MUST include an Allow header containing a list * of valid methods for the requested resource. */ n = sizeof(response405) - 1; pc = response405; while(n > 0) { m = write(c, pc, n); if(m<0) { if(errno != EINTR) { perror("write"); return; } } else { n -= m; pc += m; } } return; } mode = MODE_INVALID; /* use the request URI to know what to do */ for(i = 0; modes_array[i].mode != MODE_INVALID; i++) { if(strstr(request_uri, modes_array[i].text)) { mode = modes_array[i].mode; /* found */ break; } } switch(mode) { case MODE_CHUNKED: response_buffer = build_chunked_response(content_length, &response_len); break; case MODE_ADDCRAP: response_len = content_length+256; response_buffer = malloc(response_len); if(!response_buffer) break; n = snprintf(response_buffer, response_len, "HTTP/1.1 200 OK\r\n" "Server: minihttptestserver\r\n" "Content-Type: text/plain\r\n" "Content-Length: %lu\r\n" "\r\n", content_length); response_len = content_length+n+CRAP_LENGTH; p = realloc(response_buffer, response_len); if(p == NULL) { /* error 500 */ free(response_buffer); response_buffer = NULL; break; } response_buffer = p; build_content(response_buffer + n, content_length); build_crap(response_buffer + n + content_length, CRAP_LENGTH); break; case MODE_FAVICON: content_length = FAVICON_LENGTH; response_len = content_length + 256; response_buffer = malloc(response_len); if(!response_buffer) break; n = snprintf(response_buffer, response_len, "HTTP/1.1 200 OK\r\n" "Server: minihttptestserver\r\n" "Content-Type: image/vnd.microsoft.icon\r\n" "Content-Length: %lu\r\n" "\r\n", content_length); /* image/x-icon */ build_favicon_content((unsigned char *)(response_buffer + n), content_length); response_len = content_length + n; break; default: response_len = content_length+256; response_buffer = malloc(response_len); if(!response_buffer) break; n = snprintf(response_buffer, response_len, "HTTP/1.1 200 OK\r\n" "Server: minihttptestserver\r\n" "Content-Type: text/plain\r\n" "\r\n"); response_len = content_length+n; p = realloc(response_buffer, response_len); if(p == NULL) { /* Error 500 */ free(response_buffer); response_buffer = NULL; break; } response_buffer = p; build_content(response_buffer + n, response_len - n); } if(response_buffer) { send_response(c, response_buffer, response_len); free(response_buffer); } else { /* Error 500 */ } } /** */ int main(int argc, char * * argv) { int ipv6 = 0; int r, i; unsigned short port = 0; const char * expected_file_name = NULL; for(i = 1; i < argc; i++) { if(argv[i][0] == '-') { switch(argv[i][1]) { case '6': ipv6 = 1; break; case 'e': /* write expected file ! */ expected_file_name = argv[++i]; break; case 'p': /* port */ if(++i < argc) { port = (unsigned short)atoi(argv[i]); } break; default: fprintf(stderr, "unknown command line switch '%s'\n", argv[i]); } } else { fprintf(stderr, "unknown command line argument '%s'\n", argv[i]); } } srand(time(NULL)); r = server(port, expected_file_name, ipv6); if(r != 0) { printf("*** ERROR ***\n"); } return r; } static int server(unsigned short port, const char * expected_file_name, int ipv6) { int s, c; int i; struct sockaddr_storage server_addr; socklen_t server_addrlen; struct sockaddr_storage client_addr; socklen_t client_addrlen; pid_t pid; int child = 0; int status; struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); /*signal(SIGCHLD, handle_signal_chld);*/ sa.sa_handler = handle_signal_chld; if(sigaction(SIGCHLD, &sa, NULL) < 0) { perror("sigaction"); return 1; } /*signal(SIGINT, handle_signal_int);*/ sa.sa_handler = handle_signal_int; if(sigaction(SIGINT, &sa, NULL) < 0) { perror("sigaction"); return 1; } s = socket(ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0); if(s < 0) { perror("socket"); return 1; } memset(&server_addr, 0, sizeof(struct sockaddr_storage)); memset(&client_addr, 0, sizeof(struct sockaddr_storage)); if(ipv6) { struct sockaddr_in6 * addr = (struct sockaddr_in6 *)&server_addr; addr->sin6_family = AF_INET6; addr->sin6_port = htons(port); addr->sin6_addr = in6addr_loopback; } else { struct sockaddr_in * addr = (struct sockaddr_in *)&server_addr; addr->sin_family = AF_INET; addr->sin_port = htons(port); addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); } if(bind(s, (struct sockaddr *)&server_addr, ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) < 0) { perror("bind"); return 1; } if(listen(s, 5) < 0) { perror("listen"); } if(port == 0) { server_addrlen = sizeof(struct sockaddr_storage); if(getsockname(s, (struct sockaddr *)&server_addr, &server_addrlen) < 0) { perror("getsockname"); return 1; } if(ipv6) { struct sockaddr_in6 * addr = (struct sockaddr_in6 *)&server_addr; port = ntohs(addr->sin6_port); } else { struct sockaddr_in * addr = (struct sockaddr_in *)&server_addr; port = ntohs(addr->sin_port); } printf("Listening on port %hu\n", port); fflush(stdout); } /* write expected file */ if(expected_file_name) { FILE * f; f = fopen(expected_file_name, "wb"); if(f) { char * buffer; buffer = malloc(16*1024); if(buffer == NULL) { fprintf(stderr, "memory allocation error\n"); } else { build_content(buffer, 16*1024); i = fwrite(buffer, 1, 16*1024, f); if(i != 16*1024) { fprintf(stderr, "error writing to file %s : %dbytes written (out of %d)\n", expected_file_name, i, 16*1024); } free(buffer); } fclose(f); } else { fprintf(stderr, "error opening file %s for writing\n", expected_file_name); } } /* fork() loop */ while(!child && !quit) { while(child_to_wait_for > 0) { pid = wait(&status); if(pid < 0) { perror("wait"); } else { printf("child(%d) terminated with status %d\n", (int)pid, status); } --child_to_wait_for; } client_addrlen = sizeof(struct sockaddr_storage); c = accept(s, (struct sockaddr *)&client_addr, &client_addrlen); if(c < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; perror("accept"); return 1; } printf("accept...\n"); pid = fork(); if(pid < 0) { perror("fork"); return 1; } else if(pid == 0) { /* child */ child = 1; close(s); s = -1; handle_http_connection(c); } close(c); } if(s >= 0) { close(s); s = -1; } if(!child) { while(child_to_wait_for > 0) { pid = wait(&status); if(pid < 0) { perror("wait"); } else { printf("child(%d) terminated with status %d\n", (int)pid, status); } --child_to_wait_for; } printf("Bye...\n"); } return 0; }