/*
     This file is part of libmicrohttpd
     Copyright (C) 2007 Daniel Pittman and Christian Grothoff

     This library is free software; you can redistribute it and/or
     modify it under the terms of the GNU Lesser General Public
     License as published by the Free Software Foundation; either
     version 2.1 of the License, or (at your option) any later version.

     This library 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
     Lesser General Public License for more details.

     You should have received a copy of the GNU Lesser General Public
     License along with this library; if not, write to the Free Software
     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

/**
 * @file microhttpd/internal.c
 * @brief  internal shared structures
 * @author Daniel Pittman
 * @author Christian Grothoff
 */

#include "internal.h"
#include "mhd_str.h"

#ifdef HAVE_MESSAGES
#if DEBUG_STATES
/**
 * State to string dictionary.
 */
const char *
MHD_state_to_string (enum MHD_CONNECTION_STATE state)
{
  switch (state)
  {
  case MHD_CONNECTION_INIT:
    return "connection init";
  case MHD_CONNECTION_REQ_LINE_RECEIVING:
    return "receiving request line";
  case MHD_CONNECTION_URL_RECEIVED:
    return "connection url received";
  case MHD_CONNECTION_HEADER_PART_RECEIVED:
    return "header partially received";
  case MHD_CONNECTION_HEADERS_RECEIVED:
    return "headers received";
  case MHD_CONNECTION_HEADERS_PROCESSED:
    return "headers processed";
  case MHD_CONNECTION_CONTINUE_SENDING:
    return "continue sending";
  case MHD_CONNECTION_CONTINUE_SENT:
    return "continue sent";
  case MHD_CONNECTION_BODY_RECEIVED:
    return "body received";
  case MHD_CONNECTION_FOOTER_PART_RECEIVED:
    return "footer partially received";
  case MHD_CONNECTION_FOOTERS_RECEIVED:
    return "footers received";
  case MHD_CONNECTION_FULL_REQ_RECEIVED:
    return "full request received";
  case MHD_CONNECTION_START_REPLY:
    return "start sending reply";
  case MHD_CONNECTION_HEADERS_SENDING:
    return "headers sending";
  case MHD_CONNECTION_HEADERS_SENT:
    return "headers sent";
  case MHD_CONNECTION_NORMAL_BODY_UNREADY:
    return "normal body unready";
  case MHD_CONNECTION_NORMAL_BODY_READY:
    return "normal body ready";
  case MHD_CONNECTION_CHUNKED_BODY_UNREADY:
    return "chunked body unready";
  case MHD_CONNECTION_CHUNKED_BODY_READY:
    return "chunked body ready";
  case MHD_CONNECTION_BODY_SENT:
    return "body sent";
  case MHD_CONNECTION_FOOTERS_SENDING:
    return "footers sending";
  case MHD_CONNECTION_FOOTERS_SENT:
    return "footers sent";
  case MHD_CONNECTION_CLOSED:
    return "closed";
  default:
    return "unrecognized connection state";
  }
}


#endif
#endif


#ifdef HAVE_MESSAGES
/**
 * fprintf-like helper function for logging debug
 * messages.
 */
void
MHD_DLOG (const struct MHD_Daemon *daemon,
          const char *format,
          ...)
{
  va_list va;

  if (0 == (daemon->options & MHD_USE_ERROR_LOG))
    return;
  va_start (va, format);
  daemon->custom_error_log (daemon->custom_error_log_cls,
                            format,
                            va);
  va_end (va);
}


#endif


/**
 * Convert all occurrences of '+' to ' '.
 *
 * @param arg string that is modified (in place), must be 0-terminated
 */
void
MHD_unescape_plus (char *arg)
{
  char *p;

  for (p = strchr (arg, '+'); NULL != p; p = strchr (p + 1, '+'))
    *p = ' ';
}


/**
 * Process escape sequences ('%HH') Updates val in place; the
 * result should be UTF-8 encoded and cannot be larger than the input.
 * The result must also still be 0-terminated.
 *
 * @param val value to unescape (modified in the process)
 * @return length of the resulting val (strlen(val) maybe
 *  shorter afterwards due to elimination of escape sequences)
 */
size_t
MHD_http_unescape (char *val)
{
  char *rpos = val;
  char *wpos = val;

  while ('\0' != *rpos)
  {
    uint32_t num;
    switch (*rpos)
    {
    case '%':
      if (2 == MHD_strx_to_uint32_n_ (rpos + 1,
                                      2,
                                      &num))
      {
        *wpos = (char) ((unsigned char) num);
        wpos++;
        rpos += 3;
        break;
      }
    /* TODO: add bad sequence handling */
    /* intentional fall through! */
    default:
      *wpos = *rpos;
      wpos++;
      rpos++;
    }
  }
  *wpos = '\0'; /* add 0-terminator */
  return wpos - val;
}


/**
 * Parse and unescape the arguments given by the client
 * as part of the HTTP request URI.
 *
 * @param kind header kind to pass to @a cb
 * @param connection connection to add headers to
 * @param[in,out] args argument URI string (after "?" in URI),
 *        clobbered in the process!
 * @param cb function to call on each key-value pair found
 * @param[out] num_headers set to the number of headers found
 * @return #MHD_NO on failure (@a cb returned #MHD_NO),
 *         #MHD_YES for success (parsing succeeded, @a cb always
 *                               returned #MHD_YES)
 */
enum MHD_Result
MHD_parse_arguments_ (struct MHD_Connection *connection,
                      enum MHD_ValueKind kind,
                      char *args,
                      MHD_ArgumentIterator_ cb,
                      unsigned int *num_headers)
{
  struct MHD_Daemon *daemon = connection->daemon;
  char *equals;
  char *amper;

  *num_headers = 0;
  while ( (NULL != args) &&
          ('\0' != args[0]) )
  {
    size_t key_len;
    size_t value_len;
    equals = strchr (args, '=');
    amper = strchr (args, '&');
    if (NULL == amper)
    {
      /* last argument */
      if (NULL == equals)
      {
        /* last argument, without '=' */
        MHD_unescape_plus (args);
        key_len = daemon->unescape_callback (daemon->unescape_callback_cls,
                                             connection,
                                             args);
        if (MHD_NO == cb (connection,
                          args,
                          key_len,
                          NULL,
                          0,
                          kind))
          return MHD_NO;
        (*num_headers)++;
        break;
      }
      /* got 'foo=bar' */
      equals[0] = '\0';
      equals++;
      MHD_unescape_plus (args);
      key_len = daemon->unescape_callback (daemon->unescape_callback_cls,
                                           connection,
                                           args);
      MHD_unescape_plus (equals);
      value_len = daemon->unescape_callback (daemon->unescape_callback_cls,
                                             connection,
                                             equals);
      if (MHD_NO == cb (connection,
                        args,
                        key_len,
                        equals,
                        value_len,
                        kind))
        return MHD_NO;
      (*num_headers)++;
      break;
    }
    /* amper is non-NULL here */
    amper[0] = '\0';
    amper++;
    if ( (NULL == equals) ||
         (equals >= amper) )
    {
      /* got 'foo&bar' or 'foo&bar=val', add key 'foo' with NULL for value */
      MHD_unescape_plus (args);
      key_len = daemon->unescape_callback (daemon->unescape_callback_cls,
                                           connection,
                                           args);
      if (MHD_NO == cb (connection,
                        args,
                        key_len,
                        NULL,
                        0,
                        kind))
        return MHD_NO;
      /* continue with 'bar' */
      (*num_headers)++;
      args = amper;
      continue;
    }
    /* equals and amper are non-NULL here, and equals < amper,
 so we got regular 'foo=value&bar...'-kind of argument */
    equals[0] = '\0';
    equals++;
    MHD_unescape_plus (args);
    key_len = daemon->unescape_callback (daemon->unescape_callback_cls,
                                         connection,
                                         args);
    MHD_unescape_plus (equals);
    value_len = daemon->unescape_callback (daemon->unescape_callback_cls,
                                           connection,
                                           equals);
    if (MHD_NO == cb (connection,
                      args,
                      key_len,
                      equals,
                      value_len,
                      kind))
      return MHD_NO;
    (*num_headers)++;
    args = amper;
  }
  return MHD_YES;
}


/* end of internal.c */
