/* conversion.c - String conversion helper functions.
   Copyright (C) 2000 Werner Koch (dd9jn)
   Copyright (C) 2001, 2002, 2003, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301, USA.  */

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

#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>

#include "gpgme.h"
#include "util.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_errno (errno);

      *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_errno (errno);

      *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;
}


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

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

      if (endp)
        *endp = (char*)(timestamp + 15);
#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 */
    }
  else
    return (time_t)strtoul (timestamp, endp, 10);
}




static struct
{
  char *name;
  gpgme_error_t err;
} gnupg_errors[] =
  {
    { "EOF", GPG_ERR_EOF },
    { "No_Error", GPG_ERR_NO_ERROR },
    { "General_Error", GPG_ERR_GENERAL },
    { "Out_Of_Core", GPG_ERR_ENOMEM },
    { "Invalid_Value", GPG_ERR_INV_VALUE },
    { "IO_Error", GPG_ERR_GENERAL },
    { "Resource_Limit", GPG_ERR_RESOURCE_LIMIT },
    { "Internal_Error", GPG_ERR_INTERNAL },
    { "Bad_Certificate", GPG_ERR_BAD_CERT },
    { "Bad_Certificate_Chain", GPG_ERR_BAD_CERT_CHAIN},
    { "Missing_Certificate", GPG_ERR_MISSING_CERT },
    { "No_Data", GPG_ERR_NO_DATA },
    { "Bad_Signature", GPG_ERR_BAD_SIGNATURE },
    { "Not_Implemented", GPG_ERR_NOT_IMPLEMENTED },
    { "Conflict", GPG_ERR_CONFLICT },
    { "Bug", GPG_ERR_BUG },
    { "Read_Error", GPG_ERR_GENERAL },
    { "Write_Error", GPG_ERR_GENERAL },
    { "Invalid_Line", GPG_ERR_GENERAL },
    { "Incomplete_Line", GPG_ERR_INCOMPLETE_LINE },
    { "Invalid_Response", GPG_ERR_INV_RESPONSE },
    { "Agent_Error", GPG_ERR_AGENT },
    { "No_Public_Key", GPG_ERR_NO_PUBKEY },
    { "No_Secret_Key", GPG_ERR_NO_SECKEY },
    { "File_Open_Error", GPG_ERR_GENERAL },
    { "File_Create_Error", GPG_ERR_GENERAL },
    { "File_Error", GPG_ERR_GENERAL },
    { "Not_Supported", GPG_ERR_NOT_SUPPORTED },
    { "Invalid_Data", GPG_ERR_INV_DATA },
    { "Assuan_Server_Fault", GPG_ERR_ASSUAN_SERVER_FAULT },
    { "Assuan_Error", GPG_ERR_ASSUAN },
    { "Invalid_Session_Key", GPG_ERR_INV_SESSION_KEY },
    { "Invalid_Sexp", GPG_ERR_INV_SEXP },
    { "Unsupported_Algorithm", GPG_ERR_UNSUPPORTED_ALGORITHM },
    { "No_PIN_Entry", GPG_ERR_NO_PIN_ENTRY },
    { "PIN_Entry_Error", GPG_ERR_NO_PIN_ENTRY },
    { "Bad_PIN", GPG_ERR_BAD_PIN },
    { "Bad_Passphrase", GPG_ERR_BAD_PASSPHRASE },
    { "Invalid_Name", GPG_ERR_INV_NAME },
    { "Bad_Public_Key", GPG_ERR_BAD_PUBKEY },
    { "Bad_Secret_Key", GPG_ERR_BAD_SECKEY },
    { "Bad_Data", GPG_ERR_BAD_DATA },
    { "Invalid_Parameter", GPG_ERR_INV_PARAMETER },
    { "Tribute_to_D_A", GPG_ERR_TRIBUTE_TO_D_A },
    { "No_Dirmngr", GPG_ERR_NO_DIRMNGR },
    { "Dirmngr_Error", GPG_ERR_DIRMNGR },
    { "Certificate_Revoked", GPG_ERR_CERT_REVOKED },
    { "No_CRL_Known", GPG_ERR_NO_CRL_KNOWN },
    { "CRL_Too_Old", GPG_ERR_CRL_TOO_OLD },
    { "Line_Too_Long", GPG_ERR_LINE_TOO_LONG },
    { "Not_Trusted", GPG_ERR_NOT_TRUSTED },
    { "Canceled", GPG_ERR_CANCELED },
    { "Bad_CA_Certificate", GPG_ERR_BAD_CA_CERT },
    { "Certificate_Expired", GPG_ERR_CERT_EXPIRED },
    { "Certificate_Too_Young", GPG_ERR_CERT_TOO_YOUNG },
    { "Unsupported_Certificate", GPG_ERR_UNSUPPORTED_CERT },
    { "Unknown_Sexp", GPG_ERR_UNKNOWN_SEXP },
    { "Unsupported_Protection", GPG_ERR_UNSUPPORTED_PROTECTION },
    { "Corrupted_Protection", GPG_ERR_CORRUPTED_PROTECTION },
    { "Ambiguous_Name", GPG_ERR_AMBIGUOUS_NAME },
    { "Card_Error", GPG_ERR_CARD },
    { "Card_Reset", GPG_ERR_CARD_RESET },
    { "Card_Removed", GPG_ERR_CARD_REMOVED },
    { "Invalid_Card", GPG_ERR_INV_CARD },
    { "Card_Not_Present", GPG_ERR_CARD_NOT_PRESENT },
    { "No_PKCS15_App", GPG_ERR_NO_PKCS15_APP },
    { "Not_Confirmed", GPG_ERR_NOT_CONFIRMED },
    { "Configuration_Error", GPG_ERR_CONFIGURATION },
    { "No_Policy_Match", GPG_ERR_NO_POLICY_MATCH },
    { "Invalid_Index", GPG_ERR_INV_INDEX },
    { "Invalid_Id", GPG_ERR_INV_ID },
    { "No_Scdaemon", GPG_ERR_NO_SCDAEMON },
    { "Scdaemon_Error", GPG_ERR_SCDAEMON },
    { "Unsupported_Protocol", GPG_ERR_UNSUPPORTED_PROTOCOL },
    { "Bad_PIN_Method", GPG_ERR_BAD_PIN_METHOD },
    { "Card_Not_Initialized", GPG_ERR_CARD_NOT_INITIALIZED },
    { "Unsupported_Operation", GPG_ERR_UNSUPPORTED_OPERATION },
    { "Wrong_Key_Usage", GPG_ERR_WRONG_KEY_USAGE }
  };
    

gpgme_error_t
_gpgme_map_gnupg_error (char *err)
{
  unsigned int i;

  /* Future version of GnuPG might return the error code directly, so
     we first test for a a numerical value and use that verbatim.
     Note that this numerical value might be followed by an
     underschore and the textual representation of the error code. */
  if (*err >= '0' && *err <= '9')
    return strtoul (err, NULL, 10);

  /* Well, this is a token, use the mapping table to get the error.
     The drawback is that we won't receive an error source and have to
     use GPG as source. */
  for (i = 0; i < DIM (gnupg_errors); i++)
    if (!strcmp (gnupg_errors[i].name, err))
      return gpg_err_make (GPG_ERR_SOURCE_GPG, gnupg_errors[i].err);

  return gpg_err_make (GPG_ERR_SOURCE_GPG, GPG_ERR_GENERAL);
}
