fa0440b9c9a28b2aa91e5e6d8d9c94b349444925
[src/app-framework-binder.git] / src / http-svc.c
1 /*
2  * Copyright (C) 2016 "IoT.bzh"
3  * Author José Bollo
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 #define _GNU_SOURCE
19 #include <microhttpd.h>
20 #include <assert.h>
21 #include <poll.h>
22 #include <sys/stat.h>
23 #include "../include/local-def.h"
24
25
26
27 enum afb_method {
28         afb_method_none = 0,
29         afb_method_get = 1,
30         afb_method_post = 2,
31         afb_method_head = 4,
32         afb_method_connect = 8,
33         afb_method_delete = 16,
34         afb_method_options = 32,
35         afb_method_patch = 64,
36         afb_method_put = 128,
37         afb_method_trace = 256,
38         afb_method_all = 511
39 };
40
41 struct afb_req_post {
42         const char *upload_data;
43         size_t *upload_data_size;
44 };
45
46 struct afb_req {
47         AFB_session *session;
48         struct MHD_Connection *connection;
49         enum afb_method method;
50         const char *url;
51         size_t lenurl;
52         const char *tail;
53         size_t lentail;
54         struct afb_req **recorder;
55         int (*post_handler) (struct afb_req *, struct afb_req_post *);
56         int (*post_completed) (struct afb_req *, struct afb_req_post *);
57         void *post_data;
58 };
59
60 struct afb_req_handler {
61         struct afb_req_handler *next;
62         const char *prefix;
63         size_t length;
64         int (*handler) (struct afb_req *, struct afb_req_post *, void *);
65         void *data;
66         int priority;
67 };
68
69 static char empty_string[1] = "";
70
71 enum afb_method get_method(const char *method)
72 {
73         switch (method[0] & ~' ') {
74         case 'C':
75                 return afb_method_connect;
76         case 'D':
77                 return afb_method_delete;
78         case 'G':
79                 return afb_method_get;
80         case 'H':
81                 return afb_method_head;
82         case 'O':
83                 return afb_method_options;
84         case 'P':
85                 switch (method[1] & ~' ') {
86                 case 'A':
87                         return afb_method_patch;
88                 case 'O':
89                         return afb_method_post;
90                 case 'U':
91                         return afb_method_put;
92                 }
93                 break;
94         case 'T':
95                 return afb_method_trace;
96         }
97         return afb_method_none;
98 }
99
100 #if !defined(MHD_HTTP_METHOD_PATCH)
101 #define MHD_HTTP_METHOD_PATCH "PATCH"
102 #endif
103 const char *get_method_name(enum afb_method method)
104 {
105         switch (method) {
106         case afb_method_get:
107                 return MHD_HTTP_METHOD_GET;
108         case afb_method_post:
109                 return MHD_HTTP_METHOD_POST;
110         case afb_method_head:
111                 return MHD_HTTP_METHOD_HEAD;
112         case afb_method_connect:
113                 return MHD_HTTP_METHOD_CONNECT;
114         case afb_method_delete:
115                 return MHD_HTTP_METHOD_DELETE;
116         case afb_method_options:
117                 return MHD_HTTP_METHOD_OPTIONS;
118         case afb_method_patch:
119                 return MHD_HTTP_METHOD_PATCH;
120         case afb_method_put:
121                 return MHD_HTTP_METHOD_PUT;
122         case afb_method_trace:
123                 return MHD_HTTP_METHOD_TRACE;
124         default:
125                 return NULL;
126         }
127 }
128
129 /* a valid subpath is a relative path not looking deeper than root using .. */
130 static int validsubpath(const char *subpath)
131 {
132         int l = 0, i = 0;
133
134         while (subpath[i]) {
135                 switch (subpath[i++]) {
136                 case '.':
137                         if (!subpath[i])
138                                 break;
139                         if (subpath[i] == '/') {
140                                 i++;
141                                 break;
142                         }
143                         if (subpath[i++] == '.') {
144                                 if (!subpath[i]) {
145                                         if (--l < 0)
146                                                 return 0;
147                                         break;
148                                 }
149                                 if (subpath[i++] == '/') {
150                                         if (--l < 0)
151                                                 return 0;
152                                         break;
153                                 }
154                         }
155                 default:
156                         while (subpath[i] && subpath[i] != '/')
157                                 i++;
158                         l++;
159                 case '/':
160                         break;
161                 }
162         }
163         return 1;
164 }
165
166 /*
167  * Removes the 'prefix' of 'length' frome the tail of 'request'
168  * if and only if the prefix exists and is terminated by a leading
169  * slash
170  */
171 int afb_req_unprefix(struct afb_req *request, const char *prefix, size_t length)
172 {
173         /* check the prefix ? */
174         if (length > request->lentail || (request->tail[length] && request->tail[length] != '/')
175             || memcmp(prefix, request->tail, length))
176                 return 0;
177
178         /* removes successives / */
179         while (length < request->lentail && request->tail[length + 1] == '/')
180                 length++;
181
182         /* update the tail */
183         request->lentail -= length;
184         request->tail += length;
185         return 1;
186 }
187
188 int afb_req_valid_tail(struct afb_req *request)
189 {
190         return validsubpath(request->tail);
191 }
192
193 void afb_req_reply_error(struct afb_req *request, unsigned int status)
194 {
195         char *buffer;
196         int length;
197         struct MHD_Response *response;
198
199         length = asprintf(&buffer, "<html><body>error %u</body></html>", status);
200         if (length > 0)
201                 response = MHD_create_response_from_buffer((unsigned)length, buffer, MHD_RESPMEM_MUST_FREE);
202         else {
203                 buffer = "<html><body>error</body></html>";
204                 response = MHD_create_response_from_buffer(strlen(buffer), buffer, MHD_RESPMEM_PERSISTENT);
205         }
206         if (!MHD_queue_response(request->connection, status, response))
207                 fprintf(stderr, "Failed to reply error code %u", status);
208         MHD_destroy_response(response);
209 }
210
211 int afb_request_redirect_to(struct afb_req *request, const char *url)
212 {
213         struct MHD_Response *response;
214
215         response = MHD_create_response_from_buffer(0, empty_string, MHD_RESPMEM_PERSISTENT);
216         MHD_add_response_header(response, "Location", url);
217         MHD_queue_response(request->connection, MHD_HTTP_MOVED_PERMANENTLY, response);
218         MHD_destroy_response(response);
219         if (verbose)
220                 fprintf(stderr, "redirect from [%s] to [%s]\n", request->url, url);
221         return 1;
222 }
223
224 int afb_request_one_page_api_redirect(struct afb_req *request, struct afb_req_post *post, void *data)
225 {
226         size_t plen;
227         char *url;
228
229         if (request->lentail >= 2 && request->tail[1] == '#')
230                 return 0;
231         /*
232          * Here we have for example:
233          *    url  = "/pre/dir/page"   lenurl = 13
234          *    tail =     "/dir/page"   lentail = 9
235          *
236          * We will produce "/pre/#!dir/page"
237          *
238          * Let compute plen that include the / at end (for "/pre/")
239          */
240         plen = request->lenurl - request->lentail + 1;
241         url = alloca(request->lenurl + 3);
242         memcpy(url, request->url, plen);
243         url[plen++] = '#';
244         url[plen++] = '!';
245         memcpy(&url[plen], &request->tail[1], request->lentail);
246         return afb_request_redirect_to(request, url);
247 }
248
249 struct afb_req_handler *afb_req_handler_new(struct afb_req_handler *head, const char *prefix,
250                                             int (*handler) (struct afb_req *, struct afb_req_post *, void *),
251                                             void *data, int priority)
252 {
253         struct afb_req_handler *link, *iter, *previous;
254         size_t length;
255
256         /* get the length of the prefix without its leading / */
257         length = strlen(prefix);
258         while (length && prefix[length - 1] == '/')
259                 length--;
260
261         /* allocates the new link */
262         link = malloc(sizeof *link);
263         if (link == NULL)
264                 return NULL;
265
266         /* initialize it */
267         link->prefix = prefix;
268         link->length = length;
269         link->handler = handler;
270         link->data = data;
271         link->priority = priority;
272
273         /* adds it */
274         previous = NULL;
275         iter = head;
276         while (iter && (priority < iter->priority || (priority == iter->priority && length <= iter->length))) {
277                 previous = iter;
278                 iter = iter->next;
279         }
280         link->next = iter;
281         if (previous == NULL)
282                 return link;
283         previous->next = link;
284         return head;
285 }
286
287 int afb_req_add_handler(AFB_session * session, const char *prefix,
288                         int (*handler) (struct afb_req *, struct afb_req_post *, void *), void *data, int priority)
289 {
290         struct afb_req_handler *head;
291
292         head = afb_req_handler_new(session->handlers, prefix, handler, data, priority);
293         if (head == NULL)
294                 return 0;
295         session->handlers = head;
296         return 1;
297 }
298
299 static int relay_to_doRestApi(struct afb_req *request, struct afb_req_post *post, void *data)
300 {
301         return doRestApi(request->connection, request->session, &request->tail[1], get_method_name(request->method),
302                          post->upload_data, post->upload_data_size, (void **)request->recorder);
303 }
304
305 static int afb_req_reply_file_if_exist(struct afb_req *request, int dirfd, const char *filename)
306 {
307         int rc;
308         int fd;
309         unsigned int status;
310         struct stat st;
311         char etag[1 + 2 * sizeof(int)];
312         const char *inm;
313         struct MHD_Response *response;
314
315         /* Opens the file or directory */
316         fd = openat(dirfd, filename, O_RDONLY);
317         if (fd < 0) {
318                 if (errno == ENOENT)
319                         return 0;
320                 afb_req_reply_error(request, MHD_HTTP_FORBIDDEN);
321                 return 1;
322         }
323
324         /* Retrieves file's status */
325         if (fstat(fd, &st) != 0) {
326                 close(fd);
327                 afb_req_reply_error(request, MHD_HTTP_INTERNAL_SERVER_ERROR);
328                 return 1;
329         }
330
331         /* Don't serve directory */
332         if (S_ISDIR(st.st_mode)) {
333                 rc = afb_req_reply_file_if_exist(request, fd, "index.html");
334                 close(fd);
335                 return rc;
336         }
337
338         /* Don't serve special files */
339         if (!S_ISREG(st.st_mode)) {
340                 close(fd);
341                 afb_req_reply_error(request, MHD_HTTP_FORBIDDEN);
342                 return 1;
343         }
344
345         /* Check the method */
346         if ((request->method & (afb_method_get | afb_method_head)) == 0) {
347                 close(fd);
348                 afb_req_reply_error(request, MHD_HTTP_METHOD_NOT_ALLOWED);
349                 return 1;
350         }
351
352         /* computes the etag */
353         sprintf(etag, "%08X%08X", ((int)(st.st_mtim.tv_sec) ^ (int)(st.st_mtim.tv_nsec)), (int)(st.st_size));
354
355         /* checks the etag */
356         inm = MHD_lookup_connection_value(request->connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH);
357         if (inm && 0 == strcmp(inm, etag)) {
358                 /* etag ok, return NOT MODIFIED */
359                 close(fd);
360                 if (verbose)
361                         fprintf(stderr, "Not Modified: [%s]\n", filename);
362                 response = MHD_create_response_from_buffer(0, empty_string, MHD_RESPMEM_PERSISTENT);
363                 status = MHD_HTTP_NOT_MODIFIED;
364         } else {
365                 /* check the size */
366                 if (st.st_size != (off_t) (size_t) st.st_size) {
367                         close(fd);
368                         afb_req_reply_error(request, MHD_HTTP_INTERNAL_SERVER_ERROR);
369                         return 1;
370                 }
371
372                 /* create the response */
373                 response = MHD_create_response_from_fd((size_t) st.st_size, fd);
374                 status = MHD_HTTP_OK;
375
376 #if defined(USE_MAGIC_MIME_TYPE)
377                 /* set the type */
378                 if (request->session->magic) {
379                         const char *mimetype = magic_descriptor(request->session->magic, fd);
380                         if (mimetype != NULL)
381                                 MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, mimetype);
382                 }
383 #endif
384         }
385
386         /* fills the value and send */
387         MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL, request->session->cacheTimeout);
388         MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etag);
389         MHD_queue_response(request->connection, status, response);
390         MHD_destroy_response(response);
391         return 1;
392 }
393
394 static int afb_req_reply_file(struct afb_req *request, int dirfd, const char *filename)
395 {
396         int rc = afb_req_reply_file_if_exist(request, dirfd, filename);
397         if (rc == 0)
398                 afb_req_reply_error(request, MHD_HTTP_NOT_FOUND);
399         return 1;
400 }
401
402 static int handle_alias(struct afb_req *request, struct afb_req_post *post, void *data)
403 {
404         char *path;
405         const char *alias = data;
406         size_t lenalias;
407
408         if (request->method != afb_method_get) {
409                 afb_req_reply_error(request, MHD_HTTP_METHOD_NOT_ALLOWED);
410                 return 1;
411         }
412
413         if (!validsubpath(request->tail)) {
414                 afb_req_reply_error(request, MHD_HTTP_FORBIDDEN);
415                 return 1;
416         }
417
418         lenalias = strlen(alias);
419         path = alloca(lenalias + request->lentail + 1);
420         memcpy(path, alias, lenalias);
421         memcpy(&path[lenalias], request->tail, request->lentail + 1);
422         return afb_req_reply_file(request, AT_FDCWD, path);
423 }
424
425 int afb_req_add_alias(AFB_session * session, const char *prefix, const char *alias, int priority)
426 {
427         return afb_req_add_handler(session, prefix, handle_alias, (void *)alias, priority);
428 }
429
430 static int my_default_init(AFB_session * session)
431 {
432         int idx;
433
434         if (!afb_req_add_handler(session, session->config->rootapi, relay_to_doRestApi, NULL, 1))
435                 return 0;
436
437         for (idx = 0; session->config->aliasdir[idx].url != NULL; idx++)
438                 if (!afb_req_add_alias
439                     (session, session->config->aliasdir[idx].url, session->config->aliasdir[idx].path, 0))
440                         return 0;
441
442         if (!afb_req_add_alias(session, "", session->config->rootdir, -10))
443                 return 0;
444
445         if (!afb_req_add_handler(session, session->config->rootbase, afb_request_one_page_api_redirect, NULL, -20))
446                 return 0;
447
448         return 1;
449 }
450
451 static int access_handler(void *cls,
452                           struct MHD_Connection *connection,
453                           const char *url,
454                           const char *methodstr,
455                           const char *version, const char *upload_data, size_t * upload_data_size, void **recorder)
456 {
457         struct afb_req_post post;
458         struct afb_req request;
459         enum afb_method method;
460         AFB_session *session;
461         struct afb_req_handler *iter;
462
463         session = cls;
464         post.upload_data = upload_data;
465         post.upload_data_size = upload_data_size;
466
467 #if 0
468         struct afb_req *previous;
469
470         previous = *recorder;
471         if (previous) {
472                 assert((void **)previous->recorder == recorder);
473                 assert(previous->session == session);
474                 assert(previous->connection == connection);
475                 assert(previous->method == get_method(methodstr));
476                 assert(previous->url == url);
477
478                 /* TODO */
479 /*
480                 assert(previous->post_handler != NULL);
481                 previous->post_handler(previous, &post);
482                 return MHD_NO;
483 */
484         }
485 #endif
486
487         method = get_method(methodstr);
488         if (method == afb_method_none) {
489                 afb_req_reply_error(&request, MHD_HTTP_BAD_REQUEST);
490                 return MHD_YES;
491         }
492
493         /* init the request */
494         request.session = cls;
495         request.connection = connection;
496         request.method = method;
497         request.tail = request.url = url;
498         request.lentail = request.lenurl = strlen(url);
499         request.recorder = (struct afb_req **)recorder;
500         request.post_handler = NULL;
501         request.post_completed = NULL;
502         request.post_data = NULL;
503
504         /* search an handler for the request */
505         iter = session->handlers;
506         while (iter) {
507                 if (afb_req_unprefix(&request, iter->prefix, iter->length)) {
508                         if (iter->handler(&request, &post, iter->data))
509                                 return MHD_YES;
510                         request.tail = request.url;
511                         request.lentail = request.lenurl;
512                 }
513                 iter = iter->next;
514         }
515
516         /* no handler */
517         afb_req_reply_error(&request, method != afb_method_get ? MHD_HTTP_BAD_REQUEST : MHD_HTTP_NOT_FOUND);
518         return MHD_YES;
519 }
520
521 /* Because of POST call multiple time requestApi we need to free POST handle here */
522 static void end_handler(void *cls, struct MHD_Connection *connection, void **con_cls,
523                         enum MHD_RequestTerminationCode toe)
524 {
525         AFB_PostHandle *posthandle = *con_cls;
526
527         /* if post handle was used let's free everything */
528         if (posthandle != NULL)
529                 endPostRequest(posthandle);
530 }
531
532 static int new_client_handler(void *cls, const struct sockaddr *addr, socklen_t addrlen)
533 {
534         return MHD_YES;
535 }
536
537 #if defined(USE_MAGIC_MIME_TYPE)
538
539 #if !defined(MAGIC_DB)
540 #define MAGIC_DB "/usr/share/misc/magic.mgc"
541 #endif
542
543 static int init_lib_magic (AFB_session *session)
544 {
545         /* MAGIC_MIME tells magic to return a mime of the file, but you can specify different things */
546         if (verbose)
547                 printf("Loading mimetype default magic database\n");
548
549         session->magic = magic_open(MAGIC_MIME_TYPE);
550         if (session->magic == NULL) {
551                 fprintf(stderr,"ERROR: unable to initialize magic library\n");
552                 return 0;
553         }
554
555         /* Warning: should not use NULL for DB [libmagic bug wont pass efence check] */
556         if (magic_load(session->magic, MAGIC_DB) != 0) {
557                 fprintf(stderr,"cannot load magic database - %s\n", magic_error(session->magic));
558 /*
559                 magic_close(session->magic);
560                 session->magic = NULL;
561                 return 0;
562 */
563         }
564
565         return 1;
566 }
567 #endif
568
569 AFB_error httpdStart(AFB_session * session)
570 {
571
572         if (!my_default_init(session)) {
573                 printf("Error: initialisation of httpd failed");
574                 return AFB_FATAL;
575         }
576
577         /* Initialise Client Session Hash Table */
578         ctxStoreInit(CTX_NBCLIENTS);
579
580 #if defined(USE_MAGIC_MIME_TYPE)
581         /*TBD open libmagic cache [fail to pass EFENCE check (allocating 0 bytes)] */
582         init_lib_magic (session);
583 #endif
584
585         if (verbose) {
586                 printf("AFB:notice Waiting port=%d rootdir=%s\n", session->config->httpdPort, session->config->rootdir);
587                 printf("AFB:notice Browser URL= http:/*localhost:%d\n", session->config->httpdPort);
588         }
589
590         session->httpd = MHD_start_daemon(
591                 MHD_USE_EPOLL_LINUX_ONLY | MHD_USE_TCP_FASTOPEN | MHD_USE_DEBUG,
592                 (uint16_t) session->config->httpdPort,  /* port */
593                 new_client_handler, NULL,       /* Tcp Accept call back + extra attribute */
594                 access_handler, session,        /* Http Request Call back + extra attribute */
595                 MHD_OPTION_NOTIFY_COMPLETED, end_handler, session,
596                 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)15,        /* 15 seconds */
597                 MHD_OPTION_END);        /* options-end */
598
599         if (session->httpd == NULL) {
600                 printf("Error: httpStart invalid httpd port: %d", session->config->httpdPort);
601                 return AFB_FATAL;
602         }
603         return AFB_SUCCESS;
604 }
605
606 /* infinite loop */
607 AFB_error httpdLoop(AFB_session * session)
608 {
609         int count = 0;
610         const union MHD_DaemonInfo *info;
611         struct pollfd pfd;
612
613         info = MHD_get_daemon_info(session->httpd, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY);
614         if (info == NULL) {
615                 printf("Error: httpLoop no pollfd");
616                 goto error;
617         }
618         pfd.fd = info->listen_fd;
619         pfd.events = POLLIN;
620
621         if (verbose)
622                 fprintf(stderr, "AFB:notice entering httpd waiting loop\n");
623         while (TRUE) {
624                 if (verbose)
625                         fprintf(stderr, "AFB:notice httpd alive [%d]\n", count++);
626                 poll(&pfd, 1, 15000);   /* 15 seconds (as above timeout when starting) */
627                 MHD_run(session->httpd);
628         }
629
630  error:
631         /* should never return from here */
632         return AFB_FATAL;
633 }
634
635 int httpdStatus(AFB_session * session)
636 {
637         return MHD_run(session->httpd);
638 }
639
640 void httpdStop(AFB_session * session)
641 {
642         MHD_stop_daemon(session->httpd);
643 }