/*
  This file is part of TALER
  (C) 2014-2025 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 General Public License for more details.

  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file taler-merchant-httpd_dispatcher.c
 * @brief map requested URL and method to the respective request handler
 * @author Christian Grothoff
 */
#include "taler/platform.h"
#include "taler-merchant-httpd_get-config.h"
#include "taler-merchant-httpd_get-exchanges.h"
#include "taler-merchant-httpd_dispatcher.h"
#include "taler-merchant-httpd_get-orders-ORDER_ID.h"
#include "taler-merchant-httpd_get-sessions-SESSION_ID.h"
#include "taler-merchant-httpd_get-products-IMAGE_HASH-image.h"
#include "taler-merchant-httpd_get-templates-TEMPLATE_ID.h"
#include "taler-merchant-httpd_mhd.h"
#include "taler-merchant-httpd_delete-private-accounts-H_WIRE.h"
#include "taler-merchant-httpd_delete-private-categories-CATEGORY_ID.h"
#include "taler-merchant-httpd_delete-private-units-UNIT.h"
#include "taler-merchant-httpd_delete-management-instances-INSTANCE.h"
#include "taler-merchant-httpd_delete-private-tokens-SERIAL.h"
#include "taler-merchant-httpd_delete-private-products-PRODUCT_ID.h"
#include "taler-merchant-httpd_delete-private-orders-ORDER_ID.h"
#include "taler-merchant-httpd_delete-private-otp-devices-DEVICE_ID.h"
#include "taler-merchant-httpd_delete-private-templates-TEMPLATE_ID.h"
#include "taler-merchant-httpd_delete-private-tokenfamilies-TOKEN_FAMILY_SLUG.h"
#include "taler-merchant-httpd_delete-private-transfers-TID.h"
#include "taler-merchant-httpd_delete-private-webhooks-WEBHOOK_ID.h"
#include "taler-merchant-httpd_get-private-accounts.h"
#include "taler-merchant-httpd_get-private-accounts-H_WIRE.h"
#include "taler-merchant-httpd_get-private-categories.h"
#include "taler-merchant-httpd_get-private-categories-CATEGORY_ID.h"
#include "taler-merchant-httpd_get-private-units.h"
#include "taler-merchant-httpd_get-private-units-UNIT.h"
#include "taler-merchant-httpd_get-private-incoming.h"
#include "taler-merchant-httpd_get-private-incoming-ID.h"
#include "taler-merchant-httpd_get-management-instances.h"
#include "taler-merchant-httpd_get-management-instances-INSTANCE.h"
#include "taler-merchant-httpd_get-private-kyc.h"
#include "taler-merchant-httpd_get-private-tokens.h"
#include "taler-merchant-httpd_get-private-pos.h"
#include "taler-merchant-httpd_get-private-products.h"
#include "taler-merchant-httpd_get-private-products-PRODUCT_ID.h"
#include "taler-merchant-httpd_get-private-orders.h"
#include "taler-merchant-httpd_get-private-orders-ORDER_ID.h"
#include "taler-merchant-httpd_get-private-otp-devices.h"
#include "taler-merchant-httpd_get-private-otp-devices-DEVICE_ID.h"
#include "taler-merchant-httpd_get-private-statistics-amount-SLUG.h"
#include "taler-merchant-httpd_get-private-statistics-counter-SLUG.h"
#include "taler-merchant-httpd_get-private-statistics-report-transactions.h"
#include "taler-merchant-httpd_get-private-templates.h"
#include "taler-merchant-httpd_get-private-templates-TEMPLATE_ID.h"
#include "taler-merchant-httpd_get-private-tokenfamilies.h"
#include "taler-merchant-httpd_get-private-tokenfamilies-TOKEN_FAMILY_SLUG.h"
#include "taler-merchant-httpd_get-private-transfers.h"
#include "taler-merchant-httpd_get-private-webhooks.h"
#include "taler-merchant-httpd_get-private-webhooks-WEBHOOK_ID.h"
#include "taler-merchant-httpd_patch-private-accounts-H_WIRE.h"
#include "taler-merchant-httpd_patch-private-categories-CATEGORY_ID.h"
#include "taler-merchant-httpd_patch-private-units-UNIT.h"
#include "taler-merchant-httpd_patch-management-instances-INSTANCE.h"
#include "taler-merchant-httpd_patch-private-orders-ORDER_ID-forget.h"
#include "taler-merchant-httpd_patch-private-otp-devices-DEVICE_ID.h"
#include "taler-merchant-httpd_patch-private-products-PRODUCT_ID.h"
#include "taler-merchant-httpd_patch-private-templates-TEMPLATE_ID.h"
#include "taler-merchant-httpd_patch-private-tokenfamilies-TOKEN_FAMILY_SLUG.h"
#include "taler-merchant-httpd_patch-private-webhooks-WEBHOOK_ID.h"
#include "taler-merchant-httpd_post-private-accounts.h"
#include "taler-merchant-httpd_post-private-categories.h"
#include "taler-merchant-httpd_post-private-units.h"
#include "taler-merchant-httpd_post-management-instances.h"
#include "taler-merchant-httpd_post-management-instances-INSTANCE-auth.h"
#include "taler-merchant-httpd_post-private-token.h"
#include "taler-merchant-httpd_post-private-otp-devices.h"
#include "taler-merchant-httpd_post-private-orders.h"
#include "taler-merchant-httpd_post-private-orders-ORDER_ID-refund.h"
#include "taler-merchant-httpd_post-private-products.h"
#include "taler-merchant-httpd_post-private-products-PRODUCT_ID-lock.h"
#include "taler-merchant-httpd_post-private-templates.h"
#include "taler-merchant-httpd_post-private-tokenfamilies.h"
#include "taler-merchant-httpd_post-private-transfers.h"
#include "taler-merchant-httpd_post-private-webhooks.h"
#include "taler-merchant-httpd_post-challenge-ID.h"
#include "taler-merchant-httpd_post-challenge-ID-confirm.h"
#include "taler-merchant-httpd_post-orders-ORDER_ID-abort.h"
#include "taler-merchant-httpd_post-orders-ORDER_ID-claim.h"
#include "taler-merchant-httpd_post-orders-ORDER_ID-paid.h"
#include "taler-merchant-httpd_post-orders-ORDER_ID-pay.h"
#include "taler-merchant-httpd_post-orders-ORDER_ID-unclaim.h"
#include "taler-merchant-httpd_post-templates-TEMPLATE_ID.h"
#include "taler-merchant-httpd_post-orders-ORDER_ID-refund.h"
#include "taler-merchant-httpd_get-webui.h"
#include "taler-merchant-httpd_statics.h"
#include "taler-merchant-httpd_get-terms.h"
#include "taler-merchant-httpd_post-reports-REPORT_ID.h"
#include "taler-merchant-httpd_delete-private-reports-REPORT_ID.h"
#include "taler-merchant-httpd_get-private-reports-REPORT_ID.h"
#include "taler-merchant-httpd_get-private-reports.h"
#include "taler-merchant-httpd_patch-private-reports-REPORT_ID.h"
#include "taler-merchant-httpd_post-private-reports.h"
#include "taler-merchant-httpd_delete-private-pots-POT_ID.h"
#include "taler-merchant-httpd_get-private-pots-POT_ID.h"
#include "taler-merchant-httpd_get-private-pots.h"
#include "taler-merchant-httpd_patch-private-pots-POT_ID.h"
#include "taler-merchant-httpd_post-private-pots.h"
#include "taler-merchant-httpd_get-private-groups.h"
#include "taler-merchant-httpd_post-private-groups.h"
#include "taler-merchant-httpd_patch-private-groups-GROUP_ID.h"
#include "taler-merchant-httpd_delete-private-groups-GROUP_ID.h"

#ifdef HAVE_DONAU_DONAU_SERVICE_H
#include "taler-merchant-httpd_get-private-donau.h"
#include "taler-merchant-httpd_post-private-donau.h"
#include "taler-merchant-httpd_delete-private-donau-DONAU_SERIAL.h"
#endif


/**
 * Handle a OPTIONS "*" request.
 *
 * @param rh context of the handler
 * @param connection the MHD connection to handle
 * @param[in,out] hc context with further information about the request
 * @return MHD result code
 */
static MHD_RESULT
handle_server_options (const struct TMH_RequestHandler *rh,
                       struct MHD_Connection *connection,
                       struct TMH_HandlerContext *hc)
{
  (void) rh;
  (void) hc;
  return TALER_MHD_reply_cors_preflight (connection);
}


/**
 * Generates the response for "/", redirecting the
 * client to the "/webui/" from where we serve the SPA.
 *
 * @param rh request handler
 * @param connection MHD connection
 * @param hc handler context
 * @return MHD result code
 */
static MHD_RESULT
spa_redirect (const struct TMH_RequestHandler *rh,
              struct MHD_Connection *connection,
              struct TMH_HandlerContext *hc)
{
  const char *text = "Redirecting to /webui/";
  struct MHD_Response *response;
  char *dst;

  response = MHD_create_response_from_buffer (strlen (text),
                                              (void *) text,
                                              MHD_RESPMEM_PERSISTENT);
  if (NULL == response)
  {
    GNUNET_break (0);
    return MHD_NO;
  }
  TALER_MHD_add_global_headers (response,
                                true);
  GNUNET_break (MHD_YES ==
                MHD_add_response_header (response,
                                         MHD_HTTP_HEADER_CONTENT_TYPE,
                                         "text/plain"));
  if ( (NULL == hc->instance) ||
       (0 == strcmp ("admin",
                     hc->instance->settings.id)) )
    dst = GNUNET_strdup ("/webui/");
  else
    GNUNET_asprintf (&dst,
                     "/instances/%s/webui/",
                     hc->instance->settings.id);
  if (MHD_NO ==
      MHD_add_response_header (response,
                               MHD_HTTP_HEADER_LOCATION,
                               dst))
  {
    GNUNET_break (0);
    MHD_destroy_response (response);
    GNUNET_free (dst);
    return MHD_NO;
  }
  GNUNET_free (dst);

  {
    MHD_RESULT ret;

    ret = MHD_queue_response (connection,
                              MHD_HTTP_FOUND,
                              response);
    MHD_destroy_response (response);
    return ret;
  }
}


/**
 * 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 */
    {
      .url_prefix = "/instances",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "instances-write",
      .skip_instance = true,
      .default_only = true,
      .handler = &TMH_private_get_instances
    },
    /* POST /instances */
    {
      .url_prefix = "/instances",
      .method = MHD_HTTP_METHOD_POST,
      .permission = "instances-write",
      .skip_instance = true,
      .default_only = true,
      .handler = &TMH_private_post_instances,
      /* allow instance data of up to 8 MB, that should be plenty;
         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
         would require further changes to the allocation logic
         in the code... */
      .max_upload = 1024 * 1024 * 8
    },
    /* GET /instances/$ID/ */
    {
      .url_prefix = "/instances/",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "instances-write",
      .skip_instance = true,
      .default_only = true,
      .have_id_segment = true,
      .handler = &TMH_private_get_instances_default_ID
    },
    /* DELETE /instances/$ID */
    {
      .url_prefix = "/instances/",
      .method = MHD_HTTP_METHOD_DELETE,
      .permission = "instances-write",
      .skip_instance = true,
      .default_only = true,
      .have_id_segment = true,
      .handler = &TMH_private_delete_instances_default_ID
    },
    /* PATCH /instances/$ID */
    {
      .url_prefix = "/instances/",
      .method = MHD_HTTP_METHOD_PATCH,
      .permission = "instances-write",
      .skip_instance = true,
      .default_only = true,
      .have_id_segment = true,
      .handler = &TMH_private_patch_instances_default_ID,
      /* allow instance data of up to 8 MB, that should be plenty;
         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
         would require further changes to the allocation logic
         in the code... */
      .max_upload = 1024 * 1024 * 8
    },
    /* POST /auth: */
    {
      .url_prefix = "/instances/",
      .url_suffix = "auth",
      .method = MHD_HTTP_METHOD_POST,
      .permission = "instances-auth-write",
      .skip_instance = true,
      .default_only = true,
      .have_id_segment = true,
      .handler = &TMH_private_post_instances_default_ID_auth,
      /* Body should be pretty small. */
      .max_upload = 1024 * 1024
    },
    /* GET /kyc: */
    {
      .url_prefix = "/instances/",
      .url_suffix = "kyc",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "instances-kyc-read",
      .skip_instance = true,
      .default_only = true,
      .have_id_segment = true,
      .handler = &TMH_private_get_instances_default_ID_kyc,
    },
    {
      .url_prefix = NULL
    }
  };

  static struct TMH_RequestHandler private_handlers[] = {
    /* GET /instances/$ID/: */
    {
      .url_prefix = "/",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "instances-read",
      .handler = &TMH_private_get_instances_ID
    },
    /* DELETE /instances/$ID/: */
    {
      .url_prefix = "/",
      .method = MHD_HTTP_METHOD_DELETE,
      .permission = "instances-write",
      .allow_deleted_instance = true,
      .handler = &TMH_private_delete_instances_ID
    },
    /* PATCH /instances/$ID/: */
    {
      .url_prefix = "/",
      .method = MHD_HTTP_METHOD_PATCH,
      .handler = &TMH_private_patch_instances_ID,
      .permission = "instances-write",
      .allow_deleted_instance = true,
      /* allow instance data of up to 8 MB, that should be plenty;
         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
         would require further changes to the allocation logic
         in the code... */
      .max_upload = 1024 * 1024 * 8
    },
    /* POST /auth: */
    {
      .url_prefix = "/auth",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &TMH_private_post_instances_ID_auth,
      .permission = "auth-write",
      /* Body should be pretty small. */
      .max_upload = 1024 * 1024,
    },
    /* GET /kyc: */
    {
      .url_prefix = "/kyc",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "kyc-read",
      .handler = &TMH_private_get_instances_ID_kyc,
    },
    /* GET /pos: */
    {
      .url_prefix = "/pos",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "pos-read",
      .handler = &TMH_private_get_pos
    },
    /* GET /categories: */
    {
      .url_prefix = "/categories",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "categories-read",
      .handler = &TMH_private_get_categories
    },
    /* POST /categories: */
    {
      .url_prefix = "/categories",
      .method = MHD_HTTP_METHOD_POST,
      .permission = "categories-write",
      .handler = &TMH_private_post_categories,
      /* allow category data of up to 8 kb, that should be plenty */
      .max_upload = 1024 * 8
    },
    /* GET /categories/$ID: */
    {
      .url_prefix = "/categories/",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "categories-read",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_get_categories_ID
    },
    /* DELETE /categories/$ID: */
    {
      .url_prefix = "/categories/",
      .method = MHD_HTTP_METHOD_DELETE,
      .permission = "categories-write",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_delete_categories_ID
    },
    /* PATCH /categories/$ID/: */
    {
      .url_prefix = "/categories/",
      .method = MHD_HTTP_METHOD_PATCH,
      .permission = "categories-write",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_patch_categories_ID,
      /* allow category data of up to 8 kb, that should be plenty */
      .max_upload = 1024 * 8
    },
    /* GET /units: */
    {
      .url_prefix = "/units",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &TMH_private_get_units
    },
    /* POST /units: */
    {
      .url_prefix = "/units",
      .method = MHD_HTTP_METHOD_POST,
      .permission = "units-write",
      .handler = &TMH_private_post_units,
      .max_upload = 1024 * 8
    },
    /* GET /units/$UNIT: */
    {
      .url_prefix = "/units/",
      .method = MHD_HTTP_METHOD_GET,
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_get_units_ID
    },
    /* DELETE /units/$UNIT: */
    {
      .url_prefix = "/units/",
      .method = MHD_HTTP_METHOD_DELETE,
      .permission = "units-write",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_delete_units_ID
    },
    /* PATCH /units/$UNIT: */
    {
      .url_prefix = "/units/",
      .method = MHD_HTTP_METHOD_PATCH,
      .permission = "units-write",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_patch_units_ID,
      .max_upload = 1024 * 8
    },
    /* GET /products: */
    {
      .url_prefix = "/products",
      .permission = "products-read",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &TMH_private_get_products
    },
    /* POST /products: */
    {
      .url_prefix = "/products",
      .method = MHD_HTTP_METHOD_POST,
      .permission = "products-write",
      .handler = &TMH_private_post_products,
      /* allow product data of up to 8 MB, that should be plenty;
         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
         would require further changes to the allocation logic
         in the code... */
      .max_upload = 1024 * 1024 * 8
    },
    /* GET /products/$ID: */
    {
      .url_prefix = "/products/",
      .method = MHD_HTTP_METHOD_GET,
      .have_id_segment = true,
      .permission = "products-read",
      .allow_deleted_instance = true,
      .handler = &TMH_private_get_products_ID
    },
    /* DELETE /products/$ID/: */
    {
      .url_prefix = "/products/",
      .method = MHD_HTTP_METHOD_DELETE,
      .have_id_segment = true,
      .permission = "products-write",
      .allow_deleted_instance = true,
      .handler = &TMH_private_delete_products_ID
    },
    /* PATCH /products/$ID/: */
    {
      .url_prefix = "/products/",
      .method = MHD_HTTP_METHOD_PATCH,
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .permission = "products-write",
      .handler = &TMH_private_patch_products_ID,
      /* allow product data of up to 8 MB, that should be plenty;
         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
         would require further changes to the allocation logic
         in the code... */
      .max_upload = 1024 * 1024 * 8
    },
    /* POST /products/$ID/lock: */
    {
      .url_prefix = "/products/",
      .url_suffix = "lock",
      .method = MHD_HTTP_METHOD_POST,
      .have_id_segment = true,
      .permission = "products-lock",
      .handler = &TMH_private_post_products_ID_lock,
      /* the body should be pretty small, allow 1 MB of upload
         to set a conservative bound for sane wallets */
      .max_upload = 1024 * 1024
    },
    /* POST /orders: */
    {
      .url_prefix = "/orders",
      .method = MHD_HTTP_METHOD_POST,
      .permission = "orders-write",
      .handler = &TMH_private_post_orders,
      /* allow contracts of up to 8 MB, that should be plenty;
         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
         would require further changes to the allocation logic
         in the code... */
      .max_upload = 1024 * 1024 * 8
    },
    /* GET /orders/$ID: */
    {
      .url_prefix = "/orders/",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "orders-read",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_get_orders_ID
    },
    /* GET /orders: */
    {
      .url_prefix = "/orders",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "orders-read",
      .allow_deleted_instance = true,
      .handler = &TMH_private_get_orders
    },
    /* POST /orders/$ID/refund: */
    {
      .url_prefix = "/orders/",
      .url_suffix = "refund",
      .method = MHD_HTTP_METHOD_POST,
      .have_id_segment = true,
      .permission = "orders-refund",
      .handler = &TMH_private_post_orders_ID_refund,
      /* the body should be pretty small, allow 1 MB of upload
         to set a conservative bound for sane wallets */
      .max_upload = 1024 * 1024
    },
    /* PATCH /orders/$ID/forget: */
    {
      .url_prefix = "/orders/",
      .url_suffix = "forget",
      .method = MHD_HTTP_METHOD_PATCH,
      .permission = "orders-write",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_patch_orders_ID_forget,
      /* the body should be pretty small, allow 1 MB of upload
         to set a conservative bound for sane wallets */
      .max_upload = 1024 * 1024
    },
    /* DELETE /orders/$ID: */
    {
      .url_prefix = "/orders/",
      .method = MHD_HTTP_METHOD_DELETE,
      .permission = "orders-write",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_delete_orders_ID
    },
    /* POST /transfers: */
    {
      .url_prefix = "/transfers",
      .method = MHD_HTTP_METHOD_POST,
      .allow_deleted_instance = true,
      .handler = &TMH_private_post_transfers,
      .permission = "transfers-write",
      /* the body should be pretty small, allow 1 MB of upload
         to set a conservative bound for sane wallets */
      .max_upload = 1024 * 1024
    },
    /* DELETE /transfers/$ID: */
    {
      .url_prefix = "/transfers/",
      .method = MHD_HTTP_METHOD_DELETE,
      .permission = "transfers-write",
      .allow_deleted_instance = true,
      .handler = &TMH_private_delete_transfers_ID,
      .have_id_segment = true,
      /* the body should be pretty small, allow 1 MB of upload
         to set a conservative bound for sane wallets */
      .max_upload = 1024 * 1024
    },
    /* GET /transfers: */
    {
      .url_prefix = "/transfers",
      .permission = "transfers-read",
      .method = MHD_HTTP_METHOD_GET,
      .allow_deleted_instance = true,
      .handler = &TMH_private_get_transfers
    },
    /* GET /incoming: */
    {
      .url_prefix = "/incoming",
      .permission = "transfers-read",
      .method = MHD_HTTP_METHOD_GET,
      .allow_deleted_instance = true,
      .handler = &TMH_private_get_incoming
    },
    /* GET /incoming/$ID: */
    {
      .url_prefix = "/incoming/",
      .permission = "transfers-read",
      .method = MHD_HTTP_METHOD_GET,
      .allow_deleted_instance = true,
      .have_id_segment = true,
      .handler = &TMH_private_get_incoming_ID
    },
    /* POST /otp-devices: */
    {
      .url_prefix = "/otp-devices",
      .permission = "otp-devices-write",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &TMH_private_post_otp_devices
    },
    /* GET /otp-devices: */
    {
      .url_prefix = "/otp-devices",
      .permission = "opt-devices-read",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &TMH_private_get_otp_devices
    },
    /* GET /otp-devices/$ID: */
    {
      .url_prefix = "/otp-devices/",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "otp-devices-read",
      .have_id_segment = true,
      .handler = &TMH_private_get_otp_devices_ID
    },
    /* DELETE /otp-devices/$ID: */
    {
      .url_prefix = "/otp-devices/",
      .method = MHD_HTTP_METHOD_DELETE,
      .permission = "otp-devices-write",
      .have_id_segment = true,
      .handler = &TMH_private_delete_otp_devices_ID
    },
    /* PATCH /otp-devices/$ID: */
    {
      .url_prefix = "/otp-devices/",
      .method = MHD_HTTP_METHOD_PATCH,
      .permission = "otp-devices-write",
      .have_id_segment = true,
      .handler = &TMH_private_patch_otp_devices_ID
    },
    /* POST /templates: */
    {
      .url_prefix = "/templates",
      .method = MHD_HTTP_METHOD_POST,
      .permission = "templates-write",
      .handler = &TMH_private_post_templates,
      /* allow template data of up to 8 MB, that should be plenty;
         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
         would require further changes to the allocation logic
         in the code... */
      .max_upload = 1024 * 1024 * 8
    },
    /* GET /templates: */
    {
      .url_prefix = "/templates",
      .permission = "templates-read",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &TMH_private_get_templates
    },
    /* GET /templates/$ID/: */
    {
      .url_prefix = "/templates/",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "templates-read",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_get_templates_ID
    },
    /* DELETE /templates/$ID/: */
    {
      .url_prefix = "/templates/",
      .method = MHD_HTTP_METHOD_DELETE,
      .permission = "templates-write",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_delete_templates_ID
    },
    /* PATCH /templates/$ID/: */
    {
      .url_prefix = "/templates/",
      .method = MHD_HTTP_METHOD_PATCH,
      .permission = "templates-write",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_patch_templates_ID,
      /* allow template data of up to 8 MB, that should be plenty;
         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
         would require further changes to the allocation logic
         in the code... */
      .max_upload = 1024 * 1024 * 8
    },

    /* POST /pots: */
    {
      .url_prefix = "/pots",
      .method = MHD_HTTP_METHOD_POST,
      .permission = "pots-write",
      .handler = &TMH_private_post_pots,
    },
    /* GET /pots: */
    {
      .url_prefix = "/pots",
      .permission = "pots-read",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &TMH_private_get_pots
    },
    /* DELETE /pots/$ID: */
    {
      .url_prefix = "/pots/",
      .method = MHD_HTTP_METHOD_DELETE,
      .permission = "pots-write",
      .have_id_segment = true,
      .handler = &TMH_private_delete_pot
    },
    /* PATCH /pots/$ID: */
    {
      .url_prefix = "/pots/",
      .method = MHD_HTTP_METHOD_PATCH,
      .permission = "pots-write",
      .have_id_segment = true,
      .handler = &TMH_private_patch_pot,
    },

    /* GET /webhooks: */
    {
      .url_prefix = "/webhooks",
      .permission = "webhooks-read",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &TMH_private_get_webhooks
    },
    /* POST /webhooks: */
    {
      .url_prefix = "/webhooks",
      .method = MHD_HTTP_METHOD_POST,
      .permission = "webhooks-write",
      .handler = &TMH_private_post_webhooks,
      /* allow webhook data of up to 8 MB, that should be plenty;
         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
         would require further changes to the allocation logic
         in the code... */
      .max_upload = 1024 * 1024 * 8
    },
    /* GET /webhooks/$ID/: */
    {
      .url_prefix = "/webhooks/",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "webhooks-read",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_get_webhooks_ID
    },
    /* DELETE /webhooks/$ID/: */
    {
      .url_prefix = "/webhooks/",
      .permission = "webhooks-write",
      .method = MHD_HTTP_METHOD_DELETE,
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_delete_webhooks_ID
    },
    /* PATCH /webhooks/$ID/: */
    {
      .url_prefix = "/webhooks/",
      .method = MHD_HTTP_METHOD_PATCH,
      .permission = "webhooks-write",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .handler = &TMH_private_patch_webhooks_ID,
      /* allow webhook data of up to 8 MB, that should be plenty;
         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
         would require further changes to the allocation logic
         in the code... */
      .max_upload = 1024 * 1024 * 8
    },
    /* POST /accounts: */
    {
      .url_prefix = "/accounts",
      .method = MHD_HTTP_METHOD_POST,
      .permission = "accounts-write",
      .handler = &TMH_private_post_account,
      /* allow account details of up to 8 kb, that should be plenty */
      .max_upload = 1024 * 8
    },
    /* PATCH /accounts/$H_WIRE: */
    {
      .url_prefix = "/accounts/",
      .method = MHD_HTTP_METHOD_PATCH,
      .permission = "accounts-write",
      .handler = &TMH_private_patch_accounts_ID,
      .have_id_segment = true,
      /* allow account details of up to 8 kb, that should be plenty */
      .max_upload = 1024 * 8
    },
    /* GET /accounts: */
    {
      .url_prefix = "/accounts",
      .permission = "accounts-read",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &TMH_private_get_accounts
    },
    /* GET /accounts/$H_WIRE: */
    {
      .url_prefix = "/accounts/",
      .permission = "accounts-read",
      .method = MHD_HTTP_METHOD_GET,
      .have_id_segment = true,
      .handler = &TMH_private_get_accounts_ID
    },
    /* DELETE /accounts/$H_WIRE: */
    {
      .url_prefix = "/accounts/",
      .permission = "accounts-write",
      .method = MHD_HTTP_METHOD_DELETE,
      .handler = &TMH_private_delete_account_ID,
      .have_id_segment = true
    },
    /* GET /tokens: */
    {
      .url_prefix = "/tokens",
      .permission = "tokens-read",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &TMH_private_get_instances_ID_tokens,
    },
    /* POST /token: */
    {
      .url_prefix = "/token",
      .permission = "token-refresh",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &TMH_private_post_instances_ID_token,
      /* Body should be tiny. */
      .max_upload = 1024
    },
    /* DELETE /tokens/$SERIAL: */
    {
      .url_prefix = "/tokens/",
      .permission = "tokens-write",
      .method = MHD_HTTP_METHOD_DELETE,
      .handler = &TMH_private_delete_instances_ID_token_SERIAL,
      .have_id_segment = true
    },
    /* DELETE /token: */
    {
      .url_prefix = "/token",
      .method = MHD_HTTP_METHOD_DELETE,
      .handler = &TMH_private_delete_instances_ID_token,
    },
    /* GET /tokenfamilies: */
    {
      .url_prefix = "/tokenfamilies",
      .permission = "tokenfamilies-read",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &TMH_private_get_tokenfamilies
    },
    /* POST /tokenfamilies: */
    {
      .url_prefix = "/tokenfamilies",
      .permission = "tokenfamilies-write",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &TMH_private_post_token_families
    },
    /* GET /tokenfamilies/$SLUG/: */
    {
      .url_prefix = "/tokenfamilies/",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "tokenfamilies-read",
      .have_id_segment = true,
      .handler = &TMH_private_get_tokenfamilies_SLUG
    },
    /* DELETE /tokenfamilies/$SLUG/: */
    {
      .url_prefix = "/tokenfamilies/",
      .method = MHD_HTTP_METHOD_DELETE,
      .permission = "tokenfamilies-write",
      .have_id_segment = true,
      .handler = &TMH_private_delete_token_families_SLUG
    },
    /* PATCH /tokenfamilies/$SLUG/: */
    {
      .url_prefix = "/tokenfamilies/",
      .method = MHD_HTTP_METHOD_PATCH,
      .permission = "tokenfamilies-write",
      .have_id_segment = true,
      .handler = &TMH_private_patch_token_family_SLUG,
    },

    /* Reports endpoints */
    {
      .url_prefix = "/reports",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "reports-read",
      .handler = &TMH_private_get_reports,
    },
    {
      .url_prefix = "/reports",
      .method = MHD_HTTP_METHOD_POST,
      .permission = "reports-write",
      .handler = &TMH_private_post_reports,
    },
    {
      .url_prefix = "/reports/",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &TMH_private_get_report,
      .permission = "reports-read",
      .have_id_segment = true,
    },
    {
      .url_prefix = "/reports/",
      .method = MHD_HTTP_METHOD_PATCH,
      .handler = &TMH_private_patch_report,
      .permission = "reports-write",
      .have_id_segment = true,
    },
    {
      .url_prefix = "/reports/",
      .method = MHD_HTTP_METHOD_DELETE,
      .handler = &TMH_private_delete_report,
      .permission = "reports-write",
      .have_id_segment = true,
    },

    /* Groups endpoints */
    {
      .url_prefix = "/groups",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "groups-read",
      .handler = &TMH_private_get_groups,
    },
    {
      .url_prefix = "/groups",
      .method = MHD_HTTP_METHOD_POST,
      .permission = "groups-write",
      .handler = &TMH_private_post_groups,
    },
    {
      .url_prefix = "/groups/",
      .method = MHD_HTTP_METHOD_PATCH,
      .handler = &TMH_private_patch_group,
      .permission = "groups-write",
      .have_id_segment = true,
    },
    {
      .url_prefix = "/groups/",
      .method = MHD_HTTP_METHOD_DELETE,
      .handler = &TMH_private_delete_group,
      .permission = "groups-write",
      .have_id_segment = true,
    },

    /* Money pots endpoints */
    {
      .url_prefix = "/pots",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &TMH_private_get_pots,
      .permission = "pots-read",
    },
    {
      .url_prefix = "/pots",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &TMH_private_post_pots,
      .permission = "pots-write"
    },
    {
      .url_prefix = "/pots/",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &TMH_private_get_pot,
      .have_id_segment = true,
      .permission =  "pots-read",
    },
    {
      .url_prefix = "/pots/",
      .method = MHD_HTTP_METHOD_PATCH,
      .handler = &TMH_private_patch_pot,
      .have_id_segment = true,
      .permission = "pots-write"
    },
    {
      .url_prefix = "/pots/",
      .method = MHD_HTTP_METHOD_DELETE,
      .handler = &TMH_private_delete_pot,
      .have_id_segment = true,
      .permission = "pots-write"
    },


#ifdef HAVE_DONAU_DONAU_SERVICE_H
    /* GET /donau */
    {
      .url_prefix = "/donau",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &TMH_private_get_donau_instances
    },
    /* POST /donau */
    {
      .url_prefix = "/donau",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &TMH_private_post_donau_instance
    },
    /* DELETE /donau/$charity-id */
    {
      .url_prefix = "/donau/",
      .method = MHD_HTTP_METHOD_DELETE,
      .have_id_segment = true,
      .handler = &TMH_private_delete_donau_instance_ID
    },
    #endif
    /* GET /statistics-counter/$SLUG: */
    {
      .url_prefix = "/statistics-counter/",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "statistics-read",
      .have_id_segment = true,
      .handler = &TMH_private_get_statistics_counter_SLUG,
    },
    /* GET /statistics-amount/$SLUG: */
    {
      .url_prefix = "/statistics-amount/",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "statistics-read",
      .have_id_segment = true,
      .handler = &TMH_private_get_statistics_amount_SLUG,
    },
    /* GET /statistics-report/transactions: */
    {
      .url_prefix = "/statistics-report/",
      .url_suffix = "transactions",
      .method = MHD_HTTP_METHOD_GET,
      .permission = "statistics-read",
      .handler = &TMH_private_get_statistics_report_transactions,
    },
    {
      .url_prefix = NULL
    }
  };
  static struct TMH_RequestHandler public_handlers[] = {
    {
      /* for "admin" instance, it does not even
         have to exist before we give the WebUI */
      .url_prefix = "/",
      .method = MHD_HTTP_METHOD_GET,
      .mime_type = "text/html",
      .skip_instance = true,
      .default_only = true,
      .handler = &spa_redirect,
      .response_code = MHD_HTTP_FOUND
    },
    {
      .url_prefix = "/config",
      .method = MHD_HTTP_METHOD_GET,
      .skip_instance = true,
      .default_only = true,
      .handler = &MH_handler_config
    },
    {
      .url_prefix = "/exchanges",
      .method = MHD_HTTP_METHOD_GET,
      .skip_instance = true,
      .default_only = true,
      .handler = &MH_handler_exchanges
    },
    {
      /* for "normal" instance,s they must exist
         before we give the WebUI */
      .url_prefix = "/",
      .method = MHD_HTTP_METHOD_GET,
      .mime_type = "text/html",
      .handler = &spa_redirect,
      .response_code = MHD_HTTP_FOUND
    },
    {
      .url_prefix = "/webui/",
      .method = MHD_HTTP_METHOD_GET,
      .mime_type = "text/html",
      .skip_instance = true,
      .have_id_segment = true,
      .handler = &TMH_return_spa,
      .response_code = MHD_HTTP_OK
    },
    {
      .url_prefix = "/agpl",
      .method = MHD_HTTP_METHOD_GET,
      .skip_instance = true,
      .handler = &TMH_MHD_handler_agpl_redirect
    },
    {
      .url_prefix = "/agpl",
      .method = MHD_HTTP_METHOD_GET,
      .skip_instance = true,
      .handler = &TMH_MHD_handler_agpl_redirect
    },
    {
      .url_prefix = "/terms",
      .method = MHD_HTTP_METHOD_GET,
      .skip_instance = true,
      .handler = &TMH_handler_terms
    },
    {
      .url_prefix = "/privacy",
      .method = MHD_HTTP_METHOD_GET,
      .skip_instance = true,
      .handler = &TMH_handler_privacy
    },
    /* Also serve the same /config per instance */
    {
      .url_prefix = "/config",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &MH_handler_config
    },
    /* POST /orders/$ID/abort: */
    {
      .url_prefix = "/orders/",
      .have_id_segment = true,
      .url_suffix = "abort",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &TMH_post_orders_ID_abort,
      /* wallet may give us many coins to sign, allow 1 MB of upload
         to set a conservative bound for sane wallets */
      .max_upload = 1024 * 1024
    },
    /* POST /orders/$ID/claim: */
    {
      .url_prefix = "/orders/",
      .have_id_segment = true,
      .url_suffix = "claim",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &TMH_post_orders_ID_claim,
      /* the body should be pretty small, allow 1 MB of upload
         to set a conservative bound for sane wallets */
      .max_upload = 1024 * 1024
    },
    /* POST /orders/$ID/unclaim: */
    {
      .url_prefix = "/orders/",
      .have_id_segment = true,
      .url_suffix = "unclaim",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &TMH_post_orders_ID_unclaim,
      /* the body should be very small */
      .max_upload = 1024
    },
    /* POST /orders/$ID/pay: */
    {
      .url_prefix = "/orders/",
      .have_id_segment = true,
      .url_suffix = "pay",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &TMH_post_orders_ID_pay,
      /* wallet may give us many coins to sign, allow 1 MB of upload
         to set a conservative bound for sane wallets */
      .max_upload = 1024 * 1024
    },
    /* POST /orders/$ID/paid: */
    {
      .url_prefix = "/orders/",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .url_suffix = "paid",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &TMH_post_orders_ID_paid,
      /* the body should be pretty small, allow 1 MB of upload
         to set a conservative bound for sane wallets */
      .max_upload = 1024 * 1024
    },
    /* POST /orders/$ID/refund: */
    {
      .url_prefix = "/orders/",
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .url_suffix = "refund",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &TMH_post_orders_ID_refund,
      /* the body should be pretty small, allow 1 MB of upload
         to set a conservative bound for sane wallets */
      .max_upload = 1024 * 1024
    },
    /* GET /orders/$ID: */
    {
      .url_prefix = "/orders/",
      .method = MHD_HTTP_METHOD_GET,
      .allow_deleted_instance = true,
      .have_id_segment = true,
      .handler = &TMH_get_orders_ID
    },
    /* GET /sessions/$ID: */
    {
      .url_prefix = "/sessions/",
      .method = MHD_HTTP_METHOD_GET,
      .allow_deleted_instance = true,
      .have_id_segment = true,
      .handler = &TMH_get_sessions_ID
    },
    /* GET /static/ *: */
    {
      .url_prefix = "/static/",
      .method = MHD_HTTP_METHOD_GET,
      .have_id_segment = true,
      .handler = &TMH_return_static
    },
    /* POST /reports/$ID/ */
    {
      .url_prefix = "/reports/",
      .method = MHD_HTTP_METHOD_POST,
      .have_id_segment = true,
      .handler = &TMH_post_reports_ID,
    },
    /* GET /templates/$ID/: */
    {
      .url_prefix = "/templates/",
      .method = MHD_HTTP_METHOD_GET,
      .have_id_segment = true,
      .handler = &TMH_get_templates_ID
    },
    /* GET /products/$HASH/image: */
    {
      .url_prefix = "/products/",
      .method = MHD_HTTP_METHOD_GET,
      .have_id_segment = true,
      .allow_deleted_instance = true,
      .url_suffix = "image",
      .handler = &TMH_get_products_image
    },
    /* POST /templates/$ID: */
    {
      .url_prefix = "/templates/",
      .method = MHD_HTTP_METHOD_POST,
      .have_id_segment = true,
      .handler = &TMH_post_using_templates_ID,
      .max_upload = 1024 * 1024
    },
    /* POST /challenge/$ID: */
    {
      .url_prefix = "/challenge/",
      .method = MHD_HTTP_METHOD_POST,
      .have_id_segment = true,
      .handler = &TMH_post_challenge_ID,
      .max_upload = 1024
    },
    /* POST /challenge/$ID/confirm: */
    {
      .url_prefix = "/challenge/",
      .method = MHD_HTTP_METHOD_POST,
      .have_id_segment = true,
      .url_suffix = "confirm",
      .handler = &TMH_post_challenge_ID_confirm,
      .max_upload = 1024
    },
    /* POST /instances */
    {
      .url_prefix = "/instances",
      .method = MHD_HTTP_METHOD_POST,
      .skip_instance = true,
      .default_only = true,
      .handler = &TMH_public_post_instances,
      /* allow instance data of up to 8 MB, that should be plenty;
         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
         would require further changes to the allocation logic
         in the code... */
      .max_upload = 1024 * 1024 * 8
    },
    /* POST /forgot-password: */
    {
      .url_prefix = "/forgot-password",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &TMH_public_post_instances_ID_auth,
      /* Body should be pretty small. */
      .max_upload = 1024 * 1024
    },

    {
      .url_prefix = "*",
      .method = MHD_HTTP_METHOD_OPTIONS,
      .handler = &handle_server_options
    },
    {
      .url_prefix = NULL
    }
  };
  const char *management_prefix = "/management/";
  const char *private_prefix = "/private/";
  const char *url = *urlp;
  struct TMH_RequestHandler *handlers;

  *is_public = false; /* ensure safe default */
  if ( (0 == strncmp (url,
                      management_prefix,
                      strlen (management_prefix))) )
  {
    handlers = management_handlers;
    *urlp = 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"))
      *urlp = "/";
    else
      *urlp = url + strlen (private_prefix) - 1;
  }
  else
  {
    handlers = public_handlers;
    *is_public = true;
  }
  return handlers;
}


/**
 * Checks if the @a rh matches the given (parsed) URL.
 *
 * @param rh handler to compare against
 * @param url the main URL (without "/private/" prefix, if any)
 * @param prefix_strlen length of the prefix, i.e. 8 for '/orders/' or 7 for '/config'
 * @param infix_url infix text, i.e. "$ORDER_ID".
 * @param infix_strlen length of the string in @a infix_url
 * @param suffix_url suffix, i.e. "/refund", including the "/"
 * @param suffix_strlen number of characters in @a suffix_url
 * @return true if @a rh matches this request
 */
static bool
prefix_match (const struct TMH_RequestHandler *rh,
              const char *url,
              size_t prefix_strlen,
              const char *infix_url,
              size_t infix_strlen,
              const char *suffix_url,
              size_t suffix_strlen)
{
  if ( (prefix_strlen != strlen (rh->url_prefix)) ||
       (0 != memcmp (url,
                     rh->url_prefix,
                     prefix_strlen)) )
    return false;
  if (! rh->have_id_segment)
  {
    /* Require /$PREFIX/$SUFFIX or /$PREFIX */
    if (NULL != suffix_url)
      return false;       /* too many segments to match */
    if ( (NULL == infix_url)   /* either or */
         ^ (NULL == rh->url_suffix) )
      return false;       /* suffix existence mismatch */
    /* If /$PREFIX/$SUFFIX, check $SUFFIX matches */
    if ( (NULL != infix_url) &&
         ( (infix_strlen != strlen (rh->url_suffix)) ||
           (0 != memcmp (infix_url,
                         rh->url_suffix,
                         infix_strlen)) ) )
      return false;       /* cannot use infix as suffix: content mismatch */
  }
  else
  {
    /* Require /$PREFIX/$ID or /$PREFIX/$ID/$SUFFIX */
    if (NULL == infix_url)
      return false;       /* infix existence mismatch */
    if ( ( (NULL == suffix_url)
           ^ (NULL == rh->url_suffix) ) )
      return false;       /* suffix existence mismatch */
    if ( (NULL != suffix_url) &&
         ( (suffix_strlen != strlen (rh->url_suffix)) ||
           (0 != memcmp (suffix_url,
                         rh->url_suffix,
                         suffix_strlen)) ) )
      return false;       /* suffix content mismatch */
  }
  return true;
}


/**
 * 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 */

  /* parse the URL into the three different components */
  {
    const char *slash;

    slash = strchr (&url[1], '/');
    if (NULL == slash)
    {
      /* 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);
    }
  }

  /* find matching handler */
  {
    bool url_found = false;

    for (unsigned int i = 0; NULL != handlers[i].url_prefix; i++)
    {
      const struct TMH_RequestHandler *rh = &handlers[i];

      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))
      {
        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;
    }
    /* 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++)
      {
        const struct TMH_RequestHandler *rh = &handlers[i];

        if (rh->default_only && (! use_admin))
          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;
        }
      }
      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;
    }
  }
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
TMH_dispatch_request (struct TMH_HandlerContext *hc,
                      const char *url,
                      const char *method,
                      bool use_admin,
                      bool *is_public)
{
  const struct TMH_RequestHandler *handlers;

  *is_public = false;
  handlers = determine_handler_group (&url,
                                      is_public);
  return identify_handler (hc,
                           handlers,
                           url,
                           method,
                           use_admin);
}
