Switch to a plugin model
[src/app-framework-binder.git] / src / rest-api.c
1 /*
2  * Copyright (C) 2015 "IoT.bzh"
3  * Author "Fulup Ar Foll"
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  * Contain all generic part to handle REST/API
19  * 
20  *  https://www.gnu.org/software/libmicrohttpd/tutorial.html [search 'largepost.c']
21  */
22
23 #include "../include/local-def.h"
24
25 #include <dirent.h>
26 #include <dlfcn.h>
27 #include <setjmp.h>
28 #include <signal.h>
29
30 #define AFB_MSG_JTYPE "AJB_reply"
31
32
33 // handle to hold queryAll values
34 typedef struct {
35      char    *msg;
36      int     idx;
37      size_t  len;
38 } queryHandleT;
39
40 static json_object     *afbJsonType;
41
42
43 // Sample Generic Ping Debug API
44 PUBLIC json_object* apiPingTest(AFB_request *request) {
45     static pingcount = 0;
46     json_object *response;
47     char query  [256];
48     char session[256];
49
50     int len;
51     AFB_clientCtx *client=request->client; // get client context from request
52     
53     // request all query key/value
54     len = getQueryAll (request, query, sizeof(query));
55     if (len == 0) strncpy (query, "NoSearchQueryList", sizeof(query));
56     
57     // check if we have some post data
58     if (request->post == NULL)  request->post->data="NoData"; 
59     
60     // check is we have a session and a plugin handle
61     if (client == NULL) strcpy (session,"NoSession");       
62     else snprintf(session, sizeof(session),"uuid=%s token=%s ctx=0x%x handle=0x%x", client->uuid, client->token, client->ctx, client->ctx); 
63         
64     // return response to caller
65     response = jsonNewMessage(AFB_SUCCESS, "Ping Binder Daemon count=%d CtxtId=%d query={%s} session={%s} PostData: [%s] "
66                , pingcount++, request->client->cid, query, session, request->post->data);
67     return (response);
68 }
69
70
71 // Helper to retrieve argument from  connection
72 PUBLIC const char* getQueryValue(AFB_request * request, char *name) {
73     const char *value;
74
75     value = MHD_lookup_connection_value(request->connection, MHD_GET_ARGUMENT_KIND, name);
76     return (value);
77 }
78
79 STATIC int getQueryCB (void*handle, enum MHD_ValueKind kind, const char *key, const char *value) {
80     queryHandleT *query = (queryHandleT*)handle;
81         
82     query->idx += snprintf (&query->msg[query->idx],query->len," %s: \'%s\',", key, value);
83 }
84
85 // Helper to retrieve argument from  connection
86 PUBLIC int getQueryAll(AFB_request * request, char *buffer, size_t len) {
87     queryHandleT query;
88     buffer[0] = '\0'; // start with an empty string
89     query.msg= buffer;
90     query.len= len;
91     query.idx= 0;
92
93     MHD_get_connection_values (request->connection, MHD_GET_ARGUMENT_KIND, getQueryCB, &query);
94     return (len);
95 }
96
97
98 // Helper to retreive POST handle
99 PUBLIC AFB_PostHandle* getPostHandle (AFB_request *request) {
100     if (request->post == NULL) return (NULL);
101     return ((AFB_PostHandle*) request->post->data);
102 }
103
104 // Because of POST call multiple time requestApi we need to free POST handle here
105 // Note this method is called from http-svc just before closing session
106 PUBLIC void endPostRequest(AFB_PostHandle *postHandle) {
107
108     if (postHandle->type == AFB_POST_JSON) {
109         // if (verbose) fprintf(stderr, "End PostJson Request UID=%d\n", postHandle->uid);
110     }
111
112     if (postHandle->type == AFB_POST_FORM) {
113          if (verbose) fprintf(stderr, "End PostForm Request UID=%d\n", postHandle->uid);
114     }
115     free(postHandle->private);
116     free(postHandle);
117 }
118
119 // Check of apiurl is declare in this plugin and call it
120 STATIC AFB_error callPluginApi(AFB_plugin *plugin, AFB_request *request, void *context) {
121     json_object *jresp, *jcall;
122     int idx, status, sig;
123     int signals[]= {SIGALRM, SIGSEGV, SIGFPE, 0};
124     
125     /*---------------------------------------------------------------
126     | Signal handler defined inside CallPluginApi to access Request
127     +---------------------------------------------------------------- */
128     void pluginError (int signum) {
129       sigset_t sigset;
130       AFB_clientCtx *context;
131               
132       // unlock signal to allow a new signal to come
133       sigemptyset (&sigset);
134       sigaddset   (&sigset, signum);
135       sigprocmask (SIG_UNBLOCK, &sigset, 0);
136
137       fprintf (stderr, "Oops:%s Plugin Api Timeout timeout\n", configTime());
138       longjmp (request->checkPluginCall, signum);
139     }
140
141     
142     // If a plugin hold this urlpath call its callback
143     for (idx = 0; plugin->apis[idx].callback != NULL; idx++) {
144         if (!strcmp(plugin->apis[idx].name, request->api)) {
145             
146             // Request was found and at least partially executed
147             request->jresp  = json_object_new_object();
148             json_object_get (afbJsonType);  // increate jsontype reference count
149             json_object_object_add (request->jresp, "jtype", afbJsonType);
150             
151             // prepare an object to store calling values
152             jcall=json_object_new_object();
153             json_object_object_add(jcall, "prefix", json_object_new_string (plugin->prefix));
154             json_object_object_add(jcall, "api"   , json_object_new_string (plugin->apis[idx].name));
155             
156             // save context before calling the API
157             status = setjmp (request->checkPluginCall);
158             if (status != 0) {    
159                 
160                 // Plugin aborted somewhere during its execution
161                 json_object_object_add(jcall, "status", json_object_new_string ("abort"));
162                 json_object_object_add(jcall, "info" ,  json_object_new_string ("Plugin broke during execution"));
163                 json_object_object_add(request->jresp, "request", jcall);
164                 
165             } else {
166                 
167                 // If timeout protection==0 we are in debug and we do not apply signal protection
168                 if (request->config->apiTimeout > 0) {
169                     for (sig=0; signals[sig] != 0; sig++) {
170                        if (signal (signals[sig], pluginError) == SIG_ERR) {
171                             request->errcode = MHD_HTTP_UNPROCESSABLE_ENTITY;
172                             json_object_object_add(jcall, "status", json_object_new_string ("fail"));
173                             json_object_object_add(jcall, "info", json_object_new_string ("Setting Timeout Handler Failed"));
174                             json_object_object_add(request->jresp, "request", jcall);
175                             return AFB_DONE;
176                        }
177                     }
178                     // Trigger a timer to protect from unacceptable long time execution
179                     alarm (request->config->apiTimeout);
180                 }
181
182                 // Out of SessionNone every call get a client context session
183                 if (AFB_SESSION_NONE != plugin->apis[idx].session) {
184                     
185                     // add client context to request
186                     if (ctxClientGet(request, plugin) != AFB_SUCCESS) {
187                         request->errcode=MHD_HTTP_INSUFFICIENT_STORAGE;
188                         json_object_object_add(jcall, "status", json_object_new_string ("fail"));
189                         json_object_object_add(jcall, "info", json_object_new_string ("Client Session Context Full !!!"));
190                         json_object_object_add(request->jresp, "request", jcall);
191                         return (AFB_DONE);                              
192                     };
193                     
194                     if (verbose) fprintf(stderr, "Plugin=[%s] Api=[%s] Middleware=[%d] Client=[0x%x] Uuid=[%s] Token=[%s]\n"
195                            , request->plugin, request->api, plugin->apis[idx].session, request->client, request->client->uuid, request->client->token);                        
196                     
197                     switch(plugin->apis[idx].session) {
198
199                         case AFB_SESSION_CREATE:
200                             if (request->client->token[0] != '\0') {
201                                 request->errcode=MHD_HTTP_UNAUTHORIZED;
202                                 json_object_object_add(jcall, "status", json_object_new_string ("exist"));
203                                 json_object_object_add(jcall, "info", json_object_new_string ("AFB_SESSION_CREATE Session already exist"));
204                                 json_object_object_add(request->jresp, "request", jcall);
205                                 return (AFB_DONE);                              
206                             }
207                         
208                             if (AFB_SUCCESS != ctxTokenCreate (request)) {
209                                 request->errcode=MHD_HTTP_UNAUTHORIZED;
210                                 json_object_object_add(jcall, "status", json_object_new_string ("fail"));
211                                 json_object_object_add(jcall, "info", json_object_new_string ("AFB_SESSION_CREATE Invalid Initial Token"));
212                                 json_object_object_add(request->jresp, "request", jcall);
213                                 return (AFB_DONE);
214                             } else {
215                                 json_object_object_add(jcall, "uuid", json_object_new_string (request->client->uuid));                                
216                                 json_object_object_add(jcall, "token", json_object_new_string (request->client->token));                                
217                                 json_object_object_add(jcall, "timeout", json_object_new_int (request->config->cntxTimeout));                                
218                             }
219                             break;
220
221
222                         case AFB_SESSION_RENEW:
223                             if (AFB_SUCCESS != ctxTokenRefresh (request)) {
224                                 request->errcode=MHD_HTTP_UNAUTHORIZED;
225                                 json_object_object_add(jcall, "status", json_object_new_string ("fail"));
226                                 json_object_object_add(jcall, "info", json_object_new_string ("AFB_SESSION_REFRESH Broken Exchange Token Chain"));
227                                 json_object_object_add(request->jresp, "request", jcall);
228                                 return (AFB_DONE);
229                             } else {
230                                 json_object_object_add(jcall, "uuid", json_object_new_string (request->client->uuid));                                
231                                 json_object_object_add(jcall, "token", json_object_new_string (request->client->token));                                
232                                 json_object_object_add(jcall, "timeout", json_object_new_int (request->config->cntxTimeout));                                
233                             }
234                             break;
235
236                         case AFB_SESSION_CLOSE:
237                             if (AFB_SUCCESS != ctxTokenCheck (request)) {
238                                 request->errcode=MHD_HTTP_UNAUTHORIZED;
239                                 json_object_object_add(jcall, "status", json_object_new_string ("empty"));
240                                 json_object_object_add(jcall, "info", json_object_new_string ("AFB_SESSION_CLOSE Not a Valid Access Token"));
241                                 json_object_object_add(request->jresp, "request", jcall);
242                                 return (AFB_DONE);
243                             } else {
244                                 json_object_object_add(jcall, "uuid", json_object_new_string (request->client->uuid));                                
245                             }
246                             break;
247                         
248                         case AFB_SESSION_CHECK:
249                         default: 
250                             // default action is check
251                             if (AFB_SUCCESS != ctxTokenCheck (request)) {
252                                 request->errcode=MHD_HTTP_UNAUTHORIZED;
253                                 json_object_object_add(jcall, "status", json_object_new_string ("fail"));
254                                 json_object_object_add(jcall, "info", json_object_new_string ("AFB_SESSION_CHECK Invalid Active Token"));
255                                 json_object_object_add(request->jresp, "request", jcall);
256                                 return (AFB_DONE);
257                             }
258                             break;
259                     }
260                 }
261                 
262                 // Effectively call the API with a subset of the context
263                 jresp = plugin->apis[idx].callback(request, context);
264                 
265                 // handle intemediatry Post Iterates out of band
266                 if ((jresp == NULL) && (request->errcode == MHD_HTTP_OK)) return (AFB_SUCCESS);
267
268                 // Session close is done after the API call so API can still use session in closing API
269                 if (AFB_SESSION_CLOSE == plugin->apis[idx].session) ctxTokenReset (request);                    
270                 
271                 // API should return NULL of a valid Json Object
272                 if (jresp == NULL) {
273                     json_object_object_add(jcall, "status", json_object_new_string ("null"));
274                     json_object_object_add(request->jresp, "request", jcall);
275                     request->errcode = MHD_HTTP_NO_RESPONSE;
276                     
277                 } else {
278                     json_object_object_add(jcall, "status", json_object_new_string ("processed"));
279                     json_object_object_add(request->jresp, "request", jcall);
280                     json_object_object_add(request->jresp, "response", jresp);
281                 }
282                 // cancel timeout and plugin signal handle before next call
283                 if (request->config->apiTimeout > 0) {
284                     alarm (0);
285                     for (sig=0; signals[sig] != 0; sig++) {
286                        signal (signals[sig], SIG_DFL);
287                     }
288                 }              
289             }       
290             return (AFB_DONE);
291         }
292     }   
293     return (AFB_FAIL);
294 }
295
296 STATIC AFB_error findAndCallApi (AFB_request *request, void *context) {
297     int idx;
298     AFB_error status;
299     
300    
301     // Search for a plugin with this urlpath
302     for (idx = 0; request->plugins[idx] != NULL; idx++) {
303         if (!strcmp(request->plugins[idx]->prefix, request->plugin)) {
304             status =callPluginApi(request->plugins[idx], request, context);
305             break;
306         }
307     }
308     // No plugin was found
309     if (request->plugins[idx] == NULL) {
310         request->jresp = jsonNewMessage(AFB_FATAL, "No Plugin=[%s]", request->plugin);
311         goto ExitOnError;
312     }  
313     
314     // plugin callback did not return a valid Json Object
315     if (status == AFB_FAIL) {
316         request->jresp = jsonNewMessage(AFB_FATAL, "No API=[%s] for Plugin=[%s]", request->api, request->plugin);
317         goto ExitOnError;
318     }
319     
320     // Everything look OK
321     return (status);
322     
323 ExitOnError:
324     request->errcode = MHD_HTTP_UNPROCESSABLE_ENTITY;
325     return (AFB_FAIL);
326 }
327
328 // This CB is call for every item with a form post it reformat iterator values
329 // and callback Plugin API for each Item within PostForm.
330 doPostIterate (void *cls, enum MHD_ValueKind kind, const char *key,
331               const char *filename, const char *mimetype,
332               const char *encoding, const char *data, uint64_t offset,
333               size_t size) {
334   
335   AFB_error    status;
336   AFB_PostItem item;
337     
338   // retrieve API request from Post iterator handle  
339   AFB_PostHandle *postHandle  = (AFB_PostHandle*)cls;
340   AFB_request *request = (AFB_request*)postHandle->private;
341   AFB_PostRequest postRequest;
342   
343   fprintf (stderr, "postHandle key=%s filename=%s len=%d mime=%s\n", key, filename, size, mimetype);
344    
345   // Create and Item value for Plugin API
346   item.kind     = kind;
347   item.key      = key;
348   item.filename = filename;
349   item.mimetype = mimetype;
350   item.encoding = encoding;
351   item.len      = size;
352   item.data     = data;
353   item.offset   = offset;
354   
355   // Reformat Request to make it somehow similar to GET/PostJson case
356   postRequest.data= (char*) postHandle;
357   postRequest.len = size;
358   postRequest.type= AFB_POST_FORM;;
359   request->post = &postRequest;
360   
361   // effectively call plugin API                 
362   status = findAndCallApi (request, &item);
363   // when returning no processing of postform stop
364   if (status != AFB_SUCCESS) return MHD_NO;
365   
366   // let's allow iterator to move to next item
367   return (MHD_YES);
368 }
369
370 STATIC void freeRequest (AFB_request *request) {
371
372  free (request->plugin);    
373  free (request->api);    
374  free (request);    
375 }
376
377 STATIC AFB_request *createRequest (struct MHD_Connection *connection, AFB_session *session, const char* url) {
378     
379     AFB_request *request;
380     
381     // Start with a clean request
382     request = calloc (1, sizeof (AFB_request));
383     char *urlcpy1, *urlcpy2;
384     char *baseapi, *baseurl;  
385       
386     // Extract plugin urlpath from request and make two copy because strsep overload copy
387     urlcpy1 = urlcpy2 = strdup(url);
388     baseurl = strsep(&urlcpy2, "/");
389     if (baseurl == NULL) {
390         request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call url=[%s]", url);
391     }
392
393     // let's compute URL and call API
394     baseapi = strsep(&urlcpy2, "/");
395     if (baseapi == NULL) {
396         request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call url=[%s]", url);
397     }
398     
399     // build request structure
400     request->connection = connection;
401     request->config = session->config;
402     request->url    = url;
403     request->plugin = strdup (baseurl);
404     request->api    = strdup (baseapi);
405     request->plugins= session->plugins;
406     
407     free(urlcpy1);
408     return (request);
409 }
410
411 // process rest API query
412 PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, const char* url, const char *method
413     , const char *upload_data, size_t *upload_data_size, void **con_cls) {
414     
415     static int postcount = 0; // static counter to debug POST protocol
416     json_object *errMessage;
417     AFB_error status;
418     struct MHD_Response *webResponse;
419     const char *serialized;
420     AFB_request *request;
421     AFB_PostHandle *postHandle;
422     AFB_PostRequest postRequest;
423     int ret;
424   
425     // if post data may come in multiple calls
426     if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
427         const char *encoding, *param;
428         int contentlen = -1;
429         postHandle = *con_cls;
430
431         // This is the initial post event let's create form post structure POST datas come in multiple events
432         if (postHandle == NULL) {
433
434             // allocate application POST processor handle to zero
435             postHandle = calloc(1, sizeof (AFB_PostHandle));
436             postHandle->uid = postcount++; // build a UID for DEBUG
437             
438             // Let make sure we have the right encoding and a valid length
439             encoding = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
440             
441             // We are facing an empty post let's process it as a get
442             if (encoding == NULL) {
443                 request= createRequest (connection, session, url);
444                 goto ProcessApiCall;
445             }
446         
447             // Form post is handle through a PostProcessor and call API once per form key
448             if (strcasestr(encoding, FORM_CONTENT) != NULL) {
449                 if (verbose) fprintf(stderr, "Create PostForm[uid=%d]\n", postHandle->uid);
450
451                 request = createRequest (connection, session, url);
452                 if (request->jresp != NULL) {
453                     errMessage = request->jresp;
454                     goto ExitOnError;
455                 }
456                 postHandle = malloc(sizeof (AFB_PostHandle)); // allocate application POST processor handle
457                 postHandle->type   = AFB_POST_FORM;
458                 postHandle->pp     = MHD_create_post_processor (connection, MAX_POST_SIZE, doPostIterate, postHandle);
459                 postHandle->private= (void*)request;
460                 *con_cls = postHandle;  // update context with posthandle
461                 
462                 if (NULL == postHandle->pp) {
463                     fprintf(stderr,"OOPS: Internal error fail to allocate MHD_create_post_processor\n");
464                     free (postHandle);
465                     return MHD_NO;
466                 }
467                 return MHD_YES;
468             }           
469         
470             // POST json is store into a buffer and present in one piece to API
471             if (strcasestr(encoding, JSON_CONTENT) != NULL) {
472
473                 param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
474                 if (param) sscanf(param, "%i", &contentlen);
475
476                 // Because PostJson are build in RAM size is constrained
477                 if (contentlen > MAX_POST_SIZE) {
478                     errMessage = jsonNewMessage(AFB_FATAL, "Post Date to big %d > %d", contentlen, MAX_POST_SIZE);
479                     goto ExitOnError;
480                 }
481
482                 // Size is OK, let's allocate a buffer to hold post data
483                 postHandle->type = AFB_POST_JSON;
484                 postHandle->private = malloc(contentlen + 1); // allocate memory for full POST data + 1 for '\0' enf of string
485
486                 // if (verbose) fprintf(stderr, "Create PostJson[uid=%d] Size=%d\n", postHandle->uid, contentlen);
487                 return MHD_YES;
488
489             } else {
490                 // We only support Json and Form Post format
491                 errMessage = jsonNewMessage(AFB_FATAL, "Post Date wrong type encoding=%s != %s", encoding, JSON_CONTENT);
492                 goto ExitOnError;                
493             }   
494         }
495
496         // This time we receive partial/all Post data. Note that even if we get all POST data. We should nevertheless
497         // return MHD_YES and not process the request directly. Otherwise Libmicrohttpd is unhappy and fails with
498         // 'Internal application error, closing connection'.            
499         if (*upload_data_size) {
500     
501             if (postHandle->type == AFB_POST_FORM) {
502                 // if (verbose) fprintf(stderr, "Processing PostForm[uid=%d]\n", postHandle->uid);
503                 MHD_post_process (postHandle->pp, upload_data, *upload_data_size);
504             }
505             
506             // Process JsonPost request when buffer is completed let's call API    
507             if (postHandle->type == AFB_POST_JSON) {
508                 // if (verbose) fprintf(stderr, "Updating PostJson[uid=%d]\n", postHandle->uid);
509                 memcpy(&postHandle->private[postHandle->len], upload_data, *upload_data_size);
510                 postHandle->len = postHandle->len + *upload_data_size;
511             }
512             
513             *upload_data_size = 0;
514             return MHD_YES;
515             
516         } else {  // we have finish with Post reception let's finish the work
517             
518             // Create a request structure to finalise the request
519             request= createRequest (connection, session, url);
520             if (request->jresp != NULL) {
521                 errMessage = request->jresp;
522                 goto ExitOnError;
523             }
524             
525             // Postform add application context handle to request
526             if (postHandle->type == AFB_POST_FORM) {
527                postRequest.data = (char*) postHandle;
528                postRequest.type = postHandle->type;
529                request->post = &postRequest;
530             }
531             
532             if (postHandle->type == AFB_POST_JSON) {
533                 // if (verbose) fprintf(stderr, "Processing PostJson[uid=%d]\n", postHandle->uid);
534
535                 param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
536                 if (param) sscanf(param, "%i", &contentlen);
537
538                 // At this level we're may verify that we got everything and process DATA
539                 if (postHandle->len != contentlen) {
540                     errMessage = jsonNewMessage(AFB_FATAL, "Post Data Incomplete UID=%d Len %d != %d", postHandle->uid, contentlen, postHandle->len);
541                     goto ExitOnError;
542                 }
543
544                 // Before processing data, make sure buffer string is properly ended
545                 postHandle->private[postHandle->len] = '\0';
546                 postRequest.data = postHandle->private;
547                 postRequest.type = postHandle->type;
548                 request->post = &postRequest;
549
550                 // if (verbose) fprintf(stderr, "Close Post[%d] Buffer=%s\n", postHandle->uid, request->post->data);
551             }
552         }
553     } else {
554         // this is a get we only need a request
555         request= createRequest (connection, session, url);
556     };
557
558 ProcessApiCall:    
559     // Request is ready let's call API without any extra handle
560     status = findAndCallApi (request, NULL);
561
562     serialized = json_object_to_json_string(request->jresp);
563     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
564     
565     // client did not pass token on URI let's use cookies 
566     if ((!request->restfull) && (request->client != NULL)) {
567        char cookie[64]; 
568        snprintf (cookie, sizeof (cookie), "%s=%s", COOKIE_NAME,  request->client->uuid); 
569        MHD_add_response_header (webResponse, MHD_HTTP_HEADER_SET_COOKIE, cookie);
570     }
571     
572     // if requested add an error status
573     if (request->errcode != 0)  ret=MHD_queue_response (connection, request->errcode, webResponse);
574     else MHD_queue_response(connection, MHD_HTTP_OK, webResponse);
575     
576     MHD_destroy_response(webResponse);
577     json_object_put(request->jresp); // decrease reference rqtcount to free the json object
578     freeRequest (request);
579     return MHD_YES;
580
581 ExitOnError:
582     freeRequest (request);
583     serialized = json_object_to_json_string(errMessage);
584     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
585     MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, webResponse);
586     MHD_destroy_response(webResponse);
587     json_object_put(errMessage); // decrease reference rqtcount to free the json object
588     return MHD_YES;
589 }
590
591
592 // Loop on plugins. Check that they have the right type, prepare a JSON object with prefix
593 STATIC AFB_plugin ** RegisterJsonPlugins(AFB_plugin **plugins) {
594     int idx, jdx;
595
596     for (idx = 0; plugins[idx] != NULL; idx++) {
597         if (plugins[idx]->type != AFB_PLUGIN_JSON) {
598             fprintf(stderr, "ERROR: AFSV plugin[%d] invalid type=%d != %d\n", idx, AFB_PLUGIN_JSON, plugins[idx]->type);
599         } else {
600             // some sanity controls
601             if ((plugins[idx]->prefix == NULL) || (plugins[idx]->info == NULL) || (plugins[idx]->apis == NULL)) {
602                 if (plugins[idx]->prefix == NULL) plugins[idx]->prefix = "No URL prefix for APIs";
603                 if (plugins[idx]->info == NULL) plugins[idx]->info = "No Info describing plugin APIs";
604                 fprintf(stderr, "ERROR: plugin[%d] invalid prefix=%s info=%s", idx, plugins[idx]->prefix, plugins[idx]->info);
605                 return NULL;
606             }
607
608             if (verbose) fprintf(stderr, "Loading plugin[%d] prefix=[%s] info=%s\n", idx, plugins[idx]->prefix, plugins[idx]->info);
609             
610             // Prebuild plugin jtype to boost API response
611             plugins[idx]->jtype = json_object_new_string(plugins[idx]->prefix);
612             json_object_get(plugins[idx]->jtype); // increase reference count to make it permanent
613             plugins[idx]->prefixlen = strlen(plugins[idx]->prefix);
614             
615               
616             // Prebuild each API jtype to boost API json response
617             for (jdx = 0; plugins[idx]->apis[jdx].name != NULL; jdx++) {
618                 AFB_privateApi *private = malloc (sizeof (AFB_privateApi));
619                 if (plugins[idx]->apis[jdx].private != NULL) {
620                     fprintf (stderr, "WARNING: plugin=%s api=%s private handle should be NULL=0x%x\n"
621                             ,plugins[idx]->prefix,plugins[idx]->apis[jdx].name, plugins[idx]->apis[jdx].private);
622                 }
623                 private->len = strlen (plugins[idx]->apis[jdx].name);
624                 private->jtype=json_object_new_string(plugins[idx]->apis[jdx].name);
625                 json_object_get(private->jtype); // increase reference count to make it permanent
626                 plugins[idx]->apis[jdx].private = private;
627             }
628         }
629     }
630     return (plugins);
631 }
632
633 void initPlugins(AFB_session *session) {
634     static AFB_plugin **plugins;
635     AFB_plugin* (*pluginRegisterFct)(void);
636     void *plugin;
637     char *pluginPath;
638     struct dirent *pluginDir;
639     DIR *dir;
640     afbJsonType = json_object_new_string (AFB_MSG_JTYPE);
641     int i = 0;
642
643     if ((dir = opendir(PLUGIN_INSTALL_DIR)) == NULL) {
644         fprintf(stderr, "Could not open plugin directory=%s\n", PLUGIN_INSTALL_DIR);
645         return;
646     }
647
648     while ((pluginDir = readdir(dir)) != NULL) {
649
650         if (!strstr (pluginDir->d_name, ".so"))
651             continue;
652
653         asprintf (&pluginPath, PLUGIN_INSTALL_DIR "/%s", pluginDir->d_name);
654         plugin = dlopen (pluginPath, RTLD_NOW | RTLD_LOCAL);
655         pluginRegisterFct = dlsym (plugin, "pluginRegister");
656         free (pluginPath);
657         if (!plugin) {
658             if (verbose) fprintf(stderr, "[%s] is not a binary plugin, continuing...\n", pluginDir->d_name);
659             continue;
660         } else if (!pluginRegisterFct) {
661             if (verbose) fprintf(stderr, "[%s] is not an AFB plugin, continuing...\n", pluginDir->d_name);
662             continue;
663         }
664
665         if (verbose) fprintf(stderr, "[%s] is a valid AFB plugin, loading it\n", pluginDir->d_name);
666         plugins = (AFB_plugin **) realloc (plugins, (i+1)*sizeof(AFB_plugin));
667         plugins[i] = (AFB_plugin *) malloc (sizeof(AFB_plugin));
668         plugins[i] = (**pluginRegisterFct)();
669         i++;
670     }
671     plugins[i++] = NULL;
672
673     closedir (dir);
674     
675     // complete plugins and save them within current sessions    
676     session->plugins = RegisterJsonPlugins(plugins);
677 }