http-svc: renaming
[src/app-framework-binder.git] / src / http-svc.c
1 /*
2  * Copyright 2016 IoT.bzh
3  * Author: José Bollo <jose.bollo@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #define _GNU_SOURCE
19 #include <microhttpd.h>
20 #include <assert.h>
21 #include <poll.h>
22 #include <sys/stat.h>
23
24 #include "../include/local-def.h"
25 #include "afb-method.h"
26 #include "afb-hreq.h"
27
28
29 struct afb_hsrv_handler {
30         struct afb_hsrv_handler *next;
31         const char *prefix;
32         size_t length;
33         int (*handler) (struct afb_hreq *, struct afb_hreq_post *, void *);
34         void *data;
35         int priority;
36 };
37
38 struct afb_diralias {
39         const char *alias;
40         const char *directory;
41         size_t lendir;
42         int dirfd;
43 };
44
45
46 int afb_request_one_page_api_redirect(
47                 struct afb_hreq *request,
48                 struct afb_hreq_post *post,
49                 void *data)
50 {
51         size_t plen;
52         char *url;
53
54         if (request->lentail >= 2 && request->tail[1] == '#')
55                 return 0;
56         /*
57          * Here we have for example:
58          *    url  = "/pre/dir/page"   lenurl = 13
59          *    tail =     "/dir/page"   lentail = 9
60          *
61          * We will produce "/pre/#!dir/page"
62          *
63          * Let compute plen that include the / at end (for "/pre/")
64          */
65         plen = request->lenurl - request->lentail + 1;
66         url = alloca(request->lenurl + 3);
67         memcpy(url, request->url, plen);
68         url[plen++] = '#';
69         url[plen++] = '!';
70         memcpy(&url[plen], &request->tail[1], request->lentail);
71         return afb_hreq_redirect_to(request, url);
72 }
73
74 struct afb_hsrv_handler *afb_hsrv_handler_new(
75                 struct afb_hsrv_handler *head,
76                 const char *prefix,
77                 int (*handler) (struct afb_hreq *, struct afb_hreq_post *, void *),
78                 void *data,
79                 int priority)
80 {
81         struct afb_hsrv_handler *link, *iter, *previous;
82         size_t length;
83
84         /* get the length of the prefix without its leading / */
85         length = strlen(prefix);
86         while (length && prefix[length - 1] == '/')
87                 length--;
88
89         /* allocates the new link */
90         link = malloc(sizeof *link);
91         if (link == NULL)
92                 return NULL;
93
94         /* initialize it */
95         link->prefix = prefix;
96         link->length = length;
97         link->handler = handler;
98         link->data = data;
99         link->priority = priority;
100
101         /* adds it */
102         previous = NULL;
103         iter = head;
104         while (iter && (priority < iter->priority || (priority == iter->priority && length <= iter->length))) {
105                 previous = iter;
106                 iter = iter->next;
107         }
108         link->next = iter;
109         if (previous == NULL)
110                 return link;
111         previous->next = link;
112         return head;
113 }
114
115 int afb_req_add_handler(
116                 AFB_session * session,
117                 const char *prefix,
118                 int (*handler) (struct afb_hreq *, struct afb_hreq_post *, void *),
119                 void *data,
120                 int priority)
121 {
122         struct afb_hsrv_handler *head;
123
124         head = afb_hsrv_handler_new(session->handlers, prefix, handler, data, priority);
125         if (head == NULL)
126                 return 0;
127         session->handlers = head;
128         return 1;
129 }
130
131 static int relay_to_doRestApi(struct afb_hreq *request, struct afb_hreq_post *post, void *data)
132 {
133         return doRestApi(request->connection, request->session, &request->tail[1], get_method_name(request->method),
134                          post->upload_data, post->upload_data_size, (void **)request->recorder);
135 }
136
137 static int handle_alias(struct afb_hreq *request, struct afb_hreq_post *post, void *data)
138 {
139         struct afb_diralias *da = data;
140
141         if (request->method != afb_method_get) {
142                 afb_hreq_reply_error(request, MHD_HTTP_METHOD_NOT_ALLOWED);
143                 return 1;
144         }
145
146         if (!afb_hreq_valid_tail(request)) {
147                 afb_hreq_reply_error(request, MHD_HTTP_FORBIDDEN);
148                 return 1;
149         }
150
151         return afb_hreq_reply_file(request, da->dirfd, &request->tail[1]);
152 }
153
154 int afb_req_add_alias(AFB_session * session, const char *prefix, const char *alias, int priority)
155 {
156         struct afb_diralias *da;
157         int dirfd;
158
159         dirfd = open(alias, O_PATH|O_DIRECTORY);
160         if (dirfd < 0) {
161                 /* TODO message */
162                 return 0;
163         }
164         da = malloc(sizeof *da);
165         if (da != NULL) {
166                 da->alias = prefix;
167                 da->directory = alias;
168                 da->lendir = strlen(da->directory);
169                 da->dirfd = dirfd;
170                 if (afb_req_add_handler(session, prefix, handle_alias, (void *)alias, priority))
171                         return 1;
172                 free(da);
173         }
174         close(dirfd);
175         return 0;
176 }
177
178 static int my_default_init(AFB_session * session)
179 {
180         int idx;
181
182         if (!afb_req_add_handler(session, session->config->rootapi, relay_to_doRestApi, NULL, 1))
183                 return 0;
184
185         for (idx = 0; session->config->aliasdir[idx].url != NULL; idx++)
186                 if (!afb_req_add_alias
187                     (session, session->config->aliasdir[idx].url, session->config->aliasdir[idx].path, 0))
188                         return 0;
189
190         if (!afb_req_add_alias(session, "", session->config->rootdir, -10))
191                 return 0;
192
193         if (!afb_req_add_handler(session, session->config->rootbase, afb_request_one_page_api_redirect, NULL, -20))
194                 return 0;
195
196         return 1;
197 }
198
199 static int access_handler(
200                 void *cls,
201                 struct MHD_Connection *connection,
202                 const char *url,
203                 const char *methodstr,
204                 const char *version,
205                 const char *upload_data,
206                 size_t * upload_data_size,
207                 void **recorder)
208 {
209         struct afb_hreq_post post;
210         struct afb_hreq request;
211         enum afb_method method;
212         AFB_session *session;
213         struct afb_hsrv_handler *iter;
214
215         session = cls;
216         post.upload_data = upload_data;
217         post.upload_data_size = upload_data_size;
218
219 #if 0
220         struct afb_hreq *previous;
221
222         previous = *recorder;
223         if (previous) {
224                 assert((void **)previous->recorder == recorder);
225                 assert(previous->session == session);
226                 assert(previous->connection == connection);
227                 assert(previous->method == get_method(methodstr));
228                 assert(previous->url == url);
229
230                 /* TODO */
231 /*
232                 assert(previous->post_handler != NULL);
233                 previous->post_handler(previous, &post);
234                 return MHD_NO;
235 */
236         }
237 #endif
238
239         method = get_method(methodstr);
240         if (method == afb_method_none) {
241                 afb_hreq_reply_error(&request, MHD_HTTP_BAD_REQUEST);
242                 return MHD_YES;
243         }
244
245         /* init the request */
246         request.session = cls;
247         request.connection = connection;
248         request.method = method;
249         request.tail = request.url = url;
250         request.lentail = request.lenurl = strlen(url);
251         request.recorder = (struct afb_hreq **)recorder;
252         request.post_handler = NULL;
253         request.post_completed = NULL;
254         request.post_data = NULL;
255
256         /* search an handler for the request */
257         iter = session->handlers;
258         while (iter) {
259                 if (afb_hreq_unprefix(&request, iter->prefix, iter->length)) {
260                         if (iter->handler(&request, &post, iter->data))
261                                 return MHD_YES;
262                         request.tail = request.url;
263                         request.lentail = request.lenurl;
264                 }
265                 iter = iter->next;
266         }
267
268         /* no handler */
269         afb_hreq_reply_error(&request, method != afb_method_get ? MHD_HTTP_BAD_REQUEST : MHD_HTTP_NOT_FOUND);
270         return MHD_YES;
271 }
272
273 /* Because of POST call multiple time requestApi we need to free POST handle here */
274 static void end_handler(void *cls, struct MHD_Connection *connection, void **con_cls,
275                         enum MHD_RequestTerminationCode toe)
276 {
277         AFB_PostHandle *posthandle = *con_cls;
278
279         /* if post handle was used let's free everything */
280         if (posthandle != NULL)
281                 endPostRequest(posthandle);
282 }
283
284 static int new_client_handler(void *cls, const struct sockaddr *addr, socklen_t addrlen)
285 {
286         return MHD_YES;
287 }
288
289 #if defined(USE_MAGIC_MIME_TYPE)
290
291 #if !defined(MAGIC_DB)
292 #define MAGIC_DB "/usr/share/misc/magic.mgc"
293 #endif
294
295 static int init_lib_magic (AFB_session *session)
296 {
297         /* MAGIC_MIME tells magic to return a mime of the file, but you can specify different things */
298         if (verbose)
299                 printf("Loading mimetype default magic database\n");
300
301         session->magic = magic_open(MAGIC_MIME_TYPE);
302         if (session->magic == NULL) {
303                 fprintf(stderr,"ERROR: unable to initialize magic library\n");
304                 return 0;
305         }
306
307         /* Warning: should not use NULL for DB [libmagic bug wont pass efence check] */
308         if (magic_load(session->magic, MAGIC_DB) != 0) {
309                 fprintf(stderr,"cannot load magic database - %s\n", magic_error(session->magic));
310 /*
311                 magic_close(session->magic);
312                 session->magic = NULL;
313                 return 0;
314 */
315         }
316
317         return 1;
318 }
319 #endif
320
321 AFB_error httpdStart(AFB_session * session)
322 {
323
324         if (!my_default_init(session)) {
325                 printf("Error: initialisation of httpd failed");
326                 return AFB_FATAL;
327         }
328
329         /* Initialise Client Session Hash Table */
330         ctxStoreInit(CTX_NBCLIENTS);
331
332 #if defined(USE_MAGIC_MIME_TYPE)
333         /*TBD open libmagic cache [fail to pass EFENCE check (allocating 0 bytes)] */
334         init_lib_magic (session);
335 #endif
336
337         if (verbose) {
338                 printf("AFB:notice Waiting port=%d rootdir=%s\n", session->config->httpdPort, session->config->rootdir);
339                 printf("AFB:notice Browser URL= http:/*localhost:%d\n", session->config->httpdPort);
340         }
341
342         session->httpd = MHD_start_daemon(
343                 MHD_USE_EPOLL_LINUX_ONLY | MHD_USE_TCP_FASTOPEN | MHD_USE_DEBUG,
344                 (uint16_t) session->config->httpdPort,  /* port */
345                 new_client_handler, NULL,       /* Tcp Accept call back + extra attribute */
346                 access_handler, session,        /* Http Request Call back + extra attribute */
347                 MHD_OPTION_NOTIFY_COMPLETED, end_handler, session,
348                 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)15,        /* 15 seconds */
349                 MHD_OPTION_END);        /* options-end */
350
351         if (session->httpd == NULL) {
352                 printf("Error: httpStart invalid httpd port: %d", session->config->httpdPort);
353                 return AFB_FATAL;
354         }
355         return AFB_SUCCESS;
356 }
357
358 /* infinite loop */
359 AFB_error httpdLoop(AFB_session * session)
360 {
361         int count = 0;
362         const union MHD_DaemonInfo *info;
363         struct pollfd pfd;
364
365         info = MHD_get_daemon_info(session->httpd, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY);
366         if (info == NULL) {
367                 printf("Error: httpLoop no pollfd");
368                 goto error;
369         }
370         pfd.fd = info->listen_fd;
371         pfd.events = POLLIN;
372
373         if (verbose)
374                 fprintf(stderr, "AFB:notice entering httpd waiting loop\n");
375         while (TRUE) {
376                 if (verbose)
377                         fprintf(stderr, "AFB:notice httpd alive [%d]\n", count++);
378                 poll(&pfd, 1, 15000);   /* 15 seconds (as above timeout when starting) */
379                 MHD_run(session->httpd);
380         }
381
382  error:
383         /* should never return from here */
384         return AFB_FATAL;
385 }
386
387 int httpdStatus(AFB_session * session)
388 {
389         return MHD_run(session->httpd);
390 }
391
392 void httpdStop(AFB_session * session)
393 {
394         MHD_stop_daemon(session->httpd);
395 }