1/*
2    This file is part of libmicrospdy
3    Copyright Copyright (C) 2013 Andrey Uzunov
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17*/
18
19/**
20 * @file fileserver.c
21 * @brief   Simple example how the lib can be used for serving
22 * 			files directly read from the system
23 * @author Andrey Uzunov
24 */
25
26//for asprintf
27#define _GNU_SOURCE
28
29#include <unistd.h>
30#include <stdlib.h>
31#include <stdint.h>
32#include <stdbool.h>
33#include <string.h>
34#include <stdio.h>
35#include <ctype.h>
36#include <errno.h>
37#include "microspdy.h"
38#include "time.h"
39
40
41int run = 1;
42char* basedir;
43
44
45#define GET_MIME_TYPE(fname, mime)	do {\
46		unsigned int __len = strlen(fname);\
47		if (__len < 4 || '.' != (fname)[__len - 4]) \
48		{	\
49			(mime) = strdup("application/octet-stream");\
50			printf("MIME for %s is applic...\n", (fname));\
51		}\
52    else {\
53      const char * __ext = &(fname)[__len - 3];\
54      if(0 == strcmp(__ext, "jpg")) (mime) = strdup("image/jpeg");\
55      else if(0 == strcmp(__ext, "png")) (mime) = strdup("image/png");\
56      else if(0 == strcmp(__ext, "css")) (mime) = strdup("text/css");\
57      else if(0 == strcmp(__ext, "gif")) (mime) = strdup("image/gif");\
58      else if(0 == strcmp(__ext, "htm")) (mime) = strdup("text/html");\
59      else \
60      {	\
61        (mime) = strdup("application/octet-stream");\
62        printf("MIME for %s is applic...\n", (fname));\
63      }\
64    }\
65		if(NULL == (mime))\
66		{\
67			printf("no memory\n");\
68			abort();\
69		}\
70	} while (0)
71
72
73static const char *DAY_NAMES[] =
74  { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
75
76static const char *MONTH_NAMES[] =
77  { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
78    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
79
80//taken from http://stackoverflow.com/questions/2726975/how-can-i-generate-an-rfc1123-date-string-from-c-code-win32
81//and modified for linux
82char *Rfc1123_DateTimeNow()
83{
84    const int RFC1123_TIME_LEN = 29;
85    time_t t;
86    struct tm tm;
87    char * buf = malloc(RFC1123_TIME_LEN+1);
88
89    if (NULL == buf)
90      return NULL;
91    time(&t);
92    gmtime_r( &t, &tm);
93
94    strftime(buf, RFC1123_TIME_LEN+1, "---, %d --- %Y %H:%M:%S GMT", &tm);
95    memcpy(buf, DAY_NAMES[tm.tm_wday], 3);
96    memcpy(buf+8, MONTH_NAMES[tm.tm_mon], 3);
97
98    return buf;
99}
100
101
102ssize_t
103response_callback (void *cls,
104						void *buffer,
105						size_t max,
106						bool *more)
107{
108	FILE *fd =(FILE*)cls;
109
110	int ret = fread(buffer,1,max,fd);
111	*more = feof(fd) == 0;
112
113	//if(!(*more))
114	//	fclose(fd);
115
116	return ret;
117}
118
119
120void
121response_done_callback(void *cls,
122						struct SPDY_Response *response,
123						struct SPDY_Request *request,
124						enum SPDY_RESPONSE_RESULT status,
125						bool streamopened)
126{
127	(void)streamopened;
128	(void)status;
129	//printf("answer for %s was sent\n", (char *)cls);
130
131	/*if(SPDY_RESPONSE_RESULT_SUCCESS != status)
132	{
133		printf("answer for %s was NOT sent, %i\n", (char *)cls,status);
134	}*/
135
136	SPDY_destroy_request(request);
137	SPDY_destroy_response(response);
138	if(NULL!=cls)fclose(cls);
139}
140
141void
142standard_request_handler(void *cls,
143						struct SPDY_Request * request,
144						uint8_t priority,
145                        const char *method,
146                        const char *path,
147                        const char *version,
148                        const char *host,
149                        const char *scheme,
150						struct SPDY_NameValue * headers,
151            bool more)
152{
153	(void)cls;
154	(void)request;
155	(void)priority;
156	(void)host;
157	(void)scheme;
158	(void)headers;
159	(void)method;
160	(void)version;
161	(void)more;
162
163	struct SPDY_Response *response=NULL;
164	struct SPDY_NameValue *resp_headers;
165	char *fname;
166	char *fsize;
167	char *mime=NULL;
168	char *date=NULL;
169	ssize_t filesize = -666;
170	FILE *fd = NULL;
171	int ret = -666;
172
173	//printf("received request for '%s %s %s'\n", method, path, version);
174	if(strlen(path) > 1 && NULL == strstr(path, "..") && '/' == path[0])
175	{
176		asprintf(&fname,"%s%s",basedir,path);
177		if(0 == access(fname, R_OK))
178		{
179			if(NULL == (fd = fopen(fname,"r"))
180				|| 0 != (ret = fseek(fd, 0L, SEEK_END))
181				|| -1 == (filesize = ftell(fd))
182				|| 0 != (ret = fseek(fd, 0L, SEEK_SET)))
183			{
184				printf("Error on opening %s\n%p %i %zd\n",fname, fd, ret, filesize);
185				response = SPDY_build_response(SPDY_HTTP_INTERNAL_SERVER_ERROR,NULL,SPDY_HTTP_VERSION_1_1,NULL,NULL,0);
186			}
187			else
188			{
189				if(NULL == (resp_headers = SPDY_name_value_create()))
190				{
191					printf("SPDY_name_value_create failed\n");
192					abort();
193				}
194
195				date = Rfc1123_DateTimeNow();
196				if(NULL == date
197					|| SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_DATE,date))
198				{
199					printf("SPDY_name_value_add or Rfc1123_DateTimeNow failed\n");
200					abort();
201				}
202				free(date);
203
204				if(-1 == asprintf(&fsize, "%zd", filesize)
205					|| SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_CONTENT_LENGTH,fsize))
206				{
207					printf("SPDY_name_value_add or asprintf failed\n");
208					abort();
209				}
210				free(fsize);
211
212				GET_MIME_TYPE(path,mime);
213				if(SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_CONTENT_TYPE,mime))
214				{
215					printf("SPDY_name_value_add failed\n");
216					abort();
217				}
218				free(mime);
219
220				if(SPDY_YES != SPDY_name_value_add(resp_headers,SPDY_HTTP_HEADER_SERVER,"libmicrospdy/fileserver"))
221				{
222					printf("SPDY_name_value_add failed\n");
223					abort();
224				}
225
226				response = SPDY_build_response_with_callback(200,NULL,
227					SPDY_HTTP_VERSION_1_1,resp_headers,&response_callback,fd,SPDY_MAX_SUPPORTED_FRAME_SIZE);
228				SPDY_name_value_destroy(resp_headers);
229			}
230
231			if(NULL==response){
232				printf("no response obj\n");
233				abort();
234			}
235
236			if(SPDY_queue_response(request,response,true,false,&response_done_callback,fd)!=SPDY_YES)
237			{
238				printf("queue\n");
239				abort();
240			}
241
242			free(fname);
243			return;
244		}
245		free(fname);
246	}
247
248	if(strcmp(path,"/close")==0)
249	{
250		run = 0;
251	}
252
253	response = SPDY_build_response(SPDY_HTTP_NOT_FOUND,NULL,SPDY_HTTP_VERSION_1_1,NULL,NULL,0);
254	printf("Not found %s\n",path);
255
256	if(NULL==response){
257		printf("no response obj\n");
258		abort();
259	}
260
261	if(SPDY_queue_response(request,response,true,false,&response_done_callback,NULL)!=SPDY_YES)
262	{
263		printf("queue\n");
264		abort();
265	}
266}
267
268int
269main (int argc, char *const *argv)
270{
271	unsigned long long timeoutlong=0;
272	struct timeval timeout;
273	int ret;
274	fd_set read_fd_set;
275	fd_set write_fd_set;
276	fd_set except_fd_set;
277	int maxfd = -1;
278	struct SPDY_Daemon *daemon;
279
280	if(argc != 5)
281	{
282		printf("Usage: %s cert-file key-file base-dir port\n", argv[0]);
283		return 1;
284	}
285
286	SPDY_init();
287
288	daemon = SPDY_start_daemon(atoi(argv[4]),
289								argv[1],
290								argv[2],
291								NULL,
292								NULL,
293								&standard_request_handler,
294								NULL,
295								NULL,
296								SPDY_DAEMON_OPTION_SESSION_TIMEOUT,
297								1800,
298								SPDY_DAEMON_OPTION_END);
299
300	if(NULL==daemon){
301		printf("no daemon\n");
302		return 1;
303	}
304
305	basedir = argv[3];
306
307	do
308	{
309		FD_ZERO(&read_fd_set);
310		FD_ZERO(&write_fd_set);
311		FD_ZERO(&except_fd_set);
312
313		ret = SPDY_get_timeout(daemon, &timeoutlong);
314		if(SPDY_NO == ret || timeoutlong > 1000)
315		{
316			timeout.tv_sec = 1;
317      timeout.tv_usec = 0;
318		}
319		else
320		{
321			timeout.tv_sec = timeoutlong / 1000;
322			timeout.tv_usec = (timeoutlong % 1000) * 1000;
323		}
324
325		maxfd = SPDY_get_fdset (daemon,
326								&read_fd_set,
327								&write_fd_set,
328								&except_fd_set);
329
330		ret = select(maxfd+1, &read_fd_set, &write_fd_set, &except_fd_set, &timeout);
331
332		switch(ret) {
333			case -1:
334				printf("select error: %i\n", errno);
335				break;
336			case 0:
337
338				break;
339			default:
340				SPDY_run(daemon);
341
342			break;
343		}
344	}
345	while(run);
346
347	SPDY_stop_daemon(daemon);
348
349	SPDY_deinit();
350
351	return 0;
352}
353
354