/*
  This file is part of Challenger
  Copyright (C) 2023, 2024 Taler Systems SA

  Challenger 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.

  Challenger is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.

  You should have received a copy of the GNU Affero General Public License along with
  Challenger; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file challenger-httpd_common.c
 * @brief common helper functions
 * @author Christian Grothoff
 */
#include "platform.h"
#include "challenger-httpd_common.h"


/**
 * Prefix required for all Bearer tokens.
 */
#define RFC_8959_PREFIX "secret-token:"


// FIXME: enums would be nicer...
int
CH_get_output_type (struct MHD_Connection *connection)
{
  const char *mime;
  double q_html;
  double q_json;

  mime = MHD_lookup_connection_value (connection,
                                      MHD_HEADER_KIND,
                                      MHD_HTTP_HEADER_ACCEPT);
  if (NULL == mime)
    return 0; /* default to HTML */
  q_html = TALER_pattern_matches (mime,
                                  "text/html");
  q_json = TALER_pattern_matches (mime,
                                  "application/json");
  return (q_html > q_json) ? 0 : 1;
}


const char *
CH_get_client_secret (struct MHD_Connection *connection)
{
  const char *bearer = "Bearer ";
  const char *auth;
  const char *tok;

  auth = MHD_lookup_connection_value (connection,
                                      MHD_HEADER_KIND,
                                      MHD_HTTP_HEADER_AUTHORIZATION);
  if (NULL == auth)
    return NULL;
  if (0 != strncmp (auth,
                    bearer,
                    strlen (bearer)))
  {
    return NULL;
  }
  tok = auth + strlen (bearer);
  while (' ' == *tok)
    tok++;
  if (0 != strncasecmp (tok,
                        RFC_8959_PREFIX,
                        strlen (RFC_8959_PREFIX)))
  {
    return NULL;
  }
  return tok;
}


char *
CH_compute_code (const struct CHALLENGER_ValidationNonceP *nonce,
                 const char *client_secret,
                 const char *client_scope,
                 const json_t *address,
                 const char *client_redirect_uri)
{
  char *code;
  char *ns;
  char *hs;
  struct GNUNET_ShortHashCode h;
  char *astr;

  astr = json_dumps (address,
                     JSON_COMPACT);
  GNUNET_assert (GNUNET_YES ==
                 GNUNET_CRYPTO_kdf (&h,
                                    sizeof (h),
                                    nonce,
                                    sizeof (nonce),
                                    client_secret,
                                    strlen (client_secret),
                                    astr,
                                    strlen (astr),
                                    client_redirect_uri,
                                    strlen (client_redirect_uri),
                                    client_scope,
                                    NULL != client_scope
                                    ? strlen (client_scope)
                                    : 0,
                                    NULL,
                                    0));
  free (astr);
  ns = GNUNET_STRINGS_data_to_string_alloc (nonce,
                                            sizeof (*nonce));
  hs = GNUNET_STRINGS_data_to_string_alloc (&h,
                                            sizeof (h));
  GNUNET_asprintf (&code,
                   "%s-%s",
                   ns,
                   hs);
  GNUNET_free (ns);
  GNUNET_free (hs);
  return code;
}


enum GNUNET_GenericReturnValue
CH_code_to_nonce (const char *code,
                  struct CHALLENGER_ValidationNonceP *nonce)
{
  const char *dash = strchr (code, '-');

  if (NULL == dash)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  if (GNUNET_OK !=
      GNUNET_STRINGS_string_to_data (code,
                                     (size_t) (dash - code),
                                     nonce,
                                     sizeof (*nonce)))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


MHD_RESULT
CH_reply_with_oauth_error (
  struct MHD_Connection *connection,
  unsigned int http_status,
  const char *oauth_error,
  enum TALER_ErrorCode ec,
  const char *detail)
{
  struct MHD_Response *resp;
  MHD_RESULT mret;

  resp = TALER_MHD_make_json_steal (
    GNUNET_JSON_PACK (
      TALER_MHD_PACK_EC (ec),
      GNUNET_JSON_pack_string ("error",
                               oauth_error),
      GNUNET_JSON_pack_allow_null (
        GNUNET_JSON_pack_string ("detail",
                                 detail))));
  if (MHD_HTTP_UNAUTHORIZED == http_status)
    GNUNET_break (MHD_YES ==
                  MHD_add_response_header (
                    resp,
                    "WWW-Authenticate",
                    "Bearer, error=\"invalid_token\""));
  mret = MHD_queue_response (connection,
                             http_status,
                             resp);
  MHD_destroy_response (resp);
  return mret;
}


MHD_RESULT
TALER_MHD_redirect_with_oauth_status (
  struct MHD_Connection *connection,
  const char *client_redirect_uri,
  const char *state,
  const char *oauth_error,
  const char *oauth_error_description,
  const char *oauth_error_uri)
{
  struct MHD_Response *response;
  unsigned int http_status;

  if (0 == CH_get_output_type (connection))
  {
    char *url;
    char *enc_err;
    char *enc_state;
    char *enc_desc = NULL;
    char *enc_uri = NULL;

    response = MHD_create_response_from_buffer (strlen (oauth_error),
                                                (void *) oauth_error,
                                                MHD_RESPMEM_PERSISTENT);
    if (NULL == response)
    {
      GNUNET_break (0);
      return MHD_NO;
    }
    TALER_MHD_add_global_headers (response,
                                  false);
    GNUNET_break (MHD_YES ==
                  MHD_add_response_header (response,
                                           MHD_HTTP_HEADER_CONTENT_TYPE,
                                           "text/plain"));
    enc_err = TALER_urlencode (oauth_error);
    enc_state = TALER_urlencode (state);
    if (NULL != oauth_error_description)
      enc_desc = TALER_urlencode (oauth_error_description);
    if (NULL != oauth_error_uri)
      enc_uri = TALER_urlencode (oauth_error_uri);
    url = TALER_url_join (
      client_redirect_uri,
      "",
      "state", enc_state,
      "error", enc_err,
      "error_description", enc_desc,
      "error_uri", enc_uri,
      NULL);
    GNUNET_free (enc_err);
    GNUNET_free (enc_state);
    GNUNET_free (enc_desc);
    GNUNET_free (enc_uri);
    if (MHD_NO ==
        MHD_add_response_header (response,
                                 MHD_HTTP_HEADER_LOCATION,
                                 url))
    {
      GNUNET_break (0);
      MHD_destroy_response (response);
      GNUNET_free (url);
      return MHD_NO;
    }
    http_status = MHD_HTTP_FOUND;
    GNUNET_free (url);
  }
  else
  {
    json_t *args;

    args = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("state",
                               state),
      GNUNET_JSON_pack_string ("error",
                               oauth_error),
      GNUNET_JSON_pack_allow_null (
        GNUNET_JSON_pack_string ("description",
                                 oauth_error_description)),
      GNUNET_JSON_pack_allow_null (
        GNUNET_JSON_pack_string ("uri",
                                 oauth_error_uri)));

    response = TALER_MHD_make_json_steal (args);
    TALER_MHD_add_global_headers (response,
                                  false);
    http_status = MHD_HTTP_TOO_MANY_REQUESTS;
  }

  {
    MHD_RESULT ret;

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