diff options
Diffstat (limited to 'src/mint/taler-mint-httpd_json.c')
-rw-r--r-- | src/mint/taler-mint-httpd_json.c | 417 |
1 files changed, 417 insertions, 0 deletions
diff --git a/src/mint/taler-mint-httpd_json.c b/src/mint/taler-mint-httpd_json.c new file mode 100644 index 000000000..e9183073f --- /dev/null +++ b/src/mint/taler-mint-httpd_json.c | |||
@@ -0,0 +1,417 @@ | |||
1 | #include "platform.h" | ||
2 | #include <gnunet/gnunet_util_lib.h> | ||
3 | #include "taler-mint-httpd_json.h" | ||
4 | |||
5 | |||
6 | |||
7 | /** | ||
8 | * Initial size for POST | ||
9 | * request buffer. | ||
10 | */ | ||
11 | #define REQUEST_BUFFER_INITIAL 1024 | ||
12 | |||
13 | /** | ||
14 | * Maximum POST request size | ||
15 | */ | ||
16 | #define REQUEST_BUFFER_MAX (1024*1024) | ||
17 | |||
18 | |||
19 | /** | ||
20 | * Buffer for POST requests. | ||
21 | */ | ||
22 | struct Buffer | ||
23 | { | ||
24 | /** | ||
25 | * Allocated memory | ||
26 | */ | ||
27 | char *data; | ||
28 | |||
29 | /** | ||
30 | * Number of valid bytes in buffer. | ||
31 | */ | ||
32 | size_t fill; | ||
33 | |||
34 | /** | ||
35 | * Number of allocated bytes in buffer. | ||
36 | */ | ||
37 | size_t alloc; | ||
38 | }; | ||
39 | |||
40 | |||
41 | /** | ||
42 | * Initialize a buffer. | ||
43 | * | ||
44 | * @param buf the buffer to initialize | ||
45 | * @param data the initial data | ||
46 | * @param data_size size of the initial data | ||
47 | * @param alloc_size size of the buffer | ||
48 | * @param max_size maximum size that the buffer can grow to | ||
49 | * @return a GNUnet result code | ||
50 | */ | ||
51 | static int | ||
52 | buffer_init (struct Buffer *buf, const void *data, size_t data_size, size_t alloc_size, size_t max_size) | ||
53 | { | ||
54 | if (data_size > max_size || alloc_size > max_size) | ||
55 | return GNUNET_SYSERR; | ||
56 | if (data_size > alloc_size) | ||
57 | alloc_size = data_size; | ||
58 | buf->data = GNUNET_malloc (alloc_size); | ||
59 | memcpy (buf->data, data, data_size); | ||
60 | return GNUNET_OK; | ||
61 | } | ||
62 | |||
63 | |||
64 | /** | ||
65 | * Free the data in a buffer. Does *not* free | ||
66 | * the buffer object itself. | ||
67 | * | ||
68 | * @param buf buffer to de-initialize | ||
69 | */ | ||
70 | static void | ||
71 | buffer_deinit (struct Buffer *buf) | ||
72 | { | ||
73 | GNUNET_free (buf->data); | ||
74 | buf->data = NULL; | ||
75 | } | ||
76 | |||
77 | |||
78 | /** | ||
79 | * Append data to a buffer, growing the buffer if necessary. | ||
80 | * | ||
81 | * @param buf the buffer to append to | ||
82 | * @param data the data to append | ||
83 | * @param size the size of @a data | ||
84 | * @param max_size maximum size that the buffer can grow to | ||
85 | * @return GNUNET_OK on success, | ||
86 | * GNUNET_NO if the buffer can't accomodate for the new data | ||
87 | * GNUNET_SYSERR on fatal error (out of memory?) | ||
88 | */ | ||
89 | static int | ||
90 | buffer_append (struct Buffer *buf, const void *data, size_t data_size, size_t max_size) | ||
91 | { | ||
92 | if (buf->fill + data_size > max_size) | ||
93 | return GNUNET_NO; | ||
94 | if (data_size + buf->fill > buf->alloc) | ||
95 | { | ||
96 | char *new_buf; | ||
97 | size_t new_size = buf->alloc; | ||
98 | while (new_size < buf->fill + data_size) | ||
99 | new_size += 2; | ||
100 | if (new_size > max_size) | ||
101 | return GNUNET_NO; | ||
102 | new_buf = GNUNET_malloc (new_size); | ||
103 | memcpy (new_buf, buf->data, buf->fill); | ||
104 | buf->data = new_buf; | ||
105 | buf->alloc = new_size; | ||
106 | } | ||
107 | memcpy (buf->data + buf->fill, data, data_size); | ||
108 | buf->fill += data_size; | ||
109 | return GNUNET_OK; | ||
110 | } | ||
111 | |||
112 | |||
113 | /** | ||
114 | * Send JSON object as response. Decreases the reference count of the | ||
115 | * JSON object. | ||
116 | * | ||
117 | * @param connection the MHD connection | ||
118 | * @param json the json object | ||
119 | * @param status_code the http status code | ||
120 | * @return MHD result code | ||
121 | */ | ||
122 | int | ||
123 | send_response_json (struct MHD_Connection *connection, | ||
124 | json_t *json, | ||
125 | unsigned int status_code) | ||
126 | { | ||
127 | struct MHD_Response *resp; | ||
128 | char *json_str; | ||
129 | |||
130 | json_str = json_dumps (json, JSON_INDENT(2)); | ||
131 | json_decref (json); | ||
132 | resp = MHD_create_response_from_buffer (strlen (json_str), json_str, | ||
133 | MHD_RESPMEM_MUST_FREE); | ||
134 | if (NULL == resp) | ||
135 | return MHD_NO; | ||
136 | return MHD_queue_response (connection, status_code, resp); | ||
137 | } | ||
138 | |||
139 | |||
140 | /** | ||
141 | * Send a JSON object via an MHD connection, | ||
142 | * specified with the JANSSON pack syntax (see json_pack). | ||
143 | * | ||
144 | * @param connection connection to send the JSON over | ||
145 | * @param http_code HTTP status for the response | ||
146 | * @param fmt format string for pack | ||
147 | * @param ... varargs | ||
148 | * @return MHD_YES on success or MHD_NO on error | ||
149 | */ | ||
150 | int | ||
151 | request_send_json_pack (struct MHD_Connection *connection, | ||
152 | unsigned int http_code, | ||
153 | const char *fmt, ...) | ||
154 | { | ||
155 | json_t *msg; | ||
156 | va_list argp; | ||
157 | int ret; | ||
158 | |||
159 | va_start(argp, fmt); | ||
160 | msg = json_vpack_ex (NULL, 0, fmt, argp); | ||
161 | va_end(argp); | ||
162 | if (NULL == msg) | ||
163 | return MHD_NO; | ||
164 | ret = send_response_json (connection, msg, http_code); | ||
165 | json_decref (msg); | ||
166 | return ret; | ||
167 | } | ||
168 | |||
169 | |||
170 | /** | ||
171 | * Process a POST request containing a JSON object. | ||
172 | * | ||
173 | * @param connection the MHD connection | ||
174 | * @param con_cs the closure (contains a 'struct Buffer *') | ||
175 | * @param upload_data the POST data | ||
176 | * @param upload_data_size the POST data size | ||
177 | * @param json the JSON object for a completed request | ||
178 | * | ||
179 | * @returns | ||
180 | * GNUNET_YES if json object was parsed | ||
181 | * GNUNET_NO is request incomplete or invalid | ||
182 | * GNUNET_SYSERR on internal error | ||
183 | */ | ||
184 | int | ||
185 | process_post_json (struct MHD_Connection *connection, | ||
186 | void **con_cls, | ||
187 | const char *upload_data, | ||
188 | size_t *upload_data_size, | ||
189 | json_t **json) | ||
190 | { | ||
191 | struct Buffer *r = *con_cls; | ||
192 | |||
193 | if (NULL == *con_cls) | ||
194 | { | ||
195 | /* We are seeing a fresh POST request. */ | ||
196 | |||
197 | r = GNUNET_new (struct Buffer); | ||
198 | if (GNUNET_OK != buffer_init (r, upload_data, *upload_data_size, | ||
199 | REQUEST_BUFFER_INITIAL, REQUEST_BUFFER_MAX)) | ||
200 | { | ||
201 | *con_cls = NULL; | ||
202 | buffer_deinit (r); | ||
203 | GNUNET_free (r); | ||
204 | return GNUNET_SYSERR; | ||
205 | } | ||
206 | *upload_data_size = 0; | ||
207 | *con_cls = r; | ||
208 | return GNUNET_NO; | ||
209 | } | ||
210 | if (0 != *upload_data_size) | ||
211 | { | ||
212 | /* We are seeing an old request with more data available. */ | ||
213 | |||
214 | if (GNUNET_OK != buffer_append (r, upload_data, *upload_data_size, | ||
215 | REQUEST_BUFFER_MAX)) | ||
216 | { | ||
217 | /* Request too long or we're out of memory. */ | ||
218 | |||
219 | *con_cls = NULL; | ||
220 | buffer_deinit (r); | ||
221 | GNUNET_free (r); | ||
222 | return GNUNET_SYSERR; | ||
223 | } | ||
224 | *upload_data_size = 0; | ||
225 | return GNUNET_NO; | ||
226 | } | ||
227 | |||
228 | /* We have seen the whole request. */ | ||
229 | |||
230 | *json = json_loadb (r->data, r->fill, 0, NULL); | ||
231 | buffer_deinit (r); | ||
232 | GNUNET_free (r); | ||
233 | if (NULL == *json) | ||
234 | { | ||
235 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Can't parse JSON request body\n"); | ||
236 | return request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, | ||
237 | GNUNET_NO, GNUNET_SYSERR, | ||
238 | "{s:s}", | ||
239 | "error", "invalid json"); | ||
240 | } | ||
241 | *con_cls = NULL; | ||
242 | |||
243 | return GNUNET_YES; | ||
244 | } | ||
245 | |||
246 | |||
247 | /** | ||
248 | * Navigate through a JSON tree. | ||
249 | * | ||
250 | * Sends an error response if navigation is impossible (i.e. | ||
251 | * the JSON object is invalid) | ||
252 | * | ||
253 | * @param connection the connection to send an error response to | ||
254 | * @param root the JSON node to start the navigation at. | ||
255 | * @param ... navigation specification (see JNAV_*) | ||
256 | * @return GNUNET_YES if navigation was successful | ||
257 | * GNUNET_NO if json is malformed, error response was generated | ||
258 | * GNUNET_SYSERR on internal error | ||
259 | */ | ||
260 | int | ||
261 | request_json_require_nav (struct MHD_Connection *connection, | ||
262 | const json_t *root, ...) | ||
263 | { | ||
264 | va_list argp; | ||
265 | int ignore = GNUNET_NO; | ||
266 | // what's our current path from 'root'? | ||
267 | json_t *path; | ||
268 | |||
269 | path = json_array (); | ||
270 | |||
271 | va_start(argp, root); | ||
272 | |||
273 | while (1) | ||
274 | { | ||
275 | int command = va_arg(argp, int); | ||
276 | switch (command) | ||
277 | { | ||
278 | case JNAV_FIELD: | ||
279 | { | ||
280 | const char *fname = va_arg(argp, const char *); | ||
281 | if (GNUNET_YES == ignore) | ||
282 | break; | ||
283 | json_array_append_new (path, json_string (fname)); | ||
284 | root = json_object_get (root, fname); | ||
285 | if (NULL == root) | ||
286 | { | ||
287 | |||
288 | (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, | ||
289 | 0, 0, | ||
290 | "{s:s,s:o}", | ||
291 | "error", "missing field in JSON", | ||
292 | "path", path); | ||
293 | ignore = GNUNET_YES; | ||
294 | break; | ||
295 | } | ||
296 | } | ||
297 | break; | ||
298 | case JNAV_INDEX: | ||
299 | { | ||
300 | int fnum = va_arg(argp, int); | ||
301 | if (GNUNET_YES == ignore) | ||
302 | break; | ||
303 | json_array_append_new (path, json_integer (fnum)); | ||
304 | root = json_array_get (root, fnum); | ||
305 | if (NULL == root) | ||
306 | { | ||
307 | (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, | ||
308 | 0, 0, | ||
309 | "{s:s, s:o}", | ||
310 | "error", "missing index in JSON", | ||
311 | "path", path); | ||
312 | ignore = GNUNET_YES; | ||
313 | break; | ||
314 | } | ||
315 | } | ||
316 | break; | ||
317 | case JNAV_RET_DATA: | ||
318 | { | ||
319 | void *where = va_arg (argp, void *); | ||
320 | size_t len = va_arg (argp, size_t); | ||
321 | const char *str; | ||
322 | int res; | ||
323 | |||
324 | va_end(argp); | ||
325 | if (GNUNET_YES == ignore) | ||
326 | return GNUNET_NO; | ||
327 | str = json_string_value (root); | ||
328 | if (NULL == str) | ||
329 | { | ||
330 | (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, | ||
331 | 0, 0, | ||
332 | "{s:s, s:o}", | ||
333 | "error", "string expected", | ||
334 | "path", path); | ||
335 | return GNUNET_NO; | ||
336 | } | ||
337 | res = GNUNET_STRINGS_string_to_data (str, strlen (str), | ||
338 | where, len); | ||
339 | if (GNUNET_OK != res) | ||
340 | { | ||
341 | (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, | ||
342 | 0, 0, | ||
343 | "{s:s,s:o}", | ||
344 | "error", "malformed binary data in JSON", | ||
345 | "path", path); | ||
346 | return GNUNET_NO; | ||
347 | } | ||
348 | return GNUNET_YES; | ||
349 | } | ||
350 | break; | ||
351 | case JNAV_RET_DATA_VAR: | ||
352 | { | ||
353 | void **where = va_arg (argp, void **); | ||
354 | size_t *len = va_arg (argp, size_t *); | ||
355 | const char *str; | ||
356 | |||
357 | va_end(argp); | ||
358 | if (GNUNET_YES == ignore) | ||
359 | return GNUNET_NO; | ||
360 | str = json_string_value (root); | ||
361 | if (NULL == str) | ||
362 | { | ||
363 | GNUNET_break (0); | ||
364 | return GNUNET_SYSERR; | ||
365 | } | ||
366 | *len = (strlen (str) * 5) / 8; | ||
367 | if (where != NULL) | ||
368 | { | ||
369 | int res; | ||
370 | *where = GNUNET_malloc (*len); | ||
371 | res = GNUNET_STRINGS_string_to_data (str, strlen (str), | ||
372 | *where, *len); | ||
373 | if (GNUNET_OK != res) | ||
374 | { | ||
375 | (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, | ||
376 | 0, 0, | ||
377 | "{s:s, s:o}", | ||
378 | "error", "malformed binary data in JSON", | ||
379 | "path", path); | ||
380 | return GNUNET_NO; | ||
381 | } | ||
382 | } | ||
383 | return GNUNET_OK; | ||
384 | } | ||
385 | break; | ||
386 | case JNAV_RET_TYPED_JSON: | ||
387 | { | ||
388 | int typ = va_arg (argp, int); | ||
389 | const json_t **r_json = va_arg (argp, const json_t **); | ||
390 | |||
391 | va_end(argp); | ||
392 | if (GNUNET_YES == ignore) | ||
393 | return GNUNET_NO; | ||
394 | if (typ != -1 && json_typeof (root) != typ) | ||
395 | { | ||
396 | (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, | ||
397 | 0, 0, | ||
398 | "{s:s, s:i, s:i s:o}", | ||
399 | "error", "wrong JSON field type", | ||
400 | "type_expected", typ, | ||
401 | "type_actual", json_typeof (root), | ||
402 | "path", path); | ||
403 | return GNUNET_NO; | ||
404 | } | ||
405 | *r_json = root; | ||
406 | return GNUNET_OK; | ||
407 | } | ||
408 | break; | ||
409 | default: | ||
410 | GNUNET_assert (0); | ||
411 | } | ||
412 | } | ||
413 | GNUNET_assert (0); | ||
414 | } | ||
415 | |||
416 | |||
417 | |||