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