tinywebd

view libtinyweb/src/tinyweb.c @ 17:2874f61a43b1

implementing the directory index generation
author John Tsiombikas <nuclear@member.fsf.org>
date Tue, 21 Apr 2015 04:33:02 +0300
parents 86f703031228
children
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 <dirent.h>
16 #include <sys/stat.h>
17 #include <sys/select.h>
18 #include <sys/mman.h>
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 #include <arpa/inet.h>
22 #include "tinyweb.h"
23 #include "http.h"
24 #include "mime.h"
25 #include "logger.h"
27 /* HTTP version */
28 #define HTTP_VER_MAJOR 1
29 #define HTTP_VER_MINOR 1
30 #define HTTP_VER_STR "1.1"
32 /* maximum request length: 64mb */
33 #define MAX_REQ_LENGTH (65536 * 1024)
35 struct client {
36 int s;
37 char *rcvbuf;
38 int bufsz;
39 struct client *next;
40 };
42 static int accept_conn(int lis);
43 static void close_conn(struct client *c);
44 static int handle_client(struct client *c);
45 static int do_get(struct client *c, const char *uri, int with_body);
46 static int do_get_index(struct client *c, const char *path, int with_body);
47 static void respond_error(struct client *c, int errcode);
49 static int lis = -1;
50 static int maxfd;
51 static int port = 8080;
52 static struct client *clist;
53 static int num_clients;
55 static const char *indexfiles[] = {
56 "index.cgi",
57 "index.html",
58 "index.htm",
59 0
60 };
62 void tw_set_port(int p)
63 {
64 port = p;
65 }
67 int tw_set_root(const char *path)
68 {
69 return chdir(path);
70 }
72 int tw_set_logfile(const char *fname)
73 {
74 return set_log_file(fname);
75 }
77 int tw_start(void)
78 {
79 int s;
80 struct sockaddr_in sa;
82 logmsg("starting server ...\n");
84 if(lis != -1) {
85 logmsg("can't start tinyweb server: already running!\n");
86 return -1;
87 }
89 if((s = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
90 logmsg("failed to create listening socket: %s\n", strerror(errno));
91 return -1;
92 }
93 fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK);
95 memset(&sa, 0, sizeof sa);
96 sa.sin_family = AF_INET;
97 sa.sin_addr.s_addr = INADDR_ANY;
98 sa.sin_port = htons(port);
100 if(bind(s, (struct sockaddr*)&sa, sizeof sa) == -1) {
101 logmsg("failed to bind socket to port %d: %s\n", port, strerror(errno));
102 return -1;
103 }
104 listen(s, 16);
106 lis = s;
107 return s;
108 }
110 int tw_stop(void)
111 {
112 if(lis == -1) {
113 return -1;
114 }
116 logmsg("stopping server...\n");
118 close(lis);
119 lis = -1;
121 while(clist) {
122 struct client *c = clist;
123 clist = clist->next;
124 close_conn(c);
125 free(c);
126 }
127 clist = 0;
129 return 0;
130 }
132 int tw_get_sockets(int *socks)
133 {
134 struct client *c, dummy;
136 /* first cleanup the clients marked for removal */
137 dummy.next = clist;
138 c = &dummy;
140 while(c->next) {
141 struct client *n = c->next;
143 if(n->s == -1) {
144 /* marked for removal */
145 c->next = n->next;
146 free(n);
147 --num_clients;
148 } else {
149 c = c->next;
150 }
151 }
152 clist = dummy.next;
155 if(!socks) {
156 /* just return the count */
157 return num_clients + 1; /* +1 for the listening socket */
158 }
160 /* go through the client list and populate the array */
161 maxfd = lis;
162 *socks++ = lis;
164 c = clist;
165 while(c) {
166 *socks++ = c->s;
167 if(c->s > maxfd) {
168 maxfd = c->s;
169 }
170 c = c->next;
171 }
172 return num_clients + 1; /* +1 for the listening socket */
173 }
175 int tw_get_maxfd(void)
176 {
177 return maxfd;
178 }
180 int tw_handle_socket(int s)
181 {
182 struct client *c;
184 if(s == lis) {
185 return accept_conn(s);
186 }
188 /* find which client corresponds to this socket */
189 c = clist;
190 while(c) {
191 if(c->s == s) {
192 return handle_client(c);
193 }
194 c = c->next;
195 }
197 logmsg("socket %d doesn't correspond to any client\n");
198 return -1;
199 }
201 static int accept_conn(int lis)
202 {
203 int s;
204 struct client *c;
205 struct sockaddr_in addr;
206 socklen_t addr_sz = sizeof addr;
208 if((s = accept(lis, (struct sockaddr*)&addr, &addr_sz)) == -1) {
209 logmsg("failed to accept incoming connection: %s\n", strerror(errno));
210 return -1;
211 }
212 fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK);
214 if(!(c = malloc(sizeof *c))) {
215 logmsg("failed to allocate memory while accepting connection: %s\n", strerror(errno));
216 return -1;
217 }
218 c->s = s;
219 c->rcvbuf = 0;
220 c->bufsz = 0;
221 c->next = clist;
222 clist = c;
223 ++num_clients;
224 return 0;
225 }
227 static void close_conn(struct client *c)
228 {
229 close(c->s);
230 c->s = -1; /* mark it for removal */
231 free(c->rcvbuf);
232 c->rcvbuf = 0;
233 }
235 static int handle_client(struct client *c)
236 {
237 struct http_req_header hdr;
238 static char buf[2048];
239 int rdsz, status;
241 while((rdsz = recv(c->s, buf, sizeof buf, 0)) > 0) {
242 char *newbuf;
243 int newsz = c->bufsz + rdsz;
244 if(newsz > MAX_REQ_LENGTH) {
245 respond_error(c, 413);
246 return -1;
247 }
249 if(!(newbuf = realloc(c->rcvbuf, newsz + 1))) {
250 logmsg("failed to allocate %d byte buffer\n", newsz);
251 respond_error(c, 503);
252 return -1;
253 }
255 memcpy(newbuf + c->bufsz, buf, rdsz);
256 newbuf[newsz] = 0;
258 c->rcvbuf = newbuf;
259 c->bufsz = newsz;
260 }
262 if((status = http_parse_request(&hdr, c->rcvbuf, c->bufsz)) != HTTP_HDR_OK) {
263 http_log_request(&hdr);
264 switch(status) {
265 case HTTP_HDR_INVALID:
266 respond_error(c, 400);
267 return -1;
269 case HTTP_HDR_NOMEM:
270 respond_error(c, 503);
271 return -1;
273 case HTTP_HDR_PARTIAL:
274 return 0; /* partial header, continue reading */
275 }
276 }
277 http_log_request(&hdr);
279 /* we only support GET and HEAD at this point, so freak out on anything else */
280 switch(hdr.method) {
281 case HTTP_GET:
282 if(do_get(c, hdr.uri, 1) == -1) {
283 return -1;
284 }
285 break;
287 case HTTP_HEAD:
288 if(do_get(c, hdr.uri, 0) == -1) {
289 return -1;
290 }
291 break;
293 default:
294 respond_error(c, 501);
295 return -1;
296 }
298 close_conn(c);
299 return 0;
300 }
302 static int do_get(struct client *c, const char *uri, int with_body)
303 {
304 const char *ptr;
305 struct http_resp_header resp;
307 if((ptr = strstr(uri, "://"))) {
308 uri = ptr + 3;
309 }
311 /* skip the host part and the first slash if it exists */
312 if((ptr = strchr(uri, '/'))) {
313 uri = ptr + 1;
314 }
316 if(*uri) {
317 struct stat st;
318 char *path = 0;
319 char *rsphdr;
320 const char *type;
321 int fd, rspsize;
323 if(stat(uri, &st) == -1) {
324 respond_error(c, 404);
325 return -1;
326 }
328 if(S_ISDIR(st.st_mode)) {
329 int i;
330 path = alloca(strlen(uri) + 64);
332 for(i=0; indexfiles[i]; i++) {
333 sprintf(path, "%s/%s", uri, indexfiles[i]);
334 if(stat(path, &st) == 0 && !S_ISDIR(st.st_mode)) {
335 /* found an index file */
336 break;
337 }
338 }
340 if(indexfiles[i] == 0) {
341 return do_get_index(c, uri, with_body);
342 }
343 } else {
344 path = (char*)uri;
345 }
347 if((fd = open(path, O_RDONLY)) == -1) {
348 respond_error(c, 403);
349 return -1;
350 }
352 /* construct response header */
353 http_init_resp(&resp);
354 http_add_resp_field(&resp, "Content-Length: %d", st.st_size);
355 if((type = mime_type(path))) {
356 http_add_resp_field(&resp, "Content-Type: %s", type);
357 }
359 rspsize = http_serialize_resp(&resp, 0);
360 rsphdr = alloca(rspsize);
361 http_serialize_resp(&resp, rsphdr);
363 if(with_body) {
364 int cont_left = st.st_size;
365 char *cont = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
366 if(cont == (void*)-1) {
367 respond_error(c, 503);
368 close(fd);
369 return -1;
370 }
371 ptr = cont;
373 logmsg("GET %s OK! (%s)\n", uri, path);
375 send(c->s, rsphdr, rspsize, 0);
376 while(cont_left > 0) {
377 int sz = cont_left < 4096 ? cont_left : 4096;
378 send(c->s, ptr, sz, 0);
379 ptr += sz;
380 cont_left -= sz;
381 }
383 munmap(cont, st.st_size);
384 } else {
385 logmsg("HEAD %s OK! (%s)\n", uri, path);
386 send(c->s, rsphdr, rspsize, 0);
387 }
389 close(fd);
390 }
391 return 0;
392 }
394 static void filesizestr(char *str, unsigned long sz)
395 {
396 int uidx = 0;
397 static const char *unit[] = {"bytes", "kb", "mb", "gb", "tb", 0};
399 while(sz > 1024 && unit[uidx + 1]) {
400 sz /= 1024;
401 ++uidx;
402 }
403 sprintf(str, "%lu %s", sz, unit[uidx]);
404 }
406 struct dir_entry {
407 char *name;
408 unsigned long size;
409 int is_dir;
410 struct dir_entry *next;
411 };
413 /* precondition: path is a directory */
414 static int do_get_index(struct client *c, const char *path, int with_body)
415 {
416 }
418 static const char *error_page_fmt =
419 "<html>"
420 "<head><title>Error %d</title></head>\n"
421 "<body><h1>HTTP Error %d</h1><p>%s</p></body>"
422 "</html>";
424 static void respond_error(struct client *c, int errcode)
425 {
426 char buf[512];
428 sprintf(buf, "HTTP/" HTTP_VER_STR " %d %s\r\n\r\n", errcode, http_strmsg(errcode));
429 logmsg("error %d: %s\n", errcode, http_strmsg(errcode));
431 send(c->s, buf, strlen(buf), 0);
433 sprintf(buf, error_page_fmt, errcode, errcode, http_strmsg(errcode));
434 send(c->s, buf, strlen(buf), 0);
435 close_conn(c);
436 }