diff --git a/.gitattributes b/.gitattributes
index 86550f50eec2ea9ca1de872cc43904cf918919ec..c0303b3e70ea3e9d2d7575b1a42cc6e7bf0a46b2 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -47,6 +47,7 @@ testfont binary
 /src/modules/Image/pnm.c foreign_ident
 /src/modules/Image/quant.c foreign_ident
 /src/modules/Image/togif.c foreign_ident
+/src/modules/MIME/mime.c foreign_ident
 /src/modules/Mysql/Makefile.in foreign_ident
 /src/modules/Mysql/acconfig.h foreign_ident
 /src/modules/Mysql/configure.in foreign_ident
diff --git a/lib/modules/MIME.pmod b/lib/modules/MIME.pmod
new file mode 100644
index 0000000000000000000000000000000000000000..30cd633bd7e7fba1b0322a2b01870e4860e48716
--- /dev/null
+++ b/lib/modules/MIME.pmod
@@ -0,0 +1,342 @@
+
+class support {
+
+  inherit "MIME";
+
+  string generate_boundary( )
+  {
+    return "'ThIs-RaNdOm-StRiNg-/=_."+random(1000000000)+":";
+  }
+  
+  string decode( string data, string encoding )
+  {
+    switch(lower_case(encoding||"binary")) {
+    case "base64":
+      return decode_base64( data );
+    case "quoted-printable":
+      return decode_qp( data );
+    case "x-uue":
+      return decode_uue( data );
+    case "7bit": case "8bit": case "binary":
+      return data;
+    default:
+      throw(({ "unknown transfer encoding "+encoding+"\n",
+		 backtrace() }));
+    }
+  }
+  
+  string encode( string data, string encoding, void|string filename )
+  {
+    switch(lower_case(encoding||"binary")) {
+    case "base64":
+      return encode_base64( data );
+    case "quoted-printable":
+      return encode_qp( data );
+    case "x-uue":
+      return encode_uue( data, filename );
+    case "7bit": case "8bit": case "binary":
+      return data;
+    default:
+      throw(({ "unknown transfer encoding "+encoding+"\n",
+		 backtrace() }));
+    }
+  }
+
+
+
+  /* rfc1522 */
+
+  array(string) decode_word( string word )
+  {
+    string charset, encoding, encoded_text;
+    if(sscanf(word, "=?%[^][ \t()<>@,;:\"\\/?.=]?%[^][ \t()<>@,;:\"\\/?.=]?%s?=",
+	      charset, encoding, encoded_text) == 3) {
+      switch(lower_case(encoding)) {
+      case "b":
+	encoding = "base64"; break;
+      case "q":
+	encoding = "quoted-printable"; break;
+      default:
+	throw(({ "invalid rfc1522 encoding "+encoding+"\n", backtrace() }));
+      }
+      return ({ decode( replace(encoded_text, "_", " "), encoding ),
+		  lower_case(charset) });
+    } else
+      return ({ word, 0 });
+  }
+  
+  string encode_word( array(string) word, string encoding )
+  {
+    if(!encoding || !word[1])
+      return word[0];
+    switch(lower_case(encoding)) {
+    case "b":
+    case "base64":
+      encoding = "base64"; break;
+    case "q":
+    case "quoted-printable":
+      encoding = "quoted-printable"; break;
+    default:
+      throw(({ "invalid rfc1522 encoding "+encoding+"\n", backtrace() }));
+    }  
+    return "=?"+word[1]+"?"+encoding[0..0]+"?"+
+      replace( encode( word[0], encoding ),
+	       ({ "?", "_" }), ({ "=3F", "=5F" }))+"?=";
+  }
+
+  string guess_subtype(string typ)
+  {
+    switch(typ) {
+    case "text":
+      return "plain";
+    case "message":
+      return "rfc822";
+    case "multipart":
+      return "mixed";
+    }
+    return 0;
+  }
+
+};
+
+inherit support;
+
+class Message {
+
+  inherit support;
+  import Array;
+
+  string encoded_data;
+  string decoded_data;
+  mapping(string:string) headers;
+  array(object) body_parts;
+
+  string type, subtype, charset, boundary, transfer_encoding;
+  mapping (string:string) params;
+
+  string disposition;
+  mapping (string:string) disp_params;
+
+
+  string get_filename( )
+  {
+    return disp_params["filename"] || params["name"];
+  }
+  
+  void setdata( string data )
+  {
+    if(data != decoded_data) {
+      decoded_data = data;
+      encoded_data = 0;
+    }
+  }
+
+  string getdata( )
+  {
+    if(encoded_data && !decoded_data)
+      decoded_data=decode(encoded_data, transfer_encoding);
+    return decoded_data;
+  }
+  
+  string getencoded( )
+  {
+    if(decoded_data && !encoded_data)
+      encoded_data=encode(decoded_data, transfer_encoding, get_filename( ));
+    return encoded_data;
+  }
+  
+  void setencoding( string encoding )
+  {
+    if(encoded_data && !decoded_data)
+      decoded_data = getdata( );
+    headers["content-transfer-encoding"]=transfer_encoding=lower_case(encoding);
+    encoded_data = 0;
+  }
+  
+  void setparam( string param, string value )
+  {
+    param = lower_case(param);
+    params[param] = value;
+    switch(param) {
+    case "charset": charset = value; break;
+    case "boundary": boundary = value; break;
+    case "name":
+      if(transfer_encoding != "x-uue")
+	break;
+      if(encoded_data && !decoded_data)
+	decoded_data = getdata( );
+      encoded_data = 0;
+      break;
+    }
+    headers["content-type"] =
+      quote(({ type, '/', subtype })+
+	    `+(@map(indices(params), lambda(string param) {
+	      return ({ ';', param, '=', params[param] });
+	    })));
+  }
+  
+  void setdisp_param( string param, string value )
+  {
+    param = lower_case(param);
+    disp_params[param] = value;
+    switch(param) {
+    case "filename":
+      if(transfer_encoding != "x-uue")
+	break;
+      if(encoded_data && !decoded_data)
+	decoded_data = getdata( );
+      encoded_data = 0;
+      break;
+    }
+    headers["content-disposition"] =
+      quote(({ disposition || "attachment" })+
+	    `+(@map(indices(disp_params), lambda(string param) {
+	      return ({ ';', param, '=', disp_params[param] });
+	    })));
+  }
+  
+  void setcharset( string charset )
+  {
+    setparam( "charset", charset );
+  }
+  
+  void setboundary( string boundary )
+  {
+    setparam( "boundary", boundary );
+  }
+  
+  string cast( string dest_type )
+  {
+    string data;
+    object body_part;
+    
+    if(dest_type != "string")
+      throw(({ "can't cast Message to "+dest_type+"\n", backtrace() }));
+    
+    data = getencoded( );
+    
+    if(body_parts) {
+      
+      if(!boundary) {
+	if(type != "multipart") { type="multipart"; subtype="mixed"; }
+	setboundary(generate_boundary());
+      }
+      
+      data += "\r\n";
+      foreach( body_parts, body_part )
+	data += "--"+boundary+"\r\n"+((string)body_part)+"\r\n";
+      data += "--"+boundary+"--\r\n";
+    }
+    
+    headers["content-length"] = ""+strlen(data);
+    
+    return map(indices(headers),
+	       lambda(string hname){
+      return replace(map(hname/"-", String.capitalize)*"-", "Mime", "MIME")+
+	": "+headers[hname];
+    })*"\r\n"+"\r\n\r\n"+data;
+  }
+
+  void create(void | string message,
+	      void | mapping(string:string) hdrs,
+	      void | array(object) parts)
+  {
+    encoded_data = 0;
+    decoded_data = 0;
+    headers = ([ ]);
+    params = ([ ]);
+    disp_params = ([ ]);
+    body_parts = 0;
+    type = "text";
+    subtype = "plain";
+    charset = "us-ascii";
+    boundary = 0;
+    disposition = 0;
+    if (hdrs || parts) {
+      string hname;
+      if(message)
+	decoded_data = message;
+      else
+	decoded_data = (parts?
+			"This is a multi-part message in MIME format.\r\n":
+			"");
+      if(hdrs)
+	foreach( indices(hdrs), hname )
+	  headers[lower_case(hname)] = hdrs[hname];
+      body_parts = parts;
+    } else if (message) {
+      string head, body, header, hname, hcontents;
+      int mesgsep;
+      {
+	int mesgsep1 = search(message, "\r\n\r\n");
+	int mesgsep2 = search(message, "\n\n");
+	mesgsep = (mesgsep1<0? mesgsep2 :
+		   (mesgsep2<0? mesgsep1 :
+		    (mesgsep1<mesgsep2? mesgsep1 : mesgsep2)));
+      }
+      if(mesgsep<0) {
+	head = message;
+	body = "";
+      } else {
+	head = (mesgsep>0? message[..mesgsep-1]:"");
+	body = message[mesgsep+(message[mesgsep]=='\r'? 4:2)..];
+      }
+      foreach( replace(head, ({"\r", "\n ", "\n\t"}),
+		       ({"", " ", " "}))/"\n", header ) {
+	if(4==sscanf(header, "%[!-9;-~]%*[ \t]:%*[ \t]%s", hname, hcontents))
+	  headers[lower_case(hname)] = hcontents;
+      }
+      encoded_data = body;
+    }
+    if(headers["content-type"]) {
+      array(array(string|int)) arr =
+	tokenize(headers["content-type"]) / ({';'});
+      array(string|int) p;
+      if(sizeof(arr[0])!=3 || arr[0][1]!='/' ||
+	 !stringp(arr[0][0]) || !stringp(arr[0][2]))
+	if(sizeof(arr[0])==1 && stringp(arr[0][0]) &&
+	   (subtype = guess_subtype(lower_case(type = arr[0][0]))))
+	  arr = ({ ({ type, '/', subtype }) }) + arr[1..];
+	else
+	  throw(({ "invalid Content-Type in message\n", backtrace() }));
+      type = lower_case(arr[0][0]);
+      subtype = lower_case(arr[0][2]);
+      foreach( arr[1..], p ) {
+	if(sizeof(p)<3 || p[1]!='=' || !stringp(p[0]))
+	  throw(({ "invalid parameter in Content-Type\n", backtrace() }));
+	params[ lower_case(p[0]) ] = p[2..]*"";
+      }
+      charset = lower_case(params["charset"] || charset);
+      boundary = params["boundary"];
+    }
+    if(headers["content-disposition"]) {
+      array(array(string|int)) arr =
+	tokenize(headers["content-disposition"]) / ({';'});
+      array(string|int) p;
+      if(sizeof(arr[0])!=1 || !stringp(arr[0][0]))
+	throw(({ "invalid Content-Disposition in message\n", backtrace() }));
+      disposition = lower_case(arr[0][0]);
+      foreach( arr[1..], p ) {
+	if(sizeof(p)<3 || p[1]!='=' || !stringp(p[0]))
+	  throw(({ "invalid parameter in Content-Disposition\n", backtrace() }));
+	disp_params[ lower_case(p[0]) ] = p[2..]*"";
+      }
+    }
+    if(headers["content-transfer-encoding"]) {
+      array(string) arr=tokenize(headers["content-transfer-encoding"]);
+      if(sizeof(arr)!=1 || !stringp(arr[0]))
+	throw(({"invalid Content-Transfer-Encoding in message\n", backtrace()}));
+      transfer_encoding = lower_case(arr[0]);
+    }
+    if(boundary && type=="multipart" && !body_parts &&
+       (encoded_data || decoded_data)) {
+      array(string) parts = ("\n"+getdata())/("\n--"+boundary);
+      if(parts[-1][0..3]!="--\n" && parts[-1][0..3]!="--\r\n")
+	throw(({ "multipart message improperly terminated\n", backtrace() }));
+      encoded_data = 0;
+      decoded_data = parts[0][1..];
+      body_parts = map(parts[1..sizeof(parts)-2], lambda(string part){ return object_program(this_object())(part[1..]); });
+    }
+  }
+  
+}
diff --git a/src/modules/MIME/.cvsignore b/src/modules/MIME/.cvsignore
new file mode 100644
index 0000000000000000000000000000000000000000..66d35f0a032e2d62628e6095389746641e62dec2
--- /dev/null
+++ b/src/modules/MIME/.cvsignore
@@ -0,0 +1,10 @@
+.pure
+Makefile
+config.log
+config.status
+config.h.in
+configure
+dependencies
+linker_options
+stamp-h
+stamp-h.in
diff --git a/src/modules/MIME/.gitignore b/src/modules/MIME/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..74cf2e941ed8a052aef5431084df6d70853912f8
--- /dev/null
+++ b/src/modules/MIME/.gitignore
@@ -0,0 +1,10 @@
+/.pure
+/Makefile
+/config.log
+/config.status
+/config.h.in
+/configure
+/dependencies
+/linker_options
+/stamp-h
+/stamp-h.in
diff --git a/src/modules/MIME/Makefile.in b/src/modules/MIME/Makefile.in
new file mode 100644
index 0000000000000000000000000000000000000000..63f00eeaa58b4ebb61d0b2e365d6ba2cae658545
--- /dev/null
+++ b/src/modules/MIME/Makefile.in
@@ -0,0 +1,7 @@
+SRCDIR=@srcdir@
+VPATH=@srcdir@:@srcdir@/../..:../..
+OBJS=mime.o
+MODULE_LDFLAGS=@LDFLAGS@ @LIBS@
+
+@dynamic_module_makefile@
+@dependencies@
diff --git a/src/modules/MIME/acconfig.h b/src/modules/MIME/acconfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/modules/MIME/configure.in b/src/modules/MIME/configure.in
new file mode 100644
index 0000000000000000000000000000000000000000..e0d24b4b20f95e755849c64931646287c76ff9e6
--- /dev/null
+++ b/src/modules/MIME/configure.in
@@ -0,0 +1,6 @@
+AC_INIT(mime.c)
+AC_CONFIG_HEADER(config.h)
+
+sinclude(../module_configure.in)
+
+AC_OUTPUT(Makefile,echo FOO >stamp-h )
diff --git a/src/modules/MIME/mime.c b/src/modules/MIME/mime.c
new file mode 100644
index 0000000000000000000000000000000000000000..612736975b473c1e5af34925760663419239d96c
--- /dev/null
+++ b/src/modules/MIME/mime.c
@@ -0,0 +1,535 @@
+#include "config.h"
+
+#include "global.h"
+RCSID("$Id: mime.c,v 1.1 1997/03/08 21:09:51 marcus Exp $");
+#include "stralloc.h"
+#include "types.h"
+#include "macros.h"
+#include "object.h"
+#include "program.h"
+#include "interpret.h"
+#include "builtin_functions.h"
+#include "error.h"
+
+static void f_decode_base64(INT32 args);
+static void f_encode_base64(INT32 args);
+static void f_decode_qp(INT32 args);
+static void f_encode_qp(INT32 args);
+static void f_decode_uue(INT32 args);
+static void f_encode_uue(INT32 args);
+
+static void f_tokenize(INT32 args);
+static void f_quote(INT32 args);
+
+static char base64tab[64]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static char base64rtab[0x80-' '];
+static char qptab[16]="0123456789ABCDEF";
+static char qprtab[0x80-'0'];
+
+#define CT_CTL     0
+#define CT_WHITE   1
+#define CT_ATOM    2
+#define CT_SPECIAL 3
+#define CT_LPAR    4
+#define CT_RPAR    5
+#define CT_LBRACK  6
+#define CT_RBRACK  7
+#define CT_QUOTE   8
+unsigned char rfc822ctype[256];
+
+/* Initialize and start module */
+void pike_module_init(void)
+{
+  int i;
+  memset(base64rtab, -1, sizeof(base64rtab));
+  for(i=0; i<64; i++) base64rtab[base64tab[i]-' ']=i;
+  memset(qprtab, -1, sizeof(qprtab));
+  for(i=0; i<16; i++) qprtab[qptab[i]-'0']=i;
+  for(i=10; i<16; i++) qprtab[qptab[i]-('0'+'A'-'a')]=i;
+
+  memset(rfc822ctype, CT_ATOM, sizeof(rfc822ctype));
+  for(i=0; i<32; i++) rfc822ctype[i]=CT_CTL;
+  rfc822ctype[127]=CT_CTL;
+  rfc822ctype[' ']=CT_WHITE;
+  rfc822ctype['\t']=CT_WHITE;
+  rfc822ctype['(']=CT_LPAR;
+  rfc822ctype[')']=CT_RPAR;
+  rfc822ctype['[']=CT_LBRACK;
+  rfc822ctype[']']=CT_LBRACK;
+  rfc822ctype['"']=CT_QUOTE;
+  for(i=0; i<10; i++) rfc822ctype[(int)"<>@,;:\\/?="[i]]=CT_SPECIAL;
+
+  add_function_constant("decode_base64", f_decode_base64, "function(string:string)", OPT_TRY_OPTIMIZE);
+  add_function_constant("encode_base64", f_encode_base64, "function(string:string)", OPT_TRY_OPTIMIZE);
+  add_function_constant("decode_qp", f_decode_qp, "function(string:string)", OPT_TRY_OPTIMIZE);
+  add_function_constant("encode_qp", f_encode_qp, "function(string:string)", OPT_TRY_OPTIMIZE);
+  add_function_constant("decode_uue", f_decode_uue, "function(string:string)", OPT_TRY_OPTIMIZE);
+  add_function_constant("encode_uue", f_encode_uue, "function(string,void|string:string)", OPT_TRY_OPTIMIZE);
+
+  add_function_constant("tokenize", f_tokenize, "function(string:array(string|int))", OPT_TRY_OPTIMIZE);
+  add_function_constant("quote", f_quote, "function(array(string|int):string)", OPT_TRY_OPTIMIZE);
+
+}
+
+/* Restore and exit module */
+void pike_module_exit(void)
+{
+}
+
+static void f_decode_base64(INT32 args)
+{
+  if(args != 1)
+    error("Wrong number of arguments to MIME.decode_base64()\n");
+  else if(sp[-1].type != T_STRING)
+    error("Wrong type of argument to MIME.decode_base64()\n");
+  else {
+    dynamic_buffer buf;
+    char *src;
+    INT32 cnt, d=1;
+    int pads=0;
+
+    buf.s.str=NULL;
+    initialize_buf(&buf);
+
+    for(src = sp[-1].u.string->str, cnt = sp[-1].u.string->len; cnt--; src++)
+      if(*src>=' ' && base64rtab[*src-' ']>=0)
+	if((d=(d<<6)|base64rtab[*src-' '])>=0x1000000) {
+	  low_my_putchar( d>>16, &buf );
+	  low_my_putchar( d>>8, &buf );
+	  low_my_putchar( d, &buf );
+	  d=1;
+	}
+	else ;
+      else if(*src=='=') { pads++; d>>=2; }
+
+    switch(pads) {
+    case 1:
+      low_my_putchar( d>>8, &buf );
+    case 2:
+      low_my_putchar( d, &buf );
+    }
+
+    pop_n_elems(1);
+    push_string(low_free_buf(&buf));
+  }
+}
+
+static int do_b64_encode(INT32 groups, unsigned char **srcp, char **destp)
+{
+  unsigned char *src=*srcp;
+  char *dest=*destp;
+  int g=0;
+  while(groups--) {
+    INT32 d = *src++<<8;
+    d = (*src++|d)<<8;
+    d |= *src++;
+    *dest++ = base64tab[d>>18];
+    *dest++ = base64tab[(d>>12)&63];
+    *dest++ = base64tab[(d>>6)&63];
+    *dest++ = base64tab[d&63];
+    if(++g == 19) {
+      *dest++ = 13;
+      *dest++ = 10;
+      g=0;
+    }
+  }
+  *srcp = src;
+  *destp = dest;
+  return g;
+}
+
+static void f_encode_base64(INT32 args)
+{
+  if(args != 1)
+    error("Wrong number of arguments to MIME.encode_base64()\n");
+  else if(sp[-1].type != T_STRING)
+    error("Wrong type of argument to MIME.encode_base64()\n");
+  else {
+    INT32 groups=(sp[-1].u.string->len+2)/3;
+    int last=(sp[-1].u.string->len-1)%3+1;
+    struct pike_string *str = begin_shared_string(groups*4+(groups/19)*2);
+    unsigned char *src=(unsigned char *)sp[-1].u.string->str;
+    char *dest=str->str;
+
+    if(groups) {
+      unsigned char tmp[3], *tmpp=tmp;
+      int i;
+
+      if(do_b64_encode(groups-1, &src, &dest)==18)
+	str->len-=2;
+
+      tmp[1]=tmp[2]=0;
+      for(i=0; i<last; i++) tmp[i]=*src++;
+      do_b64_encode(1, &tmpp, &dest);
+      switch(last) {
+      case 1:
+	*--dest='=';
+      case 2:
+	*--dest='=';
+      }
+    }
+    pop_n_elems(1);
+    push_string(end_shared_string(str));
+  }
+}
+
+static void f_decode_qp(INT32 args)
+{
+  if(args != 1)
+    error("Wrong number of arguments to MIME.decode_qp()\n");
+  else if(sp[-1].type != T_STRING)
+    error("Wrong type of argument to MIME.decode_qp()\n");
+  else {
+    dynamic_buffer buf;
+    char *src;
+    INT32 cnt;
+
+    buf.s.str=NULL;
+    initialize_buf(&buf);
+
+    for(src = sp[-1].u.string->str, cnt = sp[-1].u.string->len; cnt--; src++)
+      if(*src == '=')
+	if(cnt>0 && (src[1]==10 || src[1]==13)) {
+	  if(src[1]==13) { --cnt; src++; }
+	  if(cnt>0 && src[1]==10) { --cnt; src++; }
+	} else if(cnt>=2 && src[1]>='0' && src[2]>='0' &&
+		  qprtab[src[1]-'0']>=0 && qprtab[src[2]-'0']>=0) {
+	  low_my_putchar( (qprtab[src[1]-'0']<<4)|qprtab[src[2]-'0'], &buf );
+	  cnt -= 2;
+	  src += 2;
+	} else ;
+      else
+	low_my_putchar( *src, &buf );
+
+    pop_n_elems(1);
+    push_string(low_free_buf(&buf));
+  }
+}
+
+static void f_encode_qp(INT32 args)
+{
+  if(args != 1)
+    error("Wrong number of arguments to MIME.encode_qp()\n");
+  else if(sp[-1].type != T_STRING)
+    error("Wrong type of argument to MIME.encode_qp()\n");
+  else {
+    dynamic_buffer buf;
+    unsigned char *src = (unsigned char *)sp[-1].u.string->str;
+    INT32 cnt;
+    int col=0;
+
+    buf.s.str=NULL;
+    initialize_buf(&buf);
+
+    for(cnt = sp[-1].u.string->len; cnt--; src++) {
+      if((*src >= 33 && *src <= 60) ||
+	 (*src >= 62 && *src <= 126))
+	low_my_putchar( *src, &buf );
+      else {
+	low_my_putchar( '=', &buf );
+	low_my_putchar( qptab[(*src)>>4], &buf );
+	low_my_putchar( qptab[(*src)&15], &buf );
+	col += 2;
+      }
+      if(++col >= 73) {
+	low_my_putchar( '=', &buf );
+	low_my_putchar( 13, &buf );
+	low_my_putchar( 10, &buf );
+	col = 0;
+      }
+    }
+    
+    pop_n_elems(1);
+    push_string(low_free_buf(&buf));
+  }
+}
+
+
+static void f_decode_uue(INT32 args)
+{
+  if(args != 1)
+    error("Wrong number of arguments to MIME.decode_uue()\n");
+  else if(sp[-1].type != T_STRING)
+    error("Wrong type of argument to MIME.decode_uue()\n");
+  else {
+    dynamic_buffer buf;
+    char *src;
+    INT32 cnt;
+
+    buf.s.str=NULL;
+    initialize_buf(&buf);
+
+    src = sp[-1].u.string->str;
+    cnt = sp[-1].u.string->len;
+
+    while(cnt--)
+      if(*src++=='b' && cnt>5 && !memcmp(src, "egin ", 5))
+	break;
+    if(cnt>=0)
+      while(cnt--)
+	if(*src++=='\n')
+	  break;
+    if(cnt<0) {
+      pop_n_elems(1);
+      push_int(0);
+      return;
+    }
+    for(;;) {
+      int l, g;
+      if(cnt<=0 || (l=(*src++)-' ')>=64)
+	break;
+      --cnt;
+      g=(l+2)/3;
+      l-=g*3;
+      if((cnt-=g*4)<0)
+	break;
+      while(g--) {
+	INT32 d = ((*src++-' ')&63)<<18;
+	d |= ((*src++-' ')&63)<<12;
+	d |= ((*src++-' ')&63)<<6;
+	d |= ((*src++-' ')&63);
+	low_my_putchar( d>>16, &buf );
+	low_my_putchar( d>>8, &buf );
+	low_my_putchar( d, &buf );
+      }
+      while(l++)
+	low_make_buf_space( -1, &buf );
+      while(cnt-- && *src++!=10);
+    }
+    pop_n_elems(1);
+    push_string(low_free_buf(&buf));
+  }
+}
+
+static void do_uue_encode(INT32 groups, unsigned char **srcp, char **destp,
+			  INT32 last)
+{
+  unsigned char *src=*srcp;
+  char *dest=*destp;
+
+  while(groups || last) {
+    int g=(groups>=15? 15:groups);
+    if(g<15) {
+      *dest++ = ' '+(3*g+last);
+      last = 0;
+    } else
+      *dest++ = ' '+(3*g);
+    groups -= g;
+
+    while(g--) {
+      INT32 d = *src++<<8;
+      d = (*src++|d)<<8;
+      d |= *src++;
+      *dest++ = ' '+(d>>18);
+      *dest++ = ' '+((d>>12)&63);
+      *dest++ = ' '+((d>>6)&63);
+      *dest++ = ' '+(d&63);
+    }
+
+    if(groups || last) {
+      *dest++ = 13;
+      *dest++ = 10;
+    }
+  }
+  *srcp = src;
+  *destp = dest;
+}
+
+static void f_encode_uue(INT32 args)
+{
+  if(args != 1 && args != 2)
+    error("Wrong number of arguments to MIME.encode_uue()\n");
+  else if(sp[-args].type != T_STRING ||
+	  (args == 2 && sp[-1].type != T_VOID && sp[-1].type != T_STRING &&
+	   sp[-1].type != T_INT))
+    error("Wrong type of argument to MIME.encode_uue()\n");
+  else {
+    struct pike_string *str;
+    unsigned char *src = (unsigned char *) sp[-args].u.string->str;
+    INT32 groups=(sp[-args].u.string->len+2)/3;
+    int last=(sp[-args].u.string->len-1)%3+1;
+    char *dest, *filename = "attachment";
+    if(args == 2 && sp[-1].type == T_STRING)
+      filename = sp[-1].u.string->str;
+    str = begin_shared_string(groups*4+((groups+14)/15)*3+strlen(filename)+20);
+    dest = str->str;
+    sprintf(dest, "begin 644 %s\r\n", filename);
+    dest += 12 + strlen(filename);
+    if(groups) {
+      unsigned char tmp[3], *tmpp=tmp;
+      char *kp, k;
+      int i;
+
+      do_uue_encode(groups-1, &src, &dest, last);
+
+      tmp[1]=tmp[2]=0;
+      for(i=0; i<last; i++) tmp[i]=*src++;
+      k = *--dest;
+      kp = dest;
+      do_uue_encode(1, &tmpp, &dest, 0);
+      *kp = k;
+      switch(last) {
+      case 1:
+	dest[-2]='`';
+      case 2:
+	dest[-1]='`';
+      }
+      *dest++ = 13;
+      *dest++ = 10;
+    }
+    memcpy(dest, "`\r\nend\r\n", 8);
+    pop_n_elems(args);
+    push_string(end_shared_string(str));
+  }
+}
+
+static void f_tokenize(INT32 args)
+{
+  if(args != 1)
+    error("Wrong number of arguments to MIME.tokenize()\n");
+  else if(sp[-1].type != T_STRING)
+    error("Wrong type of argument to MIME.tokenize()\n");
+  else {
+    unsigned char *src = (unsigned char *)sp[-1].u.string->str;
+    struct array *arr;
+    struct pike_string *str;
+    INT32 cnt = sp[-1].u.string->len, n = 0, l, e;
+    char *p;
+
+    while(cnt>0)
+      switch(rfc822ctype[*src]) {
+      case CT_SPECIAL:
+      case CT_RBRACK:
+      case CT_RPAR:
+	/* individual special character */
+	push_int(*src++);
+	n++;
+	--cnt;
+	break;
+      case CT_ATOM:
+	/* atom */
+	for(l=1; l<cnt; l++)
+	  if(rfc822ctype[src[l]] != CT_ATOM)
+	    break;
+	push_string(make_shared_binary_string(src, l));
+	n++;
+	src += l;
+	cnt -= l;
+	break;
+      case CT_QUOTE:
+	/* quoted-string */
+	for(e=0, l=1; l<cnt; l++)
+	  if(src[l] == '"')
+	    break;
+	  else if(src[l] == '\\') { e++; l++; }
+	str = begin_shared_string( l-e-1 );
+	for(p=str->str, e=1; e<l; e++)
+	  *p++=(src[e]=='\\'? src[++e]:src[e]);
+	push_string(end_shared_string(str));
+	n++;
+	src += l+1;
+	cnt -= l+1;
+	break;
+      case CT_LBRACK:
+	/* domain literal */
+	for(e=0, l=1; l<cnt; l++)
+	  if(src[l] == ']')
+	    break;
+	  else if(src[l] == '\\') { e++; l++; }
+	str = begin_shared_string( l-e+1 );
+	for(p=str->str, e=0; e<=l; e++)
+	  *p++=(src[e]=='\\'? src[++e]:src[e]);
+	push_string(end_shared_string(str));
+	n++;
+	src += l+1;
+	cnt -= l+1;
+	break;
+      case CT_LPAR:
+	/* comment */
+	for(e=1, l=1; l<cnt; l++)
+	  if(src[l] == '(')
+	    e++;
+	  else if(src[l] == ')')
+	    if(!--e)
+	      break;
+	    else ;
+	  else if(src[l] == '\\') l++;
+	src += l+1;
+	cnt -= l+1;
+	break;
+      case CT_WHITE:
+	/* whitespace */
+	src++;
+	--cnt;
+	break;
+      default:
+	error("invalid character in header field\n");
+      }
+
+    arr = aggregate_array(n);
+    pop_n_elems(1);
+    push_array(arr);
+  }
+}
+
+static int check_atom_chars(unsigned char *str, INT32 len)
+{
+  /* Atoms must contain at least 1 character... */
+  if(len<1) return 0;
+
+  while(len--)
+    if(*str>=0x80 || rfc822ctype[*str]!=CT_ATOM)
+      return 0;
+    else
+      str++;
+
+  return 1;
+}
+
+static void f_quote(INT32 args)
+{
+  struct svalue *item;
+  INT32 cnt;
+  dynamic_buffer buf;
+  int prev_atom = 0;
+
+  if(args != 1)
+    error("Wrong number of arguments to MIME.quote()\n");
+  else if(sp[-1].type != T_ARRAY)
+    error("Wrong type of argument to MIME.quote()\n");
+
+  buf.s.str=NULL;
+  initialize_buf(&buf);
+  for(cnt=sp[-1].u.array->size, item=sp[-1].u.array->item; cnt--; item++) {
+    if(item->type == T_INT) {
+      /* single special character */
+      low_my_putchar( item->u.integer, &buf );
+      prev_atom=0;
+    } else if(item->type != T_STRING) {
+      toss_buffer(&buf);
+      error("Wrong type of argument to MIME.quote()\n");
+    } else {
+      struct pike_string *str=item->u.string;
+      if(prev_atom)
+	low_my_putchar( ' ', &buf );
+      if(check_atom_chars((unsigned char *)str->str, str->len)) {
+	/* valid atom without quotes... */
+	low_my_binary_strcat(str->str, str->len, &buf);
+      } else {
+	/* quote string */
+	INT32 len=str->len;
+	char *src=str->str;
+	low_my_putchar( '"', &buf );
+	while(len--) {
+	  if(*src=='"' || *src=='\\' || *src=='\r')
+	    low_my_putchar( '\\', &buf );
+	  low_my_putchar( *src++, &buf );
+	}
+	low_my_putchar( '"', &buf );
+      }
+      prev_atom=1;
+    }
+  }
+  pop_n_elems(1);
+  push_string(low_free_buf(&buf));  
+}
diff --git a/src/modules/MIME/testsuite.in b/src/modules/MIME/testsuite.in
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391