tinywebd

view src/tinyweb.c @ 8:121b991ccc1d

fixed the SIGPIPE bug
author John Tsiombikas <nuclear@member.fsf.org>
date Fri, 17 Apr 2015 12:02:59 +0300
parents 5ec50ca0d071
children
line source
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <ctype.h>
5 #include <errno.h>
6 #include <unistd.h>
7 #include <fcntl.h>
8 #include <sys/stat.h>
9 #include <sys/select.h>
10 #include <sys/mman.h>
11 #include <sys/types.h>
12 #include <sys/socket.h>
13 #include <arpa/inet.h>
14 #include "tinyweb.h"
15 #include "http.h"
16 #include "mime.h"
17 #include "logger.h"
19 /* HTTP version */
20 #define HTTP_VER_MAJOR 1
21 #define HTTP_VER_MINOR 1
22 #define HTTP_VER_STR "1.1"
24 /* maximum request length: 64mb */
25 #define MAX_REQ_LENGTH (65536 * 1024)
27 struct client {
28 int s;
29 char *rcvbuf;
30 int bufsz;
31 struct client *next;
32 };
34 static int accept_conn(int lis);
35 static void close_conn(struct client *c);
36 static int handle_client(struct client *c);
37 static int do_get(struct client *c, const char *uri, int with_body);
38 static void respond_error(struct client *c, int errcode);
40 static int lis = -1;
41 static int maxfd;
42 static int port = 8080;
43 static struct client *clist;
44 static int num_clients;
46 static const char *indexfiles[] = {
47 "index.cgi",
48 "index.html",
49 "index.htm",
50 0
51 };
53 void tw_set_port(int p)
54 {
55 port = p;
56 }
58 int tw_set_root(const char *path)
59 {
60 return chdir(path);
61 }
63 int tw_set_logfile(const char *fname)
64 {
65 return set_log_file(fname);
66 }
68 int tw_start(void)
69 {
70 int s;
71 struct sockaddr_in sa;
73 logmsg("starting server ...\n");
75 if(lis != -1) {
76 logmsg("can't start tinyweb server: already running!\n");
77 return -1;
78 }
80 if((s = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
81 logmsg("failed to create listening socket: %s\n", strerror(errno));
82 return -1;
83 }
84 fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK);
86 memset(&sa, 0, sizeof sa);
87 sa.sin_family = AF_INET;
88 sa.sin_addr.s_addr = INADDR_ANY;
89 sa.sin_port = htons(port);
91 if(bind(s, (struct sockaddr*)&sa, sizeof sa) == -1) {
92 logmsg("failed to bind socket to port %d: %s\n", port, strerror(errno));
93 return -1;
94 }
95 listen(s, 16);
97 lis = s;
98 return s;
99 }
101 int tw_stop(void)
102 {
103 if(lis == -1) {
104 return -1;
105 }
107 logmsg("stopping server...\n");
109 close(lis);
110 lis = -1;
112 while(clist) {
113 struct client *c = clist;
114 clist = clist->next;
115 close_conn(c);
116 free(c);
117 }
118 clist = 0;
120 return 0;
121 }
123 int tw_get_sockets(int *socks)
124 {
125 struct client *c, dummy;
127 /* first cleanup the clients marked for removal */
128 dummy.next = clist;
129 c = &dummy;
131 while(c->next) {
132 struct client *n = c->next;
134 if(n->s == -1) {
135 /* marked for removal */
136 c->next = n->next;
137 free(n);
138 --num_clients;
139 } else {
140 c = c->next;
141 }
142 }
143 clist = dummy.next;
146 if(!socks) {
147 /* just return the count */
148 return num_clients + 1; /* +1 for the listening socket */
149 }
151 /* go through the client list and populate the array */
152 maxfd = lis;
153 *socks++ = lis;
155 c = clist;
156 while(c) {
157 *socks++ = c->s;
158 if(c->s > maxfd) {
159 maxfd = c->s;
160 }
161 c = c->next;
162 }
163 return num_clients + 1; /* +1 for the listening socket */
164 }
166 int tw_get_maxfd(void)
167 {
168 return maxfd;
169 }
171 int tw_handle_socket(int s)
172 {
173 struct client *c;
175 if(s == lis) {
176 return accept_conn(s);
177 }
179 /* find which client corresponds to this socket */
180 c = clist;
181 while(c) {
182 if(c->s == s) {
183 return handle_client(c);
184 }
185 c = c->next;
186 }
188 logmsg("socket %d doesn't correspond to any client\n");
189 return -1;
190 }
192 static int accept_conn(int lis)
193 {
194 int s;
195 struct client *c;
196 struct sockaddr_in addr;
197 socklen_t addr_sz = sizeof addr;
199 if((s = accept(lis, (struct sockaddr*)&addr, &addr_sz)) == -1) {
200 logmsg("failed to accept incoming connection: %s\n", strerror(errno));
201 return -1;
202 }
203 fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK);
205 if(!(c = malloc(sizeof *c))) {
206 logmsg("failed to allocate memory while accepting connection: %s\n", strerror(errno));
207 return -1;
208 }
209 c->s = s;
210 c->rcvbuf = 0;
211 c->bufsz = 0;
212 c->next = clist;
213 clist = c;
214 ++num_clients;
215 return 0;
216 }
218 static void close_conn(struct client *c)
219 {
220 close(c->s);
221 c->s = -1; /* mark it for removal */
222 free(c->rcvbuf);
223 c->rcvbuf = 0;
224 }
226 static int handle_client(struct client *c)
227 {
228 struct http_req_header hdr;
229 static char buf[2048];
230 int rdsz, status;
232 while((rdsz = recv(c->s, buf, sizeof buf, 0)) > 0) {
233 char *newbuf;
234 int newsz = c->bufsz + rdsz;
235 if(newsz > MAX_REQ_LENGTH) {
236 respond_error(c, 413);
237 return -1;
238 }
240 if(!(newbuf = realloc(c->rcvbuf, newsz + 1))) {
241 logmsg("failed to allocate %d byte buffer\n", newsz);
242 respond_error(c, 503);
243 return -1;
244 }
246 memcpy(newbuf + c->bufsz, buf, rdsz);
247 newbuf[newsz] = 0;
249 c->rcvbuf = newbuf;
250 c->bufsz = newsz;
251 }
253 if((status = http_parse_request(&hdr, c->rcvbuf, c->bufsz)) != HTTP_HDR_OK) {
254 http_log_request(&hdr);
255 switch(status) {
256 case HTTP_HDR_INVALID:
257 respond_error(c, 400);
258 return -1;
260 case HTTP_HDR_NOMEM:
261 respond_error(c, 503);
262 return -1;
264 case HTTP_HDR_PARTIAL:
265 return 0; /* partial header, continue reading */
266 }
267 }
268 http_log_request(&hdr);
270 /* we only support GET and HEAD at this point, so freak out on anything else */
271 switch(hdr.method) {
272 case HTTP_GET:
273 if(do_get(c, hdr.uri, 1) == -1) {
274 return -1;
275 }
276 break;
278 case HTTP_HEAD:
279 if(do_get(c, hdr.uri, 0) == -1) {
280 return -1;
281 }
282 break;
284 default:
285 respond_error(c, 501);
286 return -1;
287 }
289 close_conn(c);
290 return 0;
291 }
293 static int do_get(struct client *c, const char *uri, int with_body)
294 {
295 const char *ptr;
296 struct http_resp_header resp;
298 if((ptr = strstr(uri, "://"))) {
299 uri = ptr + 3;
300 }
302 /* skip the host part and the first slash if it exists */
303 if((ptr = strchr(uri, '/'))) {
304 uri = ptr + 1;
305 }
307 if(*uri) {
308 struct stat st;
309 char *path = 0;
310 char *rsphdr;
311 const char *type;
312 int fd, rspsize;
314 if(stat(uri, &st) == -1) {
315 respond_error(c, 404);
316 return -1;
317 }
319 if(S_ISDIR(st.st_mode)) {
320 int i;
321 path = alloca(strlen(uri) + 64);
323 for(i=0; indexfiles[i]; i++) {
324 sprintf(path, "%s/%s", uri, indexfiles[i]);
325 if(stat(path, &st) == 0 && !S_ISDIR(st.st_mode)) {
326 break;
327 }
328 }
330 if(indexfiles[i] == 0) {
331 respond_error(c, 404);
332 return -1;
333 }
334 } else {
335 path = (char*)uri;
336 }
338 if((fd = open(path, O_RDONLY)) == -1) {
339 respond_error(c, 403);
340 return -1;
341 }
343 /* construct response header */
344 http_init_resp(&resp);
345 http_add_resp_field(&resp, "Content-Length: %d", st.st_size);
346 if((type = mime_type(path))) {
347 http_add_resp_field(&resp, "Content-Type: %s", type);
348 }
350 rspsize = http_serialize_resp(&resp, 0);
351 rsphdr = alloca(rspsize);
352 http_serialize_resp(&resp, rsphdr);
354 if(with_body) {
355 int cont_left = st.st_size;
356 char *cont = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
357 if(cont == (void*)-1) {
358 respond_error(c, 503);
359 close(fd);
360 return -1;
361 }
362 ptr = cont;
364 send(c->s, rsphdr, rspsize, 0);
365 while(cont_left > 0) {
366 int sz = cont_left < 4096 ? cont_left : 4096;
367 send(c->s, ptr, sz, 0);
368 ptr += sz;
369 cont_left -= sz;
370 }
372 munmap(cont, st.st_size);
373 } else {
374 send(c->s, rsphdr, rspsize, 0);
375 }
377 close(fd);
378 }
379 return 0;
380 }
382 static void respond_error(struct client *c, int errcode)
383 {
384 char buf[512];
386 sprintf(buf, "HTTP/" HTTP_VER_STR " %d %s\r\n\r\n", errcode, http_strmsg(errcode));
388 send(c->s, buf, strlen(buf), 0);
389 close_conn(c);
390 }