merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit c1e92e30d7cedb748db63bd917a8ff11beddf88b
parent c983fb533c9a1ad5ab780b932c52a176994c08e1
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 28 Dec 2025 12:16:45 +0100

taler-merchant-httpd refactoring

Diffstat:
Msrc/backend/taler-merchant-httpd.c | 999+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
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; }