/* conversion.c - String conversion helper functions.
   Copyright (C) 2000 Werner Koch (dd9jn)
   Copyright (C) 2001, 2002, 2003, 2004, 2007 g10 Code GmbH

   This file is part of GPGME.

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

   GPGME 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 program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
   02111-1307, USA.  */

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <string.h>
#ifdef HAVE_SYS_TYPES_H
  /* Solaris 8 needs sys/types.h before time.h.  */
# include <sys/types.h>
#endif
#include <time.h>
#include <errno.h>

#include "gpgme.h"
#include "util.h"
#include "debug.h"

#define atoi_1(p)   (*(p) - '0' )
#define atoi_2(p)   ((atoi_1(p) * 10) + atoi_1((p)+1))
#define atoi_4(p)   ((atoi_2(p) * 100) + atoi_2((p)+2))



/* Convert two hexadecimal digits from STR to the value they
   represent.  Returns -1 if one of the characters is not a
   hexadecimal digit.  */
int
_gpgme_hextobyte (const char *str)
{
  int val = 0;
  int i;

#define NROFHEXDIGITS 2
  for (i = 0; i < NROFHEXDIGITS; i++)
    {
      if (*str >= '0' && *str <= '9')
	val += *str - '0';
      else if (*str >= 'A' && *str <= 'F')
	val += 10 + *str - 'A';
      else if (*str >= 'a' && *str <= 'f')
	val += 10 + *str - 'a';
      else
	return -1;
      if (i < NROFHEXDIGITS - 1)
	val *= 16;
      str++;
    }
  return val;
}


/* Decode the C formatted string SRC and store the result in the
   buffer *DESTP which is LEN bytes long.  If LEN is zero, then a
   large enough buffer is allocated with malloc and *DESTP is set to
   the result.  Currently, LEN is only used to specify if allocation
   is desired or not, the caller is expected to make sure that *DESTP
   is large enough if LEN is not zero.  */
gpgme_error_t
_gpgme_decode_c_string (const char *src, char **destp, size_t len)
{
  char *dest;

  /* Set up the destination buffer.  */
  if (len)
    {
      if (len < strlen (src) + 1)
	return gpg_error (GPG_ERR_INTERNAL);

      dest = *destp;
    }
  else
    {
      /* The converted string will never be larger than the original
	 string.  */
      dest = malloc (strlen (src) + 1);
      if (!dest)
	return gpg_error_from_syserror ();

      *destp = dest;
    }

  /* Convert the string.  */
  while (*src)
    {
      if (*src != '\\')
	{
	  *(dest++) = *(src++);
	  continue;
	}

      switch (src[1])
	{
#define DECODE_ONE(match,result)	\
	case match:			\
	  src += 2;			\
	  *(dest++) = result;		\
	  break;

	  DECODE_ONE ('\'', '\'');
	  DECODE_ONE ('\"', '\"');
	  DECODE_ONE ('\?', '\?');
	  DECODE_ONE ('\\', '\\');
	  DECODE_ONE ('a', '\a');
	  DECODE_ONE ('b', '\b');
	  DECODE_ONE ('f', '\f');
	  DECODE_ONE ('n', '\n');
	  DECODE_ONE ('r', '\r');
	  DECODE_ONE ('t', '\t');
	  DECODE_ONE ('v', '\v');

	case 'x':
	  {
	    int val = _gpgme_hextobyte (&src[2]);

	    if (val == -1)
	      {
		/* Should not happen.  */
		*(dest++) = *(src++);
		*(dest++) = *(src++);
		if (*src)
		  *(dest++) = *(src++);
		if (*src)
		  *(dest++) = *(src++);
	      }
	    else
	      {
		if (!val)
		  {
		    /* A binary zero is not representable in a C
		       string.  */
		    *(dest++) = '\\';
		    *(dest++) = '0';
		  }
		else
		  *((unsigned char *) dest++) = val;
		src += 4;
	      }
	  }
	  break;

	default:
	  {
	    /* Should not happen.  */
	    *(dest++) = *(src++);
	    *(dest++) = *(src++);
	  }
        }
    }
  *(dest++) = 0;

  return 0;
}


/* Decode the percent escaped string SRC and store the result in the
   buffer *DESTP which is LEN bytes long.  If LEN is zero, then a
   large enough buffer is allocated with malloc and *DESTP is set to
   the result.  Currently, LEN is only used to specify if allocation
   is desired or not, the caller is expected to make sure that *DESTP
   is large enough if LEN is not zero.  If BINARY is 1, then '\0'
   characters are allowed in the output.  */
gpgme_error_t
_gpgme_decode_percent_string (const char *src, char **destp, size_t len,
			      int binary)
{
  char *dest;

  /* Set up the destination buffer.  */
  if (len)
    {
      if (len < strlen (src) + 1)
	return gpg_error (GPG_ERR_INTERNAL);

      dest = *destp;
    }
  else
    {
      /* The converted string will never be larger than the original
	 string.  */
      dest = malloc (strlen (src) + 1);
      if (!dest)
	return gpg_error_from_syserror ();

      *destp = dest;
    }

  /* Convert the string.  */
  while (*src)
    {
      if (*src != '%')
	{
	  *(dest++) = *(src++);
	  continue;
	}
      else
	{
	  int val = _gpgme_hextobyte (&src[1]);

	  if (val == -1)
	    {
	      /* Should not happen.  */
	      *(dest++) = *(src++);
	      if (*src)
		*(dest++) = *(src++);
	      if (*src)
		*(dest++) = *(src++);
	    }
	  else
	    {
	      if (!val && !binary)
		{
		  /* A binary zero is not representable in a C
		     string.  */
		  *(dest++) = '\\';
		  *(dest++) = '0';
		}
	      else
		*((unsigned char *) dest++) = val;
	      src += 3;
	    }
	}
    }
  *(dest++) = 0;

  return 0;
}


/* Encode the string SRC with percent escaping and store the result in
   the buffer *DESTP which is LEN bytes long.  If LEN is zero, then a
   large enough buffer is allocated with malloc and *DESTP is set to
   the result.  Currently, LEN is only used to specify if allocation
   is desired or not, the caller is expected to make sure that *DESTP
   is large enough if LEN is not zero.  If BINARY is 1, then '\0'
   characters are allowed in the output.  */
gpgme_error_t
_gpgme_encode_percent_string (const char *src, char **destp, size_t len)
{
  size_t destlen;
  char *dest;
  const char *str;

  destlen = 0;
  str = src;
  /* We percent-escape the + character because the user might need a
     "percent plus" escaped string (special gpg format).  But we
     percent-escape the space character, which works with and without
     the special plus format.  */
  while (*str)
    {
      if (*str == '+' || *str == '\"' || *str == '%'
          || *(const unsigned char *)str <= 0x20)
        destlen += 3;
      else
        destlen++;
      str++;
    }
  /* Terminating nul byte.  */
  destlen++;

  /* Set up the destination buffer.  */
  if (len)
    {
      if (len < destlen)
	return gpg_error (GPG_ERR_INTERNAL);

      dest = *destp;
    }
  else
    {
      /* The converted string will never be larger than the original
	 string.  */
      dest = malloc (destlen);
      if (!dest)
	return gpg_error_from_syserror ();

      *destp = dest;
    }

  /* Convert the string.  */
  while (*src)
    {
      if (*src == '+' || *src == '\"' || *src == '%'
          || *(const unsigned char *)src <= 0x20)
        {
          snprintf (dest, 4, "%%%02X", *(unsigned char *)src);
	  dest += 3;
	}
      else
	*(dest++) = *src;
      src++;
    }
  *(dest++) = 0;

  return 0;
}


#ifdef HAVE_W32_SYSTEM
static time_t
_gpgme_timegm (struct tm *tm)
{
  /* This one is thread safe.  */
  SYSTEMTIME st;
  FILETIME ft;
  unsigned long long cnsecs;

  st.wYear   = tm->tm_year + 1900;
  st.wMonth  = tm->tm_mon  + 1;
  st.wDay    = tm->tm_mday;
  st.wHour   = tm->tm_hour;
  st.wMinute = tm->tm_min;
  st.wSecond = tm->tm_sec;
  st.wMilliseconds = 0; /* Not available.  */
  st.wDayOfWeek = 0;    /* Ignored.  */

  /* System time is UTC thus the conversion is pretty easy.  */
  if (!SystemTimeToFileTime (&st, &ft))
    {
      gpg_err_set_errno (EINVAL);
      return (time_t)(-1);
    }

  cnsecs = (((unsigned long long)ft.dwHighDateTime << 32)
	    | ft.dwLowDateTime);
  cnsecs -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01.  */
  return (time_t)(cnsecs / 10000000ULL);
}
#endif


/* Parse the string TIMESTAMP into a time_t.  The string may either be
   seconds since Epoch or in the ISO 8601 format like
   "20390815T143012".  Returns 0 for an empty string or seconds since
   Epoch. Leading spaces are skipped. If ENDP is not NULL, it will
   point to the next non-parsed character in TIMESTRING. */
time_t
_gpgme_parse_timestamp (const char *timestamp, char **endp)
{
  /* Need to skip leading spaces, because that is what strtoul does
     but not our ISO 8601 checking code. */
  while (*timestamp && *timestamp== ' ')
    timestamp++;
  if (!*timestamp)
    return 0;

  if (strlen (timestamp) >= 15 && timestamp[8] == 'T')
    {
      struct tm buf;
      int year;

      year = atoi_4 (timestamp);
      if (year < 1900)
        return (time_t)(-1);

      if (endp)
        *endp = (char*)(timestamp + 15);

      /* Fixme: We would better use a configure test to see whether
         mktime can handle dates beyond 2038. */
      if (sizeof (time_t) <= 4 && year >= 2038)
        return (time_t)2145914603; /* 2037-12-31 23:23:23 */

      memset (&buf, 0, sizeof buf);
      buf.tm_year = year - 1900;
      buf.tm_mon = atoi_2 (timestamp+4) - 1;
      buf.tm_mday = atoi_2 (timestamp+6);
      buf.tm_hour = atoi_2 (timestamp+9);
      buf.tm_min = atoi_2 (timestamp+11);
      buf.tm_sec = atoi_2 (timestamp+13);

#ifdef HAVE_W32_SYSTEM
      return _gpgme_timegm (&buf);
#else
#ifdef HAVE_TIMEGM
      return timegm (&buf);
#else
      {
        time_t tim;

        putenv ("TZ=UTC");
        tim = mktime (&buf);
#ifdef __GNUC__
#warning fixme: we must somehow reset TZ here.  It is not threadsafe anyway.
#endif
        return tim;
      }
#endif /* !HAVE_TIMEGM */
#endif /* !HAVE_W32_SYSTEM */
    }
  else
    return (time_t)strtoul (timestamp, endp, 10);
}


/* The GPG backend uses OpenPGP algorithm numbers which we need to map
   to our algorithm numbers.  This function MUST not change ERRNO. */
int
_gpgme_map_pk_algo (int algo, gpgme_protocol_t protocol)
{
  if (protocol == GPGME_PROTOCOL_OPENPGP)
    {
      switch (algo)
        {
        case 1: case 2: case 3: case 16: case 17: break;
        case 18: algo = GPGME_PK_ECDH; break;
        case 19: algo = GPGME_PK_ECDSA; break;
        case 20: break;
        default: algo = 0; break; /* Unknown.  */
        }
    }

  return algo;
}
