74237adca7b2babe731699a9ad5c3a783f6f92b8
[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
22 #include <microhttpd.h>
23 #include <sys/stat.h>
24 #include "../include/local-def.h"
25
26 // proto missing from GCC
27 char *strcasestr(const char *haystack, const char *needle);
28
29
30 // Because of POST call multiple time requestApi we need to free POST handle here
31 STATIC void endRequest(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) {
32     AFB_HttpPost *posthandle = *con_cls;
33
34     // if post handle was used let's free everything
35     if (posthandle) {
36         if (verbose) fprintf(stderr, "End Post Request UID=%d\n", posthandle->uid);
37         free(posthandle->data);
38         free(posthandle);
39     }
40 }
41
42
43 PUBLIC json_object* pingSample (AFB_plugin *plugin, AFB_session *session, AFB_request *post) {
44     static pingcount=0;
45     json_object *response;
46     response = jsonNewMessage(AFB_SUCCESS, "Ping Binder Daemon %d", pingcount++);
47     if (verbose) fprintf(stderr, "%d: \n", pingcount);
48     return (response);
49 }
50
51 // Check of apiurl is declare in this plugin and call it
52 STATIC json_object * callPluginApi (AFB_plugin *plugin, AFB_session *session,  AFB_request *request) {
53     json_object *response;
54     int idx;
55     
56     // If a plugin hold this urlpath call its callback
57     for (idx=0; plugin->apis[idx].callback != NULL; idx++) {
58         if (!strcmp (plugin->apis[idx].name, request->api)) {
59            response = plugin->apis[idx].callback (session, request);
60            if (response != NULL) {
61                json_object_object_add (response, "jtype" ,plugin->jtype);
62            }
63            return (response);
64         }   
65     }
66     return (NULL);
67 }
68
69
70 // process rest API query
71 PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, const char *method, const char* url) {
72
73     char *baseurl, *baseapi, *urlcpy;
74     json_object *jsonResponse, *errMessage;
75     struct MHD_Response *webResponse;
76     const char *serialized, parsedurl;
77     AFB_request request;
78     int  idx, ret;  
79
80     // Extract plugin urlpath from request
81     urlcpy=strdup (url);
82     baseurl = strsep(&urlcpy, "/");
83     if (baseurl == NULL) {
84         errMessage = jsonNewMessage(AFB_FATAL, "Invalid Plugin/API call url=%s", url);
85         goto ExitOnError;
86     }
87     
88     baseapi = strsep(&urlcpy, "/");
89     if (baseapi == NULL) {
90         errMessage = jsonNewMessage(AFB_FATAL, "Invalid Plugin/API call url=%s/%s", baseurl, url);
91         goto ExitOnError;
92     }
93     
94     // build request structure
95     memset (&request, 0, sizeof (request));
96     request.connection = connection;
97     request.url        = url;
98     request.plugin     = baseurl;
99     request.api        = baseapi;
100
101     // if post wait as data may come in multiple calls
102     if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) {
103    
104         request.post="TO Be DONE"; 
105     } else {
106         request.post=NULL;
107     };
108     
109     // Search for a plugin with this urlpath
110     for (idx=0; session->plugins[idx] != NULL; idx++) {
111         if (!strcmp (session->plugins[idx]->prefix, baseurl)) {
112            jsonResponse = callPluginApi (session->plugins[idx], session, &request );
113            // free (urlcpy);
114            break;
115         }
116     }
117     // No plugin was found
118     if (session->plugins[idx] == NULL) {
119         errMessage = jsonNewMessage(AFB_FATAL, "No Plugin for %s", baseurl);
120         free (urlcpy);
121         goto ExitOnError;
122     }
123
124     // plugin callback did not return a valid Json Object
125     if (jsonResponse == NULL) {
126        errMessage = jsonNewMessage(AFB_FATAL, "No Plugin/API for %s/%s", baseurl, baseapi);
127        goto ExitOnError;
128     }
129
130     serialized = json_object_to_json_string(jsonResponse);
131     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
132
133     ret = MHD_queue_response(connection, MHD_HTTP_OK, webResponse);
134     MHD_destroy_response(webResponse);
135     json_object_put(jsonResponse); // decrease reference rqtcount to free the json object
136     return ret;
137
138 ExitOnError:
139     serialized = json_object_to_json_string(errMessage);
140     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
141     ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, webResponse);
142     MHD_destroy_response(webResponse);
143     json_object_put(errMessage); // decrease reference rqtcount to free the json object
144     return ret;
145 }
146
147 // Helper to retreive argument from  connection
148 PUBLIC const char* getQueryValue (AFB_request * request, char *name) {
149     const char *value;
150     
151     value=MHD_lookup_connection_value(request->connection, MHD_GET_ARGUMENT_KIND, name);
152     return (value);
153 }
154
155 // Loop on plugins. Check that they have the right type, prepare a JSON object with prefix
156 STATIC AFB_plugin ** RegisterPlugins(AFB_plugin **plugins) {
157     int idx;
158     
159     for (idx=0; plugins[idx] != NULL; idx++) {
160         if (plugins[idx]->type != AFB_PLUGIN) {
161             fprintf (stderr, "ERROR: AFSV plugin[%d] invalid type=%d != %d\n", idx,  AFB_PLUGIN, plugins[idx]->type);
162         } else {
163             // some sanity controls
164             if ((plugins[idx]->prefix == NULL) || (plugins[idx]->info == NULL) || (plugins[idx]->apis == NULL)){
165                 if (plugins[idx]->prefix == NULL) plugins[idx]->prefix = "No URL prefix for APIs";
166                 if (plugins[idx]->info == NULL) plugins[idx]->info = "No Info describing plugin APIs";
167                 fprintf (stderr, "ERROR: plugin[%d] invalid prefix=%s info=%s", idx,plugins[idx]->prefix, plugins[idx]->info);
168                 return NULL;
169             }
170             
171             if (verbose) fprintf (stderr, "Loading plugin[%d] prefix=[%s] info=%s\n", idx, plugins[idx]->prefix, plugins[idx]->info);
172             
173             // Prepare Plugin name to be added into each API response
174             plugins[idx]->jtype = json_object_new_string (plugins[idx]->prefix);
175             json_object_get (plugins[idx]->jtype); // increase reference count to make it permanent
176             
177             // compute urlprefix lenght
178             plugins[idx]->prefixlen = strlen (plugins[idx]->prefix);
179         }  
180     }
181     return (plugins);
182 }
183
184 void initPlugins (AFB_session *session) {
185     static AFB_plugin *plugins[10];
186
187         plugins[0]= afsvRegister (session),
188         plugins[1]= dbusRegister (session),
189         plugins[2]= alsaRegister (session),
190         plugins[3]= NULL;
191
192     // complete plugins and save them within current sessions    
193     session->plugins=  RegisterPlugins (plugins);  
194 }