Imported Upstream version 1.0.0
[platform/upstream/nghttp2.git] / examples / tiny-nghttpd.c
1 /*
2  * nghttp2 - HTTP/2 C Library
3  *
4  * Copyright (c) 2014 Tatsuhiro Tsujikawa
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25 /*
26  * This program is intended to measure library performance, avoiding
27  * overhead of underlying I/O library (e.g., libevent, Boost ASIO).
28  */
29 #define _GNU_SOURCE
30
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif /* HAVE_CONFIG_H */
34
35 #include <sys/types.h>
36 #ifdef HAVE_SYS_SOCKET_H
37 #include <sys/socket.h>
38 #endif /* HAVE_SYS_SOCKET_H */
39 #include <sys/stat.h>
40 #ifdef HAVE_FCNTL_H
41 #include <fcntl.h>
42 #endif /* HAVE_FCNTL_H */
43 #ifdef HAVE_NETDB_H
44 #include <netdb.h>
45 #endif /* HAVE_NETDB_H */
46 #ifdef HAVE_NETINET_IN_H
47 #include <netinet/in.h>
48 #endif /* HAVE_NETINET_IN_H */
49 #include <netinet/tcp.h>
50 #ifdef HAVE_UNISTD_H
51 #include <unistd.h>
52 #endif /* HAVE_UNISTD_H */
53 #include <stdlib.h>
54 #ifdef HAVE_TIME_H
55 #include <time.h>
56 #endif /* HAVE_TIME_H */
57 #include <string.h>
58 #include <stdio.h>
59 #include <errno.h>
60 #include <assert.h>
61 #include <signal.h>
62 #include <sys/epoll.h>
63 #include <sys/timerfd.h>
64
65 #include <nghttp2/nghttp2.h>
66
67 #define SERVER_NAME "tiny-nghttpd nghttp2/" NGHTTP2_VERSION
68
69 #define MAKE_NV(name, value)                                                   \
70   {                                                                            \
71     (uint8_t *)(name), (uint8_t *)(value), sizeof((name)) - 1,                 \
72         sizeof((value)) - 1, NGHTTP2_NV_FLAG_NONE                              \
73   }
74
75 #define MAKE_NV2(name, value, valuelen)                                        \
76   {                                                                            \
77     (uint8_t *)(name), (uint8_t *)(value), sizeof((name)) - 1, (valuelen),     \
78         NGHTTP2_NV_FLAG_NONE                                                   \
79   }
80
81 #define array_size(a) (sizeof((a)) / sizeof((a)[0]))
82
83 /* Returns the length of remaning data in buffer */
84 #define io_buf_len(iobuf) ((iobuf)->last - (iobuf)->pos)
85 /* Returns the space buffer can still accept */
86 #define io_buf_left(iobuf) ((iobuf)->end - (iobuf)->last)
87
88 typedef struct {
89   /* beginning of buffer */
90   uint8_t *begin;
91   /* one byte beyond the end of buffer */
92   uint8_t *end;
93   /* next read/write position of buffer */
94   uint8_t *pos;
95   /* one byte beyond last data of buffer */
96   uint8_t *last;
97 } io_buf;
98
99 typedef struct {
100   /* epoll fd */
101   int epfd;
102 } io_loop;
103
104 typedef struct stream {
105   struct stream *prev, *next;
106   /* mandatory header fields */
107   char *method;
108   char *scheme;
109   char *authority;
110   char *path;
111   char *host;
112   /* region of response body in rawscrbuf */
113   uint8_t *res_begin, *res_end;
114   /* io_buf wrapping rawscrbuf */
115   io_buf scrbuf;
116   int64_t fileleft;
117   /* length of mandatory header fields */
118   size_t methodlen;
119   size_t schemelen;
120   size_t authoritylen;
121   size_t pathlen;
122   size_t hostlen;
123   /* stream ID of this stream */
124   int32_t stream_id;
125   /* fd for reading file */
126   int filefd;
127   /* scratch buffer for this stream */
128   uint8_t rawscrbuf[4096];
129 } stream;
130
131 typedef struct { int (*handler)(io_loop *, uint32_t, void *); } evhandle;
132
133 typedef struct {
134   evhandle evhn;
135   nghttp2_session *session;
136   /* list of stream */
137   stream strm_head;
138   /* pending library output */
139   const uint8_t *cache;
140   /* io_buf wrapping rawoutbuf */
141   io_buf buf;
142   /* length of cache */
143   size_t cachelen;
144   /* client fd */
145   int fd;
146   /* output buffer */
147   uint8_t rawoutbuf[65536];
148 } connection;
149
150 typedef struct {
151   evhandle evhn;
152   /* listening fd */
153   int fd;
154 } server;
155
156 typedef struct {
157   evhandle evhn;
158   /* timerfd */
159   int fd;
160 } timer;
161
162 /* document root */
163 const char *docroot;
164 /* length of docroot */
165 size_t docrootlen;
166
167 nghttp2_session_callbacks *shared_callbacks;
168
169 static int handle_accept(io_loop *loop, uint32_t events, void *ptr);
170 static int handle_connection(io_loop *loop, uint32_t events, void *ptr);
171 static int handle_timer(io_loop *loop, uint32_t events, void *ptr);
172
173 static void io_buf_init(io_buf *buf, uint8_t *underlying, size_t len) {
174   buf->begin = buf->pos = buf->last = underlying;
175   buf->end = underlying + len;
176 }
177
178 static void io_buf_add(io_buf *buf, const void *src, size_t len) {
179   memcpy(buf->last, src, len);
180   buf->last += len;
181 }
182
183 static char *io_buf_add_str(io_buf *buf, const void *src, size_t len) {
184   uint8_t *start = buf->last;
185
186   memcpy(buf->last, src, len);
187   buf->last += len;
188   *buf->last++ = '\0';
189
190   return (char *)start;
191 }
192
193 static int memeq(const void *a, const void *b, size_t n) {
194   return memcmp(a, b, n) == 0;
195 }
196
197 #define streq(A, B, N) ((sizeof((A)) - 1) == (N) && memeq((A), (B), (N)))
198
199 typedef enum {
200   NGHTTP2_TOKEN__AUTHORITY,
201   NGHTTP2_TOKEN__METHOD,
202   NGHTTP2_TOKEN__PATH,
203   NGHTTP2_TOKEN__SCHEME,
204   NGHTTP2_TOKEN_HOST
205 } nghttp2_token;
206
207 /* Inspired by h2o header lookup.  https://github.com/h2o/h2o */
208 static int lookup_token(const uint8_t *name, size_t namelen) {
209   switch (namelen) {
210   case 5:
211     switch (name[namelen - 1]) {
212     case 'h':
213       if (streq(":pat", name, 4)) {
214         return NGHTTP2_TOKEN__PATH;
215       }
216       break;
217     }
218     break;
219   case 7:
220     switch (name[namelen - 1]) {
221     case 'd':
222       if (streq(":metho", name, 6)) {
223         return NGHTTP2_TOKEN__METHOD;
224       }
225       break;
226     case 'e':
227       if (streq(":schem", name, 6)) {
228         return NGHTTP2_TOKEN__SCHEME;
229       }
230       break;
231     }
232     break;
233   case 10:
234     switch (name[namelen - 1]) {
235     case 'y':
236       if (streq(":authorit", name, 9)) {
237         return NGHTTP2_TOKEN__AUTHORITY;
238       }
239       break;
240     }
241     break;
242   }
243   return -1;
244 }
245
246 static char *cpydig(char *buf, int n, size_t len) {
247   char *p;
248
249   p = buf + len - 1;
250   do {
251     *p-- = (n % 10) + '0';
252     n /= 10;
253   } while (p >= buf);
254
255   return buf + len;
256 }
257
258 static const char *MONTH[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
259                               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
260 static const char *DAY_OF_WEEK[] = {"Sun", "Mon", "Tue", "Wed",
261                                     "Thu", "Fri", "Sat"};
262
263 static size_t http_date(char *buf, time_t t) {
264   struct tm tms;
265   char *p = buf;
266
267   if (gmtime_r(&t, &tms) == NULL) {
268     return 0;
269   }
270
271   /* Sat, 27 Sep 2014 06:31:15 GMT */
272
273   memcpy(p, DAY_OF_WEEK[tms.tm_wday], 3);
274   p += 3;
275   *p++ = ',';
276   *p++ = ' ';
277   p = cpydig(p, tms.tm_mday, 2);
278   *p++ = ' ';
279   memcpy(p, MONTH[tms.tm_mon], 3);
280   p += 3;
281   *p++ = ' ';
282   p = cpydig(p, tms.tm_year + 1900, 4);
283   *p++ = ' ';
284   p = cpydig(p, tms.tm_hour, 2);
285   *p++ = ':';
286   p = cpydig(p, tms.tm_min, 2);
287   *p++ = ':';
288   p = cpydig(p, tms.tm_sec, 2);
289   memcpy(p, " GMT", 4);
290   p += 4;
291
292   return p - buf;
293 }
294
295 static char date[29];
296 static char datelen;
297
298 static void update_date() { datelen = http_date(date, time(NULL)); }
299
300 static size_t utos(char *buf, size_t len, uint64_t n) {
301   size_t nwrite = 0;
302   uint64_t t = n;
303
304   if (len == 0) {
305     return 0;
306   }
307
308   if (n == 0) {
309     buf[0] = '0';
310     return 1;
311   }
312
313   for (; t; t /= 10, ++nwrite)
314     ;
315
316   if (nwrite > len) {
317     return 0;
318   }
319
320   buf += nwrite - 1;
321   do {
322     *buf-- = (n % 10) + '0';
323     n /= 10;
324   } while (n);
325
326   return nwrite;
327 }
328
329 static void print_errno(const char *prefix, int errnum) {
330   char buf[1024];
331   char *errmsg;
332
333   errmsg = strerror_r(errnum, buf, sizeof(buf));
334
335   fprintf(stderr, "%s: %s\n", prefix, errmsg);
336 }
337
338 #define list_insert(head, elem)                                                \
339   do {                                                                         \
340     (elem)->prev = (head);                                                     \
341     (elem)->next = (head)->next;                                               \
342                                                                                \
343     if ((head)->next) {                                                        \
344       (head)->next->prev = (elem);                                             \
345     }                                                                          \
346     (head)->next = (elem);                                                     \
347   } while (0)
348
349 #define list_remove(elem)                                                      \
350   do {                                                                         \
351     (elem)->prev->next = (elem)->next;                                         \
352     if ((elem)->next) {                                                        \
353       (elem)->next->prev = (elem)->prev;                                       \
354     }                                                                          \
355   } while (0)
356
357 static stream *stream_new(int32_t stream_id, connection *conn) {
358   stream *strm;
359
360   strm = malloc(sizeof(stream));
361
362   strm->prev = strm->next = NULL;
363   strm->method = NULL;
364   strm->scheme = NULL;
365   strm->authority = NULL;
366   strm->path = NULL;
367   strm->host = NULL;
368   strm->res_begin = NULL;
369   strm->res_end = NULL;
370   strm->methodlen = 0;
371   strm->schemelen = 0;
372   strm->authoritylen = 0;
373   strm->pathlen = 0;
374   strm->hostlen = 0;
375   strm->stream_id = stream_id;
376   strm->filefd = -1;
377   strm->fileleft = 0;
378
379   list_insert(&conn->strm_head, strm);
380
381   io_buf_init(&strm->scrbuf, strm->rawscrbuf, sizeof(strm->rawscrbuf));
382
383   return strm;
384 }
385
386 static void stream_del(stream *strm) {
387   list_remove(strm);
388
389   if (strm->filefd != -1) {
390     close(strm->filefd);
391   }
392
393   free(strm);
394 }
395
396 static connection *connection_new(int fd) {
397   connection *conn;
398   int rv;
399
400   conn = malloc(sizeof(connection));
401
402   rv = nghttp2_session_server_new(&conn->session, shared_callbacks, conn);
403
404   if (rv != 0) {
405     goto cleanup;
406   }
407
408   conn->fd = fd;
409   conn->cache = NULL;
410   conn->cachelen = 0;
411   io_buf_init(&conn->buf, conn->rawoutbuf, sizeof(conn->rawoutbuf));
412   conn->evhn.handler = handle_connection;
413   conn->strm_head.next = NULL;
414
415   return conn;
416
417 cleanup:
418   free(conn);
419   return NULL;
420 }
421
422 static void connection_del(connection *conn) {
423   stream *strm;
424
425   nghttp2_session_del(conn->session);
426   shutdown(conn->fd, SHUT_WR);
427   close(conn->fd);
428
429   strm = conn->strm_head.next;
430   while (strm) {
431     stream *next_strm = strm->next;
432
433     stream_del(strm);
434     strm = next_strm;
435   }
436
437   free(conn);
438 }
439
440 static int connection_start(connection *conn) {
441   nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100};
442   int rv;
443
444   rv = nghttp2_submit_settings(conn->session, NGHTTP2_FLAG_NONE, &iv, 1);
445
446   if (rv != 0) {
447     return -1;
448   }
449
450   return 0;
451 }
452
453 static int server_init(server *serv, const char *node, const char *service) {
454   int rv;
455   struct addrinfo hints;
456   struct addrinfo *res, *rp;
457   int fd;
458   int on = 1;
459   socklen_t optlen = sizeof(on);
460
461   memset(&hints, 0, sizeof(hints));
462   hints.ai_family = AF_UNSPEC;
463   hints.ai_socktype = SOCK_STREAM;
464   hints.ai_protocol = 0;
465   hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
466
467   rv = getaddrinfo(node, service, &hints, &res);
468
469   if (rv != 0) {
470     fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
471     return -1;
472   }
473
474   for (rp = res; rp; rp = rp->ai_next) {
475     fd =
476         socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK, rp->ai_protocol);
477
478     if (fd == -1) {
479       continue;
480     }
481
482     rv = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, optlen);
483
484     if (rv == -1) {
485       print_errno("setsockopt", errno);
486     }
487
488     if (bind(fd, rp->ai_addr, rp->ai_addrlen) != 0) {
489       close(fd);
490       continue;
491     }
492
493     if (listen(fd, 65536) != 0) {
494       close(fd);
495       continue;
496     }
497
498     break;
499   }
500
501   freeaddrinfo(res);
502
503   if (!rp) {
504     fprintf(stderr, "No address to bind\n");
505     return -1;
506   }
507
508   serv->fd = fd;
509   serv->evhn.handler = handle_accept;
510
511   return 0;
512 }
513
514 static int timer_init(timer *tmr) {
515   int fd;
516   struct itimerspec timerval = {{1, 0}, {1, 0}};
517
518   fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
519   if (fd == -1) {
520     print_errno("timerfd_create", errno);
521     return -1;
522   }
523
524   if (timerfd_settime(fd, 0, &timerval, NULL) != 0) {
525     print_errno("timerfd_settime", errno);
526     return -1;
527   }
528
529   tmr->fd = fd;
530   tmr->evhn.handler = handle_timer;
531
532   return 0;
533 }
534
535 static int io_loop_init(io_loop *loop) {
536   int epfd;
537
538   epfd = epoll_create1(0);
539
540   if (epfd == -1) {
541     print_errno("epoll_create", errno);
542     return -1;
543   }
544
545   loop->epfd = epfd;
546
547   return 0;
548 }
549
550 static int io_loop_ctl(io_loop *loop, int op, int fd, uint32_t events,
551                        void *ptr) {
552   int rv;
553   struct epoll_event ev;
554
555   ev.events = events;
556   ev.data.ptr = ptr;
557
558   rv = epoll_ctl(loop->epfd, op, fd, &ev);
559
560   if (rv != 0) {
561     print_errno("epoll_ctl", errno);
562     return -1;
563   }
564
565   return 0;
566 }
567
568 static int io_loop_add(io_loop *loop, int fd, uint32_t events, void *ptr) {
569   return io_loop_ctl(loop, EPOLL_CTL_ADD, fd, events, ptr);
570 }
571
572 static int io_loop_mod(io_loop *loop, int fd, uint32_t events, void *ptr) {
573   return io_loop_ctl(loop, EPOLL_CTL_MOD, fd, events, ptr);
574 }
575
576 static int io_loop_run(io_loop *loop, server *serv _U_) {
577 #define NUM_EVENTS 1024
578   struct epoll_event events[NUM_EVENTS];
579
580   for (;;) {
581     int nev;
582     evhandle *evhn;
583     struct epoll_event *ev, *end;
584
585     while ((nev = epoll_wait(loop->epfd, events, NUM_EVENTS, -1)) == -1 &&
586            errno == EINTR)
587       ;
588
589     if (nev == -1) {
590       print_errno("epoll_wait", errno);
591       return -1;
592     }
593
594     for (ev = events, end = events + nev; ev != end; ++ev) {
595       evhn = ev->data.ptr;
596       evhn->handler(loop, ev->events, ev->data.ptr);
597     }
598   }
599 }
600
601 static int handle_timer(io_loop *loop _U_, uint32_t events _U_, void *ptr) {
602   timer *tmr = ptr;
603   int64_t buf;
604   ssize_t nread;
605
606   while ((nread = read(tmr->fd, &buf, sizeof(buf))) == -1 && errno == EINTR)
607     ;
608
609   assert(nread == sizeof(buf));
610
611   update_date();
612
613   return 0;
614 }
615
616 static int handle_accept(io_loop *loop, uint32_t events _U_, void *ptr) {
617   int acfd;
618   server *serv = ptr;
619   int on = 1;
620   socklen_t optlen = sizeof(on);
621   int rv;
622
623   for (;;) {
624     connection *conn;
625
626     while ((acfd = accept4(serv->fd, NULL, NULL, SOCK_NONBLOCK)) == -1 &&
627            errno == EINTR)
628       ;
629
630     if (acfd == -1) {
631       switch (errno) {
632       case ENETDOWN:
633       case EPROTO:
634       case ENOPROTOOPT:
635       case EHOSTDOWN:
636       case ENONET:
637       case EHOSTUNREACH:
638       case EOPNOTSUPP:
639       case ENETUNREACH:
640         continue;
641       }
642       return 0;
643     }
644
645     rv = setsockopt(acfd, IPPROTO_TCP, TCP_NODELAY, &on, optlen);
646
647     if (rv == -1) {
648       print_errno("setsockopt", errno);
649     }
650
651     conn = connection_new(acfd);
652
653     if (conn == NULL) {
654       close(acfd);
655       continue;
656     }
657
658     if (connection_start(conn) != 0 ||
659         io_loop_add(loop, acfd, EPOLLIN | EPOLLOUT, conn) != 0) {
660       connection_del(conn);
661     }
662   }
663 }
664
665 static void stream_error(connection *conn, int32_t stream_id,
666                          uint32_t error_code) {
667   nghttp2_submit_rst_stream(conn->session, NGHTTP2_FLAG_NONE, stream_id,
668                             error_code);
669 }
670
671 static int send_data_callback(nghttp2_session *session _U_,
672                               nghttp2_frame *frame, const uint8_t *framehd,
673                               size_t length, nghttp2_data_source *source,
674                               void *user_data) {
675   connection *conn = user_data;
676   uint8_t *p = conn->buf.last;
677   stream *strm = source->ptr;
678
679   /* We never use padding in this program */
680   assert(frame->data.padlen == 0);
681
682   if ((size_t)io_buf_left(&conn->buf) < 9 + frame->hd.length) {
683     return NGHTTP2_ERR_WOULDBLOCK;
684   }
685
686   memcpy(p, framehd, 9);
687   p += 9;
688
689   while (length) {
690     ssize_t nread;
691     while ((nread = read(strm->filefd, p, length)) == -1 && errno == EINTR)
692       ;
693     if (nread == -1) {
694       return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
695     }
696
697     length -= nread;
698     p += nread;
699   }
700
701   conn->buf.last = p;
702
703   return 0;
704 }
705
706 static ssize_t fd_read_callback(nghttp2_session *session _U_,
707                                 int32_t stream_id _U_, uint8_t *buf _U_,
708                                 size_t length, uint32_t *data_flags,
709                                 nghttp2_data_source *source,
710                                 void *user_data _U_) {
711   stream *strm = source->ptr;
712   ssize_t nread =
713       (int64_t)length < strm->fileleft ? (int64_t)length : strm->fileleft;
714
715   *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
716
717   strm->fileleft -= nread;
718   if (nread == 0 || strm->fileleft == 0) {
719     if (strm->fileleft != 0) {
720       return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
721     }
722     *data_flags |= NGHTTP2_DATA_FLAG_EOF;
723   }
724   return nread;
725 }
726
727 static ssize_t resbuf_read_callback(nghttp2_session *session _U_,
728                                     int32_t stream_id _U_, uint8_t *buf,
729                                     size_t length, uint32_t *data_flags,
730                                     nghttp2_data_source *source,
731                                     void *user_data _U_) {
732   stream *strm = source->ptr;
733   size_t left = strm->res_end - strm->res_begin;
734   size_t nwrite = length < left ? length : left;
735
736   memcpy(buf, strm->res_begin, nwrite);
737   strm->res_begin += nwrite;
738
739   if (strm->res_begin == strm->res_end) {
740     *data_flags |= NGHTTP2_DATA_FLAG_EOF;
741   }
742
743   return nwrite;
744 }
745
746 static int hex_digit(char c) {
747   return ('0' <= c && c <= '9') || ('A' <= c && c <= 'F') ||
748          ('a' <= c && c <= 'f');
749 }
750
751 static unsigned int hex_to_uint(char c) {
752   if (c <= '9') {
753     return c - '0';
754   }
755
756   if (c <= 'F') {
757     return c - 'A' + 10;
758   }
759
760   return c - 'a' + 10;
761 }
762
763 static void percent_decode(io_buf *buf, const char *s) {
764   for (; *s; ++s) {
765     if (*s == '?' || *s == '#') {
766       break;
767     }
768
769     if (*s == '%' && hex_digit(*(s + 1)) && hex_digit(*(s + 2))) {
770       *buf->last++ = (hex_to_uint(*(s + 1)) << 4) + hex_to_uint(*(s + 2));
771       s += 2;
772       continue;
773     }
774
775     *buf->last++ = *s;
776   }
777 }
778
779 static int check_path(const char *path, size_t len) {
780   return path[0] == '/' && strchr(path, '\\') == NULL &&
781          strstr(path, "/../") == NULL && strstr(path, "/./") == NULL &&
782          (len < 3 || memcmp(path + len - 3, "/..", 3) != 0) &&
783          (len < 2 || memcmp(path + len - 2, "/.", 2) != 0);
784 }
785
786 static int make_path(io_buf *pathbuf, const char *req, size_t reqlen _U_) {
787   uint8_t *p;
788
789   if (req[0] != '/') {
790     return -1;
791   }
792
793   if (docrootlen + strlen(req) + sizeof("index.html") >
794       (size_t)io_buf_left(pathbuf)) {
795     return -1;
796   }
797
798   io_buf_add(pathbuf, docroot, docrootlen);
799
800   p = pathbuf->last;
801
802   percent_decode(pathbuf, req);
803
804   if (*(pathbuf->last - 1) == '/') {
805     io_buf_add(pathbuf, "index.html", sizeof("index.html") - 1);
806   }
807
808   *pathbuf->last++ = '\0';
809
810   if (!check_path((const char *)p, pathbuf->last - 1 - p)) {
811
812     return -1;
813   }
814
815   return 0;
816 }
817
818 static int status_response(stream *strm, connection *conn,
819                            const char *status_code) {
820   int rv;
821   size_t status_codelen = strlen(status_code);
822   char contentlength[19];
823   size_t contentlengthlen;
824   size_t reslen;
825   nghttp2_data_provider prd, *prdptr;
826   nghttp2_nv nva[5] = {
827       MAKE_NV(":status", ""), MAKE_NV("server", SERVER_NAME),
828       MAKE_NV2("date", date, datelen), MAKE_NV("content-length", ""),
829   };
830   size_t nvlen = 3;
831
832   nva[0].value = (uint8_t *)status_code;
833   nva[0].valuelen = strlen(status_code);
834
835 #define BODY1 "<html><head><title>"
836 #define BODY2 "</title></head><body><h1>"
837 #define BODY3 "</h1></body></html>"
838
839   reslen = sizeof(BODY1) - 1 + sizeof(BODY2) - 1 + sizeof(BODY3) - 1 +
840            status_codelen * 2;
841
842   if ((size_t)io_buf_left(&strm->scrbuf) < reslen) {
843     contentlength[0] = '0';
844     contentlengthlen = 1;
845     prdptr = NULL;
846   } else {
847     contentlengthlen = utos(contentlength, sizeof(contentlength), reslen);
848
849     strm->res_begin = strm->scrbuf.last;
850
851     io_buf_add(&strm->scrbuf, BODY1, sizeof(BODY1) - 1);
852     io_buf_add(&strm->scrbuf, status_code, strlen(status_code));
853     io_buf_add(&strm->scrbuf, BODY2, sizeof(BODY2) - 1);
854     io_buf_add(&strm->scrbuf, status_code, strlen(status_code));
855     io_buf_add(&strm->scrbuf, BODY3, sizeof(BODY3) - 1);
856
857     strm->res_end = strm->scrbuf.last;
858     prdptr = &prd;
859   }
860
861   nva[nvlen].value = (uint8_t *)contentlength;
862   nva[nvlen].valuelen = contentlengthlen;
863
864   ++nvlen;
865
866   prd.source.ptr = strm;
867   prd.read_callback = resbuf_read_callback;
868
869   rv = nghttp2_submit_response(conn->session, strm->stream_id, nva, nvlen,
870                                prdptr);
871   if (rv != 0) {
872     return -1;
873   }
874
875   return 0;
876 }
877
878 static int redirect_response(stream *strm, connection *conn) {
879   int rv;
880   size_t locationlen;
881   nghttp2_nv nva[5] = {
882       MAKE_NV(":status", "301"),       MAKE_NV("server", SERVER_NAME),
883       MAKE_NV2("date", date, datelen), MAKE_NV("content-length", "0"),
884       MAKE_NV("location", ""),
885   };
886
887   /* + 1 for trailing '/' */
888   locationlen = strm->schemelen + 3 + strm->hostlen + strm->pathlen + 1;
889   if ((size_t)io_buf_left(&strm->scrbuf) < locationlen) {
890     return -1;
891   }
892
893   nva[4].value = strm->scrbuf.last;
894   nva[4].valuelen = locationlen;
895
896   io_buf_add(&strm->scrbuf, strm->scheme, strm->schemelen);
897   io_buf_add(&strm->scrbuf, "://", 3);
898   io_buf_add(&strm->scrbuf, strm->host, strm->hostlen);
899   io_buf_add(&strm->scrbuf, strm->path, strm->pathlen);
900   *strm->scrbuf.last++ = '/';
901
902   rv = nghttp2_submit_response(conn->session, strm->stream_id, nva,
903                                array_size(nva), NULL);
904
905   if (rv != 0) {
906     return -1;
907   }
908
909   return 0;
910 }
911
912 static int process_request(stream *strm, connection *conn) {
913   int fd;
914   struct stat stbuf;
915   int rv;
916   nghttp2_data_provider prd;
917   char lastmod[32];
918   size_t lastmodlen;
919   char contentlength[19];
920   size_t contentlengthlen;
921   char path[1024];
922   io_buf pathbuf;
923   nghttp2_nv nva[5] = {
924       MAKE_NV(":status", "200"), MAKE_NV("server", SERVER_NAME),
925       MAKE_NV2("date", date, datelen), MAKE_NV("content-length", ""),
926   };
927   size_t nvlen = 3;
928
929   io_buf_init(&pathbuf, (uint8_t *)path, sizeof(path));
930
931   rv = make_path(&pathbuf, strm->path, strm->pathlen);
932
933   if (rv != 0) {
934     return status_response(strm, conn, "400");
935   }
936
937   fd = open(path, O_RDONLY);
938
939   if (fd == -1) {
940     return status_response(strm, conn, "404");
941   }
942
943   strm->filefd = fd;
944
945   rv = fstat(fd, &stbuf);
946
947   if (rv == -1) {
948     return status_response(strm, conn, "404");
949   }
950
951   if (stbuf.st_mode & S_IFDIR) {
952     return redirect_response(strm, conn);
953   }
954
955   prd.source.ptr = strm;
956   prd.read_callback = fd_read_callback;
957
958   strm->fileleft = stbuf.st_size;
959
960   lastmodlen = http_date(lastmod, stbuf.st_mtim.tv_sec);
961   contentlengthlen = utos(contentlength, sizeof(contentlength), stbuf.st_size);
962
963   nva[nvlen].value = (uint8_t *)contentlength;
964   nva[nvlen].valuelen = contentlengthlen;
965
966   ++nvlen;
967
968   if (lastmodlen) {
969     nva[nvlen].name = (uint8_t *)"last-modified";
970     nva[nvlen].namelen = sizeof("last-modified") - 1;
971     nva[nvlen].value = (uint8_t *)lastmod;
972     nva[nvlen].valuelen = lastmodlen;
973     nva[nvlen].flags = NGHTTP2_NV_FLAG_NONE;
974
975     ++nvlen;
976   }
977
978   rv =
979       nghttp2_submit_response(conn->session, strm->stream_id, nva, nvlen, &prd);
980   if (rv != 0) {
981     return -1;
982   }
983
984   return 0;
985 }
986
987 static int on_begin_headers_callback(nghttp2_session *session,
988                                      const nghttp2_frame *frame,
989                                      void *user_data) {
990   connection *conn = user_data;
991   stream *strm;
992
993   if (frame->hd.type != NGHTTP2_HEADERS ||
994       frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
995     return 0;
996   }
997
998   strm = stream_new(frame->hd.stream_id, conn);
999
1000   if (!strm) {
1001     nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
1002                               NGHTTP2_INTERNAL_ERROR);
1003     return 0;
1004   }
1005
1006   nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, strm);
1007
1008   return 0;
1009 }
1010
1011 static int on_header_callback(nghttp2_session *session,
1012                               const nghttp2_frame *frame, const uint8_t *name,
1013                               size_t namelen, const uint8_t *value,
1014                               size_t valuelen, uint8_t flags _U_,
1015                               void *user_data _U_) {
1016   stream *strm;
1017
1018   if (frame->hd.type != NGHTTP2_HEADERS ||
1019       frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
1020     return 0;
1021   }
1022
1023   strm = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
1024
1025   if (!strm) {
1026     return 0;
1027   }
1028
1029   switch (lookup_token(name, namelen)) {
1030   case NGHTTP2_TOKEN__METHOD:
1031     strm->method = io_buf_add_str(&strm->scrbuf, value, valuelen);
1032     if (!strm->method) {
1033       return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1034     }
1035     strm->methodlen = valuelen;
1036     break;
1037   case NGHTTP2_TOKEN__SCHEME:
1038     strm->scheme = io_buf_add_str(&strm->scrbuf, value, valuelen);
1039     if (!strm->scheme) {
1040       return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1041     }
1042     strm->schemelen = valuelen;
1043     break;
1044   case NGHTTP2_TOKEN__AUTHORITY:
1045     strm->authority = io_buf_add_str(&strm->scrbuf, value, valuelen);
1046     if (!strm->authority) {
1047       return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1048     }
1049     strm->authoritylen = valuelen;
1050     break;
1051   case NGHTTP2_TOKEN__PATH:
1052     strm->path = io_buf_add_str(&strm->scrbuf, value, valuelen);
1053     if (!strm->path) {
1054       return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1055     }
1056     strm->pathlen = valuelen;
1057     break;
1058   case NGHTTP2_TOKEN_HOST:
1059     strm->host = io_buf_add_str(&strm->scrbuf, value, valuelen);
1060     if (!strm->host) {
1061       return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1062     }
1063     strm->hostlen = valuelen;
1064     break;
1065   }
1066
1067   return 0;
1068 }
1069
1070 static int on_frame_recv_callback(nghttp2_session *session,
1071                                   const nghttp2_frame *frame, void *user_data) {
1072   connection *conn = user_data;
1073   stream *strm;
1074
1075   if (frame->hd.type != NGHTTP2_HEADERS ||
1076       frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
1077     return 0;
1078   }
1079
1080   strm = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
1081
1082   if (!strm) {
1083     return 0;
1084   }
1085
1086   if (!strm->host) {
1087     strm->host = strm->authority;
1088     strm->hostlen = strm->authoritylen;
1089   }
1090
1091   if (process_request(strm, conn) != 0) {
1092     stream_error(conn, strm->stream_id, NGHTTP2_INTERNAL_ERROR);
1093     return 0;
1094   }
1095
1096   return 0;
1097 }
1098
1099 static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
1100                                     uint32_t error_code _U_,
1101                                     void *user_data _U_) {
1102   stream *strm;
1103
1104   strm = nghttp2_session_get_stream_user_data(session, stream_id);
1105
1106   if (!strm) {
1107     return 0;
1108   }
1109
1110   stream_del(strm);
1111
1112   return 0;
1113 }
1114
1115 static int on_frame_not_send_callback(nghttp2_session *session _U_,
1116                                       const nghttp2_frame *frame,
1117                                       int lib_error_code _U_, void *user_data) {
1118   connection *conn = user_data;
1119
1120   if (frame->hd.type != NGHTTP2_HEADERS) {
1121     return 0;
1122   }
1123
1124   /* Issue RST_STREAM so that stream does not hang around. */
1125   nghttp2_submit_rst_stream(conn->session, NGHTTP2_FLAG_NONE,
1126                             frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR);
1127
1128   return 0;
1129 }
1130
1131 static int do_read(connection *conn) {
1132   uint8_t buf[32768];
1133
1134   for (;;) {
1135     ssize_t nread;
1136     ssize_t nproc;
1137
1138     while ((nread = read(conn->fd, buf, sizeof(buf))) == -1 && errno == EINTR)
1139       ;
1140     if (nread == -1) {
1141       if (errno == EAGAIN || errno == EWOULDBLOCK) {
1142         return 0;
1143       }
1144
1145       return -1;
1146     }
1147
1148     if (nread == 0) {
1149       return -1;
1150     }
1151
1152     nproc = nghttp2_session_mem_recv(conn->session, buf, nread);
1153
1154     if (nproc < 0) {
1155       return -1;
1156     }
1157   }
1158 }
1159
1160 static int do_write(connection *conn) {
1161   for (;;) {
1162     if (io_buf_len(&conn->buf)) {
1163       ssize_t nwrite;
1164       while ((nwrite = write(conn->fd, conn->buf.pos,
1165                              io_buf_len(&conn->buf))) == -1 &&
1166              errno == EINTR)
1167         ;
1168       if (nwrite == -1) {
1169         if (errno == EAGAIN || errno == EWOULDBLOCK) {
1170           return 0;
1171         }
1172         return -1;
1173       }
1174
1175       conn->buf.pos += nwrite;
1176
1177       if (io_buf_len(&conn->buf)) {
1178         return 0;
1179       }
1180
1181       io_buf_init(&conn->buf, conn->rawoutbuf, sizeof(conn->rawoutbuf));
1182     }
1183
1184     if (conn->cache) {
1185       io_buf_add(&conn->buf, conn->cache, conn->cachelen);
1186       conn->cache = NULL;
1187       conn->cachelen = 0;
1188     }
1189
1190     for (;;) {
1191       ssize_t n;
1192       const uint8_t *b;
1193
1194       n = nghttp2_session_mem_send(conn->session, &b);
1195
1196       if (n < 0) {
1197         return -1;
1198       }
1199
1200       if (n == 0) {
1201         if (io_buf_len(&conn->buf) == 0) {
1202           return 0;
1203         }
1204         break;
1205       }
1206
1207       if (io_buf_left(&conn->buf) < n) {
1208         conn->cache = b;
1209         conn->cachelen = n;
1210         break;
1211       }
1212
1213       io_buf_add(&conn->buf, b, n);
1214     }
1215   }
1216 }
1217
1218 static int handle_connection(io_loop *loop, uint32_t events, void *ptr) {
1219   connection *conn = ptr;
1220   int rv;
1221   uint32_t nextev = 0;
1222
1223   if (events & (EPOLLHUP | EPOLLERR)) {
1224     goto cleanup;
1225   }
1226
1227   if (events & EPOLLIN) {
1228     rv = do_read(conn);
1229
1230     if (rv != 0) {
1231       goto cleanup;
1232     }
1233   }
1234
1235   rv = do_write(conn);
1236
1237   if (rv != 0) {
1238     goto cleanup;
1239   }
1240
1241   if (nghttp2_session_want_read(conn->session)) {
1242     nextev |= EPOLLIN;
1243   }
1244
1245   if (io_buf_len(&conn->buf) || nghttp2_session_want_write(conn->session)) {
1246     nextev |= EPOLLOUT;
1247   }
1248
1249   if (!nextev) {
1250     goto cleanup;
1251   }
1252
1253   io_loop_mod(loop, conn->fd, nextev, conn);
1254
1255   return 0;
1256
1257 cleanup:
1258   connection_del(conn);
1259
1260   return 0;
1261 }
1262
1263 int main(int argc, char **argv) {
1264   int rv;
1265   server serv;
1266   timer tmr;
1267   io_loop loop;
1268   struct sigaction act;
1269   const char *address;
1270   const char *service;
1271
1272   if (argc < 4) {
1273     fprintf(stderr, "Usage: tiny-nghttpd <address> <port> <doc-root>\n");
1274     exit(EXIT_FAILURE);
1275   }
1276
1277   address = argv[1];
1278   service = argv[2];
1279   docroot = argv[3];
1280   docrootlen = strlen(docroot);
1281
1282   memset(&act, 0, sizeof(act));
1283   act.sa_handler = SIG_IGN;
1284   sigaction(SIGPIPE, &act, NULL);
1285
1286   rv = server_init(&serv, address, service);
1287
1288   if (rv != 0) {
1289     exit(EXIT_FAILURE);
1290   }
1291
1292   rv = timer_init(&tmr);
1293
1294   if (rv != 0) {
1295     exit(EXIT_FAILURE);
1296   }
1297
1298   rv = io_loop_init(&loop);
1299
1300   if (rv != 0) {
1301     exit(EXIT_FAILURE);
1302   }
1303
1304   rv = nghttp2_session_callbacks_new(&shared_callbacks);
1305   if (rv != 0) {
1306     fprintf(stderr, "nghttp2_session_callbacks_new: %s", nghttp2_strerror(rv));
1307     exit(EXIT_FAILURE);
1308   }
1309
1310   nghttp2_session_callbacks_set_on_begin_headers_callback(
1311       shared_callbacks, on_begin_headers_callback);
1312   nghttp2_session_callbacks_set_on_header_callback(shared_callbacks,
1313                                                    on_header_callback);
1314   nghttp2_session_callbacks_set_on_frame_recv_callback(shared_callbacks,
1315                                                        on_frame_recv_callback);
1316   nghttp2_session_callbacks_set_on_stream_close_callback(
1317       shared_callbacks, on_stream_close_callback);
1318   nghttp2_session_callbacks_set_on_frame_not_send_callback(
1319       shared_callbacks, on_frame_not_send_callback);
1320   nghttp2_session_callbacks_set_send_data_callback(shared_callbacks,
1321                                                    send_data_callback);
1322
1323   rv = io_loop_add(&loop, serv.fd, EPOLLIN, &serv);
1324
1325   if (rv != 0) {
1326     exit(EXIT_FAILURE);
1327   }
1328
1329   rv = io_loop_add(&loop, tmr.fd, EPOLLIN, &tmr);
1330
1331   if (rv != 0) {
1332     exit(EXIT_FAILURE);
1333   }
1334
1335   update_date();
1336
1337   io_loop_run(&loop, &serv);
1338
1339   nghttp2_session_callbacks_del(shared_callbacks);
1340
1341   return 0;
1342 }