tinywebd

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