diff options
Diffstat (limited to 'src/mhd')
-rw-r--r-- | src/mhd/Makefile.am | 26 | ||||
-rw-r--r-- | src/mhd/mhd_config.c | 396 | ||||
-rw-r--r-- | src/mhd/mhd_parsing.c | 294 | ||||
-rw-r--r-- | src/mhd/mhd_responses.c | 501 |
4 files changed, 1217 insertions, 0 deletions
diff --git a/src/mhd/Makefile.am b/src/mhd/Makefile.am new file mode 100644 index 000000000..cc4c70735 --- /dev/null +++ b/src/mhd/Makefile.am @@ -0,0 +1,26 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = \ + libtalermhd.la + +libtalermhd_la_SOURCES = \ + mhd_config.c \ + mhd_parsing.c \ + mhd_responses.c +libtalermhd_la_LDFLAGS = \ + -version-info 0:0:0 \ + -export-dynamic -no-undefined +libtalermhd_la_LIBADD = \ + -lgnunetjson \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil \ + -ljansson \ + $(XLIB) + diff --git a/src/mhd/mhd_config.c b/src/mhd/mhd_config.c new file mode 100644 index 000000000..d4b0e9795 --- /dev/null +++ b/src/mhd/mhd_config.c @@ -0,0 +1,396 @@ +/* + This file is part of TALER + Copyright (C) 2014--2019 Taler Systems SA + + 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, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mhd_config.c + * @brief functions to configure and setup MHD + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_mhd_lib.h" + + +/** + * Backlog for listen operation on UNIX domain sockets. + */ +#define UNIX_BACKLOG 500 + + +/** + * Parse the configuration to determine on which port + * or UNIX domain path we should run an HTTP service. + * + * @param cfg configuration to parse + * @param section section of the configuration to parse (usually "exchange") + * @param[out] rport set to the port number, or 0 for none + * @param[out] unix_path set to the UNIX path, or NULL for none + * @param[out] unix_mode set to the mode to be used for @a unix_path + * @return #GNUNET_OK on success + */ +int +TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, + uint16_t *rport, + char **unix_path, + mode_t *unix_mode) +{ + const char *choices[] = {"tcp", "unix"}; + const char *serve_type; + unsigned long long port; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_choice (cfg, + section, + "serve", + choices, + &serve_type)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "serve", + "serve type required"); + return GNUNET_SYSERR; + } + + if (0 == strcasecmp (serve_type, "tcp")) + { + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + section, + "port", + &port)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "port", + "port number required"); + return GNUNET_SYSERR; + } + + if ( (0 == port) || + (port > UINT16_MAX) ) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "port", + "value not in [1,65535]"); + return GNUNET_SYSERR; + } + *rport = (uint16_t) port; + *unix_path = NULL; + return GNUNET_OK; + } + if (0 == strcmp (serve_type, "unix")) + { + struct sockaddr_un s_un; + char *modestring; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + section, + "unixpath", + unix_path)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "unixpath", + "unixpath required"); + return GNUNET_SYSERR; + } + if (strlen (*unix_path) >= sizeof (s_un.sun_path)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "unixpath `%s' is too long\n", + *unix_path); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "UNIXPATH_MODE", + &modestring)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "UNIXPATH_MODE"); + return GNUNET_SYSERR; + } + errno = 0; + *unix_mode = (mode_t) strtoul (modestring, NULL, 8); + if (0 != errno) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "UNIXPATH_MODE", + "must be octal number"); + GNUNET_free (modestring); + return GNUNET_SYSERR; + } + GNUNET_free (modestring); + return GNUNET_OK; + } + /* not reached */ + GNUNET_assert (0); + return GNUNET_SYSERR; +} + + +/** + * Function called for logging by MHD. + * + * @param cls closure, NULL + * @param fm format string (`printf()`-style) + * @param ap arguments to @a fm + */ +void +TALER_MHD_handle_logs (void *cls, + const char *fm, + va_list ap) +{ + static int cache; + char buf[2048]; + + (void) cls; + if (-1 == cache) + return; + if (0 == cache) + { + if (0 == + GNUNET_get_log_call_status (GNUNET_ERROR_TYPE_INFO, + "libmicrohttpd", + __FILE__, + __FUNCTION__, + __LINE__)) + { + cache = -1; + return; + } + } + cache = 1; + vsnprintf (buf, + sizeof (buf), + fm, + ap); + GNUNET_log_from_nocheck (GNUNET_ERROR_TYPE_INFO, + "libmicrohttpd", + "%s", + buf); +} + + +/** + * Open UNIX domain socket for listining at @a unix_path with + * permissions @a unix_mode. + * + * @param unix_path where to listen + * @param unix_mode access permissions to set + * @return -1 on error, otherwise the listen socket + */ +int +TALER_MHD_open_unix_path (const char *unix_path, + mode_t unix_mode) +{ + struct GNUNET_NETWORK_Handle *nh; + struct sockaddr_un *un; + int fd; + + if (sizeof (un->sun_path) <= strlen (unix_path)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "unixpath `%s' is too long\n", + unix_path); + return -1; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Creating listen socket '%s' with mode %o\n", + unix_path, + unix_mode); + + if (GNUNET_OK != + GNUNET_DISK_directory_create_for_file (unix_path)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "mkdir", + unix_path); + } + + un = GNUNET_new (struct sockaddr_un); + un->sun_family = AF_UNIX; + strncpy (un->sun_path, + unix_path, + sizeof (un->sun_path) - 1); + GNUNET_NETWORK_unix_precheck (un); + + if (NULL == (nh = GNUNET_NETWORK_socket_create (AF_UNIX, + SOCK_STREAM, + 0))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "socket"); + GNUNET_free (un); + return -1; + } + if (GNUNET_OK != + GNUNET_NETWORK_socket_bind (nh, + (void *) un, + sizeof (struct sockaddr_un))) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "bind", + unix_path); + GNUNET_free (un); + GNUNET_NETWORK_socket_close (nh); + return -1; + } + GNUNET_free (un); + if (GNUNET_OK != + GNUNET_NETWORK_socket_listen (nh, + UNIX_BACKLOG)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "listen"); + GNUNET_NETWORK_socket_close (nh); + return -1; + } + + if (0 != chmod (unix_path, + unix_mode)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "chmod"); + GNUNET_NETWORK_socket_close (nh); + return -1; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "set socket '%s' to mode %o\n", + unix_path, + unix_mode); + fd = GNUNET_NETWORK_get_fd (nh); + GNUNET_NETWORK_socket_free_memory_only_ (nh); + return fd; +} + + +/** + * Bind a listen socket to the UNIX domain path + * or the TCP port and IP address as specified + * in @a cfg in section @a section. IF only a + * port was specified, set @a port and return -1. + * Otherwise, return the bound file descriptor. + * + * @param cfg configuration to parse + * @param section configuration section to use + * @param port[out] port to set, if TCP without BINDTO + * @return -1 and a port of zero on error, otherwise + * either -1 and a port, or a bound stream socket + */ +int +TALER_MHD_bind (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, + uint16_t *port) +{ + char *bind_to; + char *serve_unixpath; + mode_t unixpath_mode; + int fh; + char port_str[6]; + struct addrinfo hints; + struct addrinfo *res; + int ec; + struct GNUNET_NETWORK_Handle *nh; + + *port = 0; + if (GNUNET_OK != + TALER_MHD_parse_config (cfg, + section, + port, + &serve_unixpath, + &unixpath_mode)) + return -1; + if (NULL != serve_unixpath) + return TALER_MHD_open_unix_path (serve_unixpath, + unixpath_mode); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "BIND_TO", + &bind_to)) + return -1; /* only set port */ + /* let's have fun binding... */ + GNUNET_snprintf (port_str, + sizeof (port_str), + "%u", + (unsigned int) *port); + *port = 0; /* do NOT return port in case of errors */ + memset (&hints, 0, sizeof (hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_PASSIVE +#ifdef AI_IDN + | AI_IDN +#endif + ; + if (0 != + (ec = getaddrinfo (bind_to, + port_str, + &hints, + &res))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to resolve BIND_TO address `%s': %s\n", + bind_to, gai_strerror (ec)); + GNUNET_free (bind_to); + return -1; + } + GNUNET_free (bind_to); + + if (NULL == (nh = GNUNET_NETWORK_socket_create (res->ai_family, + res->ai_socktype, + res->ai_protocol))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "socket"); + freeaddrinfo (res); + return -1; + } + if (GNUNET_OK != + GNUNET_NETWORK_socket_bind (nh, + res->ai_addr, + res->ai_addrlen)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "bind"); + freeaddrinfo (res); + return -1; + } + freeaddrinfo (res); + if (GNUNET_OK != + GNUNET_NETWORK_socket_listen (nh, + UNIX_BACKLOG)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "listen"); + GNUNET_SCHEDULER_shutdown (); + return -1; + } + fh = GNUNET_NETWORK_get_fd (nh); + GNUNET_NETWORK_socket_free_memory_only_ (nh); + return fh; +} diff --git a/src/mhd/mhd_parsing.c b/src/mhd/mhd_parsing.c new file mode 100644 index 000000000..0b070ffe5 --- /dev/null +++ b/src/mhd/mhd_parsing.c @@ -0,0 +1,294 @@ +/* + This file is part of TALER + Copyright (C) 2014--2019 Taler Systems SA + + 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, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mhd_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 <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include "taler_json_lib.h" +#include "taler_mhd_lib.h" + + +/** + * Maximum POST request size. + */ +#define REQUEST_BUFFER_MAX (1024 * 1024) + + +/** + * 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 + * #TALER_MHD_post_cleanup_callback(). + * + * @param connection the MHD connection + * @param con_cls the closure (points 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 + * #GNUNET_YES if json object was parsed or at least + * may be parsed in the future (call again); + * `*json` will be NULL if we need to be called again, + * and non-NULL if we are done. + * #GNUNET_NO is request incomplete or invalid + * (error message was generated) + * #GNUNET_SYSERR on internal error + * (we could not even queue an error message, + * close HTTP session with MHD_NO) + */ +int +TALER_MHD_parse_post_json (struct MHD_Connection *connection, + void **con_cls, + const char *upload_data, + size_t *upload_data_size, + json_t **json) +{ + enum GNUNET_JSON_PostResult pr; + + pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, + connection, + con_cls, + upload_data, + upload_data_size, + json); + switch (pr) + { + case GNUNET_JSON_PR_OUT_OF_MEMORY: + return (MHD_NO == + TALER_MHD_reply_with_error + (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_PARSER_OUT_OF_MEMORY, + "out of memory")) ? GNUNET_SYSERR : GNUNET_NO; + + case GNUNET_JSON_PR_CONTINUE: + return GNUNET_YES; + case GNUNET_JSON_PR_REQUEST_TOO_LARGE: + return (MHD_NO == + TALER_MHD_reply_request_too_large + (connection)) ? GNUNET_SYSERR : GNUNET_NO; + case GNUNET_JSON_PR_JSON_INVALID: + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_JSON_INVALID, + "invalid JSON uploaded")) + ? GNUNET_NO : GNUNET_SYSERR; + case GNUNET_JSON_PR_SUCCESS: + GNUNET_break (NULL != *json); + return GNUNET_YES; + } + /* this should never happen */ + GNUNET_break (0); + return GNUNET_SYSERR; +} + + +/** + * Function called whenever we are done with a request + * to clean up our state. + * + * @param con_cls value as it was left by + * #TALER_MHD_post_json(), to be cleaned up + */ +void +TALER_MHD_parse_post_cleanup_callback (void *con_cls) +{ + // FIXME: this should probably NOT be done with a 'void *' like this! + GNUNET_JSON_post_parser_cleanup (con_cls); +} + + +/** + * 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_MHD_parse_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_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MISSING, + 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_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MALFORMED, + param_name)) + ? GNUNET_SYSERR : GNUNET_NO; + return GNUNET_OK; +} + + +/** + * Parse JSON object into components based on the given field + * specification. Generates error response on parse errors. + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param[in,out] spec field specification for the parser + * @return + * #GNUNET_YES if navigation was successful (caller is responsible + * for freeing allocated variable-size data using + * GNUNET_JSON_parse_free() when done) + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error + */ +int +TALER_MHD_parse_json_data (struct MHD_Connection *connection, + const json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + int ret; + const char *error_json_name; + unsigned int error_line; + + ret = GNUNET_JSON_parse (root, + spec, + &error_json_name, + &error_line); + if (GNUNET_SYSERR == ret) + { + if (NULL == error_json_name) + error_json_name = "<no field>"; + ret = (MHD_YES == + TALER_MHD_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:I, s:s, s:I}", + "hint", "JSON parse error", + "code", + (json_int_t) + TALER_EC_JSON_INVALID_WITH_DETAILS, + "field", error_json_name, + "line", (json_int_t) error_line)) + ? GNUNET_NO : GNUNET_SYSERR; + return ret; + } + return GNUNET_YES; +} + + +/** + * Parse JSON array into components based on the given field + * specification. Generates error response on parse errors. + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param[in,out] spec field specification for the parser + * @param ... -1-terminated list of array offsets of type 'int' + * @return + * #GNUNET_YES if navigation was successful (caller is responsible + * for freeing allocated variable-size data using + * GNUNET_JSON_parse_free() when done) + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error + */ +int +TALER_MHD_parse_json_array (struct MHD_Connection *connection, + const json_t *root, + struct GNUNET_JSON_Specification *spec, + ...) +{ + int ret; + const char *error_json_name; + unsigned int error_line; + va_list ap; + json_int_t dim; + + va_start (ap, spec); + dim = 0; + while ( (-1 != (ret = va_arg (ap, int))) && + (NULL != root) ) + { + dim++; + root = json_array_get (root, ret); + } + va_end (ap); + if (NULL == root) + { + ret = (MHD_YES == + TALER_MHD_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:I, s:I}", + "hint", "expected array", + "code", + (json_int_t) + TALER_EC_JSON_INVALID_WITH_DETAILS, + "dimension", dim)) + ? GNUNET_NO : GNUNET_SYSERR; + return ret; + } + ret = GNUNET_JSON_parse (root, + spec, + &error_json_name, + &error_line); + if (GNUNET_SYSERR == ret) + { + if (NULL == error_json_name) + error_json_name = "<no field>"; + ret = (MHD_YES == + TALER_MHD_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:I, s:I}", + "hint", error_json_name, + "code", + (json_int_t) + TALER_EC_JSON_INVALID_WITH_DETAILS, + "line", (json_int_t) error_line)) + ? GNUNET_NO : GNUNET_SYSERR; + return ret; + } + return GNUNET_YES; +} + + +/* end of mhd_parsing.c */ diff --git a/src/mhd/mhd_responses.c b/src/mhd/mhd_responses.c new file mode 100644 index 000000000..3642e7d54 --- /dev/null +++ b/src/mhd/mhd_responses.c @@ -0,0 +1,501 @@ +/* + This file is part of TALER + Copyright (C) 2014-2019 Taler Systems SA + + 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, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mhd_responses.c + * @brief API for generating HTTP replies + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <zlib.h> +#include "taler_util.h" +#include "taler_mhd_lib.h" + + +/** + * Enable checking signatures before we hand them out + * (even though we should have checked them before). + * So technically these checks are redundant, but good + * during testing. + */ +#define SANITY_CHECKS_ON 1 + + +/** + * Global options for response generation. + */ +static enum TALER_MHD_GlobalOptions TM_go; + + +/** + * Set global options for response generation + * within libtalermhd. + * + * @param go global options to use + */ +void +TALER_MHD_setup (enum TALER_MHD_GlobalOptions go) +{ + TM_go = go; +} + + +/** + * Add headers we want to return in every response. + * Useful for testing, like if we want to always close + * connections. + * + * @param response response to modify + */ +void +TALER_MHD_add_global_headers (struct MHD_Response *response) +{ + if (0 != (TM_go & TALER_MHD_GO_FORCE_CONNECTION_CLOSE)) + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONNECTION, + "close")); +} + + +/** + * Is HTTP body deflate compression supported by the client? + * + * @param connection connection to check + * @return #MHD_YES if 'deflate' compression is allowed + * + * Note that right now we're ignoring q-values, which is technically + * not correct, and also do not support "*" anywhere but in a line by + * itself. This should eventually be fixed, see also + * https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + */ +int +TALER_MHD_can_compress (struct MHD_Connection *connection) +{ + const char *ae; + const char *de; + + if (0 != (TM_go & TALER_MHD_GO_DISABLE_COMPRESSION)) + return MHD_NO; + ae = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_ACCEPT_ENCODING); + if (NULL == ae) + return MHD_NO; + if (0 == strcmp (ae, + "*")) + return MHD_YES; + de = strstr (ae, + "deflate"); + if (NULL == de) + return MHD_NO; + if ( ( (de == ae) || + (de[-1] == ',') || + (de[-1] == ' ') ) && + ( (de[strlen ("deflate")] == '\0') || + (de[strlen ("deflate")] == ',') || + (de[strlen ("deflate")] == ';') ) ) + return MHD_YES; + return MHD_NO; +} + + +/** + * Try to compress a response body. Updates @a buf and @a buf_size. + * + * @param[in,out] buf pointer to body to compress + * @param[in,out] buf_size pointer to initial size of @a buf + * @return #MHD_YES if @a buf was compressed + */ +int +TALER_MHD_body_compress (void **buf, + size_t *buf_size) +{ + Bytef *cbuf; + uLongf cbuf_size; + int ret; + + cbuf_size = compressBound (*buf_size); + cbuf = malloc (cbuf_size); + if (NULL == cbuf) + return MHD_NO; + ret = compress (cbuf, + &cbuf_size, + (const Bytef *) *buf, + *buf_size); + if ( (Z_OK != ret) || + (cbuf_size >= *buf_size) ) + { + /* compression failed */ + free (cbuf); + return MHD_NO; + } + free (*buf); + *buf = (void *) cbuf; + *buf_size = (size_t) cbuf_size; + return MHD_YES; +} + + +/** + * Make JSON response object. + * + * @param json the json object + * @return MHD response object + */ +struct MHD_Response * +TALER_MHD_make_json (const json_t *json) +{ + struct MHD_Response *resp; + char *json_str; + + json_str = json_dumps (json, + JSON_INDENT (2)); + if (NULL == json_str) + { + GNUNET_break (0); + return NULL; + } + resp = MHD_create_response_from_buffer (strlen (json_str), + json_str, + MHD_RESPMEM_MUST_FREE); + if (NULL == resp) + { + free (json_str); + GNUNET_break (0); + return NULL; + } + TALER_MHD_add_global_headers (resp); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_TYPE, + "application/json")); + return resp; +} + + +/** + * Send JSON object as response. + * + * @param connection the MHD connection + * @param json the json object + * @param response_code the http response code + * @return MHD result code + */ +int +TALER_MHD_reply_json (struct MHD_Connection *connection, + const json_t *json, + unsigned int response_code) +{ + struct MHD_Response *resp; + void *json_str; + size_t json_len; + int ret; + int comp; + + json_str = json_dumps (json, + JSON_INDENT (2)); + if (NULL == json_str) + { + /** + * This log helps to figure out which + * function called this one and assert-failed. + */ + TALER_LOG_ERROR ("Aborting json-packing for HTTP code: %u\n", + response_code); + + GNUNET_assert (0); + return MHD_NO; + } + json_len = strlen (json_str); + /* try to compress the body */ + comp = MHD_NO; + if (MHD_YES == + TALER_MHD_can_compress (connection)) + comp = TALER_MHD_body_compress (&json_str, + &json_len); + resp = MHD_create_response_from_buffer (json_len, + json_str, + MHD_RESPMEM_MUST_FREE); + if (NULL == resp) + { + free (json_str); + GNUNET_break (0); + return MHD_NO; + } + TALER_MHD_add_global_headers (resp); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_TYPE, + "application/json")); + if (MHD_YES == comp) + { + /* Need to indicate to client that body is compressed */ + if (MHD_NO == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_ENCODING, + "deflate")) + { + GNUNET_break (0); + MHD_destroy_response (resp); + return MHD_NO; + } + } + ret = MHD_queue_response (connection, + response_code, + resp); + MHD_destroy_response (resp); + return ret; +} + + +/** + * Function to call to handle the request by building a JSON + * reply from a format string and varargs. + * + * @param connection the MHD connection to handle + * @param response_code HTTP response code to use + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TALER_MHD_reply_json_pack (struct MHD_Connection *connection, + unsigned int response_code, + const char *fmt, + ...) +{ + json_t *json; + va_list argp; + int ret; + json_error_t jerror; + + va_start (argp, fmt); + json = json_vpack_ex (&jerror, 0, fmt, argp); + va_end (argp); + if (NULL == json) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to pack JSON with format `%s': %s\n", + fmt, + jerror.text); + GNUNET_break (0); + return MHD_NO; + } + ret = TALER_MHD_reply_json (connection, + json, + response_code); + json_decref (json); + return ret; +} + + +/** + * Make JSON response object. + * + * @param fmt format string for pack + * @param ... varargs + * @return MHD response object + */ +struct MHD_Response * +TALER_MHD_make_json_pack (const char *fmt, + ...) +{ + json_t *json; + va_list argp; + struct MHD_Response *ret; + json_error_t jerror; + + va_start (argp, fmt); + json = json_vpack_ex (&jerror, + 0, + fmt, + argp); + va_end (argp); + if (NULL == json) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to pack JSON with format `%s': %s\n", + fmt, + jerror.text); + GNUNET_break (0); + return MHD_NO; + } + ret = TALER_MHD_make_json (json); + json_decref (json); + return ret; +} + + +/** + * Create a response indicating an internal error. + * + * @param ec error code to return + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TALER_MHD_make_error (enum TALER_ErrorCode ec, + const char *hint) +{ + return TALER_MHD_make_json_pack ("{s:I, s:s}", + "code", (json_int_t) ec, + "hint", hint); +} + + +/** + * Send a response indicating an error. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param http_status HTTP status code to use + * @param hint human readable hint about the error + * @return a MHD result code + */ +int +TALER_MHD_reply_with_error (struct MHD_Connection *connection, + unsigned int http_status, + enum TALER_ErrorCode ec, + const char *hint) +{ + return TALER_MHD_reply_json_pack (connection, + http_status, + "{s:I, s:s}", + "code", (json_int_t) ec, + "hint", hint); +} + + +/** + * Send a response indicating that the request was too big. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TALER_MHD_reply_request_too_large (struct MHD_Connection *connection) +{ + struct MHD_Response *resp; + int ret; + + resp = MHD_create_response_from_buffer (0, + NULL, + MHD_RESPMEM_PERSISTENT); + if (NULL == resp) + return MHD_NO; + TALER_MHD_add_global_headers (resp); + ret = MHD_queue_response (connection, + MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, + resp); + MHD_destroy_response (resp); + return ret; +} + + +/** + * Function to call to handle the request by sending + * back a redirect to the AGPL source code. + * + * @param connection the MHD connection to handle + * @param url where to redirect for the sources + * @return MHD result code + */ +int +TALER_MHD_reply_agpl (struct MHD_Connection *connection, + const char *url) +{ + const char *agpl = + "This server is licensed under the Affero GPL. You will now be redirected to the source code."; + struct MHD_Response *response; + int ret; + + response = MHD_create_response_from_buffer (strlen (agpl), + (void *) agpl, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + TALER_MHD_add_global_headers (response); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + "text/plain")); + if (MHD_NO == + MHD_add_response_header (response, + MHD_HTTP_HEADER_LOCATION, + url)) + { + GNUNET_break (0); + MHD_destroy_response (response); + return MHD_NO; + } + ret = MHD_queue_response (connection, + MHD_HTTP_FOUND, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Function to call to handle the request by sending + * back static data. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param http_status status code to return + * @param mime_type content-type to use + * @param body response payload + * @param body_size number of bytes in @a body + * @return MHD result code + */ +int +TALER_MHD_reply_static (struct MHD_Connection *connection, + unsigned int http_status, + const char *mime_type, + const char *body, + size_t body_size) +{ + struct MHD_Response *response; + int ret; + + response = MHD_create_response_from_buffer (body_size, + (void *) body, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + TALER_MHD_add_global_headers (response); + if (NULL != mime_type) + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + mime_type)); + ret = MHD_queue_response (connection, + http_status, + response); + MHD_destroy_response (response); + return ret; +} + + +/* end of mhd_responses.c */ |