Added Session Management
[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
21 #include "../include/local-def.h"
22
23 #include <setjmp.h>
24 #include <signal.h>
25
26 #define AFB_MSG_JTYPE "AJB_reply"
27
28
29 // handle to hold queryAll values
30 typedef struct {
31      char    *msg;
32      int     idx;
33      size_t  len;
34 } queryHandleT;
35
36 static json_object     *afbJsonType;
37
38
39 // Sample Generic Ping Debug API
40 PUBLIC json_object* apiPingTest(AFB_request *request) {
41     static pingcount = 0;
42     json_object *response;
43     char query [512];
44     int len;
45
46     // request all query key/value
47     len = getQueryAll (request, query, sizeof(query));
48     if (len == 0) strcpy (query,"NoSearchQueryList");
49     
50     // check if we have some post data
51     if (request->post == NULL)  request->post="NoData";  
52         
53     // return response to caller
54     response = jsonNewMessage(AFB_SUCCESS, "Ping Binder Daemon count=%d CtxtId=%d Loa=%d query={%s} PostData: \'%s\' "
55                , pingcount++, request->client->cid, request->loa, query, request->post);
56     return (response);
57 }
58
59 // Helper to retrieve argument from  connection
60 PUBLIC const char* getQueryValue(AFB_request * request, char *name) {
61     const char *value;
62
63     value = MHD_lookup_connection_value(request->connection, MHD_GET_ARGUMENT_KIND, name);
64     return (value);
65 }
66
67 STATIC int getQueryCB (void*handle, enum MHD_ValueKind kind, const char *key, const char *value) {
68     queryHandleT *query = (queryHandleT*)handle;
69         
70     query->idx += snprintf (&query->msg[query->idx],query->len," %s: \'%s\',", key, value);
71 }
72
73 // Helper to retrieve argument from  connection
74 PUBLIC int getQueryAll(AFB_request * request, char *buffer, size_t len) {
75     queryHandleT query;
76     buffer[0] = '\0'; // start with an empty string
77     query.msg= buffer;
78     query.len= len;
79     query.idx= 0;
80
81     MHD_get_connection_values (request->connection, MHD_GET_ARGUMENT_KIND, getQueryCB, &query);
82     return (len);
83 }
84
85 // Because of POST call multiple time requestApi we need to free POST handle here
86 STATIC void endRequest(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) {
87     AFB_HttpPost *posthandle = *con_cls;
88
89     // if post handle was used let's free everything
90     if (posthandle) {
91         if (verbose) fprintf(stderr, "End Post Request UID=%d\n", posthandle->uid);
92         free(posthandle->data);
93         free(posthandle);
94     }
95 }
96
97 // Check of apiurl is declare in this plugin and call it
98 STATIC AFB_error callPluginApi(AFB_plugin *plugin, AFB_request *request) {
99     json_object *jresp, *jcall;
100     int idx, status, sig;
101     int signals[]= {SIGALRM, SIGSEGV, SIGFPE, 0};
102     
103     /*---------------------------------------------------------------
104     | Signal handler defined inside CallPluginApi to access Request
105     +---------------------------------------------------------------- */
106     void pluginError (int signum) {
107       sigset_t sigset;
108       AFB_clientCtx *context;
109               
110       // unlock signal to allow a new signal to come
111       sigemptyset (&sigset);
112       sigaddset   (&sigset, signum);
113       sigprocmask (SIG_UNBLOCK, &sigset, 0);
114
115       fprintf (stderr, "Oops:%s Plugin Api Timeout timeout\n", configTime());
116       longjmp (request->checkPluginCall, signum);
117     }
118
119     
120     // If a plugin hold this urlpath call its callback
121     for (idx = 0; plugin->apis[idx].callback != NULL; idx++) {
122         if (!strcmp(plugin->apis[idx].name, request->api)) {
123             
124             // prepare an object to store calling values
125             jcall=json_object_new_object();
126             json_object_object_add(jcall, "prefix", json_object_new_string (plugin->prefix));
127             json_object_object_add(jcall, "api"   , json_object_new_string (plugin->apis[idx].name));
128             
129             // save context before calling the API
130             status = setjmp (request->checkPluginCall);
131             if (status != 0) {    
132                 
133                 // Plugin aborted somewhere during its execution
134                 json_object_object_add(jcall, "status", json_object_new_string ("abort"));
135                 json_object_object_add(jcall, "info" ,  json_object_new_string ("Plugin broke during execution"));
136                 json_object_object_add(request->jresp, "request", jcall);
137                 
138             } else {
139                 
140                 // If timeout protection==0 we are in debug and we do not apply signal protection
141                 if (request->config->apiTimeout > 0) {
142                     for (sig=0; signals[sig] != 0; sig++) {
143                        if (signal (signals[sig], pluginError) == SIG_ERR) {
144                            request->errcode = MHD_HTTP_UNPROCESSABLE_ENTITY;
145                            fprintf (stderr, "%s ERR: main no Signal/timeout handler installed.", configTime());
146                            return AFB_FAIL;
147                        }
148                     }
149                     // Trigger a timer to protect from unacceptable long time execution
150                     alarm (request->config->apiTimeout);
151                 }
152                 
153                 // add client context to request
154                 ctxClientGet(request);      
155                 
156                 // Effectively call the API with a subset of the context
157                 jresp = plugin->apis[idx].callback(request);
158
159                 // API should return NULL of a valid Json Object
160                 if (jresp == NULL) {
161                     json_object_object_add(jcall, "status", json_object_new_string ("null"));
162                     json_object_object_add(request->jresp, "request", jcall);
163                     request->errcode = MHD_HTTP_NO_RESPONSE;
164                     
165                 } else {
166                     json_object_object_add(jcall, "status", json_object_new_string ("processed"));
167                     json_object_object_add(request->jresp, "request", jcall);
168                     json_object_object_add(request->jresp, "response", jresp);
169                 }
170                 // cancel timeout and plugin signal handle before next call
171                 if (request->config->apiTimeout > 0) {
172                     alarm (0);
173                     for (sig=0; signals[sig] != 0; sig++) {
174                        signal (signals[sig], SIG_DFL);
175                     }
176                 }              
177             }       
178             return (AFB_DONE);
179         }
180     }
181     return (AFB_FAIL);
182 }
183
184
185 // process rest API query
186 PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, const char* url, const char *method
187     , const char *upload_data, size_t *upload_data_size, void **con_cls) {
188     
189     static int postcount = 0; // static counter to debug POST protocol
190     char *baseurl, *baseapi, *urlcpy1, *urlcpy2, *query, *token, *uuid;
191     json_object *errMessage;
192     AFB_error status;
193     struct MHD_Response *webResponse;
194     const char *serialized, parsedurl;
195     AFB_request request;
196     AFB_HttpPost *posthandle = *con_cls;
197     AFB_clientCtx clientCtx;
198     int idx, ret;
199
200     // Extract plugin urlpath from request and make two copy because strsep overload copy
201     urlcpy1 = urlcpy2 = strdup(url);
202     baseurl = strsep(&urlcpy2, "/");
203     if (baseurl == NULL) {
204         errMessage = jsonNewMessage(AFB_FATAL, "Invalid API call url=[%s]", url);
205         goto ExitOnError;
206     }
207
208     baseapi = strsep(&urlcpy2, "/");
209     if (baseapi == NULL) {
210         errMessage = jsonNewMessage(AFB_FATAL, "Invalid API call url=[%s]", url);
211         goto ExitOnError;
212     }
213     
214
215     // if post data may come in multiple calls
216     if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
217         const char *encoding, *param;
218         int contentlen = -1;
219         AFB_HttpPost *posthandle = *con_cls;
220
221         // Let make sure we have the right encoding and a valid length
222         encoding = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
223         param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
224         if (param) sscanf(param, "%i", &contentlen);
225
226         // POST datas may come in multiple chunk. Even when it never happen on AFB, we still have to handle the case
227         if (strcasestr(encoding, JSON_CONTENT) == 0) {
228             errMessage = jsonNewMessage(AFB_FATAL, "Post Date wrong type encoding=%s != %s", encoding, JSON_CONTENT);
229             goto ExitOnError;
230         }
231
232         if (contentlen > MAX_POST_SIZE) {
233             errMessage = jsonNewMessage(AFB_FATAL, "Post Date to big %d > %d", contentlen, MAX_POST_SIZE);
234             goto ExitOnError;
235         }
236
237         // In POST mode first libmicrohttp call only establishes POST handling.
238         if (posthandle == NULL) {
239             posthandle = malloc(sizeof (AFB_HttpPost)); // allocate application POST processor handle
240             posthandle->uid = postcount++; // build a UID for DEBUG
241             posthandle->len = 0; // effective length within POST handler
242             posthandle->data = malloc(contentlen + 1); // allocate memory for full POST data + 1 for '\0' enf of string
243             *con_cls = posthandle; // attache POST handle to current HTTP session
244
245             if (verbose) fprintf(stderr, "Create Post[%d] Size=%d\n", posthandle->uid, contentlen);
246             return MHD_YES;
247         }
248
249         // This time we receive partial/all Post data. Note that even if we get all POST data. We should nevertheless
250         // return MHD_YES and not process the request directly. Otherwise Libmicrohttpd is unhappy and fails with
251         // 'Internal application error, closing connection'.
252         if (*upload_data_size) {
253             if (verbose) fprintf(stderr, "Update Post[%d]\n", posthandle->uid);
254
255             memcpy(&posthandle->data[posthandle->len], upload_data, *upload_data_size);
256             posthandle->len = posthandle->len + *upload_data_size;
257             *upload_data_size = 0;
258             return MHD_YES;
259         }
260
261         // We should only start to process DATA after Libmicrohttpd call or application handler with *upload_data_size==0
262         // At this level we're may verify that we got everything and process DATA
263         if (posthandle->len != contentlen) {
264             errMessage = jsonNewMessage(AFB_FATAL, "Post Data Incomplete UID=%d Len %d != %s", posthandle->uid, contentlen, posthandle->len);
265             goto ExitOnError;
266         }
267
268         // Before processing data, make sure buffer string is properly ended
269         posthandle->data[posthandle->len] = '\0';
270         request.post = posthandle->data;
271
272         if (verbose) fprintf(stderr, "Close Post[%d] Buffer=%s\n", posthandle->uid, request.post);
273
274     } else {
275         request.post = NULL;
276     };
277         
278     // build request structure
279     memset(&request, 0, sizeof (request));
280     request.connection = connection;
281     request.config     = session->config;
282     request.url = url;
283     request.plugin = baseurl;
284     request.api = baseapi;
285     request.jresp = json_object_new_object();
286     
287     // increase reference count and add jtype to response    
288     json_object_get (afbJsonType);
289     json_object_object_add (request.jresp, "jtype", afbJsonType);
290     
291     // Search for a plugin with this urlpath
292     for (idx = 0; session->plugins[idx] != NULL; idx++) {
293         if (!strcmp(session->plugins[idx]->prefix, baseurl)) {
294             status =callPluginApi(session->plugins[idx], &request);
295             break;
296         }
297     }
298     // No plugin was found
299     if (session->plugins[idx] == NULL) {
300         errMessage = jsonNewMessage(AFB_FATAL, "No Plugin=[%s]", request.plugin);
301         goto ExitOnError;
302     }
303
304     // plugin callback did not return a valid Json Object
305     if (status != AFB_DONE) {
306         errMessage = jsonNewMessage(AFB_FATAL, "No API=[%s] for Plugin=[%s]", request.api, request.plugin);
307         goto ExitOnError;
308     }
309
310     serialized = json_object_to_json_string(request.jresp);
311     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
312     free(urlcpy1);
313     
314     // client did not pass token on URI let's use cookies 
315     if (!request.restfull) {
316        char cookie[64]; 
317        snprintf (cookie, sizeof (cookie), "%s=%s", COOKIE_NAME,  request.client->uuid); 
318        MHD_add_response_header (webResponse, MHD_HTTP_HEADER_SET_COOKIE, cookie);
319        // if(verbose) fprintf(stderr,"Cookie: [%s]\n", cookie);
320     }
321     
322     // if requested add an error status
323     if (request.errcode != 0)  ret=MHD_queue_response (connection, request.errcode, webResponse);
324     else ret = MHD_queue_response(connection, MHD_HTTP_OK, webResponse);
325     
326     MHD_destroy_response(webResponse);
327     json_object_put(request.jresp); // decrease reference rqtcount to free the json object
328     return ret;
329
330 ExitOnError:
331     free(urlcpy1);
332     serialized = json_object_to_json_string(errMessage);
333     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
334     ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, webResponse);
335     MHD_destroy_response(webResponse);
336     json_object_put(errMessage); // decrease reference rqtcount to free the json object
337     return ret;
338 }
339
340
341 // Loop on plugins. Check that they have the right type, prepare a JSON object with prefix
342 STATIC AFB_plugin ** RegisterPlugins(AFB_plugin **plugins) {
343     int idx, jdx;
344
345     for (idx = 0; plugins[idx] != NULL; idx++) {
346         if (plugins[idx]->type != AFB_PLUGIN) {
347             fprintf(stderr, "ERROR: AFSV plugin[%d] invalid type=%d != %d\n", idx, AFB_PLUGIN, plugins[idx]->type);
348         } else {
349             // some sanity controls
350             if ((plugins[idx]->prefix == NULL) || (plugins[idx]->info == NULL) || (plugins[idx]->apis == NULL)) {
351                 if (plugins[idx]->prefix == NULL) plugins[idx]->prefix = "No URL prefix for APIs";
352                 if (plugins[idx]->info == NULL) plugins[idx]->info = "No Info describing plugin APIs";
353                 fprintf(stderr, "ERROR: plugin[%d] invalid prefix=%s info=%s", idx, plugins[idx]->prefix, plugins[idx]->info);
354                 return NULL;
355             }
356
357             if (verbose) fprintf(stderr, "Loading plugin[%d] prefix=[%s] info=%s\n", idx, plugins[idx]->prefix, plugins[idx]->info);
358             
359             // register private to plugin global context [cid=0]
360             plugins[idx]->ctxGlobal= malloc (sizeof (AFB_clientCtx));
361             plugins[idx]->ctxGlobal->cid=0;
362
363             // Prebuild plugin jtype to boost API response
364             plugins[idx]->jtype = json_object_new_string(plugins[idx]->prefix);
365             json_object_get(plugins[idx]->jtype); // increase reference count to make it permanent
366             plugins[idx]->prefixlen = strlen(plugins[idx]->prefix);
367             
368               
369             // Prebuild each API jtype to boost API json response
370             for (jdx = 0; plugins[idx]->apis[jdx].name != NULL; jdx++) {
371                 AFB_privateApi *private = malloc (sizeof (AFB_privateApi));
372                 if (plugins[idx]->apis[jdx].private != NULL) {
373                     fprintf (stderr, "WARNING: plugin=%s api=%s private handle should be NULL\n"
374                             ,plugins[idx]->prefix,plugins[idx]->apis[jdx].name);
375                 }
376                 private->len = strlen (plugins[idx]->apis[jdx].name);
377                 private->jtype=json_object_new_string(plugins[idx]->apis[jdx].name);
378                 json_object_get(private->jtype); // increase reference count to make it permanent
379                 plugins[idx]->apis[jdx].private = private;
380             }
381         }
382     }
383     return (plugins);
384 }
385
386 void initPlugins(AFB_session *session) {
387     static AFB_plugin * plugins[10];
388     afbJsonType = json_object_new_string (AFB_MSG_JTYPE);
389     int i = 0;
390
391     plugins[i++] = afsvRegister(session),
392     plugins[i++] = dbusRegister(session),
393     plugins[i++] = alsaRegister(session),
394 #ifdef HAVE_RADIO_PLUGIN
395     plugins[i++] = radioRegister(session),
396 #endif
397     plugins[i++] = NULL;
398     
399     // complete plugins and save them within current sessions    
400     session->plugins = RegisterPlugins(plugins);
401 }