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 #define AFB_MSG_JTYPE "AJB_reply"
29 // handle to hold queryAll values
36 static json_object *afbJsonType;
39 // Sample Generic Ping Debug API
40 PUBLIC json_object* apiPingTest(AFB_request *request) {
42 json_object *response;
46 // request all query key/value
47 len = getQueryAll (request, query, sizeof(query));
48 if (len == 0) strcpy (query,"NoSearchQueryList");
50 // check if we have some post data
51 if (request->post == NULL) request->post="NoData";
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);
59 // Helper to retrieve argument from connection
60 PUBLIC const char* getQueryValue(AFB_request * request, char *name) {
63 value = MHD_lookup_connection_value(request->connection, MHD_GET_ARGUMENT_KIND, name);
67 STATIC int getQueryCB (void*handle, enum MHD_ValueKind kind, const char *key, const char *value) {
68 queryHandleT *query = (queryHandleT*)handle;
70 query->idx += snprintf (&query->msg[query->idx],query->len," %s: \'%s\',", key, value);
73 // Helper to retrieve argument from connection
74 PUBLIC int getQueryAll(AFB_request * request, char *buffer, size_t len) {
76 buffer[0] = '\0'; // start with an empty string
81 MHD_get_connection_values (request->connection, MHD_GET_ARGUMENT_KIND, getQueryCB, &query);
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;
89 // if post handle was used let's free everything
91 if (verbose) fprintf(stderr, "End Post Request UID=%d\n", posthandle->uid);
92 free(posthandle->data);
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};
103 /*---------------------------------------------------------------
104 | Signal handler defined inside CallPluginApi to access Request
105 +---------------------------------------------------------------- */
106 void pluginError (int signum) {
108 AFB_clientCtx *context;
110 // unlock signal to allow a new signal to come
111 sigemptyset (&sigset);
112 sigaddset (&sigset, signum);
113 sigprocmask (SIG_UNBLOCK, &sigset, 0);
115 fprintf (stderr, "Oops:%s Plugin Api Timeout timeout\n", configTime());
116 longjmp (request->checkPluginCall, signum);
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)) {
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));
129 // save context before calling the API
130 status = setjmp (request->checkPluginCall);
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);
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());
149 // Trigger a timer to protect from unacceptable long time execution
150 alarm (request->config->apiTimeout);
153 // add client context to request
154 ctxClientGet(request);
156 // Effectively call the API with a subset of the context
157 jresp = plugin->apis[idx].callback(request);
159 // API should return NULL of a valid Json Object
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;
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);
170 // cancel timeout and plugin signal handle before next call
171 if (request->config->apiTimeout > 0) {
173 for (sig=0; signals[sig] != 0; sig++) {
174 signal (signals[sig], SIG_DFL);
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) {
189 static int postcount = 0; // static counter to debug POST protocol
190 char *baseurl, *baseapi, *urlcpy1, *urlcpy2, *query, *token, *uuid;
191 json_object *errMessage;
193 struct MHD_Response *webResponse;
194 const char *serialized, parsedurl;
196 AFB_HttpPost *posthandle = *con_cls;
197 AFB_clientCtx clientCtx;
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);
208 baseapi = strsep(&urlcpy2, "/");
209 if (baseapi == NULL) {
210 errMessage = jsonNewMessage(AFB_FATAL, "Invalid API call url=[%s]", url);
215 // if post data may come in multiple calls
216 if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
217 const char *encoding, *param;
219 AFB_HttpPost *posthandle = *con_cls;
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);
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);
232 if (contentlen > MAX_POST_SIZE) {
233 errMessage = jsonNewMessage(AFB_FATAL, "Post Date to big %d > %d", contentlen, MAX_POST_SIZE);
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
245 if (verbose) fprintf(stderr, "Create Post[%d] Size=%d\n", posthandle->uid, contentlen);
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);
255 memcpy(&posthandle->data[posthandle->len], upload_data, *upload_data_size);
256 posthandle->len = posthandle->len + *upload_data_size;
257 *upload_data_size = 0;
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);
268 // Before processing data, make sure buffer string is properly ended
269 posthandle->data[posthandle->len] = '\0';
270 request.post = posthandle->data;
272 if (verbose) fprintf(stderr, "Close Post[%d] Buffer=%s\n", posthandle->uid, request.post);
278 // build request structure
279 memset(&request, 0, sizeof (request));
280 request.connection = connection;
281 request.config = session->config;
283 request.plugin = baseurl;
284 request.api = baseapi;
285 request.jresp = json_object_new_object();
287 // increase reference count and add jtype to response
288 json_object_get (afbJsonType);
289 json_object_object_add (request.jresp, "jtype", afbJsonType);
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);
298 // No plugin was found
299 if (session->plugins[idx] == NULL) {
300 errMessage = jsonNewMessage(AFB_FATAL, "No Plugin=[%s]", request.plugin);
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);
310 serialized = json_object_to_json_string(request.jresp);
311 webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
314 // client did not pass token on URI let's use cookies
315 if (!request.restfull) {
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);
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);
326 MHD_destroy_response(webResponse);
327 json_object_put(request.jresp); // decrease reference rqtcount to free the json object
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
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) {
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);
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);
357 if (verbose) fprintf(stderr, "Loading plugin[%d] prefix=[%s] info=%s\n", idx, plugins[idx]->prefix, plugins[idx]->info);
359 // register private to plugin global context [cid=0]
360 plugins[idx]->ctxGlobal= malloc (sizeof (AFB_clientCtx));
361 plugins[idx]->ctxGlobal->cid=0;
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);
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);
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;
386 void initPlugins(AFB_session *session) {
387 static AFB_plugin * plugins[10];
388 afbJsonType = json_object_new_string (AFB_MSG_JTYPE);
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),
399 // complete plugins and save them within current sessions
400 session->plugins = RegisterPlugins(plugins);