commit c1e92e30d7cedb748db63bd917a8ff11beddf88b
parent c983fb533c9a1ad5ab780b932c52a176994c08e1
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 28 Dec 2025 12:16:45 +0100
taler-merchant-httpd refactoring
Diffstat:
1 file changed, 576 insertions(+), 423 deletions(-)
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
@@ -863,7 +863,7 @@ enum GNUNET_GenericReturnValue
TMH_add_instance (struct TMH_MerchantInstance *mi)
{
const char *id;
- int ret;
+ enum GNUNET_GenericReturnValue ret;
id = mi->settings.id;
if (NULL == id)
@@ -1240,21 +1240,10 @@ process_bearer_auth (struct TMH_HandlerContext *hc,
/**
- * A client has requested the given url using the given method
- * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
- * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
- * must call MHD callbacks to provide content to give back to the
- * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
- * #MHD_HTTP_NOT_FOUND, etc.).
+ * The callback was called again by MHD, continue processing
+ * the request with the already identified handler.
*
- * @param cls argument given together with the function
- * pointer when the handler was registered with MHD
- * @param connection the MHD connection to handle
- * @param url the requested url
- * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
- * #MHD_HTTP_METHOD_PUT, etc.)
- * @param version the HTTP version string (i.e.
- * #MHD_HTTP_VERSION_1_1)
+ * @param hc the handler context
* @param upload_data the data being uploaded (excluding HEADERS,
* for a POST that fits into memory and that is encoded
* with a supported encoding, the POST data will NOT be
@@ -1265,30 +1254,204 @@ process_bearer_auth (struct TMH_HandlerContext *hc,
* @param upload_data_size set initially to the size of the
* @a upload_data provided; the method must update this
* value to the number of bytes NOT processed;
- * @param con_cls pointer that the callback can set to some
- * address and that will be preserved by MHD for future
- * calls for this request; since the access handler may
- * be called many times (i.e., for a PUT/POST operation
- * with plenty of upload data) this allows the application
- * to easily associate some request-specific state.
- * If necessary, this state can be cleaned up in the
- * global #MHD_RequestCompletedCallback (which
- * can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
- * Initially, `*con_cls` will be set up by the
- * full_url_track_callback().
* @return #MHD_YES if the connection was handled successfully,
* #MHD_NO if the socket must be closed due to a serious
* error while handling the request
*/
static MHD_RESULT
-url_handler (void *cls,
- struct MHD_Connection *connection,
- const char *url,
- const char *method,
- const char *version,
- const char *upload_data,
- size_t *upload_data_size,
- void **con_cls)
+process_upload_with_handler (struct TMH_HandlerContext *hc,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ GNUNET_assert (NULL != hc->rh);
+ GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
+ if ( (hc->has_body) &&
+ (NULL == hc->request_body) )
+ {
+ size_t mul = hc->rh->max_upload;
+ enum GNUNET_GenericReturnValue res;
+
+ if (0 == mul)
+ mul = DEFAULT_MAX_UPLOAD_SIZE;
+ if ( (hc->total_upload + *upload_data_size < hc->total_upload) ||
+ (hc->total_upload + *upload_data_size > mul) )
+ {
+ /* Client exceeds upload limit. Should _usually_ be checked earlier
+ when we look at the MHD_HTTP_HEADER_CONTENT_LENGTH, alas with
+ chunked encoding an uploader MAY have omitted this, and thus
+ not permitted us to check on time. In this case, we just close
+ the connection once it exceeds our limit (instead of waiting
+ for the upload to complete and then fail). This could theoretically
+ cause some clients to retry, alas broken or malicious clients
+ are likely to retry anyway, so little we can do about it, and
+ failing earlier seems the best option here. */
+ GNUNET_break_op (0);
+ return MHD_NO;
+ }
+ hc->total_upload += *upload_data_size;
+ res = TALER_MHD_parse_post_json (hc->connection,
+ &hc->json_parse_context,
+ upload_data,
+ upload_data_size,
+ &hc->request_body);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO;
+ /* A error response was already generated */
+ if ( (GNUNET_NO == res) ||
+ /* or, need more data to accomplish parsing */
+ (NULL == hc->request_body) )
+ return MHD_YES; /* let MHD call us *again* */
+ }
+ /* Upload complete (if any), call handler to generate reply */
+ return hc->rh->handler (hc->rh,
+ hc->connection,
+ hc);
+}
+
+
+/**
+ * Log information about the request being handled.
+ *
+ * @param hc handler context
+ * @param method HTTP method of the request
+ */
+static void
+log_request (const struct TMH_HandlerContext *hc,
+ const char *method)
+{
+ const char *correlation_id;
+
+ correlation_id = MHD_lookup_connection_value (hc->connection,
+ MHD_HEADER_KIND,
+ "Taler-Correlation-Id");
+ if ( (NULL != correlation_id) &&
+ (GNUNET_YES !=
+ GNUNET_CURL_is_valid_scope_id (correlation_id)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Illegal incoming correlation ID\n");
+ correlation_id = NULL;
+ }
+ if (NULL != correlation_id)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling request for (%s) URL '%s', correlation_id=%s\n",
+ method,
+ hc->url,
+ correlation_id);
+ else
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling request (%s) for URL '%s'\n",
+ method,
+ hc->url);
+}
+
+
+/**
+ * Identify the instance of the request from the URL.
+ *
+ * @param[in,out] hc handler context
+ * @param[in,out] urlp URL path of the request, updated to point to the rest
+ * @param[out] use_admin set to true if we are using the admin instance
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if an error was queued (return #MHD_YES)
+ * #GNUNET_SYSERR to close the connection (return #MHD_NO)
+ */
+static enum GNUNET_GenericReturnValue
+identify_instance (struct TMH_HandlerContext *hc,
+ const char **urlp,
+ bool *use_admin)
+{
+ const char *url = *urlp;
+ const char *instance_prefix = "/instances/";
+
+ if (0 == strncmp (url,
+ instance_prefix,
+ strlen (instance_prefix)))
+ {
+ /* url starts with "/instances/" */
+ const char *istart = url + strlen (instance_prefix);
+ const char *slash = strchr (istart, '/');
+ char *instance_id;
+
+ if (NULL == slash)
+ instance_id = GNUNET_strdup (istart);
+ else
+ instance_id = GNUNET_strndup (istart,
+ slash - istart);
+ if (0 == strcmp (instance_id,
+ "admin"))
+ {
+ MHD_RESULT ret;
+ struct MHD_Response *response;
+ const char *rstart = hc->full_url + strlen (instance_prefix);
+ const char *rslash = strchr (rstart, '/');
+
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client used deprecated '/instances/default/' path. Redirecting to modern path\n");
+
+ response
+ = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (response,
+ true);
+ if (MHD_NO ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_LOCATION,
+ NULL == rslash
+ ? "/"
+ : rslash))
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (response);
+ GNUNET_free (instance_id);
+ return GNUNET_SYSERR;
+ }
+ ret = MHD_queue_response (hc->connection,
+ MHD_HTTP_PERMANENT_REDIRECT,
+ response);
+ MHD_destroy_response (response);
+ GNUNET_free (instance_id);
+ return (MHD_YES == ret) ? GNUNET_NO : GNUNET_SYSERR;
+ }
+ hc->instance = TMH_lookup_instance (instance_id);
+ if ( (NULL == hc->instance) &&
+ (0 == strcmp ("admin",
+ instance_id)) )
+ hc->instance = TMH_lookup_instance (NULL);
+ GNUNET_free (instance_id);
+ if (NULL == slash)
+ *urlp = "";
+ else
+ *urlp = slash;
+ }
+ else
+ {
+ /* use 'default' */
+ *use_admin = true;
+ hc->instance = TMH_lookup_instance (NULL);
+ }
+ if (NULL != hc->instance)
+ {
+ GNUNET_assert (hc->instance->rc < UINT_MAX);
+ hc->instance->rc++;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Determine the group of request handlers to call for the
+ * given URL. Removes a possible prefix from @a purl by advancing
+ * the pointer.
+ *
+ * @param[in,out] urlp pointer to the URL to analyze and update
+ * @param[out] is_public set to true if these are public handlers
+ * @return handler group to consider for the given URL
+ */
+static const struct TMH_RequestHandler *
+determine_handler_group (const char **urlp,
+ bool *is_public)
{
static struct TMH_RequestHandler management_handlers[] = {
/* GET /instances */
@@ -2278,7 +2441,6 @@ url_handler (void *cls,
.have_id_segment = true,
.permission = "pots-write"
},
-
{
.url_prefix = "*",
.method = MHD_HTTP_METHOD_OPTIONS,
@@ -2288,255 +2450,153 @@ url_handler (void *cls,
.url_prefix = NULL
}
};
- struct TMH_HandlerContext *hc = *con_cls;
+ const char *management_prefix = "/management/";
+ const char *private_prefix = "/private/";
+ const char *url = *urlp;
struct TMH_RequestHandler *handlers;
- bool use_default = false;
- (void) cls;
- (void) version;
- if (NULL != hc->url)
+ *is_public = false; /* ensure safe default */
+ if ( (0 == strncmp (url,
+ management_prefix,
+ strlen (management_prefix))) )
{
- /* MHD calls us again for a request, for first call
- see 'else' case below */
- GNUNET_assert (NULL != hc->rh);
- GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
- if ( (hc->has_body) &&
- (NULL == hc->request_body) )
- {
- size_t mul = hc->rh->max_upload;
- enum GNUNET_GenericReturnValue res;
-
- if (0 == mul)
- mul = DEFAULT_MAX_UPLOAD_SIZE;
- if ( (hc->total_upload + *upload_data_size < hc->total_upload) ||
- (hc->total_upload + *upload_data_size > mul) )
- {
- /* Client exceeds upload limit. Should _usually_ be checked earlier
- when we look at the MHD_HTTP_HEADER_CONTENT_LENGTH, alas with
- chunked encoding an uploader MAY have omitted this, and thus
- not permitted us to check on time. In this case, we just close
- the connection once it exceeds our limit (instead of waiting
- for the upload to complete and then fail). This could theoretically
- cause some clients to retry, alas broken or malicious clients
- are likely to retry anyway, so little we can do about it, and
- failing earlier seems the best option here. */
- GNUNET_break_op (0);
- return MHD_NO;
- }
- hc->total_upload += *upload_data_size;
- res = TALER_MHD_parse_post_json (connection,
- &hc->json_parse_context,
- upload_data,
- upload_data_size,
- &hc->request_body);
- if (GNUNET_SYSERR == res)
- return MHD_NO;
- /* A error response was already generated */
- if ( (GNUNET_NO == res) ||
- /* or, need more data to accomplish parsing */
- (NULL == hc->request_body) )
- return MHD_YES; /* let MHD call us *again* */
- }
- /* Upload complete (if any), call handler to generate reply */
- return hc->rh->handler (hc->rh,
- connection,
- hc);
+ handlers = management_handlers;
+ *urlp = url + strlen (management_prefix) - 1;
}
- hc->url = url;
+ else if ( (0 == strncmp (url,
+ private_prefix,
+ strlen (private_prefix))) ||
+ (0 == strcmp (url,
+ "/private")) )
{
- const char *correlation_id;
-
- correlation_id = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- "Taler-Correlation-Id");
- if ( (NULL != correlation_id) &&
- (GNUNET_YES !=
- GNUNET_CURL_is_valid_scope_id (correlation_id)) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Illegal incoming correlation ID\n");
- correlation_id = NULL;
- }
- if (NULL != correlation_id)
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Handling request for (%s) URL '%s', correlation_id=%s\n",
- method,
- url,
- correlation_id);
+ handlers = private_handlers;
+ if (0 == strcmp (url,
+ "/private"))
+ *urlp = "/";
else
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Handling request (%s) for URL '%s'\n",
- method,
- url);
+ *urlp = url + strlen (private_prefix) - 1;
}
+ else
+ {
+ handlers = public_handlers;
+ *is_public = true;
+ }
+ return handlers;
+}
+
+
+/**
+ * Identify the handler of the request from the @a url and @a method
+ *
+ * @param[in,out] hc handler context to update with applicable handler
+ * @param handlers array of handlers to consider
+ * @param url URL to match against the handlers
+ * @param method HTTP access method to consider
+ * @param use_admin set to true if we are using the admin instance
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if an error was queued (return #MHD_YES)
+ * #GNUNET_SYSERR to close the connection (return #MHD_NO)
+ */
+static enum GNUNET_GenericReturnValue
+identify_handler (struct TMH_HandlerContext *hc,
+ const struct TMH_RequestHandler *handlers,
+ const char *url,
+ const char *method,
+ bool use_admin)
+{
+ size_t prefix_strlen; /* i.e. 8 for "/orders/", or 7 for "/config" */
+ const char *infix_url = NULL; /* i.e. "$ORDER_ID", no '/'-es */
+ size_t infix_strlen = 0; /* number of characters in infix_url */
+ const char *suffix_url = NULL; /* i.e. "refund", excludes '/' at the beginning */
+ size_t suffix_strlen = 0; /* number of characters in suffix_url */
if (0 == strcasecmp (method,
MHD_HTTP_METHOD_HEAD))
method = MHD_HTTP_METHOD_GET; /* MHD will deal with the rest */
+ if (0 == strcmp (url,
+ ""))
+ url = "/"; /* code below does not like empty string */
-
- /* Find out the merchant backend instance for the request.
- * If there is an instance, remove the instance specification
- * from the beginning of the request URL. */
+ /* parse the URL into the three different components */
{
- const char *instance_prefix = "/instances/";
+ const char *slash;
- if (0 == strncmp (url,
- instance_prefix,
- strlen (instance_prefix)))
+ slash = strchr (&url[1], '/');
+ if (NULL == slash)
{
- /* url starts with "/instances/" */
- const char *istart = url + strlen (instance_prefix);
- const char *slash = strchr (istart, '/');
- char *instance_id;
-
- if (NULL == slash)
- instance_id = GNUNET_strdup (istart);
- else
- instance_id = GNUNET_strndup (istart,
- slash - istart);
- if (0 == strcmp (instance_id,
- "admin"))
- {
- MHD_RESULT ret;
- struct MHD_Response *response;
- const char *rstart = hc->full_url + strlen (instance_prefix);
- const char *rslash = strchr (rstart, '/');
-
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Client used deprecated '/instances/default/' path. Redirecting to modern path\n");
-
- response
- = MHD_create_response_from_buffer (0,
- NULL,
- MHD_RESPMEM_PERSISTENT);
- TALER_MHD_add_global_headers (response,
- true);
- if (MHD_NO ==
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_LOCATION,
- NULL == rslash
- ? "/"
- : rslash))
- {
- GNUNET_break (0);
- MHD_destroy_response (response);
- GNUNET_free (instance_id);
- return MHD_NO;
- }
- ret = MHD_queue_response (connection,
- MHD_HTTP_PERMANENT_REDIRECT,
- response);
- MHD_destroy_response (response);
- GNUNET_free (instance_id);
- return ret;
- }
- hc->instance = TMH_lookup_instance (instance_id);
- if ( (NULL == hc->instance) &&
- (0 == strcmp ("admin",
- instance_id)) )
- hc->instance = TMH_lookup_instance (NULL);
- GNUNET_free (instance_id);
- if (NULL == slash)
- url = "";
- else
- url = slash;
+ /* the prefix was everything */
+ prefix_strlen = strlen (url);
}
else
{
- /* use 'default' */
- use_default = true;
- hc->instance = TMH_lookup_instance (NULL);
- }
- if (NULL != hc->instance)
- {
- GNUNET_assert (hc->instance->rc < UINT_MAX);
- hc->instance->rc++;
- }
- }
-
- {
- const char *management_prefix = "/management/";
- const char *private_prefix = "/private/";
-
- if ( (0 == strncmp (url,
- management_prefix,
- strlen (management_prefix))) )
- {
- handlers = management_handlers;
- url += strlen (management_prefix) - 1;
- }
- else if ( (0 == strncmp (url,
- private_prefix,
- strlen (private_prefix))) ||
- (0 == strcmp (url,
- "/private")) )
- {
- handlers = private_handlers;
- if (0 == strcmp (url,
- "/private"))
- url = "/";
+ prefix_strlen = slash - url + 1; /* includes both '/'-es if present! */
+ infix_url = slash + 1;
+ slash = strchr (infix_url, '/');
+ if (NULL == slash)
+ {
+ /* the infix was the rest */
+ infix_strlen = strlen (infix_url);
+ }
else
- url += strlen (private_prefix) - 1;
- }
- else
- {
- handlers = public_handlers;
+ {
+ infix_strlen = slash - infix_url; /* excludes both '/'-es */
+ suffix_url = slash + 1; /* skip the '/' */
+ suffix_strlen = strlen (suffix_url);
+ }
+ hc->infix = GNUNET_strndup (infix_url,
+ infix_strlen);
}
}
- if (0 == strcmp (url,
- ""))
- url = "/"; /* code below does not like empty string */
-
+ /* find matching handler */
{
- /* Matching URL found, but maybe method doesn't match */
- size_t prefix_strlen; /* i.e. 8 for "/orders/", or 7 for "/config" */
- const char *infix_url = NULL; /* i.e. "$ORDER_ID", no '/'-es */
- size_t infix_strlen = 0; /* number of characters in infix_url */
- const char *suffix_url = NULL; /* i.e. "refund", excludes '/' at the beginning */
- size_t suffix_strlen = 0; /* number of characters in suffix_url */
+ bool url_found = false;
- /* parse the URL into the three different components */
+ for (unsigned int i = 0; NULL != handlers[i].url_prefix; i++)
{
- const char *slash;
+ const struct TMH_RequestHandler *rh = &handlers[i];
- slash = strchr (&url[1], '/');
- if (NULL == slash)
+ if (rh->default_only && (! use_admin))
+ continue;
+ if (! prefix_match (rh,
+ url,
+ prefix_strlen,
+ infix_url,
+ infix_strlen,
+ suffix_url,
+ suffix_strlen))
+ continue;
+ url_found = true;
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_OPTIONS))
{
- /* the prefix was everything */
- prefix_strlen = strlen (url);
- }
- else
- {
- prefix_strlen = slash - url + 1; /* includes both '/'-es if present! */
- infix_url = slash + 1;
- slash = strchr (infix_url, '/');
- if (NULL == slash)
- {
- /* the infix was the rest */
- infix_strlen = strlen (infix_url);
- }
- else
- {
- infix_strlen = slash - infix_url; /* excludes both '/'-es */
- suffix_url = slash + 1; /* skip the '/' */
- suffix_strlen = strlen (suffix_url);
- }
- hc->infix = GNUNET_strndup (infix_url,
- infix_strlen);
+ return (MHD_YES ==
+ TALER_MHD_reply_cors_preflight (hc->connection))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
+ if ( (rh->method != NULL) &&
+ (0 != strcasecmp (method,
+ rh->method)) )
+ continue;
+ hc->rh = rh;
+ break;
}
-
- /* find matching handler */
+ /* Handle HTTP 405: METHOD NOT ALLOWED case */
+ if ( (NULL == hc->rh) &&
+ (url_found) )
{
- bool url_found = false;
+ struct MHD_Response *reply;
+ MHD_RESULT ret;
+ char *allowed = NULL;
+ GNUNET_break_op (0);
+ /* compute 'Allowed:' header (required by HTTP spec for 405 replies) */
for (unsigned int i = 0; NULL != handlers[i].url_prefix; i++)
{
- struct TMH_RequestHandler *rh = &handlers[i];
+ const struct TMH_RequestHandler *rh = &handlers[i];
- if (rh->default_only && (! use_default))
+ if (rh->default_only && (! use_admin))
continue;
if (! prefix_match (rh,
url,
@@ -2546,209 +2606,297 @@ url_handler (void *cls,
suffix_url,
suffix_strlen))
continue;
- url_found = true;
- if (0 == strcasecmp (method,
- MHD_HTTP_METHOD_OPTIONS))
+ if (NULL == allowed)
{
- return TALER_MHD_reply_cors_preflight (connection);
+ allowed = GNUNET_strdup (rh->method);
}
- if ( (rh->method != NULL) &&
- (0 != strcasecmp (method,
- rh->method)) )
- continue;
- hc->rh = rh;
- break;
- }
- /* Handle HTTP 405: METHOD NOT ALLOWED case */
- if ( (NULL == hc->rh) &&
- (url_found) )
- {
- struct MHD_Response *reply;
- MHD_RESULT ret;
- char *allowed = NULL;
-
- GNUNET_break_op (0);
- /* compute 'Allowed:' header (required by HTTP spec for 405 replies) */
- for (unsigned int i = 0; NULL != handlers[i].url_prefix; i++)
+ else
{
- struct TMH_RequestHandler *rh = &handlers[i];
-
- if (rh->default_only && (! use_default))
- continue;
- if (! prefix_match (rh,
- url,
- prefix_strlen,
- infix_url,
- infix_strlen,
- suffix_url,
- suffix_strlen))
- continue;
- if (NULL == allowed)
- {
- allowed = GNUNET_strdup (rh->method);
- }
- else
- {
- char *tmp;
-
- GNUNET_asprintf (&tmp,
- "%s, %s",
- allowed,
- rh->method);
- GNUNET_free (allowed);
- allowed = tmp;
- }
- if (0 == strcasecmp (rh->method,
- MHD_HTTP_METHOD_GET))
- {
- char *tmp;
-
- GNUNET_asprintf (&tmp,
- "%s, %s",
- allowed,
- MHD_HTTP_METHOD_HEAD);
- GNUNET_free (allowed);
- allowed = tmp;
- }
+ char *tmp;
+
+ GNUNET_asprintf (&tmp,
+ "%s, %s",
+ allowed,
+ rh->method);
+ GNUNET_free (allowed);
+ allowed = tmp;
+ }
+ if (0 == strcasecmp (rh->method,
+ MHD_HTTP_METHOD_GET))
+ {
+ char *tmp;
+
+ GNUNET_asprintf (&tmp,
+ "%s, %s",
+ allowed,
+ MHD_HTTP_METHOD_HEAD);
+ GNUNET_free (allowed);
+ allowed = tmp;
}
- reply = TALER_MHD_make_error (TALER_EC_GENERIC_METHOD_INVALID,
- method);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (reply,
- MHD_HTTP_HEADER_ALLOW,
- allowed));
- GNUNET_free (allowed);
- ret = MHD_queue_response (connection,
- MHD_HTTP_METHOD_NOT_ALLOWED,
- reply);
- MHD_destroy_response (reply);
- return ret;
- }
- if (NULL == hc->rh)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Endpoint `%s' not known\n",
- hc->url);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- hc->url);
}
+ reply = TALER_MHD_make_error (TALER_EC_GENERIC_METHOD_INVALID,
+ method);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (reply,
+ MHD_HTTP_HEADER_ALLOW,
+ allowed));
+ GNUNET_free (allowed);
+ ret = MHD_queue_response (hc->connection,
+ MHD_HTTP_METHOD_NOT_ALLOWED,
+ reply);
+ MHD_destroy_response (reply);
+ return (MHD_YES == ret)
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (NULL == hc->rh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Endpoint `%s' not known\n",
+ hc->url);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (hc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ hc->url))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
}
- /* At this point, we must have found a handler */
- GNUNET_assert (NULL != hc->rh);
+ return GNUNET_OK;
+}
- /* If an instance should be there, check one exists */
- if ( (NULL == hc->instance) &&
- (! hc->rh->skip_instance) )
+
+/**
+ * Check if the client has provided the necessary credentials
+ * to access the selected endpoint of the selected instance.
+ *
+ * @param[in,out] hc handler context
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if an error was queued (return #MHD_YES)
+ * #GNUNET_SYSERR to close the connection (return #MHD_NO)
+ */
+static enum GNUNET_GenericReturnValue
+perform_access_control (struct TMH_HandlerContext *hc)
+{
+ const char *auth;
+ bool is_basic_auth = false;
+ bool auth_malformed = false;
+
+ auth = MHD_lookup_connection_value (hc->connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_AUTHORIZATION);
+
+ if (NULL != auth)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Instance for `%s' not known\n",
- hc->url);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
- hc->infix);
+ extract_auth (&auth,
+ &is_basic_auth);
+ if (NULL == auth)
+ auth_malformed = true;
+ hc->auth_token = auth;
}
- /* Access control for non-public handlers */
- if (public_handlers != handlers)
+ /* If we have zero configured instances (not even ones that have been
+ purged) or explicitly disabled authentication, THEN we accept anything
+ (no access control), as we then also have no data to protect. */
+ if ((0 == GNUNET_CONTAINER_multihashmap_size (TMH_by_id_map)) ||
+ (GNUNET_YES == TMH_auth_disabled))
{
- const char *auth;
- bool is_basic_auth = false;
- bool auth_malformed = false;
-
- auth = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_AUTHORIZATION);
-
- if (NULL != auth)
- {
- extract_auth (&auth,
- &is_basic_auth);
- if (NULL == auth)
- auth_malformed = true;
- hc->auth_token = auth;
- }
+ hc->auth_scope = TMH_AS_ALL;
+ }
+ else if (is_basic_auth)
+ {
+ process_basic_auth (hc,
+ auth);
+ }
+ else /* Check bearer token */
+ {
+ enum TALER_ErrorCode ec;
- /* If we have zero configured instances (not even ones that have been
- purged) or explicitly disabled authentication, THEN we accept anything
- (no access control), as we then also have no data to protect. */
- if ((0 == GNUNET_CONTAINER_multihashmap_size (TMH_by_id_map)) ||
- (GNUNET_YES == TMH_auth_disabled))
+ ec = process_bearer_auth (hc,
+ auth);
+ if (TALER_EC_NONE != ec)
{
- hc->auth_scope = TMH_AS_ALL;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Bearer authentication failed: %d\n",
+ (int) ec);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_ec (hc->connection,
+ ec,
+ NULL))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
- else if (is_basic_auth)
- {
- process_basic_auth (hc, auth);
+ }
+ /* We grant access if:
+ - Endpoint does not require permissions
+ - Authorization scope of bearer token contains permissions
+ required by endpoint.
+ */
+ if ( (NULL != hc->rh->permission) &&
+ (! permission_in_scope (hc->rh->permission,
+ hc->auth_scope)))
+ {
+ if (auth_malformed &&
+ (TMH_AS_NONE == hc->auth_scope) )
+ {
+ GNUNET_break_op (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (
+ hc->connection,
+ MHD_HTTP_UNAUTHORIZED,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "'" RFC_8959_PREFIX
+ "' prefix or 'Bearer' missing in 'Authorization' header"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
- else /* Check bearer token */
- {
- enum TALER_ErrorCode ec;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Credentials provided are %d which are insufficient for access to `%s'\n",
+ (int) hc->auth_scope,
+ hc->rh->permission);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (
+ hc->connection,
+ MHD_HTTP_UNAUTHORIZED,
+ TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
+ "Check credentials in 'Authorization' header"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
- ec = process_bearer_auth (hc,
- auth);
- if (TALER_EC_NONE != ec)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Bearer authentication failed: %d\n",
- (int) ec);
- return TALER_MHD_reply_with_ec (connection,
- ec,
- NULL);
- }
- }
- /* We grant access if:
- - Endpoint does not require permissions
- - Authorization scope of bearer token contains permissions
- required by endpoint.
- */
- if ( (NULL != hc->rh->permission) &&
- (! permission_in_scope (hc->rh->permission,
- hc->auth_scope)))
- {
- if (auth_malformed &&
- (TMH_AS_NONE == hc->auth_scope) )
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_UNAUTHORIZED,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "'" RFC_8959_PREFIX
- "' prefix or 'Bearer' missing in 'Authorization' header");
- }
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Credentials provided are %d which are insufficient for access to `%s'\n",
- (int) hc->auth_scope,
- hc->rh->permission);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_UNAUTHORIZED,
- TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
- "Check credentials in 'Authorization' header");
- }
- } /* if (use_private) */
+/**
+ * A client has requested the given url using the given method
+ * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
+ * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
+ * must call MHD callbacks to provide content to give back to the
+ * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
+ * #MHD_HTTP_NOT_FOUND, etc.).
+ *
+ * @param cls argument given together with the function
+ * pointer when the handler was registered with MHD
+ * @param connection the MHD connection to handle
+ * @param url the requested url
+ * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
+ * #MHD_HTTP_METHOD_PUT, etc.)
+ * @param version the HTTP version string (i.e.
+ * #MHD_HTTP_VERSION_1_1)
+ * @param upload_data the data being uploaded (excluding HEADERS,
+ * for a POST that fits into memory and that is encoded
+ * with a supported encoding, the POST data will NOT be
+ * given in upload_data and is instead available as
+ * part of #MHD_get_connection_values; very large POST
+ * data *will* be made available incrementally in
+ * @a upload_data)
+ * @param upload_data_size set initially to the size of the
+ * @a upload_data provided; the method must update this
+ * value to the number of bytes NOT processed;
+ * @param con_cls pointer that the callback can set to some
+ * address and that will be preserved by MHD for future
+ * calls for this request; since the access handler may
+ * be called many times (i.e., for a PUT/POST operation
+ * with plenty of upload data) this allows the application
+ * to easily associate some request-specific state.
+ * If necessary, this state can be cleaned up in the
+ * global #MHD_RequestCompletedCallback (which
+ * can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
+ * Initially, `*con_cls` will be set up by the
+ * full_url_track_callback().
+ * @return #MHD_YES if the connection was handled successfully,
+ * #MHD_NO if the socket must be closed due to a serious
+ * error while handling the request
+ */
+static MHD_RESULT
+url_handler (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ struct TMH_HandlerContext *hc = *con_cls;
+ bool use_admin = false;
+ bool is_public = false;
+
+ (void) cls;
+ (void) version;
+ if (NULL != hc->url)
+ {
+ /* MHD calls us again for a request, we already identified
+ the handler, just continue processing with the handler */
+ return process_upload_with_handler (hc,
+ upload_data,
+ upload_data_size);
+ }
+ hc->url = url;
+ log_request (hc,
+ method);
+
+ /* Find out the merchant backend instance for the request.
+ * If there is an instance, remove the instance specification
+ * from the beginning of the request URL. */
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = identify_instance (hc,
+ &url,
+ &use_admin);
+ if (GNUNET_OK != ret)
+ return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+ }
+
+ {
+ const struct TMH_RequestHandler *handlers;
+ enum GNUNET_GenericReturnValue ret;
+ handlers = determine_handler_group (&url,
+ &is_public);
+ ret = identify_handler (hc,
+ handlers,
+ url,
+ method,
+ use_admin);
+ if (GNUNET_OK != ret)
+ return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+ }
+
+ /* At this point, we must have found a handler */
+ GNUNET_assert (NULL != hc->rh);
+
+ /* If an instance must be there, check one exists */
if ( (NULL == hc->instance) &&
(! hc->rh->skip_instance) )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Instance for URL `%s' not known\n",
- url);
+ "Instance for `%s' not known\n",
+ hc->url);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
- url);
+ hc->url);
+ }
+
+ /* Perform access control for non-public handlers */
+ if (! is_public)
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = perform_access_control (hc);
+ if (GNUNET_OK != ret)
+ return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
}
+
if ( (NULL != hc->instance) && /* make static analysis happy */
(! hc->rh->skip_instance) &&
(hc->instance->deleted) &&
(! hc->rh->allow_deleted_instance) )
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Instance `%s' was deleted\n",
hc->instance->settings.id);
return TALER_MHD_reply_with_error (connection,
@@ -2756,7 +2904,8 @@ url_handler (void *cls,
TALER_EC_MERCHANT_GENERIC_INSTANCE_DELETED,
hc->instance->settings.id);
}
- /* parse request body */
+
+ /* Check upload constraints */
hc->has_body = ( (0 == strcasecmp (method,
MHD_HTTP_METHOD_POST)) ||
/* PUT is not yet used */
@@ -2764,13 +2913,17 @@ url_handler (void *cls,
MHD_HTTP_METHOD_PATCH)) );
if (hc->has_body)
{
+ /* This is a macro: it will queue an error response and return
+ from this function if the upload would be too large. */
TALER_MHD_check_content_length (connection,
0 == hc->rh->max_upload
? DEFAULT_MAX_UPLOAD_SIZE
: hc->rh->max_upload);
GNUNET_break (NULL == hc->request_body); /* can't have it already */
}
- return MHD_YES; /* wait for MHD to call us again */
+ /* wait for MHD to call us again, this time hc->url will be non-NULL
+ and we should jump straight into process_upload_with_handler(). */
+ return MHD_YES;
}