/*
     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_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_HEADERS_SENDING:
      return "headers sending";
    case MHD_CONNECTION_HEADERS_SENT:
      return "headers sent";
    case MHD_CONNECTION_NORMAL_BODY_READY:
      return "normal body ready";
    case MHD_CONNECTION_NORMAL_BODY_UNREADY:
      return "normal body unready";
    case MHD_CONNECTION_CHUNKED_BODY_READY:
      return "chunked body ready";
    case MHD_CONNECTION_CHUNKED_BODY_UNREADY:
      return "chunked body unready";
    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; /* = strlen(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)
 */
int
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]) )
    {
      equals = strchr (args, '=');
      amper = strchr (args, '&');
      if (NULL == amper)
	{
	  /* last argument */
	  if (NULL == equals)
	    {
	      /* last argument, without '=' */
              MHD_unescape_plus (args);
	      daemon->unescape_callback (daemon->unescape_callback_cls,
					 connection,
					 args);
	      if (MHD_YES != cb (connection,
				 args,
				 NULL,
				 kind))
		return MHD_NO;
	      (*num_headers)++;
	      break;
	    }
	  /* got 'foo=bar' */
	  equals[0] = '\0';
	  equals++;
          MHD_unescape_plus (args);
	  daemon->unescape_callback (daemon->unescape_callback_cls,
				     connection,
				     args);
          MHD_unescape_plus (equals);
	  daemon->unescape_callback (daemon->unescape_callback_cls,
				     connection,
				     equals);
	  if (MHD_YES != cb (connection,
			     args,
			     equals,
			     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);
	  daemon->unescape_callback (daemon->unescape_callback_cls,
				     connection,
				     args);
	  if (MHD_YES != cb (connection,
			     args,
			     NULL,
			     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);
      daemon->unescape_callback (daemon->unescape_callback_cls,
				 connection,
				 args);
      MHD_unescape_plus (equals);
      daemon->unescape_callback (daemon->unescape_callback_cls,
				 connection,
				 equals);
      if (MHD_YES != cb (connection,
			 args,
			 equals,
			 kind))
        return MHD_NO;
      (*num_headers)++;
      args = amper;
    }
  return MHD_YES;
}

/* end of internal.c */
