2003-05-21 Moritz Schulte <moritz@g10code.com>
[libgcrypt.git] / src / sexp.c
index 23129c1..4f8d449 100644 (file)
@@ -1,20 +1,20 @@
 /* sexp.c  -  S-Expression handling
- *     Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
+ *     Copyright (C) 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
  *
  * This file is part of Libgcrypt.
  *
  * Libgcrypt is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * 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.
  *
  * Libgcrypt 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 General Public License for more details.
+ * GNU Lesser General Public License for more details.
  *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
+ * 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
  */
 
@@ -44,12 +44,20 @@ struct gcry_sexp {
 #define ST_OPEN  3
 #define ST_CLOSE 4
 
-#define digitp(p)   (*(p) >= '0' && *(p) <= '9')
-#define hexdigitp(a) (digitp (a)                     \
-                      || (*(a) >= 'A' && *(a) <= 'F')  \
-                      || (*(a) >= 'a' && *(a) <= 'f'))
 /* the atoi macros assume that the buffer has only valid digits */
 #define atoi_1(p)   (*(p) - '0' )
+#define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
+                     *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
+#define xtoi_2(p)   ((xtoi_1(p) * 16) + xtoi_1((p)+1))
+
+#define TOKEN_SPECIALS  "-./_:*+="
+
+#define OLDPARSECODE(a) (-((GCRYERR_SEXP_ ## a)-GCRYERR_SEXP_INV_LEN_SPEC+1))
+
+
+static int
+sexp_sscan( GCRY_SEXP *retsexp, size_t *erroff ,
+           const char *buffer, size_t length, va_list arg_ptr, int argflag);
 
 
 #if 0
@@ -166,6 +174,73 @@ normalize ( GCRY_SEXP list )
     return list;
 }
 
+/* Create a new S-expression object by reading LENGTH bytes from
+   BUFFER, assuming it is canonilized encoded or autodetected encoding
+   when AUTODETECT is set to 1.  With FREEFNC not NULL, ownership of
+   the buffer is transferred to tyhe newle created object.  FREEFNC
+   should be the freefnc used to release BUFFER; there is no guarantee
+   at which point this function is called; most likey you want to use
+   free() or gcry_free(). 
+   Passing LENGTH and AUTODETECT as 0 is allowed to indicate that
+   BUFFER points to a valid canonical encoded S-expression.  A LENGTH
+   of 0 and AUTODETECT 1 indicates that buffer points to a
+   null-terminated string.
+  
+   This function returns 0 and and the pointer to the new object in
+   RETSEXP or an error code in which case RETSEXP is set to NULL.  */
+int 
+gcry_sexp_create (GCRY_SEXP *retsexp, void *buffer, size_t length,
+                  int autodetect, void (*freefnc)(void*) )
+{
+  int errcode;
+  GCRY_SEXP se;
+  volatile va_list dummy_arg_ptr;
+
+  if (!retsexp)
+    return GCRYERR_INV_ARG;
+  *retsexp = NULL;
+  if (autodetect < 0 || autodetect > 1 || !buffer)
+    return GCRYERR_INV_ARG;
+
+  if (!length && !autodetect)
+    { /* What a brave caller to assume that there is really a canonical
+         encoded S-expression in buffer */
+      length = gcry_sexp_canon_len (buffer, 0, NULL, &errcode);
+      if (!length)
+        return 200 - errcode;
+    }
+  else if (!length && autodetect)
+    { /* buffer is a string */
+      length = strlen ((char *)buffer);
+    }
+
+  errcode = sexp_sscan (&se, NULL, buffer, length, dummy_arg_ptr, 0);
+  if (errcode)
+    return 200 - errcode;
+
+  *retsexp = se;
+  if (freefnc)
+    {
+      /* For now we release the buffer immediately.  As soon as we
+         have changed the internal represenation of S-expression to
+         the canoncial format - which has the advantage of faster
+         parsing - we will use this function as a closure in our
+         GCRYSEXP object and use the BUFFER directly */
+      freefnc (buffer);
+    }
+  return 0;
+}
+
+/* Same as gcry_sexp_create but don't transfer ownership */
+int
+gcry_sexp_new (GCRY_SEXP *retsexp, const void *buffer, size_t length,
+               int autodetect)
+{
+  return gcry_sexp_create (retsexp, (void *)buffer, length, autodetect, NULL);
+}
+
+
 /****************
  * Release resource of the given SEXP object.
  */
@@ -301,7 +376,7 @@ gcry_sexp_find_token( const GCRY_SEXP list, const char *tok, size_t toklen )
 }
 
 /****************
- * return the length of the given list
+ * Return the length of the given list
  */
 int
 gcry_sexp_length( const GCRY_SEXP list )
@@ -319,14 +394,13 @@ gcry_sexp_length( const GCRY_SEXP list )
     while ( (type=*p) != ST_STOP ) {
        p++;
        if ( type == ST_DATA ) {
-           memcpy ( &n, ++p, sizeof n );
+           memcpy ( &n, p, sizeof n );
            p += sizeof n + n;
-           p--;
-           if ( !level )
+           if ( level == 1 )
                length++;
        }
        else if ( type == ST_OPEN ) {
-           if ( !level )
+           if ( level == 1 )
                length++;
            level++;
        }
@@ -670,6 +744,77 @@ make_space ( struct make_space_ctx *c, size_t n )
 }
 
 
+/* Unquote STRING of LENGTH and store it into BUF.  The surrounding
+   quotes are must already be removed from STRING.  We assume that the
+   quoted string is syntacillay correct.  */
+static size_t
+unquote_string (const unsigned char *string, size_t length, unsigned char *buf)
+{
+  int esc = 0;
+  const unsigned char *s = string;
+  unsigned char *d = buf;
+  size_t n = length;
+
+  for (; n; n--, s++)
+    {
+      if (esc)
+        {
+          switch (*s)
+            {
+            case 'b':  *d++ = '\b'; break;
+            case 't':  *d++ = '\t'; break;
+            case 'v':  *d++ = '\v'; break;
+            case 'n':  *d++ = '\n'; break;
+            case 'f':  *d++ = '\f'; break;
+            case 'r':  *d++ = '\r'; break;
+            case '"':  *d++ = '\"'; break;
+            case '\'': *d++ = '\''; break;
+            case '\\': *d++ = '\\'; break;
+
+            case '\r':  /* ignore CR[,LF] */
+              if (n>1 && s[1] == '\n')
+                {
+                  s++; n--;
+                }
+              esc = 0;
+              break;
+              
+            case '\n':  /* ignore LF[,CR] */
+              if (n>1 && s[1] == '\r')
+                {
+                  s++; n--;
+                }
+              break;
+
+            case 'x': /* hex value */
+              if (n>2 && hexdigitp (s+1) && hexdigitp (s+2))
+                {
+                  s++; n--;
+                  *d++ = xtoi_2 (s);
+                  s++; n--;
+                }
+              break;
+
+            default:
+              if (n>2 && octdigitp (s) && octdigitp (s+1) && octdigitp (s+2))
+                {
+                  *d++ = (atoi_1 (s)*64) + (atoi_1 (s+1)*8) + atoi_1 (s+2);
+                  s += 2;
+                  n -= 2;
+                }
+              break;
+           }
+          esc = 0;
+        }
+      else if( *s == '\\' )
+        esc = 1;
+      else
+        *d++ = *s;
+    } 
+
+  return d - buf;
+}
+
 /****************
  * Scan the provided buffer and return the S expression in our internal
  * format.  Returns a newly allocated expression.  If erroff is not NULL and
@@ -789,7 +934,19 @@ sexp_sscan( GCRY_SEXP *retsexp, size_t *erroff ,
            else if( *p == '\\' )
                quoted_esc = 1;
            else if( *p == '\"' ) {
-               /* fixme: add item */
+                /* keep it easy - we know that the unquoted string will
+                   never be larger */
+                char *save;
+                size_t len;
+                
+                quoted++; /* skip leading quote */
+               MAKE_SPACE (p - quoted);
+                *c.pos++ = ST_DATA;
+                save = c.pos;
+                STORE_LEN (c.pos, 0); /* will be fixed up later */
+                len = unquote_string (quoted, p - quoted, c.pos);
+                c.pos += len;
+                STORE_LEN (save, len);
                quoted = NULL;
            }
        }
@@ -799,7 +956,7 @@ sexp_sscan( GCRY_SEXP *retsexp, size_t *erroff ,
            else if( *p == '#' ) {
                if( (hexcount & 1) ) {
                    *erroff = p - buffer;
-                   return -12;  /* odd number of hex digits */
+                   return OLDPARSECODE (ODD_HEX_NUMBERS);
                }
 
                datalen = hexcount/2;
@@ -816,7 +973,7 @@ sexp_sscan( GCRY_SEXP *retsexp, size_t *erroff ,
            }
            else if( !isspace( *p ) ) {
                *erroff = p - buffer;
-               return -11;  /* invalid hex character */
+               return OLDPARSECODE (BAD_HEX_CHAR);
            }
        }
        else if( base64 ) {
@@ -864,7 +1021,7 @@ sexp_sscan( GCRY_SEXP *retsexp, size_t *erroff ,
        else if ( percent ) {
            if ( *p == 'm' ) { /* insert an MPI */
                GCRY_MPI m = va_arg (arg_ptr, GCRY_MPI);
-               size_t nm;
+               size_t nm = 0;
 
                if ( gcry_mpi_print( GCRYMPI_FMT_STD, NULL, &nm, m ) )
                    BUG ();
@@ -1020,11 +1177,132 @@ int
 gcry_sexp_sscan( GCRY_SEXP *retsexp, size_t *erroff,
                            const char *buffer, size_t length )
 {
-    va_list dummy_arg_ptr = 0;
+  /* We don't need the va_list because it is controlled by the
+     following flag, however we have to pass it but can't initialize
+     it as there is no portable way to do so.  volatile is needed to
+     suppress the compiler warning */
+  volatile va_list dummy_arg_ptr;
 
-    return sexp_sscan( retsexp, erroff, buffer, length, dummy_arg_ptr, 0 );
+  return sexp_sscan( retsexp, erroff, buffer, length, dummy_arg_ptr, 0 );
 }
 
+\f
+/* Figure out a suitable encoding for BUFFER of LENGTH.
+   Returns: 0 = Binary
+            1 = String possible
+            2 = Token possible
+*/
+static int
+suitable_encoding (const unsigned char *buffer, size_t length)
+{
+  const unsigned char *s;
+  int maybe_token = 1;
+
+  if (!length)
+    return 1;
+  
+  for (s=buffer; length; s++, length--)
+    {
+      if ( (*s < 0x20 || (*s >= 0x7f && *s <= 0xa0))
+           && !strchr ("\b\t\v\n\f\r\"\'\\", *s))
+        return 0; /*binary*/
+      if ( maybe_token
+           && !alphap (s) && !digitp (s)  && !strchr (TOKEN_SPECIALS, *s))
+        maybe_token = 0;
+    }
+  s = buffer;
+  if ( maybe_token && !digitp (s) )
+    return 2;
+  return 1;
+}
+
+
+static int
+convert_to_hex (const unsigned char *src, size_t len, unsigned char *dest)
+{
+  int i;
+
+  if (dest)
+    {
+      *dest++ = '#';
+      for (i=0; i < len; i++, dest += 2 )
+        sprintf (dest, "%02X", src[i]);
+      *dest++ = '#';
+    }
+  return len*2+2;
+}
+
+static int
+convert_to_string (const unsigned char *s, size_t len, unsigned char *dest)
+{
+  if (dest)
+    {
+      unsigned char *p = dest;
+      *p++ = '\"';
+      for (; len; len--, s++ )
+        {
+          switch (*s)
+            {
+            case '\b': *p++ = '\\'; *p++ = 'b';  break;
+            case '\t': *p++ = '\\'; *p++ = 't';  break;
+            case '\v': *p++ = '\\'; *p++ = 'v';  break;
+            case '\n': *p++ = '\\'; *p++ = 'n';  break;
+            case '\f': *p++ = '\\'; *p++ = 'f';  break;
+            case '\r': *p++ = '\\'; *p++ = 'r';  break;
+            case '\"': *p++ = '\\'; *p++ = '\"';  break;
+            case '\'': *p++ = '\\'; *p++ = '\'';  break;
+            case '\\': *p++ = '\\'; *p++ = '\\';  break;
+            default: 
+              if ( (*s < 0x20 || (*s >= 0x7f && *s <= 0xa0)))
+                {
+                  sprintf (p, "\\x%02x", *s); 
+                  p += 4;
+                }
+              else
+                *p++ = *s;
+            }
+        }
+      *p++ = '\"';
+      return p - dest;
+    }
+  else
+    {
+      int count = 2;
+      for (; len; len--, s++ )
+        {
+          switch (*s)
+            {
+            case '\b': 
+            case '\t': 
+            case '\v': 
+            case '\n': 
+            case '\f': 
+            case '\r': 
+            case '\"':
+            case '\'':
+            case '\\': count += 2; break;
+            default: 
+              if ( (*s < 0x20 || (*s >= 0x7f && *s <= 0xa0)))
+                count += 4;
+              else
+                count++;
+            }
+        }
+      return count;
+    }
+}
+
+
+
+static int
+convert_to_token (const unsigned char *src, size_t len, unsigned char *dest)
+{
+  if (dest)
+    memcpy (dest, src, len);
+  return len;
+}
+
+
 /****************
  * Print SEXP to buffer using the MODE.  Returns the length of the
  * SEXP in buffer or 0 if the buffer is too short (We have at least an
@@ -1035,65 +1313,144 @@ size_t
 gcry_sexp_sprint( const GCRY_SEXP list, int mode,
                                        char *buffer, size_t maxlength )
 {
-    static byte empty[3] = { ST_OPEN, ST_CLOSE, ST_STOP };
-    const byte *s;
-    char *d;
-    DATALEN n;
-    char numbuf[20];
-    size_t len = 0;
-
-    s = list? list->d : empty;
-    d = buffer;
-    while ( *s != ST_STOP ) {
-       switch ( *s ) {
-         case ST_OPEN:
-           s++;
-           len++;
-           if ( buffer ) {
-               if ( len >= maxlength )
-                   return 0;
-               *d++ = '(';
+  static byte empty[3] = { ST_OPEN, ST_CLOSE, ST_STOP };
+  const byte *s;
+  char *d;
+  DATALEN n;
+  char numbuf[20];
+  size_t len = 0;
+  int i, indent = 0;
+  
+  s = list? list->d : empty;
+  d = buffer;
+  while ( *s != ST_STOP )
+    {
+      switch ( *s )
+        {
+        case ST_OPEN:
+          s++;
+          if ( mode != GCRYSEXP_FMT_CANON )
+            {
+              if (indent)
+                len++; 
+              len += indent;
+            }
+          len++;
+          if ( buffer ) 
+            {
+              if ( len >= maxlength )
+                return 0;
+              if ( mode != GCRYSEXP_FMT_CANON )
+                {
+                  if (indent)
+                    *d++ = '\n'; 
+                  for (i=0; i < indent; i++)
+                    *d++ = ' ';
+                }
+              *d++ = '(';
            }
-           break;
-         case ST_CLOSE:
-           s++;
-           len++;
-           if ( mode != GCRYSEXP_FMT_CANON )
-               len++;
-           if ( buffer ) {
-               if ( len >= maxlength )
-                   return 0;
-               *d++ = ')';
-               if ( mode != GCRYSEXP_FMT_CANON )
-                   *d++ = '\n';
+          indent++;
+          break;
+        case ST_CLOSE:
+          s++;
+          len++;
+          if ( buffer ) 
+            {
+              if ( len >= maxlength )
+                return 0;
+              *d++ = ')';
            }
-           break;
-         case ST_DATA:
-           s++;
-           memcpy ( &n, s, sizeof n ); s += sizeof n;
-           sprintf (numbuf, "%u:", (unsigned int)n );
-           len += strlen (numbuf) + n;
-           if ( buffer ) {
-               if ( len >= maxlength )
+          indent--;
+          if (*s != ST_OPEN && *s != ST_STOP && mode != GCRYSEXP_FMT_CANON)
+            {
+              len++;
+              len += indent;
+              if (buffer)
+                {
+                  if (len >= maxlength)
+                    return 0;
+                  *d++ = '\n';
+                  for (i=0; i < indent; i++)
+                    *d++ = ' ';
+                }
+            }
+          break;
+        case ST_DATA:
+          s++;
+          memcpy ( &n, s, sizeof n ); s += sizeof n;
+          if (mode == GCRYSEXP_FMT_ADVANCED)
+            {
+              int type;
+              size_t nn;
+
+              switch ( (type=suitable_encoding (s, n)))
+                {
+                case 1: nn = convert_to_string (s, n, NULL); break;
+                case 2: nn = convert_to_token (s, n, NULL); break;
+                default: nn = convert_to_hex (s, n, NULL); break;
+                }
+              len += nn;
+              if (buffer)
+                {
+                  if (len >= maxlength)
+                    return 0;
+                  switch (type)
+                    {
+                    case 1: convert_to_string (s, n, d); break;
+                    case 2: convert_to_token (s, n, d); break;
+                    default: convert_to_hex (s, n, d); break;
+                    }
+                  d += nn;
+                }
+              if (s[n] != ST_CLOSE)
+                {
+                  len++;
+                  if (buffer)
+                    {
+                      if (len >= maxlength)
+                        return 0;
+                      *d++ = ' ';
+                    }
+                }
+            }
+          else
+            {
+              sprintf (numbuf, "%u:", (unsigned int)n );
+              len += strlen (numbuf) + n;
+              if ( buffer ) 
+                {
+                  if ( len >= maxlength )
                    return 0;
-               d = stpcpy ( d, numbuf );
-               memcpy ( d, s, n ); d += n;
-           }
-           s += n;
-           break;
-         default:
-           BUG ();
+                  d = stpcpy ( d, numbuf );
+                  memcpy ( d, s, n ); d += n;
+                }
+            }
+          s += n;
+          break;
+        default:
+          BUG ();
        }
     }
-    if (buffer) {
-       if ( len >= maxlength )
-           return 0;
-       *d++ = 0; /* for convenience we make a C string */
+  if ( mode != GCRYSEXP_FMT_CANON )
+    {
+      len++;
+      if (buffer)
+        {
+          if ( len >= maxlength )
+            return 0;
+          *d++ = '\n'; 
+        }
+    }
+  if (buffer) 
+    {
+      if ( len >= maxlength )
+        return 0;
+      *d++ = 0; /* for convenience we make a C string */
     }
-    else
-       len++; /* we need one byte more for this */
+  else
+    len++; /* we need one byte more for this */
 
-    return len;
+  return len;
 }
 
 
@@ -1103,7 +1460,7 @@ gcry_sexp_sprint( const GCRY_SEXP list, int mode,
    return the actual length this S-expression uses.  For a valid S-Exp
    it should never return 0.  If LENGTH is not zero, the maximum
    length to scan is given - this can be used for syntax checks of
-   data passed from outside. erroce and erroff may both be passed as
+   data passed from outside. errorcode and erroff may both be passed as
    NULL
 
    Errorcodes (for historic reasons they are all negative):
@@ -1116,7 +1473,10 @@ gcry_sexp_sprint( const GCRY_SEXP list, int mode,
     -7 := a length may not begin with zero 
     -8 := nested display hints 
     -9 := unmatched display hint close
-   -10 := unexpected reserved punctuation 
+   -10 := unexpected reserved punctuation          
+
+   Use this formula to convert the errorcodes:
+   gcryerr = 200 - errcode;
  */
 size_t
 gcry_sexp_canon_len (const unsigned char *buffer, size_t length, 
@@ -1140,14 +1500,17 @@ gcry_sexp_canon_len (const unsigned char *buffer, size_t length,
   if (!buffer)
     return 0;
   if (*buffer != '(')
-    return -4; /* not a canonical S-expression */
+    {
+      *errcode = OLDPARSECODE (NOT_CANONICAL);
+      return 0;
+    }
 
   for (p=buffer; ; p++, count++ )
     {
       if (length && count >= length)
         {
           *erroff = count;
-          *errcode = -2; /* string too long */
+          *errcode = OLDPARSECODE (STRING_TOO_LONG); 
           return 0;
         }
       
@@ -1158,7 +1521,7 @@ gcry_sexp_canon_len (const unsigned char *buffer, size_t length,
               if (length && (count+datalen) >= length)
                 {
                   *erroff = count;
-                  *errcode = -2; /* string too long */
+                  *errcode = OLDPARSECODE (STRING_TOO_LONG);
                   return 0;
                 }
               count += datalen;
@@ -1170,7 +1533,7 @@ gcry_sexp_canon_len (const unsigned char *buffer, size_t length,
           else 
             {
               *erroff = count;
-              *errcode = -1;
+              *errcode = OLDPARSECODE (INV_LEN_SPEC);
               return 0;
            }
        }
@@ -1179,7 +1542,7 @@ gcry_sexp_canon_len (const unsigned char *buffer, size_t length,
           if (disphint)
             {
               *erroff = count;
-              *errcode = -9; /* open display hint */
+              *errcode = OLDPARSECODE (UNMATCHED_DH);
               return 0;
            }
           level++;
@@ -1189,13 +1552,13 @@ gcry_sexp_canon_len (const unsigned char *buffer, size_t length,
           if (!level)
             {
               *erroff = count;
-              *errcode = -3; /* unmatched parenthesis */
+              *errcode = OLDPARSECODE (UNMATCHED_PAREN);
               return 0;
            }
           if (disphint)
             {
               *erroff = count;
-              *errcode = -9; /* open display hint */
+              *errcode = OLDPARSECODE (UNMATCHED_DH);
               return 0;
            }
           if (!--level)
@@ -1206,7 +1569,7 @@ gcry_sexp_canon_len (const unsigned char *buffer, size_t length,
           if (disphint) 
             {
               *erroff = count;
-              *errcode = -8; /* nested display hints */
+              *errcode = OLDPARSECODE (NESTED_DH);
               return 0;
             }
           disphint = p;
@@ -1216,7 +1579,7 @@ gcry_sexp_canon_len (const unsigned char *buffer, size_t length,
           if( !disphint ) 
             {
               *erroff = count;
-              *errcode = -9; /* unmatched display hint close */
+              *errcode = OLDPARSECODE (UNMATCHED_DH);
               return 0;
            }
           disphint = NULL;
@@ -1226,7 +1589,7 @@ gcry_sexp_canon_len (const unsigned char *buffer, size_t length,
           if (*p == '0')
             { 
               *erroff = count;
-              *errcode = -7; /* a length may not begin with zero */
+              *errcode = OLDPARSECODE (ZERO_PREFIX);
               return 0;
            }
           datalen = atoi_1 (p);
@@ -1234,16 +1597,14 @@ gcry_sexp_canon_len (const unsigned char *buffer, size_t length,
       else if (*p == '&' || *p == '\\')
         {
           *erroff = count;
-          *errcode = -10; /* unexpected reserved punctuation */
+          *errcode = OLDPARSECODE (UNEXPECTED_PUNC);
           return 0;
        }
       else
         { 
           *erroff = count;
-          *errcode = -5; /* bad character */
+          *errcode = OLDPARSECODE (BAD_CHARACTER);
           return 0;
        }
     }
 }
-
-