error handling
[oweals/gnunet.git] / src / rest / plugin_rest_config.c
1 /*
2    This file is part of GNUnet.
3    Copyright (C) 2012-2018 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 Martin Schanzenbach
22  * @file gns/plugin_rest_config.c
23  * @brief REST plugin for configuration
24  *
25  */
26
27 #include "platform.h"
28 #include "gnunet_rest_plugin.h"
29 #include <gnunet_rest_lib.h>
30 #include <gnunet_util_lib.h>
31 #include <jansson.h>
32
33 #define GNUNET_REST_API_NS_CONFIG "/config"
34
35 /**
36  * @brief struct returned by the initialization function of the plugin
37  */
38 struct Plugin
39 {
40   const struct GNUNET_CONFIGURATION_Handle *cfg;
41 };
42
43 const struct GNUNET_CONFIGURATION_Handle *cfg;
44
45 struct RequestHandle
46 {
47   /**
48    * Handle to rest request
49    */
50   struct GNUNET_REST_RequestHandle *rest_handle;
51
52   /**
53    * The plugin result processor
54    */
55   GNUNET_REST_ResultProcessor proc;
56
57   /**
58    * The closure of the result processor
59    */
60   void *proc_cls;
61
62   /**
63    * HTTP response code
64    */
65   int response_code;
66
67   /**
68    * The URL
69    */
70   char *url;
71 };
72
73
74 /**
75  * Cleanup request handle.
76  *
77  * @param handle Handle to clean up
78  */
79 static void
80 cleanup_handle (struct RequestHandle *handle)
81 {
82   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Cleaning up\n");
83   if (NULL != handle->url)
84     GNUNET_free (handle->url);
85   GNUNET_free (handle);
86 }
87
88
89 /**
90  * Task run on shutdown.  Cleans up everything.
91  *
92  * @param cls unused
93  * @param tc scheduler context
94  */
95 static void
96 do_error (void *cls)
97 {
98   struct RequestHandle *handle = cls;
99   struct MHD_Response *resp;
100
101   resp = GNUNET_REST_create_response (NULL);
102   handle->proc (handle->proc_cls, resp, handle->response_code);
103   cleanup_handle (handle);
104 }
105
106
107 static void
108 add_sections (void *cls,
109               const char *section,
110               const char *option,
111               const char *value)
112 {
113   json_t *sections_obj = cls;
114   json_t *sec_obj;
115
116   sec_obj = json_object_get (sections_obj, section);
117   if (NULL != sec_obj)
118   {
119     json_object_set_new (sec_obj, option, json_string (value));
120     return;
121   }
122   sec_obj = json_object ();
123   json_object_set_new (sec_obj, option, json_string (value));
124   json_object_set_new (sections_obj, section, sec_obj);
125 }
126
127
128 static void
129 add_section_contents (void *cls,
130                       const char *section,
131                       const char *option,
132                       const char *value)
133 {
134   json_t *section_obj = cls;
135
136   json_object_set_new (section_obj, option, json_string (value));
137 }
138
139
140 /**
141  * Handle rest request
142  *
143  * @param handle the lookup handle
144  */
145 static void
146 get_cont (struct GNUNET_REST_RequestHandle *con_handle,
147           const char *url,
148           void *cls)
149 {
150   struct MHD_Response *resp;
151   struct RequestHandle *handle = cls;
152   const char *section;
153   char *response;
154   json_t *result;
155
156   if (strlen (GNUNET_REST_API_NS_CONFIG) > strlen (handle->url))
157   {
158     handle->response_code = MHD_HTTP_BAD_REQUEST;
159     GNUNET_SCHEDULER_add_now (&do_error, handle);
160     return;
161   }
162   if (strlen (GNUNET_REST_API_NS_CONFIG) == strlen (handle->url))
163   {
164     result = json_object ();
165     GNUNET_CONFIGURATION_iterate (cfg, &add_sections, result);
166   }
167   else
168   {
169     result = json_object ();
170     section = &handle->url[strlen (GNUNET_REST_API_NS_CONFIG) + 1];
171     GNUNET_CONFIGURATION_iterate_section_values (cfg,
172                                                  section,
173                                                  &add_section_contents,
174                                                  result);
175   }
176   response = json_dumps (result, 0);
177   resp = GNUNET_REST_create_response (response);
178   handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
179   cleanup_handle (handle);
180   GNUNET_free (response);
181   json_decref (result);
182 }
183
184
185 struct GNUNET_CONFIGURATION_Handle *
186 set_value (struct GNUNET_CONFIGURATION_Handle *config,
187            const char *section,
188            const char *option,
189            json_t *value)
190 {
191   if (json_is_string (value))
192     GNUNET_CONFIGURATION_set_value_string (config, section, option,
193                                            json_string_value (value));
194   else if (json_is_number (value))
195     GNUNET_CONFIGURATION_set_value_number (config, section, option,
196                                            json_integer_value (value));
197   else if (json_is_null (value))
198     GNUNET_CONFIGURATION_set_value_string (config, section, option, NULL);
199   else if (json_is_true (value))
200     GNUNET_CONFIGURATION_set_value_string (config, section, option, "yes");
201   else if (json_is_false (value))
202     GNUNET_CONFIGURATION_set_value_string (config, section, option, "no");
203   else
204     return NULL;
205   return config; // for error handling (0 -> success, 1 -> error)
206 }
207
208
209 /**
210  * Handle REST POST request
211  *
212  * @param handle the lookup handle
213  */
214 static void
215 set_cont (struct GNUNET_REST_RequestHandle *con_handle,
216           const char *url,
217           void *cls)
218 {
219   struct RequestHandle *handle = cls;
220   char term_data[handle->rest_handle->data_size + 1];
221   struct GNUNET_CONFIGURATION_Handle *out = GNUNET_CONFIGURATION_dup (cfg);
222
223   json_error_t err;
224   json_t *data_json;
225   const char *section;
226   const char *option;
227   json_t *sec_obj;
228   json_t *value;
229   char *cfg_fn;
230
231   // invalid url
232   if (strlen (GNUNET_REST_API_NS_CONFIG) > strlen (handle->url))
233   {
234     handle->response_code = MHD_HTTP_BAD_REQUEST;
235     GNUNET_SCHEDULER_add_now (&do_error, handle);
236     return;
237   }
238
239   // extract data from handle
240   term_data[handle->rest_handle->data_size] = '\0';
241   GNUNET_memcpy (term_data,
242                  handle->rest_handle->data,
243                  handle->rest_handle->data_size);
244   data_json = json_loads (term_data, JSON_DECODE_ANY, &err);
245
246   if (NULL == data_json)
247   {
248     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
249                 "Unable to parse JSON Object from %s\n",
250                 term_data);
251     GNUNET_SCHEDULER_add_now (&do_error, handle);
252     return;
253   }
254
255   // POST /config => {<section> : {<option> : <value>}}
256   if (strlen (GNUNET_REST_API_NS_CONFIG) == strlen (handle->url))   // POST /config
257   {
258     // iterate over sections
259     json_object_foreach (data_json, section, sec_obj)
260     {
261       // iterate over options
262       json_object_foreach (sec_obj, option, value)
263       {
264         out = set_value (out, section, option, value);
265         if (NULL == out)
266         {
267           handle->response_code = MHD_HTTP_BAD_REQUEST;
268           GNUNET_SCHEDULER_add_now (&do_error, handle);
269           json_decref (data_json);
270           return;
271         }
272       }
273     }
274   }
275   else // POST /config/<section> => {<option> : <value>}
276   {
277     // extract the "<section>" part from the url
278     section = &handle->url[strlen (GNUNET_REST_API_NS_CONFIG) + 1];
279     // iterate over options
280     json_object_foreach (data_json, option, value)
281     {
282       out = set_value (out, section, option, value);
283       if (NULL == out)
284       {
285         handle->response_code = MHD_HTTP_BAD_REQUEST;
286         GNUNET_SCHEDULER_add_now (&do_error, handle);
287         json_decref (data_json);
288         return;
289       }
290     }
291   }
292   json_decref (data_json);
293
294
295   // get cfg file path
296   cfg_fn = NULL;
297   const char *xdg = getenv ("XDG_CONFIG_HOME");
298   if (NULL != xdg)
299     GNUNET_asprintf (&cfg_fn,
300                      "%s%s%s",
301                      xdg,
302                      DIR_SEPARATOR_STR,
303                      GNUNET_OS_project_data_get ()->config_file);
304   else
305     cfg_fn = GNUNET_strdup (GNUNET_OS_project_data_get ()->user_config_file);
306
307   GNUNET_CONFIGURATION_write (out, cfg_fn);
308   cfg = out;
309   handle->proc (handle->proc_cls,
310                 GNUNET_REST_create_response (NULL),
311                 MHD_HTTP_OK);
312   cleanup_handle (handle);
313 }
314
315
316 /**
317  * Handle rest request
318  *
319  * @param handle the lookup handle
320  */
321 static void
322 options_cont (struct GNUNET_REST_RequestHandle *con_handle,
323               const char *url,
324               void *cls)
325 {
326   struct MHD_Response *resp;
327   struct RequestHandle *handle = cls;
328
329   resp = GNUNET_REST_create_response (NULL);
330   MHD_add_response_header (resp,
331                            "Access-Control-Allow-Methods",
332                            MHD_HTTP_METHOD_GET);
333   handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
334   cleanup_handle (handle);
335 }
336
337
338 /**
339  * Function processing the REST call
340  *
341  * @param method HTTP method
342  * @param url URL of the HTTP request
343  * @param data body of the HTTP request (optional)
344  * @param data_size length of the body
345  * @param proc callback function for the result
346  * @param proc_cls closure for @a proc
347  * @return #GNUNET_OK if request accepted
348  */
349 static void
350 rest_config_process_request (struct GNUNET_REST_RequestHandle *conndata_handle,
351                              GNUNET_REST_ResultProcessor proc,
352                              void *proc_cls)
353 {
354   static const struct GNUNET_REST_RequestHandler handlers[] = {
355     { MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_CONFIG, &get_cont },
356     { MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_CONFIG, &set_cont },
357     { MHD_HTTP_METHOD_OPTIONS, GNUNET_REST_API_NS_CONFIG, &options_cont },
358     GNUNET_REST_HANDLER_END
359   };
360   struct RequestHandle *handle = GNUNET_new (struct RequestHandle);
361   struct GNUNET_REST_RequestHandlerError err;
362
363   handle->proc_cls = proc_cls;
364   handle->proc = proc;
365   handle->rest_handle = conndata_handle;
366   handle->url = GNUNET_strdup (conndata_handle->url);
367   if (handle->url[strlen (handle->url) - 1] == '/')
368     handle->url[strlen (handle->url) - 1] = '\0';
369
370   if (GNUNET_NO ==
371       GNUNET_REST_handle_request (conndata_handle, handlers, &err, handle))
372   {
373     handle->response_code = err.error_code;
374     GNUNET_SCHEDULER_add_now (&do_error, handle);
375   }
376 }
377
378
379 /**
380  * Entry point for the plugin.
381  *
382  * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*"
383  * @return NULL on error, otherwise the plugin context
384  */
385 void *
386 libgnunet_plugin_rest_config_init (void *cls)
387 {
388   static struct Plugin plugin;
389
390   cfg = cls;
391   struct GNUNET_REST_Plugin *api;
392
393   if (NULL != plugin.cfg)
394     return NULL; /* can only initialize once! */
395   memset (&plugin, 0, sizeof(struct Plugin));
396   plugin.cfg = cfg;
397   api = GNUNET_new (struct GNUNET_REST_Plugin);
398   api->cls = &plugin;
399   api->name = GNUNET_REST_API_NS_CONFIG;
400   api->process_request = &rest_config_process_request;
401   GNUNET_log (GNUNET_ERROR_TYPE_INFO, _ ("CONFIG REST API initialized\n"));
402   return api;
403 }
404
405
406 /**
407  * Exit point from the plugin.
408  *
409  * @param cls the plugin context (as returned by "init")
410  * @return always NULL
411  */
412 void *
413 libgnunet_plugin_rest_config_done (void *cls)
414 {
415   struct GNUNET_REST_Plugin *api = cls;
416   struct Plugin *plugin = api->cls;
417
418   plugin->cfg = NULL;
419   GNUNET_free (api);
420   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "CONFIG REST plugin is finished\n");
421   return NULL;
422 }
423
424
425 /* end of plugin_rest_config.c */