diff options
Diffstat (limited to 'src/mint/taler-mint-httpd_parsing.c')
-rw-r--r-- | src/mint/taler-mint-httpd_parsing.c | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/src/mint/taler-mint-httpd_parsing.c b/src/mint/taler-mint-httpd_parsing.c new file mode 100644 index 000000000..a976c0c06 --- /dev/null +++ b/src/mint/taler-mint-httpd_parsing.c | |||
@@ -0,0 +1,453 @@ | |||
1 | /* | ||
2 | This file is part of TALER | ||
3 | (C) 2014 GNUnet e.V. | ||
4 | |||
5 | TALER is free software; you can redistribute it and/or modify it under the | ||
6 | terms of the GNU Affero General Public License as published by the Free Software | ||
7 | Foundation; either version 3, or (at your option) any later version. | ||
8 | |||
9 | TALER is distributed in the hope that it will be useful, but WITHOUT ANY | ||
10 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | ||
11 | A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. | ||
12 | |||
13 | You should have received a copy of the GNU Affero General Public License along with | ||
14 | TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> | ||
15 | */ | ||
16 | |||
17 | /** | ||
18 | * @file taler-mint-httpd_parsing.c | ||
19 | * @brief functions to parse incoming requests (MHD arguments and JSON snippets) | ||
20 | * @author Florian Dold | ||
21 | * @author Benedikt Mueller | ||
22 | * @author Christian Grothoff | ||
23 | */ | ||
24 | |||
25 | #include "platform.h" | ||
26 | #include <gnunet/gnunet_util_lib.h> | ||
27 | #include "taler-mint-httpd_parsing.h" | ||
28 | #include "taler-mint-httpd_responses.h" | ||
29 | |||
30 | |||
31 | /** | ||
32 | * Initial size for POST | ||
33 | * request buffer. | ||
34 | */ | ||
35 | #define REQUEST_BUFFER_INITIAL 1024 | ||
36 | |||
37 | /** | ||
38 | * Maximum POST request size | ||
39 | */ | ||
40 | #define REQUEST_BUFFER_MAX (1024*1024) | ||
41 | |||
42 | |||
43 | /** | ||
44 | * Buffer for POST requests. | ||
45 | */ | ||
46 | struct Buffer | ||
47 | { | ||
48 | /** | ||
49 | * Allocated memory | ||
50 | */ | ||
51 | char *data; | ||
52 | |||
53 | /** | ||
54 | * Number of valid bytes in buffer. | ||
55 | */ | ||
56 | size_t fill; | ||
57 | |||
58 | /** | ||
59 | * Number of allocated bytes in buffer. | ||
60 | */ | ||
61 | size_t alloc; | ||
62 | }; | ||
63 | |||
64 | |||
65 | /** | ||
66 | * Initialize a buffer. | ||
67 | * | ||
68 | * @param buf the buffer to initialize | ||
69 | * @param data the initial data | ||
70 | * @param data_size size of the initial data | ||
71 | * @param alloc_size size of the buffer | ||
72 | * @param max_size maximum size that the buffer can grow to | ||
73 | * @return a GNUnet result code | ||
74 | */ | ||
75 | static int | ||
76 | buffer_init (struct Buffer *buf, | ||
77 | const void *data, | ||
78 | size_t data_size, | ||
79 | size_t alloc_size, | ||
80 | size_t max_size) | ||
81 | { | ||
82 | if (data_size > max_size || alloc_size > max_size) | ||
83 | return GNUNET_SYSERR; | ||
84 | if (data_size > alloc_size) | ||
85 | alloc_size = data_size; | ||
86 | buf->data = GNUNET_malloc (alloc_size); | ||
87 | memcpy (buf->data, data, data_size); | ||
88 | return GNUNET_OK; | ||
89 | } | ||
90 | |||
91 | |||
92 | /** | ||
93 | * Free the data in a buffer. Does *not* free | ||
94 | * the buffer object itself. | ||
95 | * | ||
96 | * @param buf buffer to de-initialize | ||
97 | */ | ||
98 | static void | ||
99 | buffer_deinit (struct Buffer *buf) | ||
100 | { | ||
101 | GNUNET_free (buf->data); | ||
102 | buf->data = NULL; | ||
103 | } | ||
104 | |||
105 | |||
106 | /** | ||
107 | * Append data to a buffer, growing the buffer if necessary. | ||
108 | * | ||
109 | * @param buf the buffer to append to | ||
110 | * @param data the data to append | ||
111 | * @param size the size of @a data | ||
112 | * @param max_size maximum size that the buffer can grow to | ||
113 | * @return GNUNET_OK on success, | ||
114 | * GNUNET_NO if the buffer can't accomodate for the new data | ||
115 | * GNUNET_SYSERR on fatal error (out of memory?) | ||
116 | */ | ||
117 | static int | ||
118 | buffer_append (struct Buffer *buf, | ||
119 | const void *data, | ||
120 | size_t data_size, | ||
121 | size_t max_size) | ||
122 | { | ||
123 | if (buf->fill + data_size > max_size) | ||
124 | return GNUNET_NO; | ||
125 | if (data_size + buf->fill > buf->alloc) | ||
126 | { | ||
127 | char *new_buf; | ||
128 | size_t new_size = buf->alloc; | ||
129 | while (new_size < buf->fill + data_size) | ||
130 | new_size += 2; | ||
131 | if (new_size > max_size) | ||
132 | return GNUNET_NO; | ||
133 | new_buf = GNUNET_malloc (new_size); | ||
134 | memcpy (new_buf, buf->data, buf->fill); | ||
135 | buf->data = new_buf; | ||
136 | buf->alloc = new_size; | ||
137 | } | ||
138 | memcpy (buf->data + buf->fill, data, data_size); | ||
139 | buf->fill += data_size; | ||
140 | return GNUNET_OK; | ||
141 | } | ||
142 | |||
143 | |||
144 | /** | ||
145 | * Process a POST request containing a JSON object. | ||
146 | * | ||
147 | * @param connection the MHD connection | ||
148 | * @param con_cs the closure (contains a 'struct Buffer *') | ||
149 | * @param upload_data the POST data | ||
150 | * @param upload_data_size the POST data size | ||
151 | * @param json the JSON object for a completed request | ||
152 | * | ||
153 | * @returns | ||
154 | * GNUNET_YES if json object was parsed | ||
155 | * GNUNET_NO is request incomplete or invalid | ||
156 | * GNUNET_SYSERR on internal error | ||
157 | */ | ||
158 | int | ||
159 | process_post_json (struct MHD_Connection *connection, | ||
160 | void **con_cls, | ||
161 | const char *upload_data, | ||
162 | size_t *upload_data_size, | ||
163 | json_t **json) | ||
164 | { | ||
165 | struct Buffer *r = *con_cls; | ||
166 | |||
167 | if (NULL == *con_cls) | ||
168 | { | ||
169 | /* We are seeing a fresh POST request. */ | ||
170 | |||
171 | r = GNUNET_new (struct Buffer); | ||
172 | if (GNUNET_OK != | ||
173 | buffer_init (r, | ||
174 | upload_data, | ||
175 | *upload_data_size, | ||
176 | REQUEST_BUFFER_INITIAL, | ||
177 | REQUEST_BUFFER_MAX)) | ||
178 | { | ||
179 | *con_cls = NULL; | ||
180 | buffer_deinit (r); | ||
181 | GNUNET_free (r); | ||
182 | return GNUNET_SYSERR; | ||
183 | } | ||
184 | *upload_data_size = 0; | ||
185 | *con_cls = r; | ||
186 | return GNUNET_NO; | ||
187 | } | ||
188 | if (0 != *upload_data_size) | ||
189 | { | ||
190 | /* We are seeing an old request with more data available. */ | ||
191 | |||
192 | if (GNUNET_OK != | ||
193 | buffer_append (r, | ||
194 | upload_data, | ||
195 | *upload_data_size, | ||
196 | REQUEST_BUFFER_MAX)) | ||
197 | { | ||
198 | /* Request too long or we're out of memory. */ | ||
199 | |||
200 | *con_cls = NULL; | ||
201 | buffer_deinit (r); | ||
202 | GNUNET_free (r); | ||
203 | return GNUNET_SYSERR; | ||
204 | } | ||
205 | *upload_data_size = 0; | ||
206 | return GNUNET_NO; | ||
207 | } | ||
208 | |||
209 | /* We have seen the whole request. */ | ||
210 | |||
211 | *json = json_loadb (r->data, r->fill, 0, NULL); | ||
212 | buffer_deinit (r); | ||
213 | GNUNET_free (r); | ||
214 | if (NULL == *json) | ||
215 | { | ||
216 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | ||
217 | "Can't parse JSON request body\n"); | ||
218 | return (MHD_YES == | ||
219 | TALER_MINT_reply_json_pack (connection, | ||
220 | MHD_HTTP_BAD_REQUEST, | ||
221 | "{s:s}", | ||
222 | "error", "invalid json")) | ||
223 | ? GNUNET_NO : GNUNET_SYSERR; | ||
224 | } | ||
225 | *con_cls = NULL; | ||
226 | |||
227 | return GNUNET_YES; | ||
228 | } | ||
229 | |||
230 | |||
231 | /** | ||
232 | * Navigate through a JSON tree. | ||
233 | * | ||
234 | * Sends an error response if navigation is impossible (i.e. | ||
235 | * the JSON object is invalid) | ||
236 | * | ||
237 | * @param connection the connection to send an error response to | ||
238 | * @param root the JSON node to start the navigation at. | ||
239 | * @param ... navigation specification (see JNAV_*) | ||
240 | * @return GNUNET_YES if navigation was successful | ||
241 | * GNUNET_NO if json is malformed, error response was generated | ||
242 | * GNUNET_SYSERR on internal error | ||
243 | */ | ||
244 | int | ||
245 | request_json_require_nav (struct MHD_Connection *connection, | ||
246 | const json_t *root, ...) | ||
247 | { | ||
248 | va_list argp; | ||
249 | int ignore = GNUNET_NO; | ||
250 | // what's our current path from 'root'? | ||
251 | json_t *path; | ||
252 | |||
253 | path = json_array (); | ||
254 | |||
255 | va_start(argp, root); | ||
256 | |||
257 | while (1) | ||
258 | { | ||
259 | int command = va_arg(argp, int); | ||
260 | switch (command) | ||
261 | { | ||
262 | case JNAV_FIELD: | ||
263 | { | ||
264 | const char *fname = va_arg(argp, const char *); | ||
265 | if (GNUNET_YES == ignore) | ||
266 | break; | ||
267 | json_array_append_new (path, json_string (fname)); | ||
268 | root = json_object_get (root, fname); | ||
269 | if (NULL == root) | ||
270 | { | ||
271 | /* FIXME: can't IGNORE this return value! */ | ||
272 | (void) TALER_MINT_reply_json_pack (connection, | ||
273 | MHD_HTTP_BAD_REQUEST, | ||
274 | "{s:s,s:o}", | ||
275 | "error", | ||
276 | "missing field in JSON", | ||
277 | "path", | ||
278 | path); | ||
279 | ignore = GNUNET_YES; | ||
280 | break; | ||
281 | } | ||
282 | } | ||
283 | break; | ||
284 | case JNAV_INDEX: | ||
285 | { | ||
286 | int fnum = va_arg(argp, int); | ||
287 | if (GNUNET_YES == ignore) | ||
288 | break; | ||
289 | json_array_append_new (path, json_integer (fnum)); | ||
290 | root = json_array_get (root, fnum); | ||
291 | if (NULL == root) | ||
292 | { | ||
293 | /* FIXME: can't IGNORE this return value! */ | ||
294 | (void) TALER_MINT_reply_json_pack (connection, | ||
295 | MHD_HTTP_BAD_REQUEST, | ||
296 | "{s:s, s:o}", | ||
297 | "error", "missing index in JSON", | ||
298 | "path", path); | ||
299 | ignore = GNUNET_YES; | ||
300 | break; | ||
301 | } | ||
302 | } | ||
303 | break; | ||
304 | case JNAV_RET_DATA: | ||
305 | { | ||
306 | void *where = va_arg (argp, void *); | ||
307 | size_t len = va_arg (argp, size_t); | ||
308 | const char *str; | ||
309 | int res; | ||
310 | |||
311 | va_end(argp); | ||
312 | if (GNUNET_YES == ignore) | ||
313 | return GNUNET_NO; | ||
314 | str = json_string_value (root); | ||
315 | if (NULL == str) | ||
316 | { | ||
317 | /* FIXME: can't IGNORE this return value! */ | ||
318 | (void) TALER_MINT_reply_json_pack (connection, | ||
319 | MHD_HTTP_BAD_REQUEST, | ||
320 | "{s:s, s:o}", | ||
321 | "error", | ||
322 | "string expected", | ||
323 | "path", | ||
324 | path); | ||
325 | return GNUNET_NO; | ||
326 | } | ||
327 | res = GNUNET_STRINGS_string_to_data (str, strlen (str), | ||
328 | where, len); | ||
329 | if (GNUNET_OK != res) | ||
330 | { | ||
331 | /* FIXME: can't IGNORE this return value! */ | ||
332 | (void) TALER_MINT_reply_json_pack (connection, | ||
333 | MHD_HTTP_BAD_REQUEST, | ||
334 | "{s:s,s:o}", | ||
335 | "error", | ||
336 | "malformed binary data in JSON", | ||
337 | "path", path); | ||
338 | return GNUNET_NO; | ||
339 | } | ||
340 | return GNUNET_YES; | ||
341 | } | ||
342 | break; | ||
343 | case JNAV_RET_DATA_VAR: | ||
344 | { | ||
345 | void **where = va_arg (argp, void **); | ||
346 | size_t *len = va_arg (argp, size_t *); | ||
347 | const char *str; | ||
348 | |||
349 | va_end(argp); | ||
350 | if (GNUNET_YES == ignore) | ||
351 | return GNUNET_NO; | ||
352 | str = json_string_value (root); | ||
353 | if (NULL == str) | ||
354 | { | ||
355 | GNUNET_break (0); | ||
356 | return GNUNET_SYSERR; | ||
357 | } | ||
358 | *len = (strlen (str) * 5) / 8; | ||
359 | if (where != NULL) | ||
360 | { | ||
361 | int res; | ||
362 | *where = GNUNET_malloc (*len); | ||
363 | res = GNUNET_STRINGS_string_to_data (str, strlen (str), | ||
364 | *where, *len); | ||
365 | if (GNUNET_OK != res) | ||
366 | { | ||
367 | /* FIXME: can't IGNORE this return value! */ | ||
368 | (void) TALER_MINT_reply_json_pack (connection, | ||
369 | MHD_HTTP_BAD_REQUEST, | ||
370 | "{s:s, s:o}", | ||
371 | "error", | ||
372 | "malformed binary data in JSON", | ||
373 | "path", path); | ||
374 | return GNUNET_NO; | ||
375 | } | ||
376 | } | ||
377 | return GNUNET_OK; | ||
378 | } | ||
379 | break; | ||
380 | case JNAV_RET_TYPED_JSON: | ||
381 | { | ||
382 | int typ = va_arg (argp, int); | ||
383 | const json_t **r_json = va_arg (argp, const json_t **); | ||
384 | |||
385 | va_end(argp); | ||
386 | if (GNUNET_YES == ignore) | ||
387 | return GNUNET_NO; | ||
388 | if (typ != -1 && json_typeof (root) != typ) | ||
389 | { | ||
390 | /* FIXME: can't IGNORE this return value! */ | ||
391 | (void) TALER_MINT_reply_json_pack (connection, | ||
392 | MHD_HTTP_BAD_REQUEST, | ||
393 | "{s:s, s:i, s:i s:o}", | ||
394 | "error", "wrong JSON field type", | ||
395 | "type_expected", typ, | ||
396 | "type_actual", json_typeof (root), | ||
397 | "path", path); | ||
398 | return GNUNET_NO; | ||
399 | } | ||
400 | *r_json = root; | ||
401 | return GNUNET_OK; | ||
402 | } | ||
403 | break; | ||
404 | default: | ||
405 | GNUNET_assert (0); | ||
406 | } | ||
407 | } | ||
408 | GNUNET_assert (0); | ||
409 | } | ||
410 | |||
411 | |||
412 | /** | ||
413 | * Extract base32crockford encoded data from request. | ||
414 | * | ||
415 | * Queues an error response to the connection if the parameter is missing or | ||
416 | * invalid. | ||
417 | * | ||
418 | * @param connection the MHD connection | ||
419 | * @param param_name the name of the parameter with the key | ||
420 | * @param[out] out_data pointer to store the result | ||
421 | * @param out_size expected size of data | ||
422 | * @return | ||
423 | * GNUNET_YES if the the argument is present | ||
424 | * GNUNET_NO if the argument is absent or malformed | ||
425 | * GNUNET_SYSERR on internal error (error response could not be sent) | ||
426 | */ | ||
427 | int | ||
428 | TALER_MINT_mhd_request_arg_data (struct MHD_Connection *connection, | ||
429 | const char *param_name, | ||
430 | void *out_data, | ||
431 | size_t out_size) | ||
432 | { | ||
433 | const char *str; | ||
434 | |||
435 | str = MHD_lookup_connection_value (connection, | ||
436 | MHD_GET_ARGUMENT_KIND, | ||
437 | param_name); | ||
438 | if (NULL == str) | ||
439 | { | ||
440 | return (MHD_NO == | ||
441 | TALER_MINT_reply_arg_missing (connection, param_name)) | ||
442 | ? GNUNET_SYSERR : GNUNET_NO; | ||
443 | } | ||
444 | if (GNUNET_OK != | ||
445 | GNUNET_STRINGS_string_to_data (str, | ||
446 | strlen (str), | ||
447 | out_data, | ||
448 | out_size)) | ||
449 | return (MHD_NO == | ||
450 | TALER_MINT_reply_arg_invalid (connection, param_name)) | ||
451 | ? GNUNET_SYSERR : GNUNET_NO; | ||
452 | return GNUNET_OK; | ||
453 | } | ||