tinywebd

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