tinywebd

annotate libtinyweb/src/tinyweb.c @ 16:2873d3ec8c78

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