5e984ecbeaa48a3c0eaa77a0d8d8c03196427958
diff --git a/glib/Makefile.am b/glib/Makefile.am
index b1f8cc6..f85ce07 100644
--- a/glib/Makefile.am
+++ b/glib/Makefile.am
@@ -111,6 +111,7 @@ libglib_2_0_la_SOURCES = 	\
 	gmain.c         	\
 	gmappedfile.c		\
 	gmarkup.c		\
+	gmd5.c			\
 	gmem.c			\
 	gmessages.c		\
 	gmirroringtable.h	\
@@ -192,6 +193,7 @@ glibsubinclude_HEADERS =   \
 	gmain.h		\
 	gmappedfile.h	\
 	gmarkup.h	\
+	gmd5.h		\
 	gmem.h		\
 	gmessages.h	\
 	gnode.h		\
diff --git a/glib/glib.h b/glib/glib.h
index b45d3b7..3492885 100644
--- a/glib/glib.h
+++ b/glib/glib.h
@@ -51,6 +51,7 @@
 #include <glib/gmain.h>
 #include <glib/gmappedfile.h>
 #include <glib/gmarkup.h>
+#include <glib/gmd5.h>
 #include <glib/gmem.h>
 #include <glib/gmessages.h>
 #include <glib/gnode.h>
diff --git a/glib/glib.symbols b/glib/glib.symbols
index 45c2388..f94ecee 100644
--- a/glib/glib.symbols
+++ b/glib/glib.symbols
@@ -651,6 +651,20 @@ g_markup_vprintf_escaped
 #endif
 #endif
 
+#if IN_HEADER(__G_MD5_H__)
+#if IN_FILE(__G_MD5_C__)
+g_md5_context_close
+g_md5_context_get_digest
+g_md5_context_get_string G_GNUC_MALLOC
+g_md5_context_init
+g_md5_context_update
+g_md5_digest_equal
+g_md5_digest_hash
+g_md5_get_digest
+g_md5_get_string G_GNUC_MALLOC
+#endif
+#endif
+
 #if IN_HEADER(__G_MEM_H__)
 #if IN_FILE(__G_MEM_C__)
 g_free
diff --git a/glib/gmd5.c b/glib/gmd5.c
new file mode 100644
index 0000000..a950292
--- /dev/null
+++ b/glib/gmd5.c
@@ -0,0 +1,497 @@
+/* gmd5.h - MD5 hash functions
+ *
+ * Copyright (C) 2007  Emmanuele Bassi  <ebassi@gnome.org>
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gmd5.h"
+#include "glib.h"
+#include "glibintl.h"
+
+#include "galias.h"
+
+static const gchar hex_digits[] = "0123456789abcdef";
+
+/* This MD5 digest computation is based on the equivalent code
+ * written by Colin Plumb. It came with this notice:
+ *
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.  This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * ThumbMD5Context structure, pass it to thumb_md5_init, call
+ * thumb_md5_update as needed on buffers full of bytes, and then call
+ * thumb_md5_final, which will fill a supplied 32-byte array with the
+ * digest in ascii form. 
+ *
+ */
+
+void
+g_md5_context_init (GMd5Context *context)
+{
+  /* arbitrary constants */
+  context->buf[0] = 0x67452301;
+  context->buf[1] = 0xefcdab89;
+  context->buf[2] = 0x98badcfe;
+  context->buf[3] = 0x10325476;
+
+  context->bits[0] = 0;
+  context->bits[1] = 0;
+
+  context->is_closed = FALSE;
+}
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+#define byte_reverse(buffer,length)
+#else
+
+static void inline
+byte_reverse (guchar *buffer,
+              gulong  length)
+{
+  guint32 bit;
+
+  do
+    {
+      bit = (guint32) ((unsigned) buffer[3] << 8 | buffer[2]) << 16 |
+                      ((unsigned) buffer[1] << 8 | buffer[0]);
+      * (guint32 *) buffer = bit;
+      buffer += 4;
+    }
+  while (--length);
+}
+
+#endif /* G_BYTE_ORDER == G_LITTLE_ENDIAN */
+
+/* The four core functions - F1 is optimized somewhat */
+#define F1(x, y, z)     (z ^ (x & (y ^ z)))
+#define F2(x, y, z)     F1 (z, x, y)
+#define F3(x, y, z)     (x ^ y ^ z)
+#define F4(x, y, z)     (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define md5_step(f, w, x, y, z, data, s) \
+	( w += f (x, y, z) + data,  w = w << s | w >> (32 - s),  w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data.  g_md5_context_update()
+ * blocks the data and converts bytes into longwords for this routine.
+ */
+static void 
+md5_transform (guint32	     buf[4],
+	       guint32 const in[16])
+{
+  register guint32 a, b, c, d;
+
+  a = buf[0];
+  b = buf[1];
+  c = buf[2];
+  d = buf[3];
+
+  md5_step (F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+  md5_step (F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+  md5_step (F1, c, d, a, b, in[2] + 0x242070db, 17);
+  md5_step (F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+  md5_step (F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+  md5_step (F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+  md5_step (F1, c, d, a, b, in[6] + 0xa8304613, 17);
+  md5_step (F1, b, c, d, a, in[7] + 0xfd469501, 22);
+  md5_step (F1, a, b, c, d, in[8] + 0x698098d8, 7);
+  md5_step (F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+  md5_step (F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+  md5_step (F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+  md5_step (F1, a, b, c, d, in[12] + 0x6b901122, 7);
+  md5_step (F1, d, a, b, c, in[13] + 0xfd987193, 12);
+  md5_step (F1, c, d, a, b, in[14] + 0xa679438e, 17);
+  md5_step (F1, b, c, d, a, in[15] + 0x49b40821, 22);
+		
+  md5_step (F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+  md5_step (F2, d, a, b, c, in[6] + 0xc040b340, 9);
+  md5_step (F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+  md5_step (F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+  md5_step (F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+  md5_step (F2, d, a, b, c, in[10] + 0x02441453, 9);
+  md5_step (F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+  md5_step (F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+  md5_step (F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+  md5_step (F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+  md5_step (F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+  md5_step (F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+  md5_step (F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+  md5_step (F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+  md5_step (F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+  md5_step (F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+		
+  md5_step (F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+  md5_step (F3, d, a, b, c, in[8] + 0x8771f681, 11);
+  md5_step (F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+  md5_step (F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+  md5_step (F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+  md5_step (F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+  md5_step (F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+  md5_step (F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+  md5_step (F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+  md5_step (F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+  md5_step (F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+  md5_step (F3, b, c, d, a, in[6] + 0x04881d05, 23);
+  md5_step (F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+  md5_step (F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+  md5_step (F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+  md5_step (F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+		
+  md5_step (F4, a, b, c, d, in[0] + 0xf4292244, 6);
+  md5_step (F4, d, a, b, c, in[7] + 0x432aff97, 10);
+  md5_step (F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+  md5_step (F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+  md5_step (F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+  md5_step (F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+  md5_step (F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+  md5_step (F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+  md5_step (F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+  md5_step (F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+  md5_step (F4, c, d, a, b, in[6] + 0xa3014314, 15);
+  md5_step (F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+  md5_step (F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+  md5_step (F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+  md5_step (F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+  md5_step (F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+  buf[0] += a;
+  buf[1] += b;
+  buf[2] += c;
+  buf[3] += d;
+}
+
+/**
+ * g_md5_context_update:
+ * @context: an initialized #GMd5Context
+ * @data: data used to compute the MD5
+ * @length: length of the data, or -1
+ *
+ * Feeds @context a chunk of data used to compute the MD5 digest.
+ *
+ * Since: 2.14
+ */
+void
+g_md5_context_update (GMd5Context  *context,
+                      const guchar *data,
+                      gsize         length)
+{
+  guint32 bit;
+
+  g_return_if_fail (context->is_closed != TRUE);
+
+  if (length < 0)
+    length = strlen ((char *) data);
+
+  bit = context->bits[0];
+  context->bits[0] = bit + ((guint32) length << 3);
+
+  /* carry from low to high */
+  if (context->bits[0] < bit)
+    context->bits[1] += 1;
+
+  context->bits[1] += length >> 29;
+
+  /* bytes already in shsInfo->data */
+  bit = (bit >> 3) & 0x3f;
+
+  /* handle any leading odd-sized chunks */
+  if (bit)
+    {
+      guchar *p = (guchar *) context->in + bit;
+
+      bit = 64 - bit;
+      if (length < 64)
+        {
+          memcpy (p, data, bit);
+          return;
+        }
+
+      memcpy (p, data, bit);
+      
+      byte_reverse (context->in, 16);
+      md5_transform (context->buf, (guint32 *) context->in);
+
+      data += bit;
+      length -= bit;
+    }
+
+  /* process data in 64-byte chunks */
+  while (length >= 64)
+    {
+      memcpy (context->in, data, 64);
+      
+      byte_reverse (context->in, 16);
+      md5_transform (context->buf, (guint32 *) context->in);
+
+      data += 64;
+      length -= 64;
+    }
+
+  /* handle any remaining bytes of data */
+  memcpy (context->in, data, length);
+}
+
+/**
+ * g_md5_context_close:
+ * @context: a #GMd5Context
+ *
+ * Closes the MD5 digest computation. Use g_md5_context_get_digest()
+ * or g_md5_context_get_string() to retrieve the digest as a vector
+ * or as a string. A closed #GMd5Context must be re-initialized with
+ * g_md5_context_init().
+ *
+ * Since: 2.14
+ */
+void
+g_md5_context_close (GMd5Context *context)
+{
+  guint count;
+  guchar *p;
+
+  if (context->is_closed)
+    return;
+
+  /* Compute number of bytes mod 64 */
+  count = (context->bits[0] >> 3) & 0x3F;
+
+  /* Set the first char of padding to 0x80.
+   * This is safe since there is always at least one byte free
+   */
+  p = context->in + count;
+  *p++ = 0x80;
+
+  /* Bytes of padding needed to make 64 bytes */
+  count = 64 - 1 - count;
+
+  /* Pad out to 56 mod 64 */
+  if (count < 8)
+    {
+      /* Two lots of padding:  Pad the first block to 64 bytes */
+      memset (p, 0, count);
+      byte_reverse (context->in, 16);
+      md5_transform (context->buf, (guint32 *) context->in);
+
+      /* Now fill the next block with 56 bytes */
+      memset (context->in, 0, 56);
+    }
+  else
+    {
+      /* Pad block to 56 bytes */
+      memset (p, 0, count - 8);
+    }
+
+  byte_reverse (context->in, 14);
+
+  /* Append length in bits and transform */
+  ((guint32 *) context->in)[14] = context->bits[0];
+  ((guint32 *) context->in)[15] = context->bits[1];
+
+  md5_transform (context->buf, (guint32 *) context->in);
+  byte_reverse ((guchar *) context->buf, 4);
+  memcpy (context->digest, context->buf, 16);
+  
+  /* Reset buffers in case they contains sensitive data */
+  memset (context->buf, 0, sizeof (context->buf));
+  memset (context->in, 0, sizeof (context->in));
+
+  context->is_closed = TRUE;
+}
+
+/**
+ * g_md5_context_get_digest:
+ * @context: a #GMd5Context
+ * @digest: return location for the digest in array form
+ *
+ * Gets the MD5 digest and puts into an array in ASCII form, from
+ * a closed #GMd5Context. You can call this function multiple times
+ * until you call g_md5_context_init() on the context again.
+ *
+ * Since: 2.14
+ */
+void
+g_md5_context_get_digest (GMd5Context  *context,
+                          guchar        digest[16])
+{
+  gint i;
+
+  g_return_if_fail (context != NULL);
+  g_return_if_fail (context->is_closed);
+
+  for (i = 0; i < 16; i++)
+    digest[i] = context->digest[i];
+}
+
+/**
+ * g_md5_context_get_string:
+ * @context: a #GMd5Context
+ *
+ * Gets the MD5 digest in string form from a closed #GMd5Context. You
+ * can call this function multiple times until you call g_md5_context_init()
+ * on the context again.
+ *
+ * Return value: a string containing the MD5 digest. Use g_free() on
+ *   the returned string when done.
+ *
+ * Since: 2.14
+ */
+gchar *
+g_md5_context_get_string (GMd5Context *context)
+{
+  gchar *retval;
+  gint i;
+
+  g_return_val_if_fail (context != NULL, NULL);
+  g_return_val_if_fail (context->is_closed, NULL);
+
+  retval = g_new (gchar, 33);
+
+  for (i = 0; i < 16; i++)
+    {
+      guchar digest = context->digest[i];
+      
+      retval[2 * i] = hex_digits[digest >> 4];
+      retval[2 * i + 1] = hex_digits[digest & 0xf];
+    }
+
+  retval[32] = 0;
+
+  return retval;
+}
+
+/**
+ * g_md5_get_digest:
+ * @data: the buffer to compute the MD5 digest
+ * @data_len: the length of buffer, or -1
+ * @digest: the MD5 digest as a vector
+ *
+ * Computes the MD5 digest from sequence of binary data and places the
+ * digest in ASCII form into the @digest array.
+ *
+ * Since: 2.14
+ */
+void
+g_md5_get_digest (const guchar *data,
+                  gsize         data_len,
+                  guchar        digest[16])
+{
+  GMd5Context context;
+
+  g_md5_context_init (&context);
+  g_md5_context_update (&context, data, data_len);
+  g_md5_context_close (&context);
+
+  g_md5_context_get_digest (&context, digest);
+}
+
+/**
+ * g_md5_get_string:
+ * @data: the buffer to compute the MD5 digest
+ * @data_len: the length of buffer, or -1
+ *
+ * Computes the MD5 digest from a sequence of binary data and
+ * returns the digest as a string.
+ *
+ * Return value: a string containing the MD5 digest. You should
+ *   free it with g_free() when finished.
+ *
+ * Since: 2.14
+ */
+gchar *
+g_md5_get_string (const guchar *data,
+                  gsize         data_len)
+{
+  GMd5Context context;
+
+  g_md5_context_init (&context);
+  g_md5_context_update (&context, data, data_len);
+  g_md5_context_close (&context);
+
+  return g_md5_context_get_string (&context);
+}
+
+/**
+ * g_md5_digest_equal:
+ * @v1: the first MD5 digest to compare
+ * @v2: the second MD5 digest to compare
+ *
+ * Tests the equality of @v1 and @v2. This function is useful for
+ * #GHashTable and #GCache.
+ *
+ * Return value: %TRUE if both digests are equal.
+ *
+ * Since: 2.14
+ */
+gboolean
+g_md5_digest_equal (gconstpointer v1,
+                    gconstpointer v2)
+{
+  guint *d1, *d2;
+  guint i;
+
+  if (v1 == v2)
+    return TRUE;
+
+  if (!v1 || !v2)
+    return FALSE;
+
+  d1 = (guint *) v1;
+  d2 = (guint *) v2;
+
+  for (i = 0; i < (16 / sizeof (guint)); i++)
+    {
+      if (*d1 != *d2)
+        return FALSE;
+
+      d1 += i;
+      d2 += i;
+    }
+
+  return TRUE;
+}
+
+/**
+ * g_md5_digest_hash:
+ * @v: the MD5 digest to hash
+ *
+ * Gets the numberic hash of @v, for use in #GHashTable or #GCache.
+ *
+ * Return value: an unsigned integer hash of the MD5 digest
+ *
+ * Since: 2.14
+ */
+guint
+g_md5_digest_hash (gconstpointer v)
+{
+  return *((guint *) v);
+}
+
+#define __G_MD5_C__
+#include "galiasdef.c"
diff --git a/glib/gmd5.h b/glib/gmd5.h
new file mode 100644
index 0000000..d1786e4
--- /dev/null
+++ b/glib/gmd5.h
@@ -0,0 +1,66 @@
+/* gmd5.h - MD5 hash functions
+ *
+ * Copyright (C) 2007  Emmanuele Bassi  <ebassi@gnome.org>
+ *
+ * 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_MD5_H__
+#define __G_MD5_H__
+
+#include <glib/gtypes.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GMd5Context     GMd5Context;
+
+struct _GMd5Context
+{
+  /*< private >*/
+  guint32 buf[4];
+  guint32 bits[2];
+  
+  guchar in[64];
+
+  guchar digest[16];
+
+  guint is_closed : 1;
+};
+
+
+void     g_md5_context_init       (GMd5Context  *context);
+void     g_md5_context_update     (GMd5Context  *context,
+                                   const guchar *data,
+                                   gsize         length);
+void     g_md5_context_close      (GMd5Context  *context);
+void     g_md5_context_get_digest (GMd5Context  *context,
+                                   guchar        digest[16]);
+gchar *  g_md5_context_get_string (GMd5Context  *context) G_GNUC_MALLOC;
+
+void     g_md5_get_digest         (const guchar *data,
+                                   gsize         data_len,
+                                   guchar        digest[16]);
+gchar *  g_md5_get_string         (const guchar *data,
+                                   gsize         data_len) G_GNUC_MALLOC;
+
+/* Hash functions for use with GHashTable */
+gboolean g_md5_digest_equal       (gconstpointer v1,
+                                   gconstpointer v2);
+guint    g_md5_digest_hash        (gconstpointer v);
+
+G_END_DECLS
+
+#endif /* __G_MD5_H__ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f4ab9b3..944b434 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -89,6 +89,7 @@ test_programs =					\
 	mainloop-test				\
 	mapping-test				\
 	markup-escape-test			\
+	md5-test				\
 	module-test				\
 	node-test				\
 	option-test				\
@@ -158,6 +159,7 @@ mainloop_test_LDADD = $(thread_ldadd)
 markup_test_LDADD = $(progs_ldadd)
 mapping_test_LDADD = $(progs_ldadd)
 markup_escape_test_LDADD = $(progs_ldadd)
+md5_test_LDADD = $(progs_ldadd)
 module_test_LDADD = $(module_ldadd) $(module_test_exp)
 module_test_LDFLAGS = $(G_MODULE_LDFLAGS)
 node_test_LDADD = $(progs_ldadd)
diff --git a/tests/md5-test.c b/tests/md5-test.c
new file mode 100644
index 0000000..b574838
--- /dev/null
+++ b/tests/md5-test.c
@@ -0,0 +1,113 @@
+#include "config.h"
+
+#include <glib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+
+#define BLOCK_SIZE      256
+
+static gchar *
+md5_digest_to_string (guchar digest[16])
+{
+  static const gchar hex_digits[] = "0123456789abcdef";
+  gchar *retval;
+  gint i;
+
+  retval = g_new (gchar, 33);
+
+  for (i = 0; i < 16; i++)
+    {
+      retval[2 * i] = hex_digits[digest[i] >> 4];
+      retval[2 * i + 1] = hex_digits[digest[i] & 0xf];
+    }
+
+  retval[32] = 0;
+
+  return retval;
+}
+
+static void
+test_context (const gchar *filename)
+{
+  GMd5Context md5_context;
+  gchar *data;
+  gsize data_len;
+  guchar digest1[16], digest2[16];
+  guchar *p;
+  guint hash1, hash2;
+  gchar *digest_str1, *digest_str2;
+
+  if (!g_file_get_contents (filename, &data, &data_len, NULL))
+    {
+      g_print ("Could not load `%s' contents\n", filename);
+      exit (1);
+    }
+
+  /* get the digests from the full buffer, to compare */
+  g_md5_get_digest ((guchar *) data, data_len, digest1);
+  digest_str1 = g_md5_get_string ((guchar *) data, data_len);
+  g_print ("%s\t%s\n", digest_str1, filename);
+
+  g_md5_context_init (&md5_context);
+  p = (guchar *) data;
+  do
+    {
+      if (data_len > BLOCK_SIZE)
+        {
+          g_md5_context_update (&md5_context, p, BLOCK_SIZE);
+          data_len -= BLOCK_SIZE;
+          p += BLOCK_SIZE;
+        }
+      else
+        {
+          g_md5_context_update (&md5_context, p, data_len);
+          break;
+        }
+    }
+  while (*p != '\0');
+  
+  g_md5_context_close (&md5_context);
+  g_md5_context_get_digest (&md5_context, digest2);
+  digest_str2 = g_md5_context_get_string (&md5_context);
+
+  g_free (data);
+
+  if (!g_md5_digest_equal (digest1, digest2))
+    {
+      g_print ("Wrong MD5 digest `%s' (expecting: %s)\n",
+               md5_digest_to_string (digest2),
+               md5_digest_to_string (digest1));
+      exit (1);
+    }
+
+  if (strcmp (digest_str1, digest_str2) != 0)
+    {
+      g_print ("Wrong MD5 digest string `%s' (expecting: %s)\n",
+               digest_str1,
+               digest_str2);
+      exit (1);
+    }
+
+  g_free (digest_str1);
+  g_free (digest_str2);
+
+  hash1 = g_md5_digest_hash (digest1);
+  hash2 = g_md5_digest_hash (digest2);
+  if (hash1 != hash2)
+    {
+      g_print ("Wrong MD5 hash `%d' (expecting: %d)\n",
+               hash1, hash2);
+      exit (1);
+    }
+}
+
+int
+main (int argc, char *argv[])
+{
+  test_context (__FILE__);
+
+  return EXIT_SUCCESS;
+}
