tinywebd

view libtinyweb/src/tinyweb.c @ 12:86f703031228

Attribution headers
author John Tsiombikas <nuclear@member.fsf.org>
date Sun, 19 Apr 2015 00:01:01 +0300
parents 0dd50a23f3dd
children 2873d3ec8c78
line source
1 /* tinyweb - tiny web server library and daemon
2 * Author: John Tsiombikas <nuclear@member.fsf.org>
3 *
4 * This program is placed in the public domain. Feel free to use it any
5 * way you like. Mentions and retaining this attribution header will be
6 * appreciated, but not required.
7 */
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <ctype.h>
12 #include <errno.h>
13 #include <unistd.h>
14 #include <fcntl.h>
15 #include <sys/stat.h>
16 #include <sys/select.h>
17 #include <sys/mman.h>
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 #include <arpa/inet.h>
21 #include "tinyweb.h"
22 #include "http.h"
23 #include "mime.h"
24 #include "logger.h"
26 /* HTTP version */
27 #define HTTP_VER_MAJOR 1
28 #define HTTP_VER_MINOR 1
29 #define HTTP_VER_STR "1.1"
31 /* maximum request length: 64mb */
32 #define MAX_REQ_LENGTH (65536 * 1024)
34 struct client {
35 int s;
36 char *rcvbuf;
37 int bufsz;
38 struct client *next;
39 };
41 static int accept_conn(int lis);
42 static void close_conn(struct client *c);
43 static int handle_client(struct client *c);
44 static int do_get(struct client *c, const char *uri, int with_body);
45 static void respond_error(struct client *c, int errcode);
47 static int lis = -1;
48 static int maxfd;
49 static int port = 8080;
50 static struct client *clist;
51 static int num_clients;
53 static const char *indexfiles[] = {
54 "index.cgi",
55 "index.html",
56 "index.htm",
57 0
58 };
60 void tw_set_port(int p)
61 {
62 port = p;
63 }
65 int tw_set_root(const char *path)
66 {
67 return chdir(path);
68 }
70 int tw_set_logfile(const char *fname)
71 {
72 return set_log_file(fname);
73 }
75 int tw_start(void)
76 {
77 int s;
78 struct sockaddr_in sa;
80 logmsg("starting server ...\n");
82 if(lis != -1) {
83 logmsg("can't start tinyweb server: already running!\n");
84 return -1;
85 }
87 if((s = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
88 logmsg("failed to create listening socket: %s\n", strerror(errno));
89 return -1;
90 }
91 fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK);
93 memset(&sa, 0, sizeof sa);
94 sa.sin_family = AF_INET;
95 sa.sin_addr.s_addr = INADDR_ANY;
96 sa.sin_port = htons(port);
98 if(bind(s, (struct sockaddr*)&sa, sizeof sa) == -1) {
99 logmsg("failed to bind socket to port %d: %s\n", port, strerror(errno));
100 return -1;
101 }
102 listen(s, 16);
104 lis = s;
105 return s;
106 }
108 int tw_stop(void)
109 {
110 if(lis == -1) {
111 return -1;
112 }
114 logmsg("stopping server...\n");
116 close(lis);
117 lis = -1;
119 while(clist) {
120 struct client *c = clist;
121 clist = clist->next;
122 close_conn(c);
123 free(c);
124 }
125 clist = 0;
127 return 0;
128 }
130 int tw_get_sockets(int *socks)
131 {
132 struct client *c, dummy;
134 /* first cleanup the clients marked for removal */
135 dummy.next = clist;
136 c = &dummy;
138 while(c->next) {
139 struct client *n = c->next;
141 if(n->s == -1) {
142 /* marked for removal */
143 c->next = n->next;
144 free(n);
145 --num_clients;
146 } else {
147 c = c->next;
148 }
149 }
150 clist = dummy.next;
153 if(!socks) {
154 /* just return the count */
155 return num_clients + 1; /* +1 for the listening socket */
156 }
158 /* go through the client list and populate the array */
159 maxfd = lis;
160 *socks++ = lis;
162 c = clist;
163 while(c) {
164 *socks++ = c->s;
165 if(c->s > maxfd) {
166 maxfd = c->s;
167 }
168 c = c->next;
169 }
170 return num_clients + 1; /* +1 for the listening socket */
171 }
173 int tw_get_maxfd(void)
174 {
175 return maxfd;
176 }
178 int tw_handle_socket(int s)
179 {
180 struct client *c;
182 if(s == lis) {
183 return accept_conn(s);
184 }
186 /* find which client corresponds to this socket */
187 c = clist;
188 while(c) {
189 if(c->s == s) {
190 return handle_client(c);
191 }
192 c = c->next;
193 }
195 logmsg("socket %d doesn't correspond to any client\n");
196 return -1;
197 }
199 static int accept_conn(int lis)
200 {
201 int s;
202 struct client *c;
203 struct sockaddr_in addr;
204 socklen_t addr_sz = sizeof addr;
206 if((s = accept(lis, (struct sockaddr*)&addr, &addr_sz)) == -1) {
207 logmsg("failed to accept incoming connection: %s\n", strerror(errno));
208 return -1;
209 }
210 fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK);
212 if(!(c = malloc(sizeof *c))) {
213 logmsg("failed to allocate memory while accepting connection: %s\n", strerror(errno));
214 return -1;
215 }
216 c->s = s;
217 c->rcvbuf = 0;
218 c->bufsz = 0;
219 c->next = clist;
220 clist = c;
221 ++num_clients;
222 return 0;
223 }
225 static void close_conn(struct client *c)
226 {
227 close(c->s);
228 c->s = -1; /* mark it for removal */
229 free(c->rcvbuf);
230 c->rcvbuf = 0;
231 }
233 static int handle_client(struct client *c)
234 {
235 struct http_req_header hdr;
236 static char buf[2048];
237 int rdsz, status;
239 while((rdsz = recv(c->s, buf, sizeof buf, 0)) > 0) {
240 char *newbuf;
241 int newsz = c->bufsz + rdsz;
242 if(newsz > MAX_REQ_LENGTH) {
243 respond_error(c, 413);
244 return -1;
245 }
247 if(!(newbuf = realloc(c->rcvbuf, newsz + 1))) {
248 logmsg("failed to allocate %d byte buffer\n", newsz);
249 respond_error(c, 503);
250 return -1;
251 }
253 memcpy(newbuf + c->bufsz, buf, rdsz);
254 newbuf[newsz] = 0;
256 c->rcvbuf = newbuf;
257 c->bufsz = newsz;
258 }
260 if((status = http_parse_request(&hdr, c->rcvbuf, c->bufsz)) != HTTP_HDR_OK) {
261 http_log_request(&hdr);
262 switch(status) {
263 case HTTP_HDR_INVALID:
264 respond_error(c, 400);
265 return -1;
267 case HTTP_HDR_NOMEM:
268 respond_error(c, 503);
269 return -1;
271 case HTTP_HDR_PARTIAL:
272 return 0; /* partial header, continue reading */
273 }
274 }
275 http_log_request(&hdr);
277 /* we only support GET and HEAD at this point, so freak out on anything else */
278 switch(hdr.method) {
279 case HTTP_GET:
280 if(do_get(c, hdr.uri, 1) == -1) {
281 return -1;
282 }
283 break;
285 case HTTP_HEAD:
286 if(do_get(c, hdr.uri, 0) == -1) {
287 return -1;
288 }
289 break;
291 default:
292 respond_error(c, 501);
293 return -1;
294 }
296 close_conn(c);
297 return 0;
298 }
300 static int do_get(struct client *c, const char *uri, int with_body)
301 {
302 const char *ptr;
303 struct http_resp_header resp;
305 if((ptr = strstr(uri, "://"))) {
306 uri = ptr + 3;
307 }
309 /* skip the host part and the first slash if it exists */
310 if((ptr = strchr(uri, '/'))) {
311 uri = ptr + 1;
312 }
314 if(*uri) {
315 struct stat st;
316 char *path = 0;
317 char *rsphdr;
318 const char *type;
319 int fd, rspsize;
321 if(stat(uri, &st) == -1) {
322 respond_error(c, 404);
323 return -1;
324 }
326 if(S_ISDIR(st.st_mode)) {
327 int i;
328 path = alloca(strlen(uri) + 64);
330 for(i=0; indexfiles[i]; i++) {
331 sprintf(path, "%s/%s", uri, indexfiles[i]);
332 if(stat(path, &st) == 0 && !S_ISDIR(st.st_mode)) {
333 break;
334 }
335 }
337 if(indexfiles[i] == 0) {
338 respond_error(c, 404);
339 return -1;
340 }
341 } else {
342 path = (char*)uri;
343 }
345 if((fd = open(path, O_RDONLY)) == -1) {
346 respond_error(c, 403);
347 return -1;
348 }
350 /* construct response header */
351 http_init_resp(&resp);
352 http_add_resp_field(&resp, "Content-Length: %d", st.st_size);
353 if((type = mime_type(path))) {
354 http_add_resp_field(&resp, "Content-Type: %s", type);
355 }
357 rspsize = http_serialize_resp(&resp, 0);
358 rsphdr = alloca(rspsize);
359 http_serialize_resp(&resp, rsphdr);
361 if(with_body) {
362 int cont_left = st.st_size;
363 char *cont = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
364 if(cont == (void*)-1) {
365 respond_error(c, 503);
366 close(fd);
367 return -1;
368 }
369 ptr = cont;
371 send(c->s, rsphdr, rspsize, 0);
372 while(cont_left > 0) {
373 int sz = cont_left < 4096 ? cont_left : 4096;
374 send(c->s, ptr, sz, 0);
375 ptr += sz;
376 cont_left -= sz;
377 }
379 munmap(cont, st.st_size);
380 } else {
381 send(c->s, rsphdr, rspsize, 0);
382 }
384 close(fd);
385 }
386 return 0;
387 }
389 static void respond_error(struct client *c, int errcode)
390 {
391 char buf[512];
393 sprintf(buf, "HTTP/" HTTP_VER_STR " %d %s\r\n\r\n", errcode, http_strmsg(errcode));
395 send(c->s, buf, strlen(buf), 0);
396 close_conn(c);
397 }