1 /* 2 A trivial static http webserver using Libevent's evhttp. 3 4 This is not the best code in the world, and it does some fairly stupid stuff 5 that you would never want to do in a production webserver. Caveat hackor! 6 7 */ 8 9 /* Compatibility for possible missing IPv6 declarations */ 10 #include "../util-internal.h" 11 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <string.h> 15 16 #include <sys/types.h> 17 #include <sys/stat.h> 18 19 #ifdef _WIN32 20 #include <winsock2.h> 21 #include <ws2tcpip.h> 22 #include <windows.h> 23 #include <getopt.h> 24 #include <io.h> 25 #include <fcntl.h> 26 #ifndef S_ISDIR 27 #define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR) 28 #endif 29 #else 30 #include <sys/stat.h> 31 #include <sys/socket.h> 32 #include <fcntl.h> 33 #include <unistd.h> 34 #include <dirent.h> 35 #endif 36 #include <signal.h> 37 38 #include <event2/event.h> 39 #include <event2/http.h> 40 #include <event2/buffer.h> 41 #include <event2/util.h> 42 #include <event2/keyvalq_struct.h> 43 44 #ifdef _WIN32 45 #include <event2/thread.h> 46 #endif 47 48 #ifdef EVENT__HAVE_NETINET_IN_H 49 #include <netinet/in.h> 50 # ifdef _XOPEN_SOURCE_EXTENDED 51 # include <arpa/inet.h> 52 # endif 53 #endif 54 55 #ifdef _WIN32 56 #ifndef stat 57 #define stat _stat 58 #endif 59 #ifndef fstat 60 #define fstat _fstat 61 #endif 62 #ifndef open 63 #define open _open 64 #endif 65 #ifndef close 66 #define close _close 67 #endif 68 #ifndef O_RDONLY 69 #define O_RDONLY _O_RDONLY 70 #endif 71 #endif 72 73 char uri_root[512]; 74 75 static const struct table_entry { 76 const char *extension; 77 const char *content_type; 78 } content_type_table[] = { 79 { "txt", "text/plain" }, 80 { "c", "text/plain" }, 81 { "h", "text/plain" }, 82 { "html", "text/html" }, 83 { "htm", "text/htm" }, 84 { "css", "text/css" }, 85 { "gif", "image/gif" }, 86 { "jpg", "image/jpeg" }, 87 { "jpeg", "image/jpeg" }, 88 { "png", "image/png" }, 89 { "pdf", "application/pdf" }, 90 { "ps", "application/postscript" }, 91 { NULL, NULL }, 92 }; 93 94 struct options 95 { 96 int port; 97 int iocp; 98 int verbose; 99 }; 100 101 /* Try to guess a good content-type for 'path' */ 102 static const char * 103 guess_content_type(const char *path) 104 { 105 const char *last_period, *extension; 106 const struct table_entry *ent; 107 last_period = strrchr(path, '.'); 108 if (!last_period || strchr(last_period, '/')) 109 goto not_found; /* no exension */ 110 extension = last_period + 1; 111 for (ent = &content_type_table[0]; ent->extension; ++ent) { 112 if (!evutil_ascii_strcasecmp(ent->extension, extension)) 113 return ent->content_type; 114 } 115 116 not_found: 117 return "application/misc"; 118 } 119 120 /* Callback used for the /dump URI, and for every non-GET request: 121 * dumps all information to stdout and gives back a trivial 200 ok */ 122 static void 123 dump_request_cb(struct evhttp_request *req, void *arg) 124 { 125 const char *cmdtype; 126 struct evkeyvalq *headers; 127 struct evkeyval *header; 128 struct evbuffer *buf; 129 130 switch (evhttp_request_get_command(req)) { 131 case EVHTTP_REQ_GET: cmdtype = "GET"; break; 132 case EVHTTP_REQ_POST: cmdtype = "POST"; break; 133 case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break; 134 case EVHTTP_REQ_PUT: cmdtype = "PUT"; break; 135 case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break; 136 case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break; 137 case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break; 138 case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break; 139 case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break; 140 default: cmdtype = "unknown"; break; 141 } 142 143 printf("Received a %s request for %s\nHeaders:\n", 144 cmdtype, evhttp_request_get_uri(req)); 145 146 headers = evhttp_request_get_input_headers(req); 147 for (header = headers->tqh_first; header; 148 header = header->next.tqe_next) { 149 printf(" %s: %s\n", header->key, header->value); 150 } 151 152 buf = evhttp_request_get_input_buffer(req); 153 puts("Input data: <<<"); 154 while (evbuffer_get_length(buf)) { 155 int n; 156 char cbuf[128]; 157 n = evbuffer_remove(buf, cbuf, sizeof(cbuf)); 158 if (n > 0) 159 (void) fwrite(cbuf, 1, n, stdout); 160 } 161 puts(">>>"); 162 163 evhttp_send_reply(req, 200, "OK", NULL); 164 } 165 166 /* This callback gets invoked when we get any http request that doesn't match 167 * any other callback. Like any evhttp server callback, it has a simple job: 168 * it must eventually call evhttp_send_error() or evhttp_send_reply(). 169 */ 170 static void 171 send_document_cb(struct evhttp_request *req, void *arg) 172 { 173 struct evbuffer *evb = NULL; 174 const char *docroot = arg; 175 const char *uri = evhttp_request_get_uri(req); 176 struct evhttp_uri *decoded = NULL; 177 const char *path; 178 char *decoded_path; 179 char *whole_path = NULL; 180 size_t len; 181 int fd = -1; 182 struct stat st; 183 184 if (evhttp_request_get_command(req) != EVHTTP_REQ_GET) { 185 dump_request_cb(req, arg); 186 return; 187 } 188 189 printf("Got a GET request for <%s>\n", uri); 190 191 /* Decode the URI */ 192 decoded = evhttp_uri_parse(uri); 193 if (!decoded) { 194 printf("It's not a good URI. Sending BADREQUEST\n"); 195 evhttp_send_error(req, HTTP_BADREQUEST, 0); 196 return; 197 } 198 199 /* Let's see what path the user asked for. */ 200 path = evhttp_uri_get_path(decoded); 201 if (!path) path = "/"; 202 203 /* We need to decode it, to see what path the user really wanted. */ 204 decoded_path = evhttp_uridecode(path, 0, NULL); 205 if (decoded_path == NULL) 206 goto err; 207 /* Don't allow any ".."s in the path, to avoid exposing stuff outside 208 * of the docroot. This test is both overzealous and underzealous: 209 * it forbids aceptable paths like "/this/one..here", but it doesn't 210 * do anything to prevent symlink following." */ 211 if (strstr(decoded_path, "..")) 212 goto err; 213 214 len = strlen(decoded_path)+strlen(docroot)+2; 215 if (!(whole_path = malloc(len))) { 216 perror("malloc"); 217 goto err; 218 } 219 evutil_snprintf(whole_path, len, "%s/%s", docroot, decoded_path); 220 221 if (stat(whole_path, &st)<0) { 222 goto err; 223 } 224 225 /* This holds the content we're sending. */ 226 evb = evbuffer_new(); 227 228 if (S_ISDIR(st.st_mode)) { 229 /* If it's a directory, read the comments and make a little 230 * index page */ 231 #ifdef _WIN32 232 HANDLE d; 233 WIN32_FIND_DATAA ent; 234 char *pattern; 235 size_t dirlen; 236 #else 237 DIR *d; 238 struct dirent *ent; 239 #endif 240 const char *trailing_slash = ""; 241 242 if (!strlen(path) || path[strlen(path)-1] != '/') 243 trailing_slash = "/"; 244 245 #ifdef _WIN32 246 dirlen = strlen(whole_path); 247 pattern = malloc(dirlen+3); 248 memcpy(pattern, whole_path, dirlen); 249 pattern[dirlen] = '\\'; 250 pattern[dirlen+1] = '*'; 251 pattern[dirlen+2] = '\0'; 252 d = FindFirstFileA(pattern, &ent); 253 free(pattern); 254 if (d == INVALID_HANDLE_VALUE) 255 goto err; 256 #else 257 if (!(d = opendir(whole_path))) 258 goto err; 259 #endif 260 261 evbuffer_add_printf(evb, 262 "<!DOCTYPE html>\n" 263 "<html>\n <head>\n" 264 " <meta charset='utf-8'>\n" 265 " <title>%s</title>\n" 266 " <base href='%s%s'>\n" 267 " </head>\n" 268 " <body>\n" 269 " <h1>%s</h1>\n" 270 " <ul>\n", 271 decoded_path, /* XXX html-escape this. */ 272 path, /* XXX html-escape this? */ 273 trailing_slash, 274 decoded_path /* XXX html-escape this */); 275 #ifdef _WIN32 276 do { 277 const char *name = ent.cFileName; 278 #else 279 while ((ent = readdir(d))) { 280 const char *name = ent->d_name; 281 #endif 282 evbuffer_add_printf(evb, 283 " <li><a href=\"%s\">%s</a>\n", 284 name, name);/* XXX escape this */ 285 #ifdef _WIN32 286 } while (FindNextFileA(d, &ent)); 287 #else 288 } 289 #endif 290 evbuffer_add_printf(evb, "</ul></body></html>\n"); 291 #ifdef _WIN32 292 FindClose(d); 293 #else 294 closedir(d); 295 #endif 296 evhttp_add_header(evhttp_request_get_output_headers(req), 297 "Content-Type", "text/html"); 298 } else { 299 /* Otherwise it's a file; add it to the buffer to get 300 * sent via sendfile */ 301 const char *type = guess_content_type(decoded_path); 302 if ((fd = open(whole_path, O_RDONLY)) < 0) { 303 perror("open"); 304 goto err; 305 } 306 307 if (fstat(fd, &st)<0) { 308 /* Make sure the length still matches, now that we 309 * opened the file :/ */ 310 perror("fstat"); 311 goto err; 312 } 313 evhttp_add_header(evhttp_request_get_output_headers(req), 314 "Content-Type", type); 315 evbuffer_add_file(evb, fd, 0, st.st_size); 316 } 317 318 evhttp_send_reply(req, 200, "OK", evb); 319 goto done; 320 err: 321 evhttp_send_error(req, 404, "Document was not found"); 322 if (fd>=0) 323 close(fd); 324 done: 325 if (decoded) 326 evhttp_uri_free(decoded); 327 if (decoded_path) 328 free(decoded_path); 329 if (whole_path) 330 free(whole_path); 331 if (evb) 332 evbuffer_free(evb); 333 } 334 335 static struct options 336 parse_opts(int argc, char **argv) 337 { 338 struct options o; 339 int opt; 340 341 memset(&o, 0, sizeof(o)); 342 343 while ((opt = getopt(argc, argv, "p:Iv")) != -1) { 344 switch (opt) { 345 case 'p': o.port = atoi(optarg); break; 346 case 'I': o.iocp = 1; break; 347 case 'v': ++o.verbose; break; 348 default : fprintf(stderr, "Unknown option %c\n", opt); break; 349 } 350 } 351 352 if (optind >= argc || (argc-optind) > 1) { 353 fprintf(stdout, "Syntax: %s <docroot>\n", argv[0]); 354 exit(1); 355 } 356 357 return o; 358 } 359 360 static void 361 do_term(int sig, short events, void *arg) 362 { 363 struct event_base *base = arg; 364 event_base_loopbreak(base); 365 fprintf(stderr, "Got %i, Terminating\n", sig); 366 } 367 368 int 369 main(int argc, char **argv) 370 { 371 struct event_config *cfg = NULL; 372 struct event_base *base = NULL; 373 struct evhttp *http = NULL; 374 struct evhttp_bound_socket *handle = NULL; 375 struct event *term = NULL; 376 struct options o = parse_opts(argc, argv); 377 int ret = 0; 378 379 #ifdef _WIN32 380 { 381 WORD wVersionRequested; 382 WSADATA wsaData; 383 wVersionRequested = MAKEWORD(2, 2); 384 WSAStartup(wVersionRequested, &wsaData); 385 } 386 #else 387 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { 388 ret = 1; 389 goto err; 390 } 391 #endif 392 393 setbuf(stdout, NULL); 394 setbuf(stderr, NULL); 395 396 /** Read env like in regress" */ 397 if (o.verbose || getenv("EVENT_DEBUG_LOGGING_ALL")) 398 event_enable_debug_logging(EVENT_DBG_ALL); 399 400 cfg = event_config_new(); 401 #ifdef _WIN32 402 if (o.iocp) { 403 evthread_use_windows_threads(); 404 event_config_set_flag(cfg, EVENT_BASE_FLAG_STARTUP_IOCP); 405 event_config_set_num_cpus_hint(cfg, 8); 406 } 407 #endif 408 409 base = event_base_new_with_config(cfg); 410 if (!base) { 411 fprintf(stderr, "Couldn't create an event_base: exiting\n"); 412 ret = 1; 413 } 414 event_config_free(cfg); 415 cfg = NULL; 416 417 /* Create a new evhttp object to handle requests. */ 418 http = evhttp_new(base); 419 if (!http) { 420 fprintf(stderr, "couldn't create evhttp. Exiting.\n"); 421 ret = 1; 422 } 423 424 /* The /dump URI will dump all requests to stdout and say 200 ok. */ 425 evhttp_set_cb(http, "/dump", dump_request_cb, NULL); 426 427 /* We want to accept arbitrary requests, so we need to set a "generic" 428 * cb. We can also add callbacks for specific paths. */ 429 evhttp_set_gencb(http, send_document_cb, argv[1]); 430 431 /* Now we tell the evhttp what port to listen on */ 432 handle = evhttp_bind_socket_with_handle(http, "0.0.0.0", o.port); 433 if (!handle) { 434 fprintf(stderr, "couldn't bind to port %d. Exiting.\n", o.port); 435 ret = 1; 436 } 437 438 { 439 /* Extract and display the address we're listening on. */ 440 struct sockaddr_storage ss; 441 evutil_socket_t fd; 442 ev_socklen_t socklen = sizeof(ss); 443 char addrbuf[128]; 444 void *inaddr; 445 const char *addr; 446 int got_port = -1; 447 fd = evhttp_bound_socket_get_fd(handle); 448 memset(&ss, 0, sizeof(ss)); 449 if (getsockname(fd, (struct sockaddr *)&ss, &socklen)) { 450 perror("getsockname() failed"); 451 ret = 1; 452 } 453 if (ss.ss_family == AF_INET) { 454 got_port = ntohs(((struct sockaddr_in*)&ss)->sin_port); 455 inaddr = &((struct sockaddr_in*)&ss)->sin_addr; 456 } else if (ss.ss_family == AF_INET6) { 457 got_port = ntohs(((struct sockaddr_in6*)&ss)->sin6_port); 458 inaddr = &((struct sockaddr_in6*)&ss)->sin6_addr; 459 } else { 460 fprintf(stderr, "Weird address family %d\n", 461 ss.ss_family); 462 ret = 1; 463 } 464 addr = evutil_inet_ntop(ss.ss_family, inaddr, addrbuf, 465 sizeof(addrbuf)); 466 if (addr) { 467 printf("Listening on %s:%d\n", addr, got_port); 468 evutil_snprintf(uri_root, sizeof(uri_root), 469 "http://%s:%d",addr,got_port); 470 } else { 471 fprintf(stderr, "evutil_inet_ntop failed\n"); 472 ret = 1; 473 } 474 } 475 476 term = evsignal_new(base, SIGINT, do_term, base); 477 if (!term) 478 goto err; 479 if (event_add(term, NULL)) 480 goto err; 481 482 event_base_dispatch(base); 483 484 #ifdef _WIN32 485 WSACleanup(); 486 #endif 487 488 err: 489 if (cfg) 490 event_config_free(cfg); 491 if (http) 492 evhttp_free(http); 493 if (term) 494 event_free(term); 495 if (base) 496 event_base_free(base); 497 498 return ret; 499 } 500