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