Add base64 encode/decode functions
authorAlexander Larsson <alexl@redhat.com>
Tue, 4 Apr 2006 13:03:23 +0000 (13:03 +0000)
committerAlexander Larsson <alexl@src.gnome.org>
Tue, 4 Apr 2006 13:03:23 +0000 (13:03 +0000)
2006-04-04  Alexander Larsson  <alexl@redhat.com>

* glib/Makefile.am:
* glib/gbase64.[ch]:
* glib/glib.symbols:
Add base64 encode/decode functions

* glib/glib.h:
Include gbase64.h

* tests/Makefile.am:
* tests/base64-test.c:
Tests for base64 functions

ChangeLog
ChangeLog.pre-2-12
glib/Makefile.am
glib/gbase64.c [new file with mode: 0644]
glib/gbase64.h [new file with mode: 0644]
glib/glib.h
glib/glib.symbols
tests/Makefile.am
tests/base64-test.c [new file with mode: 0644]

index 8c24421..03845d0 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2006-04-04  Alexander Larsson  <alexl@redhat.com>
+
+       * glib/Makefile.am:
+       * glib/gbase64.[ch]:
+       * glib/glib.symbols:
+       Add base64 encode/decode functions
+       
+       * glib/glib.h:
+       Include gbase64.h
+       
+       * tests/Makefile.am:
+       * tests/base64-test.c:
+       Tests for base64 functions
+
 2006-04-04  Matthias Clasen  <mclasen@redhat.com>
 
        * glib/gdate.c: Move short_month_names and long_month_names
index 8c24421..03845d0 100644 (file)
@@ -1,3 +1,17 @@
+2006-04-04  Alexander Larsson  <alexl@redhat.com>
+
+       * glib/Makefile.am:
+       * glib/gbase64.[ch]:
+       * glib/glib.symbols:
+       Add base64 encode/decode functions
+       
+       * glib/glib.h:
+       Include gbase64.h
+       
+       * tests/Makefile.am:
+       * tests/base64-test.c:
+       Tests for base64 functions
+
 2006-04-04  Matthias Clasen  <mclasen@redhat.com>
 
        * glib/gdate.c: Move short_month_names and long_month_names
index bc5afc2..a55f6e6 100644 (file)
@@ -72,6 +72,7 @@ libglib_2_0_la_SOURCES =      \
        gasyncqueue.c           \
        gatomic.c               \
        gbacktrace.c            \
+       gbase64.c               \
        gbookmarkfile.c         \
        gbsearcharray.h         \
        gcache.c                \
@@ -149,6 +150,7 @@ glibsubinclude_HEADERS =   \
        gasyncqueue.h   \
        gatomic.h       \
        gbacktrace.h    \
+       gbase64.h       \
        gbookmarkfile.h \
        gcache.h        \
        gcompletion.h   \
diff --git a/glib/gbase64.c b/glib/gbase64.c
new file mode 100644 (file)
index 0000000..f6e0cd0
--- /dev/null
@@ -0,0 +1,356 @@
+/* gbase64.c - Base64 encoding/decoding
+ *
+ *  Copyright (C) 2006 Alexander Larsson <alexl@redhat.com>
+ *  Copyright (C) 2000-2003 Ximian Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * This is based on code in camel, written by:
+ *    Michael Zucchi <notzed@ximian.com>
+ *    Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gbase64.h"
+#include "glib.h"
+#include "glibintl.h"
+
+#include "galias.h"
+
+static const char base64_alphabet[] =
+       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/**
+ * g_base64_encode_step:
+ * @in: the binary data to encode.
+ * @len: the length of @in.
+ * @break_lines: whether to break long lines
+ * @out: pointer to destination buffer
+ * @state: Saved state between steps, initialize to 0
+ * @save: Saved state between steps, initialize to 0
+ *
+ * Incrementally encode a sequence of binary data into it's Base-64 stringified
+ * representation. By calling this functions multiple times you can convert data
+ * in chunks to avoid having to have the full encoded data in memory.
+ *
+ * When all the data has been converted you must call g_base64_encode_close()
+ * to flush the saved state.
+ *
+ * The output buffer must be large enough to fit all the data that will
+ * be written to it. Due to the way base64 encodes you will need
+ * at least: @len * 4 / 3 + 6 bytes. If you enable line-breaking you will
+ * need at least: @len * 4 / 3 + @len * 4 / (3 * 72) + 7 bytes.
+ *
+ * @break_lines is typically used when putting base64-encoded data in emails.
+ * It breaks the lines at 72 columns instead of putting all text on the same
+ * line. This avoids problems with long lines in the email system.
+ *
+ * Return value: The number of bytes of output that was written
+ *
+ * Since: 2.12
+ */
+gsize
+g_base64_encode_step (const guchar  *in, 
+                     gsize          len, 
+                     gboolean       break_lines, 
+                     char          *out, 
+                     int           *state, 
+                     int           *save)
+{
+  char *outptr;
+  const guchar *inptr;
+  
+  if (len <= 0)
+    return 0;
+  
+  inptr = in;
+  outptr = out;
+  
+  if (len + ((char *) save) [0] > 2)
+    {
+      const guchar *inend = in+len-2;
+      int c1, c2, c3;
+      int already;
+      
+      already = *state;
+      
+      switch (((char *) save) [0])
+       {
+       case 1: c1 = ((unsigned char *) save) [1]; goto skip1;
+       case 2: c1 = ((unsigned char *) save) [1];
+         c2 = ((unsigned char *) save) [2]; goto skip2;
+       }
+      
+      /* 
+       * yes, we jump into the loop, no i'm not going to change it, 
+       * it's beautiful! 
+       */
+      while (inptr < inend)
+       {
+         c1 = *inptr++;
+       skip1:
+         c2 = *inptr++;
+       skip2:
+         c3 = *inptr++;
+         *outptr++ = base64_alphabet [ c1 >> 2 ];
+         *outptr++ = base64_alphabet [ c2 >> 4 | 
+                                       ((c1&0x3) << 4) ];
+         *outptr++ = base64_alphabet [ ((c2 &0x0f) << 2) | 
+                                       (c3 >> 6) ];
+         *outptr++ = base64_alphabet [ c3 & 0x3f ];
+         /* this is a bit ugly ... */
+         if (break_lines && (++already)>=19)
+           {
+             *outptr++='\n';
+             already = 0;
+           }
+       }
+      
+      ((char *)save)[0] = 0;
+      len = 2-(inptr-inend);
+      *state = already;
+    }
+  
+  if (len>0)
+    {
+      char *saveout;
+      
+      /* points to the slot for the next char to save */
+      saveout = & (((char *)save)[1]) + ((char *)save)[0];
+      
+      /* len can only be 0 1 or 2 */
+      switch(len)
+       {
+       case 2: *saveout++ = *inptr++;
+       case 1: *saveout++ = *inptr++;
+       }
+      ((char *)save)[0]+=len;
+    }
+  
+  return outptr-out;
+}
+
+/**
+ * g_base64_encode_close:
+ * @break_lines: whether to break long lines
+ * @out: pointer to destination buffer
+ * @state: Saved state from g_base64_encode_step()
+ * @save: Saved state from g_base64_encode_step()
+ *
+ * Flush the status from a sequence of calls to g_base64_encode_step().
+ *
+ * Return value: The number of bytes of output that was written
+ *
+ * Since: 2.12
+ */
+gsize
+g_base64_encode_close (gboolean       break_lines,
+                      char          *out, 
+                      int           *state, 
+                      int           *save)
+{
+  int c1, c2;
+  char *outptr = out;
+
+  c1 = ((unsigned char *) save) [1];
+  c2 = ((unsigned char *) save) [2];
+  
+  switch (((char *) save) [0])
+    {
+    case 2:
+      outptr [2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ];
+      g_assert (outptr [2] != 0);
+      goto skip;
+    case 1:
+      outptr[2] = '=';
+    skip:
+      outptr [0] = base64_alphabet [ c1 >> 2 ];
+      outptr [1] = base64_alphabet [ c2 >> 4 | ( (c1&0x3) << 4 )];
+      outptr [3] = '=';
+      outptr += 4;
+      break;
+    }
+  if (break_lines)
+    *outptr++ = '\n';
+  
+  *save = 0;
+  *state = 0;
+  
+  return outptr-out;
+}
+
+/**
+ * g_base64_encode:
+ * @data: the binary data to encode.
+ * @len: the length of @data.
+ *
+ * Encode a sequence of binary data into it's Base-64 stringified
+ * representation.
+ *
+ * Return value: a newly allocated, zero-terminated Base-64 encoded
+ *               string representing @data.
+ *
+ * Since: 2.12
+ */
+char *
+g_base64_encode (const guchar *data, gsize len)
+{
+  char *out;
+  int state = 0, outlen;
+  int save = 0;
+
+  /* We can use a smaller limit here, since we know the saved state is 0 */
+  out = g_malloc (len * 4 / 3 + 4);
+  outlen = g_base64_encode_step (data, len, FALSE, out, &state, &save);
+  outlen += g_base64_encode_close (FALSE,
+                                  out + outlen, 
+                                  &state, 
+                                  &save);
+  out[outlen] = '\0';
+  return (char *) out;
+}
+
+static const unsigned char mime_base64_rank[256] = {
+  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+  255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,
+   52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,  0,255,255,
+  255,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+   15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
+  255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+   41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255,
+  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+};
+
+/**
+ * g_base64_decode_step: decode a chunk of base64 encoded data
+ * @in: binary input data
+ * @len: max length of @in data to decode
+ * @out: output buffer
+ * @state: Saved state between steps, initialize to 0
+ * @save: Saved state between steps, initialize to 0
+ *
+ * Incrementally decode a sequence of binary data from it's Base-64 stringified
+ * representation. By calling this functions multiple times you can convert data
+ * in chunks to avoid having to have the full encoded data in memory.
+ *
+ * The output buffer must be large enough to fit all the data that will
+ * be written to it. Since base64 encodes 3 bytes in 4 chars you need
+ * at least: @len * 3 / 4 bytes.
+ * 
+ * Return value: The number of bytes of output that was written
+ *
+ * Since: 2.12
+ **/
+gsize
+g_base64_decode_step (const char  *in, 
+                     gsize        len, 
+                     guchar      *out, 
+                     int         *state, 
+                     guint       *save)
+{
+  const guchar *inptr;
+  guchar *outptr;
+  const guchar *inend;
+  guchar c;
+  unsigned int v;
+  int i;
+  
+  inend = (const guchar *)in+len;
+  outptr = out;
+  
+  /* convert 4 base64 bytes to 3 normal bytes */
+  v=*save;
+  i=*state;
+  inptr = (const guchar *)in;
+  while (inptr < inend)
+    {
+      c = mime_base64_rank [*inptr++];
+      if (c != 0xff)
+       {
+         v = (v<<6) | c;
+         i++;
+         if (i==4)
+           {
+             *outptr++ = v>>16;
+             *outptr++ = v>>8;
+             *outptr++ = v;
+             i=0;
+           }
+       }
+    }
+  
+  *save = v;
+  *state = i;
+  
+  /* quick scan back for '=' on the end somewhere */
+  /* fortunately we can drop 1 output char for each trailing = (upto 2) */
+  i=2;
+  while (inptr > (const guchar *)in && i)
+    {
+      inptr--;
+      if (mime_base64_rank [*inptr] != 0xff)
+       {
+         if (*inptr == '=')
+           outptr--;
+         i--;
+       }
+    }
+
+  /* if i!= 0 then there is a truncation error! */
+  return outptr - out;
+}
+
+/**
+ * g_base64_decode:
+ * @text: zero-terminated string with base64 text to decode.
+ * @out_len: The lenght of the decoded data is written here.
+ *
+ * Decode a sequence of Base-64 encoded text into binary data
+ *
+ * Return value: a newly allocated, buffer containing the binary data
+ *               that @text represents
+ *
+ * Since: 2.12
+ */
+guchar *
+g_base64_decode (const char   *text,
+                gsize        *out_len)
+{
+  guchar *ret;
+  int inlen, state = 0;
+  guint save = 0;
+  
+  inlen = strlen (text);
+  ret = g_malloc0 (inlen * 3 / 4);
+  
+  *out_len = g_base64_decode_step (text, inlen, ret, &state, &save);
+  
+  return ret; 
+}
+
+#define __G_BASE64_C__
+#include "galiasdef.c"
diff --git a/glib/gbase64.h b/glib/gbase64.h
new file mode 100644 (file)
index 0000000..1aa2bff
--- /dev/null
@@ -0,0 +1,50 @@
+/* gbase64.h - Base64 coding functions
+ *
+ *  Copyright (C) 2005  Alexander Larsson <alexl@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __G_BASE64_H__
+#define __G_BASE64_H__
+
+#include <glib/gtypes.h>
+
+G_BEGIN_DECLS
+
+gsize   g_base64_encode_step  (const guchar *in,
+                              gsize         len,
+                              gboolean      break_lines,
+                              char         *out,
+                              int          *state,
+                              int          *save);
+gsize   g_base64_encode_close (gboolean      break_lines,
+                              char         *out,
+                              int          *state,
+                              int          *save);
+char *  g_base64_encode       (const guchar *data,
+                              gsize         len);
+gsize   g_base64_decode_step  (const char   *in,
+                              gsize         len,
+                              guchar       *out,
+                              int          *state,
+                              guint        *save);
+guchar *g_base64_decode       (const char   *text,
+                              gsize        *out_len);
+
+G_END_DECLS
+
+#endif /* __G_BASE64_H__ */
index 7fa0ef1..e5044f9 100644 (file)
@@ -32,6 +32,7 @@
 #include <glib/gasyncqueue.h>
 #include <glib/gatomic.h>
 #include <glib/gbacktrace.h>
+#include <glib/gbase64.h>
 #include <glib/gbookmarkfile.h>
 #include <glib/gcache.h>
 #include <glib/gcompletion.h>
index 9f5ac88..3a03e07 100644 (file)
@@ -103,6 +103,16 @@ g_on_error_stack_trace
 #endif
 #endif
 
+#if IN_HEADER(__G_BASE64_H__)
+#if IN_FILE(__G_BASE64_C__)
+g_base64_encode_step
+g_base64_encode_close
+g_base64_encode
+g_base64_decode_step
+g_base64_decode
+#endif
+#endif
+
 #if IN_HEADER(__G_BOOKMARK_FILE_H__)
 #if IN_FILE(__G_BOOKMARK_FILE_C__)
 g_bookmark_file_error_quark
index 9c14f85..e5589b5 100644 (file)
@@ -63,6 +63,7 @@ endif
 test_programs =                                        \
        atomic-test                             \
        array-test                              \
+       base64-test                             \
        bookmarkfile-test                               \
        $(CXX_TEST)                             \
        child-test                              \
@@ -127,6 +128,7 @@ module_ldadd = $(libgmodule) $(G_MODULE_LIBS) $(progs_ldadd)
 
 atomic_test_LDADD = $(progs_ldadd)
 array_test_LDADD = $(progs_ldadd)
+base64_test_LDADD = $(progs_ldadd)
 bookmarkfile_test_LDADD = $(progs_ldadd)
 child_test_LDADD = $(thread_ldadd)
 completion_test_LDADD = $(progs_ldadd)
diff --git a/tests/base64-test.c b/tests/base64-test.c
new file mode 100644 (file)
index 0000000..6c458d2
--- /dev/null
@@ -0,0 +1,107 @@
+#include <glib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#define DATA_SIZE 1024
+#define BLOCK_SIZE 32
+#define NUM_BLOCKS 32
+static guchar data[DATA_SIZE];
+
+static void
+test_incremental (gboolean line_break)
+{
+  char text[DATA_SIZE * 2];
+  char *p;
+  guchar data2[DATA_SIZE];
+  int i;
+  gsize len, decoded_len, max;
+  int state, save;
+  guint decoder_save;
+
+  len = 0;
+  state = 0;
+  save = 0;
+  for (i = 0; i < NUM_BLOCKS; i++)
+    len += g_base64_encode_step (data + i * BLOCK_SIZE, BLOCK_SIZE,
+                                line_break, text + len, &state, &save);
+  len += g_base64_encode_close (line_break, text + len, &state, &save);
+
+  if (line_break)
+    max = DATA_SIZE * 4 / 3 + DATA_SIZE * 4 / (3 * 72) + 7;
+  else
+    max = DATA_SIZE * 4 / 3 + 6;
+  if (len > max)
+    {
+      g_print ("To long encoded length: got %d, expected max %d\n",
+              len, max);
+      exit (1);
+    }
+
+  decoded_len = 0;
+  state = 0;
+  decoder_save = 0;
+  p = text;
+  while (len > 0)
+    {
+      int chunk_len = MAX (32, len);
+      decoded_len += g_base64_decode_step (p, 
+                                          chunk_len, 
+                                          data2 + decoded_len,
+                                          &state, &decoder_save);
+      p += chunk_len;
+      len -= chunk_len;
+    }
+  
+  if (decoded_len != DATA_SIZE)
+    {
+      g_print ("Wrong decoded length: got %d, expected %d\n",
+              decoded_len, DATA_SIZE);
+      exit (1);
+    }
+
+  if (memcmp (data, data2, DATA_SIZE) != 0)
+    {
+      g_print ("Wrong decoded base64 data\n");
+      exit (1);
+    }
+}
+
+static void
+test_full (void)
+{
+  char *text;
+  guchar *data2;
+  gsize len;
+
+  text = g_base64_encode (data, DATA_SIZE);
+  data2 = g_base64_decode (text, &len);
+  g_free (text);
+
+  if (len != DATA_SIZE)
+    {
+      g_print ("Wrong decoded length: got %d, expected %d\n",
+              len, DATA_SIZE);
+      exit (1);
+    }
+
+  if (memcmp (data, data2, DATA_SIZE) != 0)
+    {
+      g_print ("Wrong decoded base64 data\n");
+      exit (1);
+    }
+}
+
+int
+main (int argc, char *argv[])
+{
+  int i;
+  for (i = 0; i < DATA_SIZE; i++)
+    data[i] = (guchar)i;
+
+  test_full ();
+  test_incremental (FALSE);
+  test_incremental (TRUE);
+  
+  return 0;
+}