/* This file is part of GNUnet Copyright (C) 2014, 2015, 2016 GNUnet e.V. GNUnet is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . SPDX-License-Identifier: AGPL3.0-or-later */ /** * @file json/json_mhd.c * @brief functions to parse JSON snippets we receive via MHD * @author Florian Dold * @author Benedikt Mueller * @author Christian Grothoff */ #include "platform.h" #include "gnunet_json_lib.h" #include /** * Initial size for POST request buffers. Should be big enough to * usually not require a reallocation, but not so big that it hurts in * terms of memory use. */ #define REQUEST_BUFFER_INITIAL (2 * 1024) /** * Buffer for POST requests. */ struct Buffer { /** * Allocated memory */ char *data; /** * Number of valid bytes in buffer. */ size_t fill; /** * Number of allocated bytes in buffer. */ size_t alloc; /** * Maximum buffer size allowed. */ size_t max; }; /** * Initialize a buffer. * * @param buf the buffer to initialize * @param data the initial data * @param data_size size of the initial data * @param alloc_size size of the buffer * @param max_size maximum size that the buffer can grow to * @return a GNUnet result code */ static int buffer_init (struct Buffer *buf, const void *data, size_t data_size, size_t alloc_size, size_t max_size) { if ((data_size > max_size) || (alloc_size > max_size)) return GNUNET_SYSERR; if (data_size > alloc_size) alloc_size = data_size; buf->data = GNUNET_malloc (alloc_size); buf->alloc = alloc_size; GNUNET_memcpy (buf->data, data, data_size); buf->fill = data_size; buf->max = max_size; return GNUNET_OK; } /** * Free the data in a buffer. Does *not* free * the buffer object itself. * * @param buf buffer to de-initialize */ static void buffer_deinit (struct Buffer *buf) { GNUNET_free (buf->data); buf->data = NULL; } /** * Append data to a buffer, growing the buffer if necessary. * * @param buf the buffer to append to * @param data the data to append * @param data_size the size of @a data * @param max_size maximum size that the buffer can grow to * @return #GNUNET_OK on success, * #GNUNET_NO if the buffer can't accomodate for the new data */ static int buffer_append (struct Buffer *buf, const void *data, size_t data_size, size_t max_size) { if (buf->fill + data_size > max_size) return GNUNET_NO; if (buf->fill + data_size > buf->alloc) { char *new_buf; size_t new_size = buf->alloc; while (new_size < buf->fill + data_size) new_size += 2; if (new_size > max_size) return GNUNET_NO; new_buf = GNUNET_malloc (new_size); GNUNET_memcpy (new_buf, buf->data, buf->fill); GNUNET_free (buf->data); buf->data = new_buf; buf->alloc = new_size; } GNUNET_memcpy (buf->data + buf->fill, data, data_size); buf->fill += data_size; return GNUNET_OK; } /** * Decompress data in @a buf. * * @param buf input data to inflate * @return result code indicating the status of the operation */ static enum GNUNET_JSON_PostResult inflate_data (struct Buffer *buf) { z_stream z; char *tmp; size_t tmp_size; int ret; memset (&z, 0, sizeof (z)); z.next_in = (Bytef *) buf->data; z.avail_in = buf->fill; tmp_size = GNUNET_MIN (buf->max, buf->fill * 4); tmp = GNUNET_malloc (tmp_size); z.next_out = (Bytef *) tmp; z.avail_out = tmp_size; ret = inflateInit (&z); switch (ret) { case Z_MEM_ERROR: GNUNET_break (0); return GNUNET_JSON_PR_OUT_OF_MEMORY; case Z_STREAM_ERROR: GNUNET_break_op (0); return GNUNET_JSON_PR_JSON_INVALID; case Z_OK: break; } while (1) { ret = inflate (&z, 0); switch (ret) { case Z_MEM_ERROR: GNUNET_break (0); GNUNET_break (Z_OK == inflateEnd (&z)); GNUNET_free (tmp); return GNUNET_JSON_PR_OUT_OF_MEMORY; case Z_DATA_ERROR: GNUNET_break (0); GNUNET_break (Z_OK == inflateEnd (&z)); GNUNET_free (tmp); return GNUNET_JSON_PR_JSON_INVALID; case Z_NEED_DICT: GNUNET_break (0); GNUNET_break (Z_OK == inflateEnd (&z)); GNUNET_free (tmp); return GNUNET_JSON_PR_JSON_INVALID; case Z_OK: if ((0 < z.avail_out) && (0 == z.avail_in)) { /* truncated input stream */ GNUNET_break (0); GNUNET_break (Z_OK == inflateEnd (&z)); GNUNET_free (tmp); return GNUNET_JSON_PR_JSON_INVALID; } if (0 < z.avail_out) continue; /* just call it again */ /* output buffer full, can we grow it? */ if (tmp_size == buf->max) { /* already at max */ GNUNET_break (0); GNUNET_break (Z_OK == inflateEnd (&z)); GNUNET_free (tmp); return GNUNET_JSON_PR_OUT_OF_MEMORY; } if (tmp_size * 2 < tmp_size) tmp_size = buf->max; else tmp_size = GNUNET_MIN (buf->max, tmp_size * 2); tmp = GNUNET_realloc (tmp, tmp_size); z.next_out = (Bytef *) &tmp[z.total_out]; continue; case Z_STREAM_END: /* decompression successful, make 'tmp' the new 'data' */ GNUNET_free (buf->data); buf->data = tmp; buf->alloc = tmp_size; buf->fill = z.total_out; GNUNET_break (Z_OK == inflateEnd (&z)); return GNUNET_JSON_PR_SUCCESS; /* at least for now */ } } /* while (1) */ } /** * Process a POST request containing a JSON object. This function * realizes an MHD POST processor that will (incrementally) process * JSON data uploaded to the HTTP server. It will store the required * state in the @a con_cls, which must be cleaned up using * #GNUNET_JSON_post_parser_callback(). * * @param buffer_max maximum allowed size for the buffer * @param connection MHD connection handle (for meta data about the upload) * @param con_cls the closure (will point to a `struct Buffer *`) * @param upload_data the POST data * @param upload_data_size number of bytes in @a upload_data * @param json the JSON object for a completed request * @return result code indicating the status of the operation */ enum GNUNET_JSON_PostResult GNUNET_JSON_post_parser (size_t buffer_max, struct MHD_Connection *connection, void **con_cls, const char *upload_data, size_t *upload_data_size, json_t **json) { struct Buffer *r = *con_cls; const char *ce; int ret; *json = NULL; if (NULL == *con_cls) { /* We are seeing a fresh POST request. */ r = GNUNET_new (struct Buffer); if (GNUNET_OK != buffer_init (r, upload_data, *upload_data_size, REQUEST_BUFFER_INITIAL, buffer_max)) { *con_cls = NULL; buffer_deinit (r); GNUNET_free (r); return GNUNET_JSON_PR_OUT_OF_MEMORY; } /* everything OK, wait for more POST data */ *upload_data_size = 0; *con_cls = r; return GNUNET_JSON_PR_CONTINUE; } if (0 != *upload_data_size) { /* We are seeing an old request with more data available. */ if (GNUNET_OK != buffer_append (r, upload_data, *upload_data_size, buffer_max)) { /* Request too long */ *con_cls = NULL; buffer_deinit (r); GNUNET_free (r); return GNUNET_JSON_PR_REQUEST_TOO_LARGE; } /* everything OK, wait for more POST data */ *upload_data_size = 0; return GNUNET_JSON_PR_CONTINUE; } /* We have seen the whole request. */ ce = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_ENCODING); if ((NULL != ce) && (0 == strcasecmp ("deflate", ce))) { ret = inflate_data (r); if (GNUNET_JSON_PR_SUCCESS != ret) { buffer_deinit (r); GNUNET_free (r); *con_cls = NULL; return ret; } } *json = json_loadb (r->data, r->fill, 0, NULL); if (NULL == *json) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to parse JSON request body\n"); buffer_deinit (r); GNUNET_free (r); *con_cls = NULL; return GNUNET_JSON_PR_JSON_INVALID; } buffer_deinit (r); GNUNET_free (r); *con_cls = NULL; return GNUNET_JSON_PR_SUCCESS; } /** * Function called whenever we are done with a request * to clean up our state. * * @param con_cls value as it was left by * #GNUNET_JSON_post_parser(), to be cleaned up */ void GNUNET_JSON_post_parser_cleanup (void *con_cls) { struct Buffer *r = con_cls; if (NULL != r) { buffer_deinit (r); GNUNET_free (r); } } /* end of mhd_json.c */