sync with 0.7.8cvs23
[claws.git] / src / unmime.c
index 05d05fca82f81a67fa97b40c0f01a26e48843b5e..442c5dc09dda6d466191e42178787a6da27caff2 100644 (file)
 /*
- * MIME mail decoding.
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2002 Hiroyuki Yamamoto
  *
- * This module contains decoding routines for converting
- * quoted-printable data into pure 8-bit data, in MIME
- * formatted messages.
+ * This program 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.
  *
- * By Henrik Storner <storner@image.dk>
+ * This program 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.
  *
- * Configuration file support for fetchmail 4.3.8 by 
- * Frank Damgaard <frda@post3.tele.dk>
- *
- * Modified by Hiroyuki Yamamoto <hiro-y@kcn.ne.jp>
- * 
+ * You should have received a copy of the GNU 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.
  */
 
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
 #endif
 
+#include <glib.h>
 #include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
 #include <ctype.h>
-#if HAVE_ALLOCA_H
-#  include <alloca.h>
-#endif
-#if HAVE_LIBJCONV
-#  include <jconv.h>
-#  include "prefs_common.h"
-#endif
 
-#include "unmime.h"
+#include "codeconv.h"
 #include "base64.h"
-#include "rfc822.h"
-#include "utils.h"
 
-#ifndef TRUE
-#define FALSE  0
-#define TRUE   1
-#endif /* TRUE */
+#define ENCODED_WORD_BEGIN     "=?"
+#define ENCODED_WORD_END       "?="
 
-#define xalloca(ptr, t, n)     if (!(ptr = (t) alloca(n))) \
-               {fprintf(stderr, "alloca failed"); exit(1);}
+static gboolean get_hex_value(gchar *out, gchar c1, gchar c2);
 
-static unsigned char unhex(unsigned char c)
-{
-  if ((c >= '0') && (c <= '9'))
-    return (c - '0');
-  else if ((c >= 'A') && (c <= 'F'))
-    return (c - 'A' + 10);
-  else if ((c >= 'a') && (c <= 'f'))
-    return (c - 'a' + 10);
-  else
-    return c;
-}
+/* Decodes headers based on RFC2045 and RFC2047. */
 
-static int qp_char(unsigned char c1, unsigned char c2, unsigned char *c_out)
+void unmime_header(gchar *out, const gchar *str)
 {
-  c1 = unhex(c1);
-  c2 = unhex(c2);
-
-  if ((c1 > 15) || (c2 > 15)) 
-    return 1;
-  else {
-    *c_out = 16*c1+c2;
-    return 0;
-  }
-}
-
-
-/*
- * Routines to decode MIME QP-encoded headers, as per RFC 2047.
- */
-
-/* States of the decoding state machine */
-#define S_COPY_PLAIN        0  /* Just copy, but watch for the QP flag */
-#define S_SKIP_MIMEINIT     1  /* Get the encoding, and skip header */
-#define S_COPY_MIME         2  /* Decode a sequence of coded characters */
-
-static const char MIMEHDR_INIT[]  = "=?";      /* Start of coded sequence */
-static const char MIMEHDR_END[]   = "?=";      /* End of coded sequence */
-
-#if !HAVE_LIBJCONV
-void UnMimeHeader(unsigned char *hdr)
-{
-  /* Decode a buffer containing data encoded according to RFC
-   * 2047. This only handles content-transfer-encoding; conversion
-   * between character sets is not implemented.  In other words: We
-   * assume the charsets used can be displayed by your mail program
-   * without problems. 
-   */
-
-  /* Note: Decoding is done "in-situ", i.e. without using an
-   * additional buffer for temp. storage. This is possible, since the
-   * decoded string will always be shorter than the encoded string,
-   * due to the en- coding scheme.
-   */
-
-  int  state = S_COPY_PLAIN;
-  unsigned char *p_in, *p_out, *p;
-  unsigned char enc = '\0';            /* initialization pacifies -Wall */
-  int  i;
-
-  /* Speed up in case this is not a MIME-encoded header */
-  p = strstr(hdr, MIMEHDR_INIT);
-  if (p == NULL)
-    return;   /* No MIME header */
-
-  /* Loop through the buffer.
-   *  p_in : Next char to be processed.
-   *  p_out: Where to put the next processed char
-   *  enc  : Encoding used (usually, 'q' = quoted-printable)
-   */
-  for (p_out = p_in = hdr; (*p_in); ) {
-    switch (state) {
-    case S_COPY_PLAIN:
-      p = strstr(p_in, MIMEHDR_INIT);
-      if (p == NULL) {
-       /* 
-        * No more coded data in buffer, 
-         * just move remainder into place. 
-        */
-        i = strlen(p_in);   /* How much left */
-       memmove(p_out, p_in, i);
-       p_in += i; p_out += i;
-      }
-      else {
-       /* MIME header init found at location p */
-       if (p > p_in) {
-          /* There are some uncoded chars at the beginning. */
-          i = (p - p_in);
-         memmove(p_out, p_in, i);
-         p_out += i;
-       }
-       p_in = (p + 2);
-       state = S_SKIP_MIMEINIT;
-      }
-      break;
-
-    case S_SKIP_MIMEINIT:
-      /* Mime type definition: "charset?encoding?" */
-      p = strchr(p_in, '?');
-      if (p != NULL) {
-       /* p_in .. (p-1) holds the charset */
-
-       /* *(p+1) is the transfer encoding, *(p+2) must be a '?' */
-       if (*(p+2) == '?') {
-         enc = tolower(*(p+1));
-         p_in = p+3;
-         state = S_COPY_MIME;
-       }
-       else
-         state = S_COPY_PLAIN;
-      }
-      else
-       state = S_COPY_PLAIN;   /* Invalid data */
-      break;
-
-    case S_COPY_MIME:
-      p = strstr(p_in, MIMEHDR_END);  /* Find end of coded data */
-      if (p == NULL) p = p_in + strlen(p_in);
-      for (; (p_in < p); ) {
-       /* Decode all encoded data */
-       if (enc == 'q') {
-         if (*p_in == '=') {
-           /* Decode one char qp-coded at (p_in+1) and (p_in+2) */
-           if (qp_char(*(p_in+1), *(p_in+2), p_out) == 0)
-             p_in += 3;
-           else {
-             /* Invalid QP data - pass through unchanged. */
-             *p_out = *p_in;
-             p_in++;
-           }
-         }
-         else if (*p_in == '_') {
-           /* 
-             * RFC 2047: '_' inside encoded word represents 0x20.
-             * NOT a space - always the value 0x20.
-             */
-           *p_out = 0x20;
-           p_in++;
-         }
-         else {
-           /* Copy unchanged */
-           *p_out = *p_in;
-           p_in++;
-         }
-         p_out++;
-       }
-       else if (enc == 'b') {
-         /* Decode base64 encoded data */
-         char delimsave;
-         int decoded_count;
-
-         delimsave = *p; *p = '\r';
-         decoded_count = from64tobits(p_out, p_in);
-         *p = delimsave;
-         if (decoded_count > 0) 
-           p_out += decoded_count;            
-         p_in = p;
-       }
-       else {
-         /* Copy unchanged */
-         *p_out = *p_in;
-         p_in++;
-         p_out++;
-       }
-      }
-      if (*p_in)
-       p_in += 2;   /* Skip the MIMEHDR_END delimiter */
-
-      /* 
-       * We've completed decoding one encoded sequence. But another
-       * may follow immediately, in which case whitespace before the
-       * new MIMEHDR_INIT delimiter must be discarded.
-       * See if that is the case 
-       */
-      p = strstr(p_in, MIMEHDR_INIT);
-      state = S_COPY_PLAIN;
-      if (p != NULL) {
-       /*
-        * There is more MIME data later on. Is there
-         * whitespace  only before the delimiter? 
-        */
-        unsigned char *q;
-        int  wsp_only = 1;
-
-        for (q=p_in; (wsp_only && (q < p)); q++)
-          wsp_only = isspace(*q);
-
-        if (wsp_only) {
-         /* 
-          * Whitespace-only before the MIME delimiter. OK,
-           * just advance p_in to past the new MIMEHDR_INIT,
-           * and prepare to process the new MIME charset/encoding
-          * header.
-          */
-         p_in = p + strlen(MIMEHDR_INIT);
-         state = S_SKIP_MIMEINIT;
-        }
-      }
-      break;
-    }
-  }
-
-  *p_out = '\0';
-}
-#else /* HAVE_LIBJCONV */
-void UnMimeHeaderConv(unsigned char *hdr, unsigned char *conv_r, int conv_len)
-{
-  int  state = S_COPY_PLAIN;
-  unsigned char *p_in, *p_out, *p;
-  unsigned char enc = '\0';            /* initialization pacifies -Wall */
-  int  i;
-  unsigned char *p_mimestart = NULL;
-  iconv_t cd = (iconv_t)-1;
-
-  if (conv_r && conv_len < 0) {
-    conv_len = 0;
-    conv_r = NULL;
-  }
-  else
-    conv_len--;                /* reserve for terminating NULL character */
-
-  /* Speed up in case this is not a MIME-encoded header */
-  p = strstr(hdr, MIMEHDR_INIT);
-  if (p == NULL) {
-    /* No MIME header */
-    const char *const *codesets;
-    int n_codesets, actual_codeset, r;
-    char *newstr;
-    size_t newlen = 0;
-
-    if (prefs_common.force_charset) {
-      codesets = (const char **)&prefs_common.force_charset;
-      n_codesets = 1;
-    }
-    else
-      codesets = jconv_info_get_pref_codesets(&n_codesets);
-
-    r = jconv_alloc_conv(hdr, strlen(hdr), &newstr, &newlen,
-                        codesets, n_codesets, &actual_codeset,
-                        jconv_info_get_current_codeset());
-    if (r == 0) {
-      if (newlen > conv_len) newlen = conv_len;
-      strncpy(conv_r, newstr, newlen);
-      conv_r[newlen] = '\0';
-      free(newstr);
-    }
-    else {
-      strncpy(conv_r, hdr, conv_len);
-      conv_r[conv_len] = '\0';
-    }
-
-    return;
-  }
-
-  /* Loop through the buffer.
-   *  p_in : Next char to be processed.
-   *  p_out: Where to put the next processed char
-   *  enc  : Encoding used (usually, 'q' = quoted-printable)
-   */
-  for (p_out = p_in = hdr; (*p_in); ) {
-    switch (state) {
-    case S_COPY_PLAIN:
-      p = strstr(p_in, MIMEHDR_INIT);
-      if (p == NULL) {
-       /* 
-        * No more coded data in buffer, 
-         * just move remainder into place. 
-        */
-        i = strlen(p_in);   /* How much left */
-        if (conv_r) {
-         int len;
-
-         len = conv_len > i ? i : conv_len;
-         memcpy(conv_r, p_in, len);
-         conv_r += len;
-         conv_len -= len;
+       const gchar *p = str;
+       gchar *outp = out;
+       const gchar *sp;
+       const gchar *eword_begin_p, *encoding_begin_p, *text_begin_p,
+                   *eword_end_p;
+       gchar *charset;
+       gchar encoding;
+       gchar *conv_str;
+       gint len;
+
+       while (*p != '\0') {
+               gchar *decoded_text = NULL;
+
+               eword_begin_p = strstr(p, ENCODED_WORD_BEGIN);
+               if (!eword_begin_p) {
+                       strcpy(outp, p);
+                       return;
+               }
+               encoding_begin_p = strchr(eword_begin_p + 2, '?');
+               if (!encoding_begin_p) {
+                       strcpy(outp, p);
+                       return;
+               }
+               text_begin_p = strchr(encoding_begin_p + 1, '?');
+               if (!text_begin_p) {
+                       strcpy(outp, p);
+                       return;
+               }
+               eword_end_p = strstr(text_begin_p + 1, ENCODED_WORD_END);
+               if (!eword_end_p) {
+                       strcpy(outp, p);
+                       return;
+               }
+
+               if (p == str) {
+                       memcpy(outp, p, eword_begin_p - p);
+                       outp += eword_begin_p - p;
+                       p = eword_begin_p;
+               } else {
+                       /* ignore spaces between encoded words */
+                       for (sp = p; sp < eword_begin_p; sp++) {
+                               if (!isspace(*sp)) {
+                                       memcpy(outp, p, eword_begin_p - p);
+                                       outp += eword_begin_p - p;
+                                       p = eword_begin_p;
+                                       break;
+                               }
+                       }
+               }
+
+               charset = g_strndup(eword_begin_p + 2,
+                                   encoding_begin_p - (eword_begin_p + 2));
+               encoding = toupper(*(encoding_begin_p + 1));
+
+               if (encoding == 'B') {
+                       gchar *encoded_text;
+
+                       encoded_text =
+                               g_strndup(text_begin_p + 1,
+                                         eword_end_p - (text_begin_p + 1));
+                       decoded_text = g_malloc
+                               (eword_end_p - (text_begin_p + 1) + 1);
+
+                       len = from64tobits(decoded_text, encoded_text); //
+                       decoded_text[len] = '\0';
+
+                       g_free(encoded_text);
+               } else if (encoding == 'Q') {
+                       const gchar *ep = text_begin_p + 1;
+                       gchar *dp;
+
+                       dp = decoded_text = g_malloc(eword_end_p - ep + 1);
+
+                       while (ep < eword_end_p) {
+                               if (*ep == '=' && ep + 3 <= eword_end_p) {
+                                       if (get_hex_value(dp, ep[1], ep[2])
+                                           == TRUE) {
+                                               ep += 3;
+                                       } else {
+                                               *dp = *ep++;
+                                       }
+                               } else if (*ep == '_') {
+                                       *dp = ' ';
+                                       ep++;
+                               } else {
+                                       *dp = *ep++;
+                               }
+                               dp++;
+                       }
+
+                       *dp = '\0';
+               } else {
+                       memcpy(outp, p, eword_end_p + 2 - p);
+                       outp += eword_end_p + 2 - p;
+                       p = eword_end_p + 2;
+                       g_free(charset);
+                       continue;
+               }
+
+               /* convert to locale encoding */
+               conv_str = conv_codeset_strdup(decoded_text, charset, NULL);
+               if (conv_str) {
+                       len = strlen(conv_str);
+                       memcpy(outp, conv_str, len);
+                       g_free(conv_str);
+               } else {
+                       len = strlen(decoded_text);
+                       memcpy(outp, decoded_text, len);
+               }
+               outp += len;
+
+               g_free(decoded_text);
+               g_free(charset);
+
+               p = eword_end_p + 2;
        }
-       memmove(p_out, p_in, i);
-       p_in += i; p_out += i;
-      }
-      else {
-       /* MIME header init found at location p */
-       if (p > p_in) {
-          /* There are some uncoded chars at the beginning. */
-          i = (p - p_in);
-         if (conv_r) {
-           int len;
 
-           len = conv_len > i ? i : conv_len;
-           memcpy(conv_r, p_in, len);
-           conv_r += len;
-           conv_len -= len;
-         }
-         memmove(p_out, p_in, i);
-         p_out += i;
-       }
-       p_in = (p + 2);
-       state = S_SKIP_MIMEINIT;
-      }
-      break;
-
-    case S_SKIP_MIMEINIT:
-      /* Mime type definition: "charset?encoding?" */
-      p = strchr(p_in, '?');
-      if (p != NULL) {
-       /* p_in .. (p-1) holds the charset */
-       char *charset;
-
-       charset = malloc(p - p_in + 1);
-       memcpy(charset, p_in, p - p_in);
-       charset[p - p_in] = '\0';
-       if (cd != (iconv_t)-1) iconv_close(cd);
-       cd = iconv_open(jconv_info_get_current_codeset(), charset);
-       free(charset);
-
-       /* *(p+1) is the transfer encoding, *(p+2) must be a '?' */
-       if (*(p+2) == '?') {
-         enc = tolower(*(p+1));
-         p_in = p+3;
-         state = S_COPY_MIME;
-       }
-       else
-         state = S_COPY_PLAIN;
-      }
-      else
-       state = S_COPY_PLAIN;   /* Invalid data */
-      break;
-
-    case S_COPY_MIME:
-      p_mimestart = p_out;
-      p = strstr(p_in, MIMEHDR_END);  /* Find end of coded data */
-      if (p == NULL) p = p_in + strlen(p_in);
-      for (; (p_in < p); ) {
-       /* Decode all encoded data */
-       if (enc == 'q') {
-         if (*p_in == '=') {
-           /* Decode one char qp-coded at (p_in+1) and (p_in+2) */
-           if (qp_char(*(p_in+1), *(p_in+2), p_out) == 0)
-             p_in += 3;
-           else {
-             /* Invalid QP data - pass through unchanged. */
-             *p_out = *p_in;
-             p_in++;
-           }
-         }
-         else if (*p_in == '_') {
-           /* 
-             * RFC 2047: '_' inside encoded word represents 0x20.
-             * NOT a space - always the value 0x20.
-             */
-           *p_out = 0x20;
-           p_in++;
-         }
-         else {
-           /* Copy unchanged */
-           *p_out = *p_in;
-           p_in++;
-         }
-         p_out++;
-       }
-       else if (enc == 'b') {
-         /* Decode base64 encoded data */
-         char delimsave;
-         int decoded_count;
-
-         delimsave = *p; *p = '\r';
-         decoded_count = from64tobits(p_out, p_in);
-         *p = delimsave;
-         if (decoded_count > 0) 
-           p_out += decoded_count;            
-         p_in = p;
-       }
-       else {
-         /* Copy unchanged */
-         *p_out = *p_in;
-         p_in++;
-         p_out++;
-       }
-      }
-      if (conv_r && cd != (iconv_t)-1) {
-       size_t inleft;
-
-       inleft = p_out - p_mimestart;
-       iconv(cd, (char **)&p_mimestart, &inleft, (char **)&conv_r, &conv_len);
-      }
-      if (*p_in)
-       p_in += 2;   /* Skip the MIMEHDR_END delimiter */
-
-      /* 
-       * We've completed decoding one encoded sequence. But another
-       * may follow immediately, in which case whitespace before the
-       * new MIMEHDR_INIT delimiter must be discarded.
-       * See if that is the case 
-       */
-      p = strstr(p_in, MIMEHDR_INIT);
-      state = S_COPY_PLAIN;
-      if (p != NULL) {
-       /*
-        * There is more MIME data later on. Is there
-         * whitespace  only before the delimiter? 
-        */
-        unsigned char *q;
-        int  wsp_only = 1;
-
-        for (q=p_in; (wsp_only && (q < p)); q++)
-          wsp_only = isspace(*q);
-
-        if (wsp_only) {
-         /* 
-          * Whitespace-only before the MIME delimiter. OK,
-           * just advance p_in to past the new MIMEHDR_INIT,
-           * and prepare to process the new MIME charset/encoding
-          * header.
-          */
-         p_in = p + strlen(MIMEHDR_INIT);
-         state = S_SKIP_MIMEINIT;
-        }
-      }
-      break;
-    }
-  }
-
-  *p_out = '\0';
-  if (conv_r)
-    *conv_r = '\0';
-  if (cd != (iconv_t)-1) iconv_close(cd);
+       *outp = '\0';
 }
 
-#endif /* !HAVE_LIBJCONV */
-
-
-/*
- * Routines for decoding body-parts of a message.
- *
- * Since the "fetch" part of fetchmail gets a message body
- * one line at a time, we need to maintain some state variables
- * across multiple invokations of the UnMimeBodyline() routine.
- * The driver routine should call MimeBodyType() when all
- * headers have been received, and then UnMimeBodyline() for
- * every line in the message body.
- *
- */
-#define S_BODY_DATA 0
-#define S_BODY_HDR  1
-
-/* 
- * Flag indicating if we are currently processing 
- * the headers or the body of a (multipart) message.
- */
-static int  BodyState = S_BODY_DATA;
-
-/* 
- * Flag indicating if we are in the process of decoding
- * a quoted-printable body part.
- */
-static int  CurrEncodingIsQP = 0;
-static int  CurrTypeNeedsDecode = 0;
-
-/* 
- * Delimiter for multipart messages. RFC 2046 states that this must
- * NEVER be longer than 70 characters. Add 3 for the two hyphens
- * at the beginning, and a terminating null.
- */
-#define MAX_DELIM_LEN 70
-static unsigned char MultipartDelimiter[MAX_DELIM_LEN+3];
-
-
-/* This string replaces the "Content-Transfer-Encoding: quoted-printable"
- * string in all headers, including those in body-parts. The replacement
- * must be no longer than the original string.
- */
-static const char ENC8BIT[] = "Content-Transfer-Encoding: 8bit";
-static void SetEncoding8bit(unsigned char *XferEncOfs)
-{
-  unsigned char *p;
-
-  if (XferEncOfs != NULL) {
-     memcpy(XferEncOfs, ENC8BIT, strlen(ENC8BIT));
-
-     /* If anything left, in this header, replace with whitespace */
-     for (p=XferEncOfs+strlen(ENC8BIT); (*p >= ' '); p++) *p=' ';
-  }
-}
-
-static char *GetBoundary(char *CntType)
+gint unmime_quoted_printable_line(gchar *str)
 {
-  char *p1, *p2;
-  int flag;
-
-  /* Find the "boundary" delimiter. It must be preceded with a ';'
-   * and optionally some whitespace.
-   */
-  p1 = CntType;
-  do {
-    p2 = strchr(p1, ';'); 
-    if (p2)
-      for (p2++; isspace(*p2); p2++);
-
-    p1 = p2;
-  } while ((p1) && (strncasecmp(p1, "boundary", 8) != 0));
-
-  if (p1 == NULL)
-    /* No boundary delimiter */
-    return NULL;
-
-  /* Skip "boundary", whitespace and '='; check that we do have a '=' */
-  for (p1+=8, flag=0; (isspace(*p1) || (*p1 == '=')); p1++)
-    flag |= (*p1 == '=');
-  if (!flag)
-    return NULL;
-
-  /* Find end of boundary delimiter string */
-  if (*p1 == '\"') {
-    /* The delimiter is inside quotes */
-    p1++;
-    p2 = strchr(p1, '\"');
-    if (p2 == NULL)
-      return NULL;  /* No closing '"' !?! */
-  }
-  else {
-    /* There might be more text after the "boundary" string. */
-    p2 = strchr(p1, ';');  /* Safe - delimiter with ';' must be in quotes */
-  }
-
-  /* Zero-terminate the boundary string */
-  if (p2 != NULL)
-    *p2 = '\0';
-
-  return (p1 && strlen(p1)) ? p1 : NULL;
-}
-
-
-int CheckContentType(char *CntType)
-{
-  /*
-   * Static array of Content-Type's for which we will do
-   * quoted-printable decoding, if requested. 
-   * It is probably wise to do this only on known text-only types;
-   * be really careful if you change this.
-   */
-
-  static char *DecodedTypes[] = {
-    "text/",        /* Will match ALL content-type's starting with 'text/' */
-    "message/rfc822", 
-    NULL
-  };
-
-  char *p = CntType;
-  int i;
-
-  /* If no Content-Type header, it isn't MIME - don't touch it */
-  if (CntType == NULL) return 0;
-
-  /* Skip whitespace, if any */
-  for (; isspace(*p); p++) ;
-
-  for (i=0; 
-       (DecodedTypes[i] && 
-       (strncasecmp(p, DecodedTypes[i], strlen(DecodedTypes[i])))); 
-       i++) ;
-
-  return (DecodedTypes[i] != NULL);
-}
-
-
-/*
- * This routine does three things:
- * 1) It determines - based on the message headers - whether the
- *    message body is a MIME message that may hold 8 bit data.
- *    - A message that has a "quoted-printable" or "8bit" transfer 
- *      encoding is assumed to contain 8-bit data (when decoded).
- *    - A multipart message is assumed to contain 8-bit data
- *      when decoded (there might be quoted-printable body-parts).
- *    - All other messages are assumed NOT to include 8-bit data.
- * 2) It determines the delimiter-string used in multi-part message
- *    bodies.
- * 3) It sets the initial values of the CurrEncodingIsQP, 
- *    CurrTypeNeedsDecode, and BodyState variables, from the header 
- *    contents.
- *
- * The return value is a bitmask.
- */
-int MimeBodyType(unsigned char *hdrs, int WantDecode)
-{
-  unsigned char *NxtHdr = hdrs;
-  unsigned char *XferEnc, *XferEncOfs, *CntType, *MimeVer, *p;
-  int  HdrsFound = 0;     /* We only look for three headers */
-  int  BodyType;          /* Return value */ 
-
-  /* Setup for a standard (no MIME, no QP, 7-bit US-ASCII) message */
-  MultipartDelimiter[0] = '\0';
-  CurrEncodingIsQP = CurrTypeNeedsDecode = 0;
-  BodyState = S_BODY_DATA;
-  BodyType = 0;
-
-  /* Just in case ... */
-  if (hdrs == NULL)
-    return BodyType;
-
-  XferEnc = XferEncOfs = CntType = MimeVer = NULL;
-
-  do {
-    if (strncasecmp("Content-Transfer-Encoding:", NxtHdr, 26) == 0) {
-      XferEncOfs = NxtHdr;
-      p = nxtaddr(NxtHdr);
-      if (p != NULL) {
-       xalloca(XferEnc, char *, strlen(p) + 1);
-       strcpy(XferEnc, p);
-       HdrsFound++;
-      }
-    }
-    else if (strncasecmp("Content-Type:", NxtHdr, 13) == 0) {
-      /*
-       * This one is difficult. We cannot use the standard
-       * nxtaddr() routine, since the boundary-delimiter is
-       * (probably) enclosed in quotes - and thus appears
-       * as an rfc822 comment, and nxtaddr() "eats" up any
-       * spaces in the delimiter. So, we have to do this
-       * by hand.
-       */
-
-      /* Skip the "Content-Type:" part and whitespace after it */
-      for (NxtHdr += 13; ((*NxtHdr == ' ') || (*NxtHdr == '\t')); NxtHdr++);
-
-      /* 
-       * Get the full value of the Content-Type header;
-       * it might span multiple lines. So search for
-       * a newline char, but ignore those that have a
-       * have a TAB or space just after the NL (continued
-       * lines).
-       */
-      p = NxtHdr-1;
-      do {
-        p=strchr((p+1),'\n'); 
-      } while ( (p != NULL) && ((*(p+1) == '\t') || (*(p+1) == ' ')) );
-      if (p == NULL) p = NxtHdr + strlen(NxtHdr);
-
-      xalloca(CntType, char *, p-NxtHdr+2);
-      strncpy(CntType, NxtHdr, (p-NxtHdr));
-      *(CntType+(p-NxtHdr)) = '\0';
-      HdrsFound++;
-    }
-    else if (strncasecmp("MIME-Version:", NxtHdr, 13) == 0) {
-      p = nxtaddr(NxtHdr);
-      if (p != NULL) {
-       xalloca(MimeVer, char *, strlen(p) + 1);
-       strcpy(MimeVer, p);
-       HdrsFound++;
-      }
-    }
-
-    NxtHdr = (strchr(NxtHdr, '\n'));
-    if (NxtHdr != NULL) NxtHdr++;
-  } while ((NxtHdr != NULL) && (*NxtHdr) && (HdrsFound != 3));
-
-
-  /* Done looking through the headers, now check what they say */
-  if ((MimeVer != NULL) && (strcmp(MimeVer, "1.0") == 0)) {
-
-    CurrTypeNeedsDecode = CheckContentType(CntType);
-
-    /* Check Content-Type to see if this is a multipart message */
-    if ( (CntType != NULL) &&
-         ((strncasecmp(CntType, "multipart/mixed", 16) == 0) ||
-         (strncasecmp(CntType, "message/", 8) == 0)) ) {
-
-      char *p1 = GetBoundary(CntType);
-
-      if (p1 != NULL) {
-       /* The actual delimiter is "--" followed by 
-          the boundary string */
-       strcpy(MultipartDelimiter, "--");
-       strncat(MultipartDelimiter, p1, MAX_DELIM_LEN);
-       BodyType = (MSG_IS_8BIT | MSG_NEEDS_DECODE);
-      }
-    }
-
-    /* 
-     * Check Content-Transfer-Encoding, but
-     * ONLY for non-multipart messages (BodyType == 0).
-     */
-    if ((XferEnc != NULL) && (BodyType == 0)) {
-      if (strcasecmp(XferEnc, "quoted-printable") == 0) {
-       CurrEncodingIsQP = 1;
-       BodyType = (MSG_IS_8BIT | MSG_NEEDS_DECODE);
-       if (WantDecode && CurrTypeNeedsDecode) {
-           SetEncoding8bit(XferEncOfs);
-        }
-      }
-      else if (strcasecmp(XferEnc, "7bit") == 0) {
-       CurrEncodingIsQP = 0;
-       BodyType = (MSG_IS_7BIT);
-      }
-      else if (strcasecmp(XferEnc, "8bit") == 0) {
-       CurrEncodingIsQP = 0;
-       BodyType = (MSG_IS_8BIT);
-      }
-    }
-
-  }
-
-  return BodyType;
-}
-
-
-/*
- * Decode one line of data containing QP data.
- * Return flag set if this line ends with a soft line-break.
- * 'bufp' is modified to point to the end of the output buffer.
- */
-int DoOneQPLine(unsigned char **bufp, flag delimited, flag issoftline)
-{
-  unsigned char *buf = *bufp;
-  unsigned char *p_in, *p_out, *p;
-  int n;
-  int ret = 0;
-
-  /*
-   * Special case: line consists of a single =2E and messages are 
-   * dot-terminated.  Line has to be dot-stuffed after decoding.
-   */
-  if (delimited && !issoftline && buf[0]=='=' && !strncmp(*bufp, "=2E\n", 4))
-  {
-      strcpy(buf, "..\n");
-      *bufp += 4;
-      return(FALSE);
-  }
-
-  p_in = buf;
-  if (delimited && issoftline && (strncmp(buf, "..", 2) == 0))
-    p_in++;
-
-  for (p_out = buf; (*p_in); ) {
-    p = strchr(p_in, '=');
-    if (p == NULL) {
-      /* No more QP data, just move remainder into place */
-      n = strlen(p_in);
-      memmove(p_out, p_in, n);
-      p_in += n; p_out += n;
-    }
-    else {
-      if (p > p_in) {
-       /* There are some uncoded chars at the beginning. */
-       n = (p - p_in);
-       memmove(p_out, p_in, n);
-       p_out += n;
-      }
-              
-      switch (*(p+1)) {
-      case '\0': case '\r': case '\n':
-       /* Soft line break, skip '=' */
-       p_in = p+1; 
-       if (*p_in == '\r') p_in++;
-       if (*p_in == '\n') p_in++;
-        ret = 1;
-       break;
-
-      default:
-       /* There is a QP encoded byte */
-       if (qp_char(*(p+1), *(p+2), p_out) == 0) {
-         p_in = p+3;
-       }
-       else {
-         /* Invalid QP data - pass through unchanged. */
-         *p_out = '=';
-         p_in = p+1;
+       gchar *inp = str, *outp = str;
+
+       while (*inp != '\0') {
+               if (*inp == '=') {
+                       if (inp[1] && inp[2] &&
+                           get_hex_value(outp, inp[1], inp[2]) == TRUE) {
+                               inp += 3;
+                       } else if (inp[1] == '\0' || isspace(inp[1])) {
+                               /* soft line break */
+                               break;
+                       } else {
+                               /* broken QP string */
+                               *outp = *inp++;
+                       }
+               } else {
+                       *outp = *inp++;
+               }
+               outp++;
        }
-       p_out++;
-       break;
-      }
-    }
-  }
-
-  *p_out = '\0';
-  *bufp = p_out;
-  return ret;
-}
-
-
-/* This is called once per line in the message body.  We need to scan
- * all lines in the message body for the multipart delimiter string,
- * and handle any body-part headers in such messages (these can toggle
- * qp-decoding on and off).
- *
- * Note: Messages that are NOT multipart-messages go through this
- * routine quickly, since BodyState will always be S_BODY_DATA,
- * and MultipartDelimiter is NULL.
- *
- * Return flag set if this line ends with a soft line-break.
- * 'bufp' is modified to point to the end of the output buffer.
- */
-
-#if 0
-int UnMimeBodyline(unsigned char **bufp, flag delimited, flag softline)
-{
-  unsigned char *buf = *bufp;
-  int ret = 0;
-
-  switch (BodyState) {
-  case S_BODY_HDR:
-    UnMimeHeader(buf);   /* Headers in body-parts can be encoded, too! */
-    if ((*buf == '\0') || (*buf == '\n') || (strcmp(buf, "\r\n") == 0)) {
-      BodyState = S_BODY_DATA;
-    } 
-    else if (strncasecmp("Content-Transfer-Encoding:", buf, 26) == 0) {
-      char *XferEnc;
 
-      XferEnc = nxtaddr(buf);
-      if ((XferEnc != NULL) && (strcasecmp(XferEnc, "quoted-printable") == 0)) {
-       CurrEncodingIsQP = 1;
+       *outp = '\0';
 
-        /*
-        * Hmm ... we cannot be really sure that CurrTypeNeedsDecode
-         * has been set - we may not have seen the Content-Type header
-         * yet. But *usually* the Content-Type header comes first, so
-         * this will work. And there is really no way of doing it 
-         * "right" as long as we stick with the line-by-line processing.
-        */
-       if (CurrTypeNeedsDecode)
-           SetEncoding8bit(buf);
-      }
-    }
-    else if (strncasecmp("Content-Type:", buf, 13) == 0) {
-      CurrTypeNeedsDecode = CheckContentType(nxtaddr(buf));
-    }
-
-    *bufp = (buf + strlen(buf));
-    break;
-
-  case S_BODY_DATA:
-    if ((*MultipartDelimiter) && 
-       (strncmp(buf, MultipartDelimiter, strlen(MultipartDelimiter)) == 0)) {
-      BodyState = S_BODY_HDR;
-      CurrEncodingIsQP = CurrTypeNeedsDecode = 0;
-    }
-
-    if (CurrEncodingIsQP && CurrTypeNeedsDecode) 
-      ret = DoOneQPLine(bufp, delimited, softline);
-    else
-     *bufp = (buf + strlen(buf));
-    break;
-  }
-
-  return ret;
+       return outp - str;
 }
-#endif /* 0 */
-
 
-#ifdef STANDALONE
-#include <stdio.h>
-#include <unistd.h>
-
-char *program_name = "unmime";
-int outlevel = 0;
-
-#define BUFSIZE_INCREMENT 4096
-
-#ifdef DEBUG
-#define DBG_FWRITE(B,L,BS,FD) fwrite(B, L, BS, FD)
-#else
-#define DBG_FWRITE(B,L,BS,FD)
-#endif
+#define HEX_TO_INT(val, hex)                   \
+{                                              \
+       gchar c = hex;                          \
+                                               \
+       if ('0' <= c && c <= '9') {             \
+               val = c - '0';                  \
+       } else if ('a' <= c && c <= 'f') {      \
+               val = c - 'a' + 10;             \
+       } else if ('A' <= c && c <= 'F') {      \
+               val = c - 'A' + 10;             \
+       } else {                                \
+               val = -1;                       \
+       }                                       \
+}
 
-int main(int argc, char *argv[])
+static gboolean get_hex_value(gchar *out, gchar c1, gchar c2)
 {
-  unsigned int BufSize;
-  unsigned char *buffer, *buf_p;
-  int nl_count, i, bodytype;
-
-#ifdef DEBUG
-  pid_t pid;
-  FILE *fd_orig, *fd_conv;
-  char fnam[100];
-
-  pid = getpid();
-  sprintf(fnam, "/tmp/i_unmime.%x", pid);
-  fd_orig = fopen(fnam, "wb");
-  sprintf(fnam, "/tmp/o_unmime.%x", pid);
-  fd_conv = fopen(fnam, "wb");
-#endif
-
-  BufSize = BUFSIZE_INCREMENT;    /* Initial size of buffer */
-  buf_p = buffer = (unsigned char *) xmalloc(BufSize);
-  nl_count = 0;
-
-  do {
-    i = fread(buf_p, 1, 1, stdin);
-    switch (*buf_p) {
-     case '\n':
-       nl_count++;
-       break;
+       gint hi, lo;
 
-     case '\r':
-       break;
+       HEX_TO_INT(hi, c1);
+       HEX_TO_INT(lo, c2);
 
-     default:
-       nl_count = 0;
-       break;
-    }
+       if (hi == -1 || lo == -1)
+               return FALSE;
 
-    buf_p++;
-    if ((buf_p - buffer) == BufSize) {
-       /* Buffer is full! Get more room. */
-       buffer = xrealloc(buffer, BufSize+BUFSIZE_INCREMENT);
-       buf_p = buffer + BufSize;
-       BufSize += BUFSIZE_INCREMENT;
-    }
-  } while ((i > 0) && (nl_count < 2));
-
-  *buf_p = '\0';
-  DBG_FWRITE(buffer, strlen(buffer), 1, fd_orig);
-
-  UnMimeHeader(buffer);
-  bodytype = MimeBodyType(buffer, 1);
-
-  i = strlen(buffer);
-  fwrite(buffer, i, 1, stdout);
-  DBG_FWRITE(buffer, i, 1, fd_conv);
-  
-  do {
-     buf_p = (buffer - 1);
-     do {
-        buf_p++;
-        i = fread(buf_p, 1, 1, stdin);
-     } while ((i == 1) && (*buf_p != '\n'));
-     if (i == 1) buf_p++;
-     *buf_p = '\0';
-     DBG_FWRITE(buf, (buf_p - buffer), 1, fd_orig);
-
-     if (buf_p > buffer) {
-        if (bodytype & MSG_NEEDS_DECODE) {
-           buf_p = buffer;
-           UnMimeBodyline(&buf_p, 0);
-        }
-        fwrite(buffer, (buf_p - buffer), 1, stdout);
-        DBG_FWRITE(buffer, (buf_p - buffer), 1, fd_conv);
-     }
-  } while (buf_p > buffer);
-
-  free(buffer);
-  fflush(stdout);
-
-#ifdef DEBUG
-  fclose(fd_orig);
-  fclose(fd_conv);
-#endif
-
-  return 0;
+       *out = (hi << 4) + lo;
+       return TRUE;
 }
-#endif
-