/*
This file is part of TALER
(C) 2014 GNUnet e.V.
TALER 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, or (at your option) any later version.
TALER 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
TALER; see the file COPYING. If not, If not, see
*/
/**
* @file taler-mint-httpd_parsing.c
* @brief functions to parse incoming requests (MHD arguments and JSON snippets)
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include "taler-mint-httpd_parsing.h"
#include "taler-mint-httpd_responses.h"
/**
* Initial size for POST
* request buffer.
*/
#define REQUEST_BUFFER_INITIAL 1024
/**
* Maximum POST request size
*/
#define REQUEST_BUFFER_MAX (1024*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;
};
/**
* 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);
memcpy (buf->data, data, data_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 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
* GNUNET_SYSERR on fatal error (out of memory?)
*/
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 (data_size + buf->fill > 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);
memcpy (new_buf, buf->data, buf->fill);
buf->data = new_buf;
buf->alloc = new_size;
}
memcpy (buf->data + buf->fill, data, data_size);
buf->fill += data_size;
return GNUNET_OK;
}
/**
* Process a POST request containing a JSON object.
*
* @param connection the MHD connection
* @param con_cs the closure (contains a 'struct Buffer *')
* @param upload_data the POST data
* @param upload_data_size the POST data size
* @param json the JSON object for a completed request
*
* @returns
* GNUNET_YES if json object was parsed
* GNUNET_NO is request incomplete or invalid
* GNUNET_SYSERR on internal error
*/
int
TALER_MINT_parse_post_json (struct MHD_Connection *connection,
void **con_cls,
const char *upload_data,
size_t *upload_data_size,
json_t **json)
{
struct Buffer *r = *con_cls;
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,
REQUEST_BUFFER_MAX))
{
*con_cls = NULL;
buffer_deinit (r);
GNUNET_free (r);
return GNUNET_SYSERR;
}
*upload_data_size = 0;
*con_cls = r;
return GNUNET_NO;
}
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,
REQUEST_BUFFER_MAX))
{
/* Request too long or we're out of memory. */
*con_cls = NULL;
buffer_deinit (r);
GNUNET_free (r);
return GNUNET_SYSERR;
}
*upload_data_size = 0;
return GNUNET_NO;
}
/* We have seen the whole request. */
*json = json_loadb (r->data, r->fill, 0, NULL);
buffer_deinit (r);
GNUNET_free (r);
if (NULL == *json)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Can't parse JSON request body\n");
return (MHD_YES ==
TALER_MINT_reply_json_pack (connection,
MHD_HTTP_BAD_REQUEST,
"{s:s}",
"error", "invalid json"))
? GNUNET_NO : GNUNET_SYSERR;
}
*con_cls = NULL;
return GNUNET_YES;
}
/**
* Function called whenever we are done with a request
* to clean up our state.
*
* @param con_cls value as it was left by
* #TALER_MINT_parse_post_json(), to be cleaned up
*/
void
TALER_MINT_parse_post_cleanup_callback (void *con_cls)
{
struct Buffer *r = con_cls;
if (NULL != r)
buffer_deinit (r);
}
/**
* Navigate through a JSON tree.
*
* Sends an error response if navigation is impossible (i.e.
* the JSON object is invalid)
*
* @param connection the connection to send an error response to
* @param root the JSON node to start the navigation at.
* @param ... navigation specification (see JNAV_*)
* @return GNUNET_YES if navigation was successful
* GNUNET_NO if json is malformed, error response was generated
* GNUNET_SYSERR on internal error
*/
int
request_json_require_nav (struct MHD_Connection *connection,
const json_t *root, ...)
{
va_list argp;
int ignore = GNUNET_NO;
// what's our current path from 'root'?
json_t *path;
path = json_array ();
va_start(argp, root);
while (1)
{
int command = va_arg(argp, int);
switch (command)
{
case JNAV_FIELD:
{
const char *fname = va_arg(argp, const char *);
if (GNUNET_YES == ignore)
break;
json_array_append_new (path, json_string (fname));
root = json_object_get (root, fname);
if (NULL == root)
{
/* FIXME: can't IGNORE this return value! */
(void) TALER_MINT_reply_json_pack (connection,
MHD_HTTP_BAD_REQUEST,
"{s:s,s:o}",
"error",
"missing field in JSON",
"path",
path);
ignore = GNUNET_YES;
break;
}
}
break;
case JNAV_INDEX:
{
int fnum = va_arg(argp, int);
if (GNUNET_YES == ignore)
break;
json_array_append_new (path, json_integer (fnum));
root = json_array_get (root, fnum);
if (NULL == root)
{
/* FIXME: can't IGNORE this return value! */
(void) TALER_MINT_reply_json_pack (connection,
MHD_HTTP_BAD_REQUEST,
"{s:s, s:o}",
"error", "missing index in JSON",
"path", path);
ignore = GNUNET_YES;
break;
}
}
break;
case JNAV_RET_DATA:
{
void *where = va_arg (argp, void *);
size_t len = va_arg (argp, size_t);
const char *str;
int res;
va_end(argp);
if (GNUNET_YES == ignore)
return GNUNET_NO;
str = json_string_value (root);
if (NULL == str)
{
/* FIXME: can't IGNORE this return value! */
(void) TALER_MINT_reply_json_pack (connection,
MHD_HTTP_BAD_REQUEST,
"{s:s, s:o}",
"error",
"string expected",
"path",
path);
return GNUNET_NO;
}
res = GNUNET_STRINGS_string_to_data (str, strlen (str),
where, len);
if (GNUNET_OK != res)
{
/* FIXME: can't IGNORE this return value! */
(void) TALER_MINT_reply_json_pack (connection,
MHD_HTTP_BAD_REQUEST,
"{s:s,s:o}",
"error",
"malformed binary data in JSON",
"path", path);
return GNUNET_NO;
}
return GNUNET_YES;
}
break;
case JNAV_RET_DATA_VAR:
{
void **where = va_arg (argp, void **);
size_t *len = va_arg (argp, size_t *);
const char *str;
va_end(argp);
if (GNUNET_YES == ignore)
return GNUNET_NO;
str = json_string_value (root);
if (NULL == str)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
*len = (strlen (str) * 5) / 8;
if (where != NULL)
{
int res;
*where = GNUNET_malloc (*len);
res = GNUNET_STRINGS_string_to_data (str, strlen (str),
*where, *len);
if (GNUNET_OK != res)
{
/* FIXME: can't IGNORE this return value! */
(void) TALER_MINT_reply_json_pack (connection,
MHD_HTTP_BAD_REQUEST,
"{s:s, s:o}",
"error",
"malformed binary data in JSON",
"path", path);
return GNUNET_NO;
}
}
return GNUNET_OK;
}
break;
case JNAV_RET_TYPED_JSON:
{
int typ = va_arg (argp, int);
const json_t **r_json = va_arg (argp, const json_t **);
va_end(argp);
if (GNUNET_YES == ignore)
return GNUNET_NO;
if (typ != -1 && json_typeof (root) != typ)
{
/* FIXME: can't IGNORE this return value! */
(void) TALER_MINT_reply_json_pack (connection,
MHD_HTTP_BAD_REQUEST,
"{s:s, s:i, s:i s:o}",
"error", "wrong JSON field type",
"type_expected", typ,
"type_actual", json_typeof (root),
"path", path);
return GNUNET_NO;
}
*r_json = root;
return GNUNET_OK;
}
break;
default:
GNUNET_assert (0);
}
}
GNUNET_assert (0);
}
/**
* Extract base32crockford encoded data from request.
*
* Queues an error response to the connection if the parameter is missing or
* invalid.
*
* @param connection the MHD connection
* @param param_name the name of the parameter with the key
* @param[out] out_data pointer to store the result
* @param out_size expected size of data
* @return
* GNUNET_YES if the the argument is present
* GNUNET_NO if the argument is absent or malformed
* GNUNET_SYSERR on internal error (error response could not be sent)
*/
int
TALER_MINT_mhd_request_arg_data (struct MHD_Connection *connection,
const char *param_name,
void *out_data,
size_t out_size)
{
const char *str;
str = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
param_name);
if (NULL == str)
{
return (MHD_NO ==
TALER_MINT_reply_arg_missing (connection, param_name))
? GNUNET_SYSERR : GNUNET_NO;
}
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (str,
strlen (str),
out_data,
out_size))
return (MHD_NO ==
TALER_MINT_reply_arg_invalid (connection, param_name))
? GNUNET_SYSERR : GNUNET_NO;
return GNUNET_OK;
}
/* end of taler-mint-httpd_parsing.c */