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