Intermediary Version Does not Compile
[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 'FILE *fp']
21  */
22
23 #include "../include/local-def.h"
24
25 #include <setjmp.h>
26 #include <signal.h>
27
28 #define AFB_MSG_JTYPE "AJB_reply"
29
30
31 // handle to hold queryAll values
32 typedef struct {
33      char    *msg;
34      int     idx;
35      size_t  len;
36 } queryHandleT;
37
38 static json_object     *afbJsonType;
39
40
41 // Sample Generic Ping Debug API
42 PUBLIC json_object* apiPingTest(AFB_request *request) {
43     static pingcount = 0;
44     json_object *response;
45     char query  [256];
46     char session[256];
47
48     int len;
49     AFB_clientCtx *client=request->client; // get client context from request
50     
51     // request all query key/value
52     len = getQueryAll (request, query, sizeof(query));
53     if (len == 0) strncpy (query, "NoSearchQueryList", sizeof(query));
54     
55     // check if we have some post data
56     if (request->post == NULL)  request->post="NoData"; 
57     
58     // check is we have a session and a plugin handle
59     if (client == NULL) strcpy (session,"NoSession");       
60     else snprintf(session, sizeof(session),"uuid=%s token=%s ctx=0x%x handle=0x%x", client->uuid, client->token, client->ctx, client->ctx); 
61         
62     // return response to caller
63     response = jsonNewMessage(AFB_SUCCESS, "Ping Binder Daemon count=%d CtxtId=%d query={%s} session={%s} PostData: [%s] "
64                , pingcount++, request->client->cid, query, session, request->post);
65     return (response);
66 }
67
68 // Helper to retrieve argument from  connection
69 PUBLIC const char* getQueryValue(AFB_request * request, char *name) {
70     const char *value;
71
72     value = MHD_lookup_connection_value(request->connection, MHD_GET_ARGUMENT_KIND, name);
73     return (value);
74 }
75
76 STATIC int getQueryCB (void*handle, enum MHD_ValueKind kind, const char *key, const char *value) {
77     queryHandleT *query = (queryHandleT*)handle;
78         
79     query->idx += snprintf (&query->msg[query->idx],query->len," %s: \'%s\',", key, value);
80 }
81
82 // Helper to retrieve argument from  connection
83 PUBLIC int getQueryAll(AFB_request * request, char *buffer, size_t len) {
84     queryHandleT query;
85     buffer[0] = '\0'; // start with an empty string
86     query.msg= buffer;
87     query.len= len;
88     query.idx= 0;
89
90     MHD_get_connection_values (request->connection, MHD_GET_ARGUMENT_KIND, getQueryCB, &query);
91     return (len);
92 }
93
94 // Because of POST call multiple time requestApi we need to free POST handle here
95 PUBLIC void endPostRequest(AFB_PostHandle *posthandle) {
96
97     if (posthandle->type == AFB_POST_JSON) {
98         if (verbose) fprintf(stderr, "End PostJson Request UID=%d\n", posthandle->uid);
99     }
100
101     if (posthandle->type == AFB_POST_FORM) {
102         AFB_PostHandle *postform = (AFB_PostHandle*) posthandle->private;
103         if (verbose) fprintf(stderr, "End PostForm Request UID=%d\n", posthandle->uid);
104
105         // call API termination callback
106         if (!posthandle->private) {
107
108             && !posthandle->private->completeCB) {
109            posthandle->private->completeCB (posthandle->private); 
110         }
111     }
112     freeRequest (posthandle->private);
113     free(posthandle);
114
115 }
116
117 // Check of apiurl is declare in this plugin and call it
118 STATIC AFB_error callPluginApi(AFB_plugin *plugin, AFB_request *request) {
119     json_object *jresp, *jcall;
120     int idx, status, sig;
121     int signals[]= {SIGALRM, SIGSEGV, SIGFPE, 0};
122     
123     /*---------------------------------------------------------------
124     | Signal handler defined inside CallPluginApi to access Request
125     +---------------------------------------------------------------- */
126     void pluginError (int signum) {
127       sigset_t sigset;
128       AFB_clientCtx *context;
129               
130       // unlock signal to allow a new signal to come
131       sigemptyset (&sigset);
132       sigaddset   (&sigset, signum);
133       sigprocmask (SIG_UNBLOCK, &sigset, 0);
134
135       fprintf (stderr, "Oops:%s Plugin Api Timeout timeout\n", configTime());
136       longjmp (request->checkPluginCall, signum);
137     }
138
139     
140     // If a plugin hold this urlpath call its callback
141     for (idx = 0; plugin->apis[idx].callback != NULL; idx++) {
142         if (!strcmp(plugin->apis[idx].name, request->api)) {
143             
144             // prepare an object to store calling values
145             jcall=json_object_new_object();
146             json_object_object_add(jcall, "prefix", json_object_new_string (plugin->prefix));
147             json_object_object_add(jcall, "api"   , json_object_new_string (plugin->apis[idx].name));
148             
149             // save context before calling the API
150             status = setjmp (request->checkPluginCall);
151             if (status != 0) {    
152                 
153                 // Plugin aborted somewhere during its execution
154                 json_object_object_add(jcall, "status", json_object_new_string ("abort"));
155                 json_object_object_add(jcall, "info" ,  json_object_new_string ("Plugin broke during execution"));
156                 json_object_object_add(request->jresp, "request", jcall);
157                 
158             } else {
159                 
160                 // If timeout protection==0 we are in debug and we do not apply signal protection
161                 if (request->config->apiTimeout > 0) {
162                     for (sig=0; signals[sig] != 0; sig++) {
163                        if (signal (signals[sig], pluginError) == SIG_ERR) {
164                            request->errcode = MHD_HTTP_UNPROCESSABLE_ENTITY;
165                            request->jresp = jsonNewMessage(AFB_FATAL, "%s ERR: Signal/timeout handler activation fail.", configTime());
166                            return AFB_FAIL;
167                        }
168                     }
169                     // Trigger a timer to protect from unacceptable long time execution
170                     alarm (request->config->apiTimeout);
171                 }
172                 
173                 // add client context to request
174                 ctxClientGet(request, plugin);      
175              
176                 // Effectively call the API with a subset of the context
177                 jresp = plugin->apis[idx].callback(request);
178
179                 // Allocate Json object and build response
180                 request->jresp  = json_object_new_object();
181                 json_object_get (afbJsonType);  // increate jsontype reference count
182                 json_object_object_add (request->jresp, "jtype", afbJsonType);
183                 
184                 // API should return NULL of a valid Json Object
185                 if (jresp == NULL) {
186                     json_object_object_add(jcall, "status", json_object_new_string ("null"));
187                     json_object_object_add(request->jresp, "request", jcall);
188                     request->errcode = MHD_HTTP_NO_RESPONSE;
189                     
190                 } else {
191                     json_object_object_add(jcall, "status", json_object_new_string ("processed"));
192                     json_object_object_add(request->jresp, "request", jcall);
193                     json_object_object_add(request->jresp, "response", jresp);
194                 }
195                 // cancel timeout and plugin signal handle before next call
196                 if (request->config->apiTimeout > 0) {
197                     alarm (0);
198                     for (sig=0; signals[sig] != 0; sig++) {
199                        signal (signals[sig], SIG_DFL);
200                     }
201                 }              
202             }       
203             return (AFB_DONE);
204         }
205     }
206     return (AFB_FAIL);
207 }
208
209 STATIC AFB_error findAndCallApi (AFB_request *request, void *extractx) {
210     int idx;
211     char *baseurl, *baseapi;
212     AFB_error status;
213    
214     // Search for a plugin with this urlpath
215     for (idx = 0; request->plugins[idx] != NULL; idx++) {
216         if (!strcmp(request->plugins[idx]->prefix, baseurl)) {
217             status =callPluginApi(request->plugins[idx], request, extractx);
218             break;
219         }
220     }
221     // No plugin was found
222     if (request->plugins[idx] == NULL) {
223         request->jresp = jsonNewMessage(AFB_FATAL, "No Plugin=[%s]", request->plugin);
224         goto ExitOnError;
225     }  
226     
227     // plugin callback did not return a valid Json Object
228     if (status != AFB_DONE) {
229         request->jresp = jsonNewMessage(AFB_FATAL, "No API=[%s] for Plugin=[%s]", request->api, request->plugin);
230         goto ExitOnError;
231     }
232    
233     // Everything look OK
234     return (status);
235     
236 ExitOnError:
237     request->errcode = MHD_HTTP_UNPROCESSABLE_ENTITY;
238     return (AFB_FAIL);
239 }
240
241 // This CB is call for every item with a form post it reformat iterator values
242 // and callback Plugin API for each Item within PostForm.
243 doPostIterate (void *cls, enum MHD_ValueKind kind, const char *key,
244               const char *filename, const char *mimetype,
245               const char *encoding, const char *data, uint64_t off,
246               size_t size) {
247   
248   AFB_error    status;
249   AFB_HttpItem item;
250     
251   // retrieve API request from Post iterator handle  
252   AFB_PostHandle *postctx  = (AFB_PostHandle*)cls;
253   AFB_request *request = (AFB_request*)post->private;
254   AFB_PostRequest post;
255   
256    
257   // Create and Item value for Plugin API
258   item.kind     = kind;
259   item.key      = key;
260   item.filename = filename;
261   item.mimetype = mimetype;
262   item.encoding = encoding;
263   item.len      = size;
264   item.data     = data;
265   item.off      = off;
266   
267   // Reformat Request to make it somehow similar to GET/PostJson case
268   post.data= (char*) postctx;
269   post.len = size;
270   post.type= AFB_POST_FORM;;
271   request->post = &post;
272   
273   // effectively call plugin API                 
274   status = findAndCallApi (request, &item);
275   
276   // when returning no processing of postform stop
277   if (status != AFB_SUCCESS) return MHD_NO;
278   
279   // let's allow iterator to move to next item
280   return (MHD_YES;);
281 }
282
283 STATIC void freeRequest (AFB_request *request) {
284  free (request->plugin);    
285  free (request->api);    
286  free (request);    
287 }
288
289 STATIC AFB_request *createRequest (struct MHD_Connection *connection, AFB_session *session, const char* url) {
290     
291     AFB_request *request;
292     
293     // Start with a clean request
294     request = calloc (1, sizeof (AFB_request));
295     char *urlcpy1, urlcpy2;
296       
297     // Extract plugin urlpath from request and make two copy because strsep overload copy
298     urlcpy1 = urlcpy2 = strdup(url);
299     baseurl = strsep(&urlcpy2, "/");
300     if (baseurl == NULL) {
301         errMessage = jsonNewMessage(AFB_FATAL, "Invalid API call url=[%s]", url);
302         goto ExitOnError;
303     }
304
305     // let's compute URL and call API
306     baseapi = strsep(&urlcpy2, "/");
307     if (baseapi == NULL) {
308         errMessage = jsonNewMessage(AFB_FATAL, "Invalid API call url=[%s]", url);
309         goto ExitOnError;
310     }
311     
312     // build request structure
313     request->connection = connection;
314     request->config = session.config;
315     request->url    = url;
316     request->plugin = strdup (baseurl);
317     request->api    = strdup (baseapi);
318     request->plugins= session->plugins;
319     
320     free(urlcpy1);
321 }
322
323 // process rest API query
324 PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, const char* url, const char *method
325     , const char *upload_data, size_t *upload_data_size, void **con_cls) {
326     
327     static int postcount = 0; // static counter to debug POST protocol
328     json_object *errMessage;
329     AFB_error status;
330     struct MHD_Response *webResponse;
331     const char *serialized;
332     AFB_request request;
333     AFB_PostHandle *posthandle = *con_cls;
334     int ret;
335   
336     // if post data may come in multiple calls
337     if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
338         const char *encoding, *param;
339         int contentlen = -1;
340         AFB_PostHandle *posthandle = *con_cls;
341
342         // This is the initial post event let's create form post structure POST datas come in multiple events
343         if (posthandle == NULL) {
344             fprintf(stderr, "This is the 1st Post Event postuid=%d\n", posthandle->uid);
345
346             // allocate application POST processor handle to zero
347             posthandle = cmalloc(1, sizeof (AFB_PostHandle));
348             posthandle->uid = postcount++; // build a UID for DEBUG
349             *con_cls = posthandle;         // attache POST handle to current HTTP request
350             
351             // Let make sure we have the right encoding and a valid length
352             encoding = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
353             param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
354             if (param) sscanf(param, "%i", &contentlen);
355         
356             // Form post is handle through a PostProcessor and call API once per form key
357             if (strcasestr(encoding, FORM_CONTENT) != NULL) {
358                
359                 posthandle = malloc(sizeof (AFB_PostHandle)); // allocate application POST processor handle
360                 posthandle->type   = AFB_POST_FORM;
361                 posthandle->private= (void*)createRequest (connection, session, url);
362                 posthandle->pp     = MHD_create_post_processor (connection, MAX_POST_SIZE, doPostIterate, posthandle);
363                
364                 if (NULL == posthandle->pp) {
365                     fprintf(stderr,"OOPS: Internal error fail to allocate MHD_create_post_processor\n");
366                     free (posthandle);
367                     return MHD_NO;
368                 }
369                 return MHD_YES;
370             }           
371         
372             // POST json is store into a buffer and present in one piece to API
373             if (strcasestr(encoding, JSON_CONTENT) != NULL) {
374
375                 if (contentlen > MAX_POST_SIZE) {
376                     errMessage = jsonNewMessage(AFB_FATAL, "Post Date to big %d > %d", contentlen, MAX_POST_SIZE);
377                     goto ExitOnError;
378                 }
379
380                 if (posthandle == NULL) {
381                     posthandle->type = AFB_POST_JSON;
382                     posthandle->private = malloc(contentlen + 1); // allocate memory for full POST data + 1 for '\0' enf of string
383
384                     if (verbose) fprintf(stderr, "Create PostJson[%d] Size=%d\n", posthandle->uid, contentlen);
385                     return MHD_YES;
386                 }
387
388                 // We only support Json and Form Post format
389                 errMessage = jsonNewMessage(AFB_FATAL, "Post Date wrong type encoding=%s != %s", encoding, JSON_CONTENT);
390                 goto ExitOnError;
391             }    
392         }
393
394         // This time we receive partial/all Post data. Note that even if we get all POST data. We should nevertheless
395         // return MHD_YES and not process the request directly. Otherwise Libmicrohttpd is unhappy and fails with
396         // 'Internal application error, closing connection'.            
397         if (*upload_data_size) {
398             if (verbose) fprintf(stderr, "Update Post[%d]\n", posthandle->uid);
399     
400             if (posthandle->type == AFB_POST_FORM) {
401                 MHD_post_process (con_info->postprocessor, upload_data, *upload_data_size);
402             }
403             
404             // Process JsonPost request when buffer is completed let's call API    
405             if (posthandle->type == AFB_POST_JSON) {
406
407                 memcpy(&posthandle->private[posthandle->len], upload_data, *upload_data_size);
408                 posthandle->len = posthandle->len + *upload_data_size;
409                 *upload_data_size = 0;
410             }
411             return MHD_YES;
412             
413         } else {  // we have finish with Post reception let's finish the work
414             
415             // Create a request structure to finalise the request
416             request= createRequest (connection, session, url);
417
418             // We should only start to process DATA after Libmicrohttpd call or application handler with *upload_data_size==0
419             if (posthandle->type == AFB_POST_FORM) {
420                 MHD_post_process (posthandle->pp, upload_data, *upload_data_size);
421             }
422             
423             if (posthandle->type == AFB_POST_JSON) {
424                 // At this level we're may verify that we got everything and process DATA
425                 if (posthandle->len != contentlen) {
426                     errMessage = jsonNewMessage(AFB_FATAL, "Post Data Incomplete UID=%d Len %d != %s", posthandle->uid, contentlen, posthandle->len);
427                     goto ExitOnError;
428                 }
429
430                 // Before processing data, make sure buffer string is properly ended
431                 posthandle->private[posthandle->len] = '\0';
432                 request->post.data = posthandle->private;
433                 request->post.type = posthandle->type;
434
435                 if (verbose) fprintf(stderr, "Close Post[%d] Buffer=%s\n", posthandle->uid, request.post);
436             }
437         }
438     } else {
439         // this is a get we only need a request
440         request= createRequest (connection, session, url);
441     };
442     
443     // Request is ready let's call API without any extra handle
444     status = findAndCallApi (request, NULL);
445     
446 ExitOnResponse:
447     freeRequest (request);
448     serialized = json_object_to_json_string(request.jresp);
449     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
450     
451     // client did not pass token on URI let's use cookies 
452     if ((!request.restfull) && (request.client != NULL)) {
453        char cookie[64]; 
454        snprintf (cookie, sizeof (cookie), "%s=%s", COOKIE_NAME,  request.client->uuid); 
455        MHD_add_response_header (webResponse, MHD_HTTP_HEADER_SET_COOKIE, cookie);
456     }
457     
458     // if requested add an error status
459     if (request.errcode != 0)  ret=MHD_queue_response (connection, request.errcode, webResponse);
460     else MHD_queue_response(connection, MHD_HTTP_OK, webResponse);
461     
462     MHD_destroy_response(webResponse);
463     json_object_put(request.jresp); // decrease reference rqtcount to free the json object
464     return MHD_YES;
465
466 ExitOnError:
467     freeRequest (request);
468     serialized = json_object_to_json_string(errMessage);
469     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
470     MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, webResponse);
471     MHD_destroy_response(webResponse);
472     json_object_put(errMessage); // decrease reference rqtcount to free the json object
473     return MHD_YES;
474 }
475
476
477 // Loop on plugins. Check that they have the right type, prepare a JSON object with prefix
478 STATIC AFB_plugin ** RegisterJsonPlugins(AFB_plugin **plugins) {
479     int idx, jdx;
480
481     for (idx = 0; plugins[idx] != NULL; idx++) {
482         if (plugins[idx]->type != AFB_PLUGIN_JSON) {
483             fprintf(stderr, "ERROR: AFSV plugin[%d] invalid type=%d != %d\n", idx, AFB_PLUGIN_JSON, plugins[idx]->type);
484         } else {
485             // some sanity controls
486             if ((plugins[idx]->prefix == NULL) || (plugins[idx]->info == NULL) || (plugins[idx]->apis == NULL)) {
487                 if (plugins[idx]->prefix == NULL) plugins[idx]->prefix = "No URL prefix for APIs";
488                 if (plugins[idx]->info == NULL) plugins[idx]->info = "No Info describing plugin APIs";
489                 fprintf(stderr, "ERROR: plugin[%d] invalid prefix=%s info=%s", idx, plugins[idx]->prefix, plugins[idx]->info);
490                 return NULL;
491             }
492
493             if (verbose) fprintf(stderr, "Loading plugin[%d] prefix=[%s] info=%s\n", idx, plugins[idx]->prefix, plugins[idx]->info);
494             
495             // Prebuild plugin jtype to boost API response
496             plugins[idx]->jtype = json_object_new_string(plugins[idx]->prefix);
497             json_object_get(plugins[idx]->jtype); // increase reference count to make it permanent
498             plugins[idx]->prefixlen = strlen(plugins[idx]->prefix);
499             
500               
501             // Prebuild each API jtype to boost API json response
502             for (jdx = 0; plugins[idx]->apis[jdx].name != NULL; jdx++) {
503                 AFB_privateApi *private = malloc (sizeof (AFB_privateApi));
504                 if (plugins[idx]->apis[jdx].private != NULL) {
505                     fprintf (stderr, "WARNING: plugin=%s api=%s private handle should be NULL=0x%x\n"
506                             ,plugins[idx]->prefix,plugins[idx]->apis[jdx].name, plugins[idx]->apis[jdx].private);
507                 }
508                 private->len = strlen (plugins[idx]->apis[jdx].name);
509                 private->jtype=json_object_new_string(plugins[idx]->apis[jdx].name);
510                 json_object_get(private->jtype); // increase reference count to make it permanent
511                 plugins[idx]->apis[jdx].private = private;
512             }
513         }
514     }
515     return (plugins);
516 }
517
518 void initPlugins(AFB_session *session) {
519     static AFB_plugin * plugins[10];
520     afbJsonType = json_object_new_string (AFB_MSG_JTYPE);
521     int i = 0;
522
523     plugins[i++] = afsvRegister(session),
524     plugins[i++] = dbusRegister(session),
525     plugins[i++] = alsaRegister(session),
526 #ifdef HAVE_RADIO_PLUGIN
527     plugins[i++] = radioRegister(session),
528 #endif
529     plugins[i++] = NULL;
530     
531     // complete plugins and save them within current sessions    
532     session->plugins = RegisterJsonPlugins(plugins);
533 }