uncrustify as demanded.
[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   const struct GNUNET_CONFIGURATION_Handle *cfg;
70 };
71
72 /**
73  * The request handle
74  */
75 struct RequestHandle {
76   /**
77    * Connection to GNS
78    */
79   struct GNUNET_GNS_Handle *gns;
80
81   /**
82    * Active GNS lookup
83    */
84   struct GNUNET_GNS_LookupWithTldRequest *gns_lookup;
85
86   /**
87    * Name to look up
88    */
89   char *name;
90
91   /**
92    * Record type to look up
93    */
94   int record_type;
95
96   /**
97    * Rest connection
98    */
99   struct GNUNET_REST_RequestHandle *rest_handle;
100
101   /**
102    * Desired timeout for the lookup (default is no timeout).
103    */
104   struct GNUNET_TIME_Relative timeout;
105
106   /**
107    * ID of a task associated with the resolution process.
108    */
109   struct GNUNET_SCHEDULER_Task *timeout_task;
110
111   /**
112    * The plugin result processor
113    */
114   GNUNET_REST_ResultProcessor proc;
115
116   /**
117    * The closure of the result processor
118    */
119   void *proc_cls;
120
121   /**
122    * The url
123    */
124   char *url;
125
126   /**
127    * Error response message
128    */
129   char *emsg;
130
131   /**
132    * Response code
133    */
134   int response_code;
135 };
136
137
138 /**
139  * Cleanup lookup handle
140  * @param handle Handle to clean up
141  */
142 static void
143 cleanup_handle(void *cls)
144 {
145   struct RequestHandle *handle = cls;
146
147   GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Cleaning up\n");
148
149   if (NULL != handle->gns_lookup)
150     {
151       GNUNET_GNS_lookup_with_tld_cancel(handle->gns_lookup);
152       handle->gns_lookup = NULL;
153     }
154   if (NULL != handle->gns)
155     {
156       GNUNET_GNS_disconnect(handle->gns);
157       handle->gns = NULL;
158     }
159
160   if (NULL != handle->timeout_task)
161     {
162       GNUNET_SCHEDULER_cancel(handle->timeout_task);
163       handle->timeout_task = NULL;
164     }
165   if (NULL != handle->url)
166     GNUNET_free(handle->url);
167   if (NULL != handle->name)
168     GNUNET_free(handle->name);
169   if (NULL != handle->emsg)
170     GNUNET_free(handle->emsg);
171
172   GNUNET_free(handle);
173 }
174
175
176 /**
177  * Task run on errors.  Reports an error and cleans up everything.
178  *
179  * @param cls the `struct RequestHandle`
180  */
181 static void
182 do_error(void *cls)
183 {
184   struct RequestHandle *handle = cls;
185   struct MHD_Response *resp;
186   json_t *json_error = json_object();
187   char *response;
188
189   if (NULL != handle->timeout_task)
190     GNUNET_SCHEDULER_cancel(handle->timeout_task);
191   handle->timeout_task = NULL;
192   if (NULL == handle->emsg)
193     handle->emsg = GNUNET_strdup(GNUNET_REST_GNS_ERROR_UNKNOWN);
194
195   json_object_set_new(json_error, "error", json_string(handle->emsg));
196
197   if (0 == handle->response_code)
198     handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
199   response = json_dumps(json_error, 0);
200   resp = GNUNET_REST_create_response(response);
201   handle->proc(handle->proc_cls, resp, handle->response_code);
202   json_decref(json_error);
203   GNUNET_free(response);
204   GNUNET_SCHEDULER_add_now(&cleanup_handle, handle);
205 }
206
207
208 static void
209 do_timeout(void *cls)
210 {
211   struct RequestHandle *handle = cls;
212
213   handle->timeout_task = NULL;
214   handle->response_code = MHD_HTTP_REQUEST_TIMEOUT;
215   do_error(handle);
216 }
217
218
219 /**
220  * Iterator called on obtained result for a GNS lookup.
221  *
222  * @param cls closure with the object
223  * @param was_gns #GNUNET_NO if name was not a GNS name
224  * @param rd_count number of records in @a rd
225  * @param rd the records in reply
226  */
227 static void
228 handle_gns_response(void *cls,
229                     int was_gns,
230                     uint32_t rd_count,
231                     const struct GNUNET_GNSRECORD_Data *rd)
232 {
233   struct RequestHandle *handle = cls;
234   struct MHD_Response *resp;
235   json_t *result_obj;
236   char *result;
237
238   handle->gns_lookup = NULL;
239
240   if (GNUNET_NO == was_gns)
241     {
242       handle->response_code = MHD_HTTP_NOT_FOUND;
243       handle->emsg = GNUNET_strdup(GNUNET_REST_GNS_NOT_FOUND);
244       GNUNET_SCHEDULER_add_now(&do_error, handle);
245       return;
246     }
247
248   result_obj = GNUNET_JSON_from_gnsrecord(handle->name, rd, rd_count);
249
250   result = json_dumps(result_obj, 0);
251   GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Result %s\n", result);
252   resp = GNUNET_REST_create_response(result);
253   handle->proc(handle->proc_cls, resp, MHD_HTTP_OK);
254   GNUNET_free(result);
255   json_decref(result_obj);
256   GNUNET_SCHEDULER_add_now(&cleanup_handle, handle);
257 }
258
259
260 /**
261  * Handle gns GET request
262  *
263  * @param con_handle the connection handle
264  * @param url the url
265  * @param cls the RequestHandle
266  */
267 void
268 get_gns_cont(struct GNUNET_REST_RequestHandle *con_handle,
269              const char *url,
270              void *cls)
271 {
272   struct RequestHandle *handle = cls;
273   struct GNUNET_HashCode key;
274   char *record_type;
275   char *name;
276
277   name = NULL;
278   handle->name = NULL;
279   if (strlen(GNUNET_REST_API_NS_GNS) < strlen(handle->url))
280     {
281       name = &handle->url[strlen(GNUNET_REST_API_NS_GNS) + 1];
282     }
283
284   if (NULL == name)
285     {
286       handle->response_code = MHD_HTTP_NOT_FOUND;
287       handle->emsg = GNUNET_strdup(GNUNET_REST_GNS_NOT_FOUND);
288       GNUNET_SCHEDULER_add_now(&do_error, handle);
289       return;
290     }
291   if (0 >= strlen(name))
292     {
293       handle->response_code = MHD_HTTP_NOT_FOUND;
294       handle->emsg = GNUNET_strdup(GNUNET_REST_GNS_NOT_FOUND);
295       GNUNET_SCHEDULER_add_now(&do_error, handle);
296       return;
297     }
298   handle->name = GNUNET_strdup(name);
299
300   handle->record_type = UINT32_MAX;
301   GNUNET_CRYPTO_hash(GNUNET_REST_GNS_PARAM_RECORD_TYPE,
302                      strlen(GNUNET_REST_GNS_PARAM_RECORD_TYPE),
303                      &key);
304   if (GNUNET_YES ==
305       GNUNET_CONTAINER_multihashmap_contains(con_handle->url_param_map, &key))
306     {
307       record_type =
308         GNUNET_CONTAINER_multihashmap_get(con_handle->url_param_map, &key);
309       handle->record_type = GNUNET_GNSRECORD_typename_to_number(record_type);
310     }
311
312   if (UINT32_MAX == handle->record_type)
313     {
314       handle->record_type = GNUNET_GNSRECORD_TYPE_ANY;
315     }
316
317   handle->gns_lookup = GNUNET_GNS_lookup_with_tld(handle->gns,
318                                                   handle->name,
319                                                   handle->record_type,
320                                                   GNUNET_NO,
321                                                   &handle_gns_response,
322                                                   handle);
323 }
324
325
326 /**
327  * Respond to OPTIONS request
328  *
329  * @param con_handle the connection handle
330  * @param url the url
331  * @param cls the RequestHandle
332  */
333 static void
334 options_cont(struct GNUNET_REST_RequestHandle *con_handle,
335              const char *url,
336              void *cls)
337 {
338   struct MHD_Response *resp;
339   struct RequestHandle *handle = cls;
340
341   //independent of path return all options
342   resp = GNUNET_REST_create_response(NULL);
343   MHD_add_response_header(resp, "Access-Control-Allow-Methods", allow_methods);
344   handle->proc(handle->proc_cls, resp, MHD_HTTP_OK);
345   GNUNET_SCHEDULER_add_now(&cleanup_handle, handle);
346   return;
347 }
348
349
350 /**
351  * Handle rest request
352  *
353  * @param handle the request handle
354  */
355 static void
356 init_cont(struct RequestHandle *handle)
357 {
358   struct GNUNET_REST_RequestHandlerError err;
359   static const struct GNUNET_REST_RequestHandler handlers[] =
360   { { MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_GNS, &get_gns_cont },
361     { MHD_HTTP_METHOD_OPTIONS, GNUNET_REST_API_NS_GNS, &options_cont },
362     GNUNET_REST_HANDLER_END };
363
364   if (GNUNET_NO ==
365       GNUNET_REST_handle_request(handle->rest_handle, handlers, &err, handle))
366     {
367       handle->response_code = err.error_code;
368       GNUNET_SCHEDULER_add_now(&do_error, handle);
369     }
370 }
371
372
373 /**
374  * Function processing the REST call
375  *
376  * @param method HTTP method
377  * @param url URL of the HTTP request
378  * @param data body of the HTTP request (optional)
379  * @param data_size length of the body
380  * @param proc callback function for the result
381  * @param proc_cls closure for callback function
382  * @return GNUNET_OK if request accepted
383  */
384 static void
385 rest_process_request(struct GNUNET_REST_RequestHandle *rest_handle,
386                      GNUNET_REST_ResultProcessor proc,
387                      void *proc_cls)
388 {
389   struct RequestHandle *handle = GNUNET_new(struct RequestHandle);
390
391   handle->response_code = 0;
392   handle->timeout =
393     GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 60);
394   handle->proc_cls = proc_cls;
395   handle->proc = proc;
396   handle->rest_handle = rest_handle;
397
398   handle->url = GNUNET_strdup(rest_handle->url);
399   if (handle->url[strlen(handle->url) - 1] == '/')
400     handle->url[strlen(handle->url) - 1] = '\0';
401   GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Connecting...\n");
402   handle->gns = GNUNET_GNS_connect(cfg);
403   init_cont(handle);
404
405   handle->timeout_task =
406     GNUNET_SCHEDULER_add_delayed(handle->timeout, &do_timeout, handle);
407
408   GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Connected\n");
409 }
410
411
412 /**
413  * Entry point for the plugin.
414  *
415  * @param cls Config info
416  * @return NULL on error, otherwise the plugin context
417  */
418 void *
419 libgnunet_plugin_rest_gns_init(void *cls)
420 {
421   static struct Plugin plugin;
422   struct GNUNET_REST_Plugin *api;
423
424   cfg = cls;
425   if (NULL != plugin.cfg)
426     return NULL; /* can only initialize once! */
427   memset(&plugin, 0, sizeof(struct Plugin));
428   plugin.cfg = cfg;
429   api = GNUNET_new(struct GNUNET_REST_Plugin);
430   api->cls = &plugin;
431   api->name = GNUNET_REST_API_NS_GNS;
432   api->process_request = &rest_process_request;
433   GNUNET_asprintf(&allow_methods,
434                   "%s, %s, %s, %s, %s",
435                   MHD_HTTP_METHOD_GET,
436                   MHD_HTTP_METHOD_POST,
437                   MHD_HTTP_METHOD_PUT,
438                   MHD_HTTP_METHOD_DELETE,
439                   MHD_HTTP_METHOD_OPTIONS);
440
441   GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, _("Gns REST API initialized\n"));
442   return api;
443 }
444
445
446 /**
447  * Exit point from the plugin.
448  *
449  * @param cls the plugin context (as returned by "init")
450  * @return always NULL
451  */
452 void *
453 libgnunet_plugin_rest_gns_done(void *cls)
454 {
455   struct GNUNET_REST_Plugin *api = cls;
456   struct Plugin *plugin = api->cls;
457
458   plugin->cfg = NULL;
459
460   GNUNET_free_non_null(allow_methods);
461   GNUNET_free(api);
462   GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Gns REST plugin is finished\n");
463   return NULL;
464 }
465
466 /* end of plugin_rest_gns.c */
467