From 5cf8f1d4a88bda74d2e205b9103f23b83a6bc427 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 4 Apr 2006 13:03:23 +0000 Subject: [PATCH] Add base64 encode/decode functions 2006-04-04 Alexander Larsson * 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 | 14 ++ ChangeLog.pre-2-12 | 14 ++ glib/Makefile.am | 2 + glib/gbase64.c | 356 ++++++++++++++++++++++++++++++++++++++++++++ glib/gbase64.h | 50 +++++++ glib/glib.h | 1 + glib/glib.symbols | 10 ++ tests/Makefile.am | 2 + tests/base64-test.c | 107 +++++++++++++ 9 files changed, 556 insertions(+) create mode 100644 glib/gbase64.c create mode 100644 glib/gbase64.h create mode 100644 tests/base64-test.c diff --git a/ChangeLog b/ChangeLog index 8c2442154..03845d015 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +2006-04-04 Alexander Larsson + + * 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 * glib/gdate.c: Move short_month_names and long_month_names diff --git a/ChangeLog.pre-2-12 b/ChangeLog.pre-2-12 index 8c2442154..03845d015 100644 --- a/ChangeLog.pre-2-12 +++ b/ChangeLog.pre-2-12 @@ -1,3 +1,17 @@ +2006-04-04 Alexander Larsson + + * 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 * glib/gdate.c: Move short_month_names and long_month_names diff --git a/glib/Makefile.am b/glib/Makefile.am index bc5afc2f1..a55f6e68d 100644 --- a/glib/Makefile.am +++ b/glib/Makefile.am @@ -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 index 000000000..f6e0cd0d9 --- /dev/null +++ b/glib/gbase64.c @@ -0,0 +1,356 @@ +/* gbase64.c - Base64 encoding/decoding + * + * Copyright (C) 2006 Alexander Larsson + * 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 + * Jeffrey Stedfast + */ + +#include "config.h" + +#include + +#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 index 000000000..1aa2bff63 --- /dev/null +++ b/glib/gbase64.h @@ -0,0 +1,50 @@ +/* gbase64.h - Base64 coding functions + * + * Copyright (C) 2005 Alexander Larsson + * + * 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 + +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__ */ diff --git a/glib/glib.h b/glib/glib.h index 7fa0ef1fa..e5044f982 100644 --- a/glib/glib.h +++ b/glib/glib.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include diff --git a/glib/glib.symbols b/glib/glib.symbols index 9f5ac88e9..3a03e077b 100644 --- a/glib/glib.symbols +++ b/glib/glib.symbols @@ -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 diff --git a/tests/Makefile.am b/tests/Makefile.am index 9c14f85cb..e5589b520 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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 index 000000000..6c458d2ea --- /dev/null +++ b/tests/base64-test.c @@ -0,0 +1,107 @@ +#include +#include +#include +#include + +#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; +}