2 * Copyright (C) 2015 "IoT.bzh"
3 * Author "Fulup Ar Foll"
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.
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.
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/>.
18 * Contain all generic part to handle REST/API
21 #include "../include/local-def.h"
26 // context save for timeout set/longjmp
27 static sigjmp_buf checkPluginCall;
29 // handle to hold queryAll values
37 // Helper to retrieve argument from connection
38 PUBLIC const char* getQueryValue(AFB_request * request, char *name) {
41 value = MHD_lookup_connection_value(request->connection, MHD_GET_ARGUMENT_KIND, name);
45 STATIC int getQueryCB (void*handle, enum MHD_ValueKind kind, const char *key, const char *value) {
46 queryHandleT *query = (queryHandleT*)handle;
48 query->idx += snprintf (&query->msg[query->idx],query->len," %s: \'%s\',", key, value);
51 // Helper to retrieve argument from connection
52 PUBLIC const char* getQueryAll(AFB_request * request, char *buffer, size_t len) {
59 MHD_get_connection_values (request->connection, MHD_GET_ARGUMENT_KIND, getQueryCB, &query);
64 // Sample Generic Ping Debug API
65 PUBLIC json_object* apiPingTest(AFB_session *session, AFB_request *request, void* handle) {
67 json_object *response;
70 // request all query key/value
71 getQueryAll (request, query, sizeof(query));
73 // check if we have some post data
74 if (request->post == NULL) request->post="NoData";
76 // return response to caller
77 response = jsonNewMessage(AFB_SUCCESS, "Ping Binder Daemon %d query={%s} PostData: \'%s\' ", pingcount++, query, request->post);
82 // Because of POST call multiple time requestApi we need to free POST handle here
84 STATIC void endRequest(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) {
85 AFB_HttpPost *posthandle = *con_cls;
87 // if post handle was used let's free everything
89 if (verbose) fprintf(stderr, "End Post Request UID=%d\n", posthandle->uid);
90 free(posthandle->data);
95 /*----------------------------------------------------------
97 +--------------------------------------------------------- */
98 STATIC void pluginError (int signum) {
102 // unlock timeout signal to allow a new signal to come
103 sigemptyset (&sigset);
104 sigaddset (&sigset, SIGALRM);
105 sigprocmask (SIG_UNBLOCK, &sigset, 0);
107 fprintf (stderr, "Oops:%s Plugin Api Timeout timeout\n", configTime());
108 longjmp (checkPluginCall, signum);
112 // Check of apiurl is declare in this plugin and call it
114 STATIC json_object * callPluginApi(AFB_plugin *plugin, AFB_session *session, AFB_request *request) {
115 json_object *response;
118 // If a plugin hold this urlpath call its callback
119 for (idx = 0; plugin->apis[idx].callback != NULL; idx++) {
120 if (!strcmp(plugin->apis[idx].name, request->api)) {
122 // save context before calling the API
123 status = setjmp (checkPluginCall);
125 response = jsonNewMessage(AFB_FATAL, "Plugin Call Fail prefix=%s api=%s info=%s", plugin->prefix, request->api, plugin->info);
127 if (signal (SIGALRM, pluginError) == SIG_ERR) {
128 fprintf (stderr, "%s ERR: main no Signal/timeout handler installed.", configTime());
132 if (signal (SIGSEGV, pluginError) == SIG_ERR) {
133 fprintf (stderr, "%s ERR: main no Signal/memory handler installed.", configTime());
137 if (signal (SIGFPE , pluginError) == SIG_ERR) {
138 fprintf (stderr, "%s ERR: main no Signal/memory handler installed.", configTime());
142 // protect plugin call with a timeout
143 alarm (session->config->apiTimeout);
145 response = plugin->apis[idx].callback(session, request, plugin->apis[idx].handle);
146 if (response != NULL) json_object_object_add(response, "jtype", plugin->jtype);
148 // cancel timeout and sleep before next aquisition
150 signal(SIGALRM, SIG_DFL);
151 signal(SIGSEGV, SIG_DFL);
152 signal(SIGFPE , SIG_DFL);
162 // process rest API query
164 PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, const char* url, const char *method
165 , const char *upload_data, size_t *upload_data_size, void **con_cls) {
167 static int postcount = 0; // static counter to debug POST protocol
168 char *baseurl, *baseapi, *urlcpy1, *urlcpy2, *query;
169 json_object *jsonResponse, *errMessage;
170 struct MHD_Response *webResponse;
171 const char *serialized, parsedurl;
173 AFB_HttpPost *posthandle = *con_cls;
176 // Extract plugin urlpath from request and make two copy because strsep overload copy
177 urlcpy1 = urlcpy2 = strdup(url);
178 baseurl = strsep(&urlcpy2, "/");
179 if (baseurl == NULL) {
180 errMessage = jsonNewMessage(AFB_FATAL, "Invalid Plugin/API call url=%s", url);
184 baseapi = strsep(&urlcpy2, "/");
185 if (baseapi == NULL) {
186 errMessage = jsonNewMessage(AFB_FATAL, "Invalid Plugin/API call url=%s/%s", baseurl, url);
191 // if post data may come in multiple calls
192 if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
193 const char *encoding, *param;
195 AFB_HttpPost *posthandle = *con_cls;
197 // Let make sure we have the right encoding and a valid length
198 encoding = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
199 param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
200 if (param) sscanf(param, "%i", &contentlen);
202 // POST datas may come in multiple chunk. Even when it never happen on AFB, we still have to handle the case
203 if (strcasestr(encoding, JSON_CONTENT) == 0) {
204 errMessage = jsonNewMessage(AFB_FATAL, "Post Date wrong type encoding=%s != %s", encoding, JSON_CONTENT);
208 if (contentlen > MAX_POST_SIZE) {
209 errMessage = jsonNewMessage(AFB_FATAL, "Post Date to big %d > %d", contentlen, MAX_POST_SIZE);
213 // In POST mode first libmicrohttp call only establishes POST handling.
214 if (posthandle == NULL) {
215 posthandle = malloc(sizeof (AFB_HttpPost)); // allocate application POST processor handle
216 posthandle->uid = postcount++; // build a UID for DEBUG
217 posthandle->len = 0; // effective length within POST handler
218 posthandle->data = malloc(contentlen + 1); // allocate memory for full POST data + 1 for '\0' enf of string
219 *con_cls = posthandle; // attache POST handle to current HTTP session
221 if (verbose) fprintf(stderr, "Create Post[%d] Size=%d\n", posthandle->uid, contentlen);
225 // This time we receive partial/all Post data. Note that even if we get all POST data. We should nevertheless
226 // return MHD_YES and not process the request directly. Otherwise Libmicrohttpd is unhappy and fails with
227 // 'Internal application error, closing connection'.
228 if (*upload_data_size) {
229 if (verbose) fprintf(stderr, "Update Post[%d]\n", posthandle->uid);
231 memcpy(&posthandle->data[posthandle->len], upload_data, *upload_data_size);
232 posthandle->len = posthandle->len + *upload_data_size;
233 *upload_data_size = 0;
237 // We should only start to process DATA after Libmicrohttpd call or application handler with *upload_data_size==0
238 // At this level we're may verify that we got everything and process DATA
239 if (posthandle->len != contentlen) {
240 errMessage = jsonNewMessage(AFB_FATAL, "Post Data Incomplete UID=%d Len %d != %s", posthandle->uid, contentlen, posthandle->len);
244 // Before processing data, make sure buffer string is properly ended
245 posthandle->data[posthandle->len] = '\0';
246 request.post = posthandle->data;
248 if (verbose) fprintf(stderr, "Close Post[%d] Buffer=%s\n", posthandle->uid, request.post);
255 // build request structure
256 memset(&request, 0, sizeof (request));
257 request.connection = connection;
259 request.plugin = baseurl;
260 request.api = baseapi;
262 // Search for a plugin with this urlpath
263 for (idx = 0; session->plugins[idx] != NULL; idx++) {
264 if (!strcmp(session->plugins[idx]->prefix, baseurl)) {
265 jsonResponse = callPluginApi(session->plugins[idx], session, &request);
270 // No plugin was found
271 if (session->plugins[idx] == NULL) {
272 errMessage = jsonNewMessage(AFB_FATAL, "No Plugin for %s", baseurl);
277 // plugin callback did not return a valid Json Object
278 if (jsonResponse == NULL) {
279 errMessage = jsonNewMessage(AFB_FATAL, "No Plugin/API for %s/%s", baseurl, baseapi);
283 serialized = json_object_to_json_string(jsonResponse);
284 webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
286 ret = MHD_queue_response(connection, MHD_HTTP_OK, webResponse);
287 MHD_destroy_response(webResponse);
288 json_object_put(jsonResponse); // decrease reference rqtcount to free the json object
292 serialized = json_object_to_json_string(errMessage);
293 webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
294 ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, webResponse);
295 MHD_destroy_response(webResponse);
296 json_object_put(errMessage); // decrease reference rqtcount to free the json object
301 // Loop on plugins. Check that they have the right type, prepare a JSON object with prefix
303 STATIC AFB_plugin ** RegisterPlugins(AFB_plugin **plugins) {
306 for (idx = 0; plugins[idx] != NULL; idx++) {
307 if (plugins[idx]->type != AFB_PLUGIN) {
308 fprintf(stderr, "ERROR: AFSV plugin[%d] invalid type=%d != %d\n", idx, AFB_PLUGIN, plugins[idx]->type);
310 // some sanity controls
311 if ((plugins[idx]->prefix == NULL) || (plugins[idx]->info == NULL) || (plugins[idx]->apis == NULL)) {
312 if (plugins[idx]->prefix == NULL) plugins[idx]->prefix = "No URL prefix for APIs";
313 if (plugins[idx]->info == NULL) plugins[idx]->info = "No Info describing plugin APIs";
314 fprintf(stderr, "ERROR: plugin[%d] invalid prefix=%s info=%s", idx, plugins[idx]->prefix, plugins[idx]->info);
318 if (verbose) fprintf(stderr, "Loading plugin[%d] prefix=[%s] info=%s\n", idx, plugins[idx]->prefix, plugins[idx]->info);
320 // Prepare Plugin name to be added into each API response
321 plugins[idx]->jtype = json_object_new_string(plugins[idx]->prefix);
322 json_object_get(plugins[idx]->jtype); // increase reference count to make it permanent
324 // compute urlprefix lenght
325 plugins[idx]->prefixlen = strlen(plugins[idx]->prefix);
331 void initPlugins(AFB_session *session) {
332 static AFB_plugin * plugins[10];
334 plugins[0] = afsvRegister(session),
335 plugins[1] = dbusRegister(session),
336 plugins[2] = alsaRegister(session),
339 // complete plugins and save them within current sessions
340 session->plugins = RegisterPlugins(plugins);