REST: nothing triggers rest
[oweals/gnunet.git] / src / gns / plugin_rest_gns.c
1 /*
2    This file is part of GNUnet.
3    Copyright (C) 2012-2015 GNUnet e.V.
4
5    GNUnet is free software: you can redistribute it and/or modify it
6    under the terms of the GNU Affero General Public License as published
7    by the Free Software Foundation, either version 3 of the License,
8    or (at your option) any later version.
9
10    GNUnet is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Affero General Public License for more details.
14
15    You should have received a copy of the GNU Affero General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19    */
20 /**
21  * @author Philippe Buschmann
22  * @file gns/plugin_rest_gns.c
23  * @brief GNUnet Gns REST plugin
24  */
25
26 #include "platform.h"
27 #include "gnunet_rest_plugin.h"
28 #include "gnunet_rest_lib.h"
29 #include "gnunet_json_lib.h"
30 #include "gnunet_gnsrecord_lib.h"
31 #include "gnunet_gns_service.h"
32 #include "microhttpd.h"
33 #include <jansson.h>
34
35 /**
36  * Rest API GNS Namespace
37  */
38 #define GNUNET_REST_API_NS_GNS "/gns"
39
40 /**
41  * Rest API GNS Parameter record_type
42  */
43 #define GNUNET_REST_GNS_PARAM_RECORD_TYPE "record_type"
44
45 /**
46  * Rest API GNS ERROR Unknown Error
47  */
48 #define GNUNET_REST_GNS_ERROR_UNKNOWN "Unknown Error"
49
50 /**
51  * Rest API GNS ERROR Record not found
52  */
53 #define GNUNET_REST_GNS_NOT_FOUND "Record not found"
54
55 /**
56  * The configuration handle
57  */
58 const struct GNUNET_CONFIGURATION_Handle *cfg;
59
60 /**
61  * HTTP methods allows for this plugin
62  */
63 static char* allow_methods;
64
65 /**
66  * @brief struct returned by the initialization function of the plugin
67  */
68 struct Plugin
69 {
70   const struct GNUNET_CONFIGURATION_Handle *cfg;
71 };
72
73 /**
74  * The request handle
75  */
76 struct RequestHandle
77 {
78
79   /**
80    * Connection to GNS
81    */
82   struct GNUNET_GNS_Handle *gns;
83
84   /**
85    * Active GNS lookup
86    */
87   struct GNUNET_GNS_LookupWithTldRequest *gns_lookup;
88
89   /**
90    * Name to look up
91    */
92   char *name;
93
94   /**
95    * Record type to look up
96    */
97   int record_type;
98
99   /**
100    * Rest connection
101    */
102   struct GNUNET_REST_RequestHandle *rest_handle;
103   
104   /**
105    * Desired timeout for the lookup (default is no timeout).
106    */
107   struct GNUNET_TIME_Relative timeout;
108
109   /**
110    * ID of a task associated with the resolution process.
111    */
112   struct GNUNET_SCHEDULER_Task *timeout_task;
113
114   /**
115    * The plugin result processor
116    */
117   GNUNET_REST_ResultProcessor proc;
118
119   /**
120    * The closure of the result processor
121    */
122   void *proc_cls;
123
124   /**
125    * The url
126    */
127   char *url;
128
129   /**
130    * Error response message
131    */
132   char *emsg;
133
134   /**
135    * Response code
136    */
137   int response_code;
138
139 };
140
141
142 /**
143  * Cleanup lookup handle
144  * @param handle Handle to clean up
145  */
146 static void
147 cleanup_handle (void *cls)
148 {
149   struct RequestHandle *handle = cls;
150   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
151               "Cleaning up\n");
152
153   if (NULL != handle->gns_lookup)
154   {
155     GNUNET_GNS_lookup_with_tld_cancel (handle->gns_lookup);
156     handle->gns_lookup = NULL;
157   }
158   if (NULL != handle->gns)
159   {
160     GNUNET_GNS_disconnect (handle->gns);
161     handle->gns = NULL;
162   }
163
164   if (NULL != handle->timeout_task)
165   {
166     GNUNET_SCHEDULER_cancel (handle->timeout_task);
167     handle->timeout_task = NULL;
168   }
169   if (NULL != handle->url)
170     GNUNET_free (handle->url);
171   if (NULL != handle->name)
172     GNUNET_free (handle->name);
173   if (NULL != handle->emsg)
174     GNUNET_free (handle->emsg);
175   
176   GNUNET_free (handle);
177 }
178
179
180 /**
181  * Task run on errors.  Reports an error and cleans up everything.
182  *
183  * @param cls the `struct RequestHandle`
184  */
185 static void
186 do_error (void *cls)
187 {
188   struct RequestHandle *handle = cls;
189   struct MHD_Response *resp;
190   json_t *json_error = json_object();
191   char *response;
192
193   if (NULL == handle->emsg)
194     handle->emsg = GNUNET_strdup(GNUNET_REST_GNS_ERROR_UNKNOWN);
195
196   json_object_set_new(json_error,"error", json_string(handle->emsg));
197
198   if (0 == handle->response_code)
199     handle->response_code = MHD_HTTP_OK;
200   response = json_dumps (json_error, 0);
201   resp = GNUNET_REST_create_response (response);
202   handle->proc (handle->proc_cls, resp, handle->response_code);
203   json_decref(json_error);
204   GNUNET_free(response);
205   GNUNET_SCHEDULER_add_now (&cleanup_handle, handle);
206 }
207
208
209 /**
210  * Iterator called on obtained result for a GNS lookup.
211  *
212  * @param cls closure with the object
213  * @param was_gns #GNUNET_NO if name was not a GNS name
214  * @param rd_count number of records in @a rd
215  * @param rd the records in reply
216  */
217 static void
218 handle_gns_response (void *cls,
219                      int was_gns,
220                      uint32_t rd_count,
221                      const struct GNUNET_GNSRECORD_Data *rd)
222 {
223   struct RequestHandle *handle = cls;
224   struct MHD_Response *resp;
225   json_t *result_array;
226   json_t *record_obj;
227   char *result;
228
229   handle->gns_lookup = NULL;
230
231   if (GNUNET_NO == was_gns)
232   {
233     handle->response_code = MHD_HTTP_NOT_FOUND;
234     handle->emsg = GNUNET_strdup(GNUNET_REST_GNS_NOT_FOUND);
235     GNUNET_SCHEDULER_add_now (&do_error, handle);
236     return;
237   }
238
239   result_array = json_array();
240   for (uint32_t i=0;i<rd_count;i++)
241   {
242     if ((rd[i].record_type != handle->record_type) &&
243         (GNUNET_GNSRECORD_TYPE_ANY != handle->record_type) )
244     {
245       continue;
246     }
247
248     record_obj = GNUNET_JSON_from_gns_record(NULL,&rd[i]);
249     json_array_append (result_array, record_obj);
250     json_decref (record_obj);
251   }
252
253   result = json_dumps(result_array, 0);
254   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Result %s\n", result);
255   resp = GNUNET_REST_create_response (result);
256   handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
257   GNUNET_free (result);
258   json_decref (result_array);
259   GNUNET_SCHEDULER_add_now(&cleanup_handle, handle);
260 }
261
262
263 /**
264  * Handle gns GET request
265  *
266  * @param con_handle the connection handle
267  * @param url the url
268  * @param cls the RequestHandle
269  */
270 void
271 get_gns_cont (struct GNUNET_REST_RequestHandle *con_handle,
272               const char* url,
273               void *cls)
274 {
275   struct RequestHandle *handle = cls;
276   struct GNUNET_HashCode key;
277   char *record_type;
278   char *name;
279
280   name = NULL;
281   handle->name = NULL;
282   if (strlen (GNUNET_REST_API_NS_GNS) < strlen (handle->url))
283   {
284     name = &handle->url[strlen (GNUNET_REST_API_NS_GNS) + 1];
285   }
286
287   if (NULL == name)
288   {
289     handle->response_code = MHD_HTTP_NOT_FOUND;
290     handle->emsg = GNUNET_strdup(GNUNET_REST_GNS_NOT_FOUND);
291     GNUNET_SCHEDULER_add_now (&do_error, handle);
292     return;
293   }
294   if (0 >= strlen (name))
295   {
296     handle->response_code = MHD_HTTP_NOT_FOUND;
297     handle->emsg = GNUNET_strdup(GNUNET_REST_GNS_NOT_FOUND);
298     GNUNET_SCHEDULER_add_now (&do_error, handle);
299     return;
300   }
301   handle->name = GNUNET_strdup(name);
302
303   handle->record_type = UINT32_MAX;
304   GNUNET_CRYPTO_hash (GNUNET_REST_GNS_PARAM_RECORD_TYPE,
305                       strlen (GNUNET_REST_GNS_PARAM_RECORD_TYPE),
306                       &key);
307   if ( GNUNET_YES
308        == GNUNET_CONTAINER_multihashmap_contains (con_handle->url_param_map,
309                                                   &key))
310   {
311     record_type = GNUNET_CONTAINER_multihashmap_get (con_handle->url_param_map, &key);
312     handle->record_type = GNUNET_GNSRECORD_typename_to_number(record_type);
313   }
314
315   if(UINT32_MAX == handle->record_type)
316   {
317     handle->record_type = GNUNET_GNSRECORD_TYPE_ANY;
318   }
319
320   handle->gns_lookup = GNUNET_GNS_lookup_with_tld (handle->gns,
321                                                    handle->name,
322                                                    handle->record_type,
323                                                    GNUNET_NO,
324                                                    &handle_gns_response,
325                                                    handle);
326 }
327
328
329
330 /**
331  * Respond to OPTIONS request
332  *
333  * @param con_handle the connection handle
334  * @param url the url
335  * @param cls the RequestHandle
336  */
337 static void
338 options_cont (struct GNUNET_REST_RequestHandle *con_handle,
339               const char* url,
340               void *cls)
341 {
342   struct MHD_Response *resp;
343   struct RequestHandle *handle = cls;
344
345   //independent of path return all options
346   resp = GNUNET_REST_create_response (NULL);
347   MHD_add_response_header (resp,
348                            "Access-Control-Allow-Methods",
349                            allow_methods);
350   handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
351   GNUNET_SCHEDULER_add_now(&cleanup_handle, handle);
352   return;
353 }
354
355
356 /**
357  * Handle rest request
358  *
359  * @param handle the request handle
360  */
361 static void
362 init_cont (struct RequestHandle *handle)
363 {
364   struct GNUNET_REST_RequestHandlerError err;
365   static const struct GNUNET_REST_RequestHandler handlers[] = {
366     {MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_GNS, &get_gns_cont},
367     {MHD_HTTP_METHOD_OPTIONS, GNUNET_REST_API_NS_GNS, &options_cont},
368     GNUNET_REST_HANDLER_END
369   };
370
371   if (GNUNET_NO == GNUNET_REST_handle_request (handle->rest_handle,
372                                                handlers,
373                                                &err,
374                                                handle))
375   {
376     handle->response_code = err.error_code;
377     GNUNET_SCHEDULER_add_now (&do_error, handle);
378   }
379 }
380
381
382 /**
383  * Function processing the REST call
384  *
385  * @param method HTTP method
386  * @param url URL of the HTTP request
387  * @param data body of the HTTP request (optional)
388  * @param data_size length of the body
389  * @param proc callback function for the result
390  * @param proc_cls closure for callback function
391  * @return GNUNET_OK if request accepted
392  */
393 static void
394 rest_process_request(struct GNUNET_REST_RequestHandle *rest_handle,
395                      GNUNET_REST_ResultProcessor proc,
396                      void *proc_cls)
397 {
398   struct RequestHandle *handle = GNUNET_new (struct RequestHandle);
399
400   handle->response_code = 0;
401   handle->timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60);
402   handle->proc_cls = proc_cls;
403   handle->proc = proc;
404   handle->rest_handle = rest_handle;
405
406   handle->url = GNUNET_strdup (rest_handle->url);
407   if (handle->url[strlen (handle->url)-1] == '/')
408     handle->url[strlen (handle->url)-1] = '\0';
409   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting...\n");
410   handle->gns = GNUNET_GNS_connect (cfg);
411   init_cont(handle);
412
413   handle->timeout_task =
414     GNUNET_SCHEDULER_add_delayed (handle->timeout,
415                                   &do_error,
416                                   handle);
417
418   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connected\n");
419 }
420
421
422 /**
423  * Entry point for the plugin.
424  *
425  * @param cls Config info
426  * @return NULL on error, otherwise the plugin context
427  */
428 void *
429 libgnunet_plugin_rest_gns_init (void *cls)
430 {
431   static struct Plugin plugin;
432   struct GNUNET_REST_Plugin *api;
433
434   cfg = cls;
435   if (NULL != plugin.cfg)
436     return NULL;                /* can only initialize once! */
437   memset (&plugin, 0, sizeof (struct Plugin));
438   plugin.cfg = cfg;
439   api = GNUNET_new (struct GNUNET_REST_Plugin);
440   api->cls = &plugin;
441   api->name = GNUNET_REST_API_NS_GNS;
442   api->process_request = &rest_process_request;
443   GNUNET_asprintf (&allow_methods,
444                    "%s, %s, %s, %s, %s",
445                    MHD_HTTP_METHOD_GET,
446                    MHD_HTTP_METHOD_POST,
447                    MHD_HTTP_METHOD_PUT,
448                    MHD_HTTP_METHOD_DELETE,
449                    MHD_HTTP_METHOD_OPTIONS);
450
451   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
452               _("Gns REST API initialized\n"));
453   return api;
454 }
455
456
457 /**
458  * Exit point from the plugin.
459  *
460  * @param cls the plugin context (as returned by "init")
461  * @return always NULL
462  */
463 void *
464 libgnunet_plugin_rest_gns_done (void *cls)
465 {
466   struct GNUNET_REST_Plugin *api = cls;
467   struct Plugin *plugin = api->cls;
468   plugin->cfg = NULL;
469
470   GNUNET_free_non_null (allow_methods);
471   GNUNET_free (api);
472   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
473               "Gns REST plugin is finished\n");
474   return NULL;
475 }
476
477 /* end of plugin_rest_gns.c */
478