[INETCOMM] Sync with Wine Staging 4.18. CORE-16441
[reactos.git] / dll / win32 / inetcomm / mimeole.c
index 8a1e3d4..d69c40c 100644 (file)
 
 #include "windef.h"
 #include "winbase.h"
+#include "wine/winternl.h"
 #include "winuser.h"
 #include "objbase.h"
 #include "ole2.h"
 #include "mimeole.h"
+#ifdef __REACTOS__
+#include <winreg.h>
+#endif
+#include "propvarutil.h"
 
+#include "wine/heap.h"
 #include "wine/list.h"
 #include "wine/debug.h"
 
@@ -55,26 +61,81 @@ typedef struct
 
 static const property_t default_props[] =
 {
-    {"References",                   PID_HDR_REFS,       0,                               VT_LPSTR},
-    {"Subject",                      PID_HDR_SUBJECT,    0,                               VT_LPSTR},
-    {"From",                         PID_HDR_FROM,       MPF_ADDRESS,                     VT_LPSTR},
-    {"Message-ID",                   PID_HDR_MESSAGEID,  0,                               VT_LPSTR},
-    {"Return-Path",                  PID_HDR_RETURNPATH, MPF_ADDRESS,                     VT_LPSTR},
-    {"Date",                         PID_HDR_DATE,       0,                               VT_LPSTR},
-    {"Received",                     PID_HDR_RECEIVED,   0,                               VT_LPSTR},
-    {"Reply-To",                     PID_HDR_REPLYTO,    MPF_ADDRESS,                     VT_LPSTR},
-    {"X-Mailer",                     PID_HDR_XMAILER,    0,                               VT_LPSTR},
-    {"Bcc",                          PID_HDR_BCC,        MPF_ADDRESS,                     VT_LPSTR},
-    {"MIME-Version",                 PID_HDR_MIMEVER,    MPF_MIME,                        VT_LPSTR},
-    {"Content-Type",                 PID_HDR_CNTTYPE,    MPF_MIME | MPF_HASPARAMS,        VT_LPSTR},
-    {"Content-Transfer-Encoding",    PID_HDR_CNTXFER,    MPF_MIME,                        VT_LPSTR},
-    {"Content-ID",                   PID_HDR_CNTID,      MPF_MIME,                        VT_LPSTR},
-    {"Content-Disposition",          PID_HDR_CNTDISP,    MPF_MIME | MPF_HASPARAMS,        VT_LPSTR},
-    {"To",                           PID_HDR_TO,         MPF_ADDRESS,                     VT_LPSTR},
-    {"Cc",                           PID_HDR_CC,         MPF_ADDRESS,                     VT_LPSTR},
-    {"Sender",                       PID_HDR_SENDER,     MPF_ADDRESS,                     VT_LPSTR},
-    {"In-Reply-To",                  PID_HDR_INREPLYTO,  0,                               VT_LPSTR},
-    {NULL,                           0,                  0,                               0}
+    {"X-Newsgroup",                  PID_HDR_NEWSGROUP,    0,                               VT_LPSTR},
+    {"Newsgroups",                   PID_HDR_NEWSGROUPS,   0,                               VT_LPSTR},
+    {"References",                   PID_HDR_REFS,         0,                               VT_LPSTR},
+    {"Subject",                      PID_HDR_SUBJECT,      0,                               VT_LPSTR},
+    {"From",                         PID_HDR_FROM,         MPF_ADDRESS,                     VT_LPSTR},
+    {"Message-ID",                   PID_HDR_MESSAGEID,    0,                               VT_LPSTR},
+    {"Return-Path",                  PID_HDR_RETURNPATH,   MPF_ADDRESS,                     VT_LPSTR},
+    {"Rr",                           PID_HDR_RR,           0,                               VT_LPSTR},
+    {"Return-Receipt-To",            PID_HDR_RETRCPTO,     MPF_ADDRESS,                     VT_LPSTR},
+    {"Apparently-To",                PID_HDR_APPARTO,      MPF_ADDRESS,                     VT_LPSTR},
+    {"Date",                         PID_HDR_DATE,         0,                               VT_LPSTR},
+    {"Received",                     PID_HDR_RECEIVED,     0,                               VT_LPSTR},
+    {"Reply-To",                     PID_HDR_REPLYTO,      MPF_ADDRESS,                     VT_LPSTR},
+    {"X-Mailer",                     PID_HDR_XMAILER,      0,                               VT_LPSTR},
+    {"Bcc",                          PID_HDR_BCC,          MPF_ADDRESS,                     VT_LPSTR},
+    {"MIME-Version",                 PID_HDR_MIMEVER,      MPF_MIME,                        VT_LPSTR},
+    {"Content-Type",                 PID_HDR_CNTTYPE,      MPF_MIME | MPF_HASPARAMS,        VT_LPSTR},
+    {"Content-Transfer-Encoding",    PID_HDR_CNTXFER,      MPF_MIME,                        VT_LPSTR},
+    {"Content-ID",                   PID_HDR_CNTID,        MPF_MIME,                        VT_LPSTR},
+    {"Content-Description",          PID_HDR_CNTDESC,      MPF_MIME,                        VT_LPSTR},
+    {"Content-Disposition",          PID_HDR_CNTDISP,      MPF_MIME | MPF_HASPARAMS,        VT_LPSTR},
+    {"Content-Base",                 PID_HDR_CNTBASE,      MPF_MIME,                        VT_LPSTR},
+    {"Content-Location",             PID_HDR_CNTLOC,       MPF_MIME,                        VT_LPSTR},
+    {"To",                           PID_HDR_TO,           MPF_ADDRESS,                     VT_LPSTR},
+    {"Path",                         PID_HDR_PATH,         0,                               VT_LPSTR},
+    {"Followup-To",                  PID_HDR_FOLLOWUPTO,   0,                               VT_LPSTR},
+    {"Expires",                      PID_HDR_EXPIRES,      0,                               VT_LPSTR},
+    {"Cc",                           PID_HDR_CC,           MPF_ADDRESS,                     VT_LPSTR},
+    {"Control",                      PID_HDR_CONTROL,      0,                               VT_LPSTR},
+    {"Distribution",                 PID_HDR_DISTRIB,      0,                               VT_LPSTR},
+    {"Keywords",                     PID_HDR_KEYWORDS,     0,                               VT_LPSTR},
+    {"Summary",                      PID_HDR_SUMMARY,      0,                               VT_LPSTR},
+    {"Approved",                     PID_HDR_APPROVED,     0,                               VT_LPSTR},
+    {"Lines",                        PID_HDR_LINES,        0,                               VT_LPSTR},
+    {"Xref",                         PID_HDR_XREF,         0,                               VT_LPSTR},
+    {"Organization",                 PID_HDR_ORG,          0,                               VT_LPSTR},
+    {"X-Newsreader",                 PID_HDR_XNEWSRDR,     0,                               VT_LPSTR},
+    {"X-Priority",                   PID_HDR_XPRI,         0,                               VT_LPSTR},
+    {"X-MSMail-Priority",            PID_HDR_XMSPRI,       0,                               VT_LPSTR},
+    {"par:content-disposition:filename", PID_PAR_FILENAME, 0,                               VT_LPSTR},
+    {"par:content-type:boundary",    PID_PAR_BOUNDARY,     0,                               VT_LPSTR},
+    {"par:content-type:charset",     PID_PAR_CHARSET,      0,                               VT_LPSTR},
+    {"par:content-type:name",        PID_PAR_NAME,         0,                               VT_LPSTR},
+    {"att:filename",                 PID_ATT_FILENAME,     0,                               VT_LPSTR},
+    {"att:pri-content-type",         PID_ATT_PRITYPE,      0,                               VT_LPSTR},
+    {"att:sub-content-type",         PID_ATT_SUBTYPE,      0,                               VT_LPSTR},
+    {"att:illegal-lines",            PID_ATT_ILLEGAL,      0,                               VT_LPSTR},
+    {"att:rendered",                 PID_ATT_RENDERED,     0,                               VT_LPSTR},
+    {"att:sent-time",                PID_ATT_SENTTIME,     0,                               VT_LPSTR},
+    {"att:priority",                 PID_ATT_PRIORITY,     0,                               VT_LPSTR},
+    {"Comment",                      PID_HDR_COMMENT,      0,                               VT_LPSTR},
+    {"Encoding",                     PID_HDR_ENCODING,     0,                               VT_LPSTR},
+    {"Encrypted",                    PID_HDR_ENCRYPTED,    0,                               VT_LPSTR},
+    {"X-Offsets",                    PID_HDR_OFFSETS,      0,                               VT_LPSTR},
+    {"X-Unsent",                     PID_HDR_XUNSENT,      0,                               VT_LPSTR},
+    {"X-ArticleId",                  PID_HDR_ARTICLEID,    0,                               VT_LPSTR},
+    {"Sender",                       PID_HDR_SENDER,       MPF_ADDRESS,                     VT_LPSTR},
+    {"att:athena-server",            PID_ATT_SERVER,       0,                               VT_LPSTR},
+    {"att:athena-account-id",        PID_ATT_ACCOUNT,      0,                               VT_LPSTR},
+    {"att:athena-pop3-uidl",         PID_ATT_UIDL,         0,                               VT_LPSTR},
+    {"att:athena-store-msgid",       PID_ATT_STOREMSGID,   0,                               VT_LPSTR},
+    {"att:athena-user-name",         PID_ATT_USERNAME,     0,                               VT_LPSTR},
+    {"att:athena-forward-to",        PID_ATT_FORWARDTO,    0,                               VT_LPSTR},
+    {"att:athena-store-fdrid",       PID_ATT_STOREFOLDERID,0,                               VT_LPSTR},
+    {"att:athena-ghosted",           PID_ATT_GHOSTED,      0,                               VT_LPSTR},
+    {"att:athena-uncachedsize",      PID_ATT_UNCACHEDSIZE, 0,                               VT_LPSTR},
+    {"att:athena-combined",          PID_ATT_COMBINED,     0,                               VT_LPSTR},
+    {"att:auto-inlined",             PID_ATT_AUTOINLINED,  0,                               VT_LPSTR},
+    {"Disposition-Notification-To",  PID_HDR_DISP_NOTIFICATION_TO,  0,                      VT_LPSTR},
+    {"par:Content-Type:reply-type",  PID_PAR_REPLYTYPE,    0,                               VT_LPSTR},
+    {"par:Content-Type:format",      PID_PAR_FORMAT ,      0,                               VT_LPSTR},
+    {"att:format",                   PID_ATT_FORMAT ,      0,                               VT_LPSTR},
+    {"In-Reply-To",                  PID_HDR_INREPLYTO,    0,                               VT_LPSTR},
+    {"att:athena-account-name",      PID_ATT_ACCOUNTNAME,  0,                               VT_LPSTR},
+    {NULL,                           0,                    0,                               0}
 };
 
 typedef struct
@@ -94,8 +155,8 @@ typedef struct
 
 typedef struct MimeBody
 {
-    const IMimeBodyVtbl *lpVtbl;
-    LONG refs;
+    IMimeBody IMimeBody_iface;
+    LONG ref;
 
     HBODY handle;
 
@@ -110,9 +171,312 @@ typedef struct MimeBody
     BODYOFFSETS body_offsets;
 } MimeBody;
 
-static inline MimeBody *impl_from_IMimeBody( IMimeBody *iface )
+typedef struct
+{
+    IStream IStream_iface;
+    LONG ref;
+    IStream *base;
+    ULARGE_INTEGER pos, start, length;
+} sub_stream_t;
+
+static inline sub_stream_t *impl_from_IStream(IStream *iface)
+{
+    return CONTAINING_RECORD(iface, sub_stream_t, IStream_iface);
+}
+
+static HRESULT WINAPI sub_stream_QueryInterface(IStream *iface, REFIID riid, void **ppv)
+{
+    sub_stream_t *This = impl_from_IStream(iface);
+
+    TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv);
+    *ppv = NULL;
+
+    if(IsEqualIID(riid, &IID_IUnknown) ||
+       IsEqualIID(riid, &IID_ISequentialStream) ||
+       IsEqualIID(riid, &IID_IStream))
+    {
+        IStream_AddRef(iface);
+        *ppv = iface;
+        return S_OK;
+    }
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI sub_stream_AddRef(IStream *iface)
+{
+    sub_stream_t *This = impl_from_IStream(iface);
+    LONG ref = InterlockedIncrement(&This->ref);
+
+    TRACE("(%p) ref=%d\n", This, ref);
+
+    return ref;
+}
+
+static ULONG WINAPI sub_stream_Release(IStream *iface)
+{
+    sub_stream_t *This = impl_from_IStream(iface);
+    LONG ref = InterlockedDecrement(&This->ref);
+
+    TRACE("(%p) ref=%d\n", This, ref);
+
+    if(!ref)
+    {
+        IStream_Release(This->base);
+        HeapFree(GetProcessHeap(), 0, This);
+    }
+    return ref;
+}
+
+static HRESULT WINAPI sub_stream_Read(
+        IStream* iface,
+        void *pv,
+        ULONG cb,
+        ULONG *pcbRead)
+{
+    sub_stream_t *This = impl_from_IStream(iface);
+    HRESULT hr;
+    LARGE_INTEGER tmp_pos;
+
+    TRACE("(%p, %d, %p)\n", pv, cb, pcbRead);
+
+    tmp_pos.QuadPart = This->pos.QuadPart + This->start.QuadPart;
+    IStream_Seek(This->base, tmp_pos, STREAM_SEEK_SET, NULL);
+
+    if(This->pos.QuadPart + cb > This->length.QuadPart)
+        cb = This->length.QuadPart - This->pos.QuadPart;
+
+    hr = IStream_Read(This->base, pv, cb, pcbRead);
+
+    This->pos.QuadPart += *pcbRead;
+
+    return hr;
+}
+
+static HRESULT WINAPI sub_stream_Write(
+        IStream* iface,
+        const void *pv,
+        ULONG cb,
+        ULONG *pcbWritten)
+{
+    FIXME("stub\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI sub_stream_Seek(
+        IStream* iface,
+        LARGE_INTEGER dlibMove,
+        DWORD dwOrigin,
+        ULARGE_INTEGER *plibNewPosition)
+{
+    sub_stream_t *This = impl_from_IStream(iface);
+    LARGE_INTEGER new_pos;
+
+    TRACE("(%08x.%08x, %x, %p)\n", dlibMove.u.HighPart, dlibMove.u.LowPart, dwOrigin, plibNewPosition);
+
+    switch(dwOrigin)
+    {
+    case STREAM_SEEK_SET:
+        new_pos = dlibMove;
+        break;
+    case STREAM_SEEK_CUR:
+        new_pos.QuadPart = This->pos.QuadPart + dlibMove.QuadPart;
+        break;
+    case STREAM_SEEK_END:
+        new_pos.QuadPart = This->length.QuadPart + dlibMove.QuadPart;
+        break;
+    default:
+        return STG_E_INVALIDFUNCTION;
+    }
+
+    if(new_pos.QuadPart < 0) new_pos.QuadPart = 0;
+    else if(new_pos.QuadPart > This->length.QuadPart) new_pos.QuadPart = This->length.QuadPart;
+
+    This->pos.QuadPart = new_pos.QuadPart;
+
+    if(plibNewPosition) *plibNewPosition = This->pos;
+    return S_OK;
+}
+
+static HRESULT WINAPI sub_stream_SetSize(
+        IStream* iface,
+        ULARGE_INTEGER libNewSize)
+{
+    FIXME("stub\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI sub_stream_CopyTo(
+        IStream* iface,
+        IStream *pstm,
+        ULARGE_INTEGER cb,
+        ULARGE_INTEGER *pcbRead,
+        ULARGE_INTEGER *pcbWritten)
+{
+    HRESULT        hr = S_OK;
+    BYTE           tmpBuffer[128];
+    ULONG          bytesRead, bytesWritten, copySize;
+    ULARGE_INTEGER totalBytesRead;
+    ULARGE_INTEGER totalBytesWritten;
+
+    TRACE("(%p)->(%p, %d, %p, %p)\n", iface, pstm, cb.u.LowPart, pcbRead, pcbWritten);
+
+    totalBytesRead.QuadPart = 0;
+    totalBytesWritten.QuadPart = 0;
+
+    while ( cb.QuadPart > 0 )
+    {
+        if ( cb.QuadPart >= sizeof(tmpBuffer) )
+            copySize = sizeof(tmpBuffer);
+        else
+            copySize = cb.u.LowPart;
+
+        hr = IStream_Read(iface, tmpBuffer, copySize, &bytesRead);
+        if (FAILED(hr)) break;
+
+        totalBytesRead.QuadPart += bytesRead;
+
+        if (bytesRead)
+        {
+            hr = IStream_Write(pstm, tmpBuffer, bytesRead, &bytesWritten);
+            if (FAILED(hr)) break;
+            totalBytesWritten.QuadPart += bytesWritten;
+        }
+
+        if (bytesRead != copySize)
+            cb.QuadPart = 0;
+        else
+            cb.QuadPart -= bytesRead;
+    }
+
+    if (pcbRead) pcbRead->QuadPart = totalBytesRead.QuadPart;
+    if (pcbWritten) pcbWritten->QuadPart = totalBytesWritten.QuadPart;
+
+    return hr;
+}
+
+static HRESULT WINAPI sub_stream_Commit(
+        IStream* iface,
+        DWORD grfCommitFlags)
+{
+    FIXME("stub\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI sub_stream_Revert(
+        IStream* iface)
+{
+    FIXME("stub\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI sub_stream_LockRegion(
+        IStream* iface,
+        ULARGE_INTEGER libOffset,
+        ULARGE_INTEGER cb,
+        DWORD dwLockType)
+{
+    FIXME("stub\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI sub_stream_UnlockRegion(
+        IStream* iface,
+        ULARGE_INTEGER libOffset,
+        ULARGE_INTEGER cb,
+        DWORD dwLockType)
+{
+    FIXME("stub\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI sub_stream_Stat(
+        IStream* iface,
+        STATSTG *pstatstg,
+        DWORD grfStatFlag)
+{
+    sub_stream_t *This = impl_from_IStream(iface);
+    FIXME("(%p)->(%p, %08x)\n", This, pstatstg, grfStatFlag);
+    memset(pstatstg, 0, sizeof(*pstatstg));
+    pstatstg->cbSize = This->length;
+    return S_OK;
+}
+
+static HRESULT WINAPI sub_stream_Clone(
+        IStream* iface,
+        IStream **ppstm)
+{
+    FIXME("stub\n");
+    return E_NOTIMPL;
+}
+
+static struct IStreamVtbl sub_stream_vtbl =
+{
+    sub_stream_QueryInterface,
+    sub_stream_AddRef,
+    sub_stream_Release,
+    sub_stream_Read,
+    sub_stream_Write,
+    sub_stream_Seek,
+    sub_stream_SetSize,
+    sub_stream_CopyTo,
+    sub_stream_Commit,
+    sub_stream_Revert,
+    sub_stream_LockRegion,
+    sub_stream_UnlockRegion,
+    sub_stream_Stat,
+    sub_stream_Clone
+};
+
+static HRESULT create_sub_stream(IStream *stream, ULARGE_INTEGER start, ULARGE_INTEGER length, IStream **out)
+{
+    sub_stream_t *This;
+
+    *out = NULL;
+    This = HeapAlloc(GetProcessHeap(), 0, sizeof(*This));
+    if(!This) return E_OUTOFMEMORY;
+
+    This->IStream_iface.lpVtbl = &sub_stream_vtbl;
+    This->ref = 1;
+    This->start = start;
+    This->length = length;
+    This->pos.QuadPart = 0;
+    IStream_AddRef(stream);
+    This->base = stream;
+
+    *out = &This->IStream_iface;
+    return S_OK;
+}
+
+static HRESULT get_stream_size(IStream *stream, ULARGE_INTEGER *size)
+{
+    STATSTG statstg = {NULL};
+    LARGE_INTEGER zero;
+    HRESULT hres;
+
+    hres = IStream_Stat(stream, &statstg, STATFLAG_NONAME);
+    if(SUCCEEDED(hres)) {
+        *size = statstg.cbSize;
+        return S_OK;
+    }
+
+    zero.QuadPart = 0;
+    return IStream_Seek(stream, zero, STREAM_SEEK_END, size);
+}
+
+static inline MimeBody *impl_from_IMimeBody(IMimeBody *iface)
+{
+    return CONTAINING_RECORD(iface, MimeBody, IMimeBody_iface);
+}
+
+typedef struct propschema
+{
+    IMimePropertySchema IMimePropertySchema_iface;
+    LONG ref;
+} propschema;
+
+static inline propschema *impl_from_IMimePropertySchema(IMimePropertySchema *iface)
 {
-    return (MimeBody *)((char*)iface - FIELD_OFFSET(MimeBody, lpVtbl));
+    return CONTAINING_RECORD(iface, propschema, IMimePropertySchema_iface);
 }
 
 static LPSTR strdupA(LPCSTR str)
@@ -137,7 +501,7 @@ static HRESULT copy_headers_to_buf(IStream *stm, char **ptr)
     char *buf = NULL;
     DWORD size = PARSER_BUF_SIZE, offset = 0, last_end = 0;
     HRESULT hr;
-    int done = 0;
+    BOOL done = FALSE;
 
     *ptr = NULL;
 
@@ -165,7 +529,7 @@ static HRESULT copy_headers_to_buf(IStream *stm, char **ptr)
         offset += read;
         buf[offset] = '\0';
 
-        if(read == 0) done = 1;
+        if(read == 0) done = TRUE;
 
         while(!done && (end = strstr(buf + last_end, "\r\n")))
         {
@@ -173,10 +537,10 @@ static HRESULT copy_headers_to_buf(IStream *stm, char **ptr)
             if(new_end - last_end == 2)
             {
                 LARGE_INTEGER off;
-                off.QuadPart = new_end;
-                IStream_Seek(stm, off, STREAM_SEEK_SET, NULL);
+                off.QuadPart = (LONGLONG)new_end - offset;
+                IStream_Seek(stm, off, STREAM_SEEK_CUR, NULL);
                 buf[new_end] = '\0';
-                done = 1;
+                done = TRUE;
             }
             else
                 last_end = new_end;
@@ -203,7 +567,7 @@ static header_t *read_prop(MimeBody *body, char **ptr)
 
     for(prop = default_props; prop->name; prop++)
     {
-        if(!strcasecmp(*ptr, prop->name))
+        if(!lstrcmpiA(*ptr, prop->name))
         {
             TRACE("%s: found match with default property id %d\n", *ptr, prop->id);
             break;
@@ -215,7 +579,7 @@ static header_t *read_prop(MimeBody *body, char **ptr)
         property_list_entry_t *prop_entry;
         LIST_FOR_EACH_ENTRY(prop_entry, &body->new_props, property_list_entry_t, entry)
         {
-            if(!strcasecmp(*ptr, prop_entry->prop.name))
+            if(!lstrcmpiA(*ptr, prop_entry->prop.name))
             {
                 TRACE("%s: found match with already added new property id %d\n", *ptr, prop_entry->prop.id);
                 prop = &prop_entry->prop;
@@ -271,14 +635,14 @@ static void unfold_header(char *header, int len)
 
 static char *unquote_string(const char *str)
 {
-    int quoted = 0;
+    BOOL quoted = FALSE;
     char *ret, *cp;
 
     while(*str == ' ' || *str == '\t') str++;
 
     if(*str == '"')
     {
-        quoted = 1;
+        quoted = TRUE;
         str++;
     }
     ret = strdupA(str);
@@ -334,20 +698,19 @@ static void add_param(header_t *header, const char *p)
 static void split_params(header_t *header, char *value)
 {
     char *cp = value, *start = value;
-    int in_quote = 0;
-    int done_value = 0;
+    BOOL in_quotes = FALSE, done_value = FALSE;
 
     while(*cp)
     {
-        if(!in_quote && *cp == ';')
+        if(!in_quotes && *cp == ';')
         {
             *cp = '\0';
             if(done_value) add_param(header, start);
-            done_value = 1;
+            done_value = TRUE;
             start = cp + 1;
         }
         else if(*cp == '"')
-            in_quote = !in_quote;
+            in_quotes = !in_quotes;
         cp++;
     }
     if(done_value) add_param(header, start);
@@ -388,12 +751,6 @@ static void init_content_type(MimeBody *body, header_t *header)
     char *slash;
     DWORD len;
 
-    if(header->prop->id != PID_HDR_CNTTYPE)
-    {
-        ERR("called with header %s\n", header->prop->name);
-        return;
-    }
-
     slash = strchr(header->value.u.pszVal, '/');
     if(!slash)
     {
@@ -407,6 +764,22 @@ static void init_content_type(MimeBody *body, header_t *header)
     body->content_sub_type = strdupA(slash + 1);
 }
 
+static void init_content_encoding(MimeBody *body, header_t *header)
+{
+    const char *encoding = header->value.u.pszVal;
+
+    if(!_strnicmp(encoding, "base64", -1))
+        body->encoding = IET_BASE64;
+    else if(!_strnicmp(encoding, "quoted-printable", -1))
+        body->encoding = IET_QP;
+    else if(!_strnicmp(encoding, "7bit", -1))
+        body->encoding = IET_7BIT;
+    else if(!_strnicmp(encoding, "8bit", -1))
+        body->encoding = IET_8BIT;
+    else
+        FIXME("unknown encoding %s\n", debugstr_a(encoding));
+}
+
 static HRESULT parse_headers(MimeBody *body, IStream *stm)
 {
     char *header_buf, *cur_header_ptr;
@@ -422,8 +795,14 @@ static HRESULT parse_headers(MimeBody *body, IStream *stm)
         read_value(header, &cur_header_ptr);
         list_add_tail(&body->headers, &header->entry);
 
-        if(header->prop->id == PID_HDR_CNTTYPE)
+        switch(header->prop->id) {
+        case PID_HDR_CNTTYPE:
             init_content_type(body, header);
+            break;
+        case PID_HDR_CNTXFER:
+            init_content_encoding(body, header);
+            break;
+        }
     }
 
     HeapFree(GetProcessHeap(), 0, header_buf);
@@ -443,16 +822,21 @@ static void empty_param_list(struct list *list)
     }
 }
 
+static void free_header(header_t *header)
+{
+    list_remove(&header->entry);
+    PropVariantClear(&header->value);
+    empty_param_list(&header->params);
+    heap_free(header);
+}
+
 static void empty_header_list(struct list *list)
 {
     header_t *header, *cursor2;
 
     LIST_FOR_EACH_ENTRY_SAFE(header, cursor2, list, header_t, entry)
     {
-        list_remove(&header->entry);
-        PropVariantClear(&header->value);
-        empty_param_list(&header->params);
-        HeapFree(GetProcessHeap(), 0, header);
+        free_header(header);
     }
 }
 
@@ -486,7 +870,15 @@ static HRESULT find_prop(MimeBody *body, const char *name, header_t **prop)
 
     LIST_FOR_EACH_ENTRY(header, &body->headers, header_t, entry)
     {
-        if(!strcasecmp(name, header->prop->name))
+        if(ISPIDSTR(name))
+        {
+            if(STRTOPID(name) == header->prop->id)
+            {
+                *prop = header;
+                return S_OK;
+            }
+        }
+        else if(!lstrcmpiA(name, header->prop->name))
         {
             *prop = header;
             return S_OK;
@@ -496,6 +888,33 @@ static HRESULT find_prop(MimeBody *body, const char *name, header_t **prop)
     return MIME_E_NOT_FOUND;
 }
 
+static const property_t *find_default_prop(const char *name)
+{
+    const property_t *prop_def = NULL;
+
+    for(prop_def = default_props; prop_def->name; prop_def++)
+    {
+        if(ISPIDSTR(name))
+        {
+            if(STRTOPID(name) == prop_def->id)
+            {
+                break;
+            }
+        }
+        else if(!lstrcmpiA(name, prop_def->name))
+        {
+            break;
+        }
+    }
+
+    if(prop_def->id)
+       TRACE("%s: found match with default property id %d\n", prop_def->name, prop_def->id);
+    else
+       prop_def = NULL;
+
+    return prop_def;
+}
+
 static HRESULT WINAPI MimeBody_QueryInterface(IMimeBody* iface,
                                      REFIID riid,
                                      void** ppvObject)
@@ -523,22 +942,24 @@ static HRESULT WINAPI MimeBody_QueryInterface(IMimeBody* iface,
     return E_NOINTERFACE;
 }
 
-static ULONG WINAPI MimeBody_AddRef(IMimeBodyiface)
+static ULONG WINAPI MimeBody_AddRef(IMimeBody *iface)
 {
     MimeBody *This = impl_from_IMimeBody(iface);
-    TRACE("(%p)->()\n", iface);
-    return InterlockedIncrement(&This->refs);
+    LONG ref = InterlockedIncrement(&This->ref);
+
+    TRACE("(%p) ref=%d\n", This, ref);
+
+    return ref;
 }
 
-static ULONG WINAPI MimeBody_Release(IMimeBodyiface)
+static ULONG WINAPI MimeBody_Release(IMimeBody *iface)
 {
     MimeBody *This = impl_from_IMimeBody(iface);
-    ULONG refs;
+    LONG ref = InterlockedDecrement(&This->ref);
 
-    TRACE("(%p)->()\n", iface);
+    TRACE("(%p) ref=%d\n", This, ref);
 
-    refs = InterlockedDecrement(&This->refs);
-    if (!refs)
+    if (!ref)
     {
         empty_header_list(&This->headers);
         empty_new_prop_list(&This->new_props);
@@ -551,40 +972,43 @@ static ULONG WINAPI MimeBody_Release(IMimeBody* iface)
         HeapFree(GetProcessHeap(), 0, This);
     }
 
-    return refs;
+    return ref;
 }
 
 static HRESULT WINAPI MimeBody_GetClassID(
                                  IMimeBody* iface,
                                  CLSID* pClassID)
 {
-    FIXME("stub\n");
-    return E_NOTIMPL;
-}
+    MimeBody *This = impl_from_IMimeBody(iface);
+
+    TRACE("(%p)->(%p)\n", This, pClassID);
 
+    if(!pClassID)
+        return E_INVALIDARG;
+
+    *pClassID = IID_IMimeBody;
+    return S_OK;
+}
 
 static HRESULT WINAPI MimeBody_IsDirty(
                               IMimeBody* iface)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->() stub\n", This);
     return E_NOTIMPL;
 }
 
-static HRESULT WINAPI MimeBody_Load(
-                           IMimeBody* iface,
-                           LPSTREAM pStm)
+static HRESULT WINAPI MimeBody_Load(IMimeBody *iface, IStream *pStm)
 {
     MimeBody *This = impl_from_IMimeBody(iface);
-    TRACE("(%p)->(%p)\n", iface, pStm);
+    TRACE("(%p)->(%p)\n", This, pStm);
     return parse_headers(This, pStm);
 }
 
-static HRESULT WINAPI MimeBody_Save(
-                           IMimeBody* iface,
-                           LPSTREAM pStm,
-                           BOOL fClearDirty)
+static HRESULT WINAPI MimeBody_Save(IMimeBody *iface, IStream *pStm, BOOL fClearDirty)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%p, %d)\n", This, pStm, fClearDirty);
     return E_NOTIMPL;
 }
 
@@ -592,14 +1016,16 @@ static HRESULT WINAPI MimeBody_GetSizeMax(
                                  IMimeBody* iface,
                                  ULARGE_INTEGER* pcbSize)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%p) stub\n", This, pcbSize);
     return E_NOTIMPL;
 }
 
 static HRESULT WINAPI MimeBody_InitNew(
                               IMimeBody* iface)
 {
-    TRACE("%p->()\n", iface);
+    MimeBody *This = impl_from_IMimeBody(iface);
+    TRACE("(%p)->()\n", This);
     return S_OK;
 }
 
@@ -608,8 +1034,43 @@ static HRESULT WINAPI MimeBody_GetPropInfo(
                                   LPCSTR pszName,
                                   LPMIMEPROPINFO pInfo)
 {
-    FIXME("stub\n");
-    return E_NOTIMPL;
+    MimeBody *This = impl_from_IMimeBody(iface);
+    header_t *header;
+    HRESULT hr;
+    DWORD supported = PIM_PROPID | PIM_VTDEFAULT;
+
+    TRACE("(%p)->(%s, %p) semi-stub\n", This, debugstr_a(pszName), pInfo);
+
+    if(!pszName || !pInfo)
+        return E_INVALIDARG;
+
+    TRACE("mask 0x%04x\n", pInfo->dwMask);
+
+    if(pInfo->dwMask & ~supported)
+         FIXME("Unsupported mask flags 0x%04x\n", pInfo->dwMask & ~supported);
+
+    hr = find_prop(This, pszName, &header);
+    if(hr == S_OK)
+    {
+        if(pInfo->dwMask & PIM_CHARSET)
+            pInfo->hCharset = 0;
+        if(pInfo->dwMask & PIM_FLAGS)
+            pInfo->dwFlags = 0x00000000;
+        if(pInfo->dwMask & PIM_ROWNUMBER)
+            pInfo->dwRowNumber = 0;
+        if(pInfo->dwMask & PIM_ENCODINGTYPE)
+            pInfo->ietEncoding = 0;
+        if(pInfo->dwMask & PIM_VALUES)
+            pInfo->cValues = 0;
+        if(pInfo->dwMask & PIM_PROPID)
+            pInfo->dwPropId = header->prop->id;
+        if(pInfo->dwMask & PIM_VTDEFAULT)
+            pInfo->vtDefault = header->prop->default_vt;
+        if(pInfo->dwMask & PIM_VTCURRENT)
+            pInfo->vtCurrent = 0;
+    }
+
+    return hr;
 }
 
 static HRESULT WINAPI MimeBody_SetPropInfo(
@@ -617,7 +1078,8 @@ static HRESULT WINAPI MimeBody_SetPropInfo(
                                   LPCSTR pszName,
                                   LPCMIMEPROPINFO pInfo)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%s, %p) stub\n", This, debugstr_a(pszName), pInfo);
     return E_NOTIMPL;
 }
 
@@ -628,9 +1090,15 @@ static HRESULT WINAPI MimeBody_GetProp(
                               LPPROPVARIANT pValue)
 {
     MimeBody *This = impl_from_IMimeBody(iface);
-    TRACE("(%p)->(%s, %d, %p)\n", This, pszName, dwFlags, pValue);
+    header_t *header;
+    HRESULT hr;
+
+    TRACE("(%p)->(%s, 0x%x, %p)\n", This, debugstr_a(pszName), dwFlags, pValue);
 
-    if(!strcasecmp(pszName, "att:pri-content-type"))
+    if(!pszName || !pValue)
+        return E_INVALIDARG;
+
+    if(!ISPIDSTR(pszName) && !lstrcmpiA(pszName, "att:pri-content-type"))
     {
         PropVariantClear(pValue);
         pValue->vt = VT_LPSTR;
@@ -638,8 +1106,17 @@ static HRESULT WINAPI MimeBody_GetProp(
         return S_OK;
     }
 
-    FIXME("stub!\n");
-    return E_FAIL;
+    hr = find_prop(This, pszName, &header);
+    if(hr == S_OK)
+    {
+        TRACE("type %d->%d\n", header->value.vt, pValue->vt);
+
+        hr = PropVariantChangeType(pValue, &header->value, 0,  pValue->vt);
+        if(FAILED(hr))
+            FIXME("Conversion not currently supported (%d->%d)\n", header->value.vt, pValue->vt);
+    }
+
+    return hr;
 }
 
 static HRESULT WINAPI MimeBody_SetProp(
@@ -648,8 +1125,89 @@ static HRESULT WINAPI MimeBody_SetProp(
                               DWORD dwFlags,
                               LPCPROPVARIANT pValue)
 {
-    FIXME("stub\n");
-    return E_NOTIMPL;
+    MimeBody *This = impl_from_IMimeBody(iface);
+    header_t *header;
+    HRESULT hr;
+
+    TRACE("(%p)->(%s, 0x%x, %p)\n", This, debugstr_a(pszName), dwFlags, pValue);
+
+    if(!pszName || !pValue)
+        return E_INVALIDARG;
+
+    hr = find_prop(This, pszName, &header);
+    if(hr != S_OK)
+    {
+        property_list_entry_t *prop_entry;
+        const property_t *prop = NULL;
+
+        LIST_FOR_EACH_ENTRY(prop_entry, &This->new_props, property_list_entry_t, entry)
+        {
+            if(ISPIDSTR(pszName))
+            {
+                if(STRTOPID(pszName) == prop_entry->prop.id)
+                {
+                    TRACE("Found match with already added new property id %d\n", prop_entry->prop.id);
+                    prop = &prop_entry->prop;
+                    break;
+                }
+            }
+            else if(!lstrcmpiA(pszName, prop_entry->prop.name))
+            {
+                TRACE("Found match with already added new property id %d\n", prop_entry->prop.id);
+                prop = &prop_entry->prop;
+                break;
+            }
+        }
+
+        header = HeapAlloc(GetProcessHeap(), 0, sizeof(*header));
+        if(!header)
+            return E_OUTOFMEMORY;
+
+        if(!prop)
+        {
+            const property_t *prop_def = NULL;
+            prop_entry = HeapAlloc(GetProcessHeap(), 0, sizeof(*prop_entry));
+            if(!prop_entry)
+            {
+                HeapFree(GetProcessHeap(), 0, header);
+                return E_OUTOFMEMORY;
+            }
+
+            prop_def = find_default_prop(pszName);
+            if(prop_def)
+            {
+                prop_entry->prop.name = strdupA(prop_def->name);
+                prop_entry->prop.id =  prop_def->id;
+            }
+            else
+            {
+                if(ISPIDSTR(pszName))
+                {
+                    HeapFree(GetProcessHeap(), 0, prop_entry);
+                    HeapFree(GetProcessHeap(), 0, header);
+                    return MIME_E_NOT_FOUND;
+                }
+
+                prop_entry->prop.name = strdupA(pszName);
+                prop_entry->prop.id = This->next_prop_id++;
+            }
+
+            prop_entry->prop.flags = 0;
+            prop_entry->prop.default_vt = pValue->vt;
+            list_add_tail(&This->new_props, &prop_entry->entry);
+            prop = &prop_entry->prop;
+            TRACE("Allocating new prop id %d\n", prop_entry->prop.id);
+        }
+
+        header->prop = prop;
+        PropVariantInit(&header->value);
+        list_init(&header->params);
+        list_add_tail(&This->headers, &header->entry);
+    }
+
+    PropVariantCopy(&header->value, pValue);
+
+    return S_OK;
 }
 
 static HRESULT WINAPI MimeBody_AppendProp(
@@ -658,7 +1216,8 @@ static HRESULT WINAPI MimeBody_AppendProp(
                                  DWORD dwFlags,
                                  LPPROPVARIANT pValue)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%s, 0x%x, %p) stub\n", This, debugstr_a(pszName), dwFlags, pValue);
     return E_NOTIMPL;
 }
 
@@ -666,8 +1225,27 @@ static HRESULT WINAPI MimeBody_DeleteProp(
                                  IMimeBody* iface,
                                  LPCSTR pszName)
 {
-    FIXME("stub\n");
-    return E_NOTIMPL;
+    MimeBody *This = impl_from_IMimeBody(iface);
+    header_t *cursor;
+    BOOL found;
+
+    TRACE("(%p)->(%s) stub\n", This, debugstr_a(pszName));
+
+    LIST_FOR_EACH_ENTRY(cursor, &This->headers, header_t, entry)
+    {
+        if(ISPIDSTR(pszName))
+            found = STRTOPID(pszName) == cursor->prop->id;
+        else
+            found = !lstrcmpiA(pszName, cursor->prop->name);
+
+        if(found)
+        {
+             free_header(cursor);
+             return S_OK;
+        }
+    }
+
+    return MIME_E_NOT_FOUND;
 }
 
 static HRESULT WINAPI MimeBody_CopyProps(
@@ -676,7 +1254,8 @@ static HRESULT WINAPI MimeBody_CopyProps(
                                 LPCSTR* prgszName,
                                 IMimePropertySet* pPropertySet)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%d, %p, %p) stub\n", This, cNames, prgszName, pPropertySet);
     return E_NOTIMPL;
 }
 
@@ -686,7 +1265,8 @@ static HRESULT WINAPI MimeBody_MoveProps(
                                 LPCSTR* prgszName,
                                 IMimePropertySet* pPropertySet)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%d, %p, %p) stub\n", This, cNames, prgszName, pPropertySet);
     return E_NOTIMPL;
 }
 
@@ -695,7 +1275,8 @@ static HRESULT WINAPI MimeBody_DeleteExcept(
                                    ULONG cNames,
                                    LPCSTR* prgszName)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%d, %p) stub\n", This, cNames, prgszName);
     return E_NOTIMPL;
 }
 
@@ -706,7 +1287,8 @@ static HRESULT WINAPI MimeBody_QueryProp(
                                 boolean fSubString,
                                 boolean fCaseSensitive)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%s, %s, %d, %d) stub\n", This, debugstr_a(pszName), debugstr_a(pszCriteria), fSubString, fCaseSensitive);
     return E_NOTIMPL;
 }
 
@@ -714,7 +1296,8 @@ static HRESULT WINAPI MimeBody_GetCharset(
                                  IMimeBody* iface,
                                  LPHCHARSET phCharset)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%p) stub\n", This, phCharset);
     *phCharset = NULL;
     return S_OK;
 }
@@ -724,7 +1307,8 @@ static HRESULT WINAPI MimeBody_SetCharset(
                                  HCHARSET hCharset,
                                  CSETAPPLYTYPE applytype)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%p, %d) stub\n", This, hCharset, applytype);
     return E_NOTIMPL;
 }
 
@@ -785,14 +1369,14 @@ static HRESULT WINAPI MimeBody_IsContentType(
     {
         const char *pri = This->content_pri_type;
         if(!pri) pri = "text";
-        if(strcasecmp(pri, pszPriType)) return S_FALSE;
+        if(lstrcmpiA(pri, pszPriType)) return S_FALSE;
     }
 
     if(pszSubType)
     {
         const char *sub = This->content_sub_type;
         if(!sub) sub = "plain";
-        if(strcasecmp(sub, pszSubType)) return S_FALSE;
+        if(lstrcmpiA(sub, pszSubType)) return S_FALSE;
     }
 
     return S_OK;
@@ -803,7 +1387,8 @@ static HRESULT WINAPI MimeBody_BindToObject(
                                    REFIID riid,
                                    void** ppvObject)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%s, %p) stub\n", This, debugstr_guid(riid), ppvObject);
     return E_NOTIMPL;
 }
 
@@ -811,7 +1396,8 @@ static HRESULT WINAPI MimeBody_Clone(
                             IMimeBody* iface,
                             IMimePropertySet** ppPropertySet)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%p) stub\n", This, ppPropertySet);
     return E_NOTIMPL;
 }
 
@@ -820,8 +1406,9 @@ static HRESULT WINAPI MimeBody_SetOption(
                                 const TYPEDID oid,
                                 LPCPROPVARIANT pValue)
 {
+    MimeBody *This = impl_from_IMimeBody(iface);
     HRESULT hr = E_NOTIMPL;
-    TRACE("(%p)->(%08x, %p)\n", iface, oid, pValue);
+    TRACE("(%p)->(%08x, %p)\n", This, oid, pValue);
 
     if(pValue->vt != TYPEDID_TYPE(oid))
     {
@@ -835,6 +1422,10 @@ static HRESULT WINAPI MimeBody_SetOption(
         FIXME("OID_SECURITY_HWND_OWNER (value %08x): ignoring\n", pValue->u.ulVal);
         hr = S_OK;
         break;
+    case OID_TRANSMIT_BODY_ENCODING:
+        FIXME("OID_TRANSMIT_BODY_ENCODING (value %08x): ignoring\n", pValue->u.ulVal);
+        hr = S_OK;
+        break;
     default:
         FIXME("Unhandled oid %08x\n", oid);
     }
@@ -847,7 +1438,8 @@ static HRESULT WINAPI MimeBody_GetOption(
                                 const TYPEDID oid,
                                 LPPROPVARIANT pValue)
 {
-    FIXME("(%p)->(%08x, %p): stub\n", iface, oid, pValue);
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%08x, %p): stub\n", This, oid, pValue);
     return E_NOTIMPL;
 }
 
@@ -856,7 +1448,8 @@ static HRESULT WINAPI MimeBody_EnumProps(
                                 DWORD dwFlags,
                                 IMimeEnumProperties** ppEnum)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(0x%x, %p) stub\n", This, dwFlags, ppEnum);
     return E_NOTIMPL;
 }
 
@@ -866,7 +1459,7 @@ static HRESULT WINAPI MimeBody_IsType(
 {
     MimeBody *This = impl_from_IMimeBody(iface);
 
-    TRACE("(%p)->(%d)\n", iface, bodytype);
+    TRACE("(%p)->(%d)\n", This, bodytype);
     switch(bodytype)
     {
     case IBT_EMPTY:
@@ -881,7 +1474,8 @@ static HRESULT WINAPI MimeBody_SetDisplayName(
                                      IMimeBody* iface,
                                      LPCSTR pszDisplay)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%s) stub\n", This, debugstr_a(pszDisplay));
     return E_NOTIMPL;
 }
 
@@ -889,7 +1483,8 @@ static HRESULT WINAPI MimeBody_GetDisplayName(
                                      IMimeBody* iface,
                                      LPSTR* ppszDisplay)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%p) stub\n", This, ppszDisplay);
     return E_NOTIMPL;
 }
 
@@ -935,7 +1530,8 @@ static HRESULT WINAPI MimeBody_GetEstimatedSize(
                                        ENCODINGTYPE ietEncoding,
                                        ULONG* pcbSize)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%d, %p) stub\n", This, ietEncoding, pcbSize);
     return E_NOTIMPL;
 }
 
@@ -944,21 +1540,210 @@ static HRESULT WINAPI MimeBody_GetDataHere(
                                   ENCODINGTYPE ietEncoding,
                                   IStream* pStream)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%d, %p) stub\n", This, ietEncoding, pStream);
     return E_NOTIMPL;
 }
 
+static const signed char base64_decode_table[] =
+{
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x00 */
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x10 */
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 0x20 */
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 0x30 */
+    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, /* 0x40 */
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 0x50 */
+    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 0x60 */
+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1  /* 0x70 */
+};
+
+static HRESULT decode_base64(IStream *input, IStream **ret_stream)
+{
+    const unsigned char *ptr, *end;
+    unsigned char buf[1024];
+    LARGE_INTEGER pos;
+    unsigned char *ret;
+    unsigned char in[4];
+    IStream *output;
+    DWORD size;
+    int n = 0;
+    HRESULT hres;
+
+    pos.QuadPart = 0;
+    hres = IStream_Seek(input, pos, STREAM_SEEK_SET, NULL);
+    if(FAILED(hres))
+        return hres;
+
+    hres = CreateStreamOnHGlobal(NULL, TRUE, &output);
+    if(FAILED(hres))
+        return hres;
+
+    while(1) {
+        hres = IStream_Read(input, buf, sizeof(buf), &size);
+        if(FAILED(hres) || !size)
+            break;
+
+        ptr = ret = buf;
+        end = buf + size;
+
+        while(1) {
+            /* skip invalid chars */
+            while(ptr < end && (*ptr >= ARRAY_SIZE(base64_decode_table)
+                                || base64_decode_table[*ptr] == -1))
+                ptr++;
+            if(ptr == end)
+                break;
+
+            in[n++] = base64_decode_table[*ptr++];
+            switch(n) {
+            case 2:
+                *ret++ = in[0] << 2 | in[1] >> 4;
+                continue;
+            case 3:
+                *ret++ = in[1] << 4 | in[2] >> 2;
+                continue;
+            case 4:
+                *ret++ = ((in[2] << 6) & 0xc0) | in[3];
+                n = 0;
+            }
+        }
+
+        if(ret > buf) {
+            hres = IStream_Write(output, buf, ret - buf, NULL);
+            if(FAILED(hres))
+                break;
+        }
+    }
+
+    if(SUCCEEDED(hres))
+        hres = IStream_Seek(output, pos, STREAM_SEEK_SET, NULL);
+    if(FAILED(hres)) {
+        IStream_Release(output);
+        return hres;
+    }
+
+    *ret_stream = output;
+    return S_OK;
+}
+
+static int hex_digit(char c)
+{
+    if('0' <= c && c <= '9')
+        return c - '0';
+    if('A' <= c && c <= 'F')
+        return c - 'A' + 10;
+    if('a' <= c && c <= 'f')
+        return c - 'a' + 10;
+    return -1;
+}
+
+static HRESULT decode_qp(IStream *input, IStream **ret_stream)
+{
+    const unsigned char *ptr, *end;
+    unsigned char *ret, prev = 0;
+    unsigned char buf[1024];
+    LARGE_INTEGER pos;
+    IStream *output;
+    DWORD size;
+    int n = -1;
+    HRESULT hres;
+
+    pos.QuadPart = 0;
+    hres = IStream_Seek(input, pos, STREAM_SEEK_SET, NULL);
+    if(FAILED(hres))
+        return hres;
+
+    hres = CreateStreamOnHGlobal(NULL, TRUE, &output);
+    if(FAILED(hres))
+        return hres;
+
+    while(1) {
+        hres = IStream_Read(input, buf, sizeof(buf), &size);
+        if(FAILED(hres) || !size)
+            break;
+
+        ptr = ret = buf;
+        end = buf + size;
+
+        while(ptr < end) {
+            unsigned char byte = *ptr++;
+
+            switch(n) {
+            case -1:
+                if(byte == '=')
+                    n = 0;
+                else
+                    *ret++ = byte;
+                continue;
+            case 0:
+                prev = byte;
+                n = 1;
+                continue;
+            case 1:
+                if(prev != '\r' || byte != '\n') {
+                    int h1 = hex_digit(prev), h2 = hex_digit(byte);
+                    if(h1 != -1 && h2 != -1)
+                        *ret++ = (h1 << 4) | h2;
+                    else
+                        *ret++ = '=';
+                }
+                n = -1;
+                continue;
+            }
+        }
+
+        if(ret > buf) {
+            hres = IStream_Write(output, buf, ret - buf, NULL);
+            if(FAILED(hres))
+                break;
+        }
+    }
+
+    if(SUCCEEDED(hres))
+        hres = IStream_Seek(output, pos, STREAM_SEEK_SET, NULL);
+    if(FAILED(hres)) {
+        IStream_Release(output);
+        return hres;
+    }
+
+    *ret_stream = output;
+    return S_OK;
+}
+
 static HRESULT WINAPI MimeBody_GetData(
                               IMimeBody* iface,
                               ENCODINGTYPE ietEncoding,
                               IStream** ppStream)
 {
     MimeBody *This = impl_from_IMimeBody(iface);
-    FIXME("(%p)->(%d, %p). Ignoring encoding type.\n", This, ietEncoding, ppStream);
+    ULARGE_INTEGER start, size;
+    HRESULT hres;
 
-    *ppStream = This->data;
-    IStream_AddRef(*ppStream);
-    return S_OK;
+    TRACE("(%p)->(%d %p)\n", This, ietEncoding, ppStream);
+
+    if(This->encoding != ietEncoding) {
+        switch(This->encoding) {
+        case IET_BASE64:
+            hres = decode_base64(This->data, ppStream);
+            break;
+        case IET_QP:
+            hres = decode_qp(This->data, ppStream);
+            break;
+        default:
+            FIXME("Decoding %d is not supported.\n", This->encoding);
+            hres = S_FALSE;
+        }
+        if(ietEncoding != IET_BINARY)
+            FIXME("Encoding %d is not supported.\n", ietEncoding);
+        if(hres != S_FALSE)
+            return hres;
+    }
+
+    start.QuadPart = 0;
+    hres = get_stream_size(This->data, &size);
+    if(SUCCEEDED(hres))
+        hres = create_sub_stream(This->data, start, size, ppStream);
+    return hres;
 }
 
 static HRESULT WINAPI MimeBody_SetData(
@@ -982,7 +1767,7 @@ static HRESULT WINAPI MimeBody_SetData(
     }
 
     if(This->data)
-        FIXME("release old data\n");
+        release_data(&This->data_iid, This->data);
 
     This->data_iid = *riid;
     This->data = pvObject;
@@ -999,7 +1784,8 @@ static HRESULT WINAPI MimeBody_SetData(
 static HRESULT WINAPI MimeBody_EmptyData(
                                 IMimeBody* iface)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->() stub\n", This);
     return E_NOTIMPL;
 }
 
@@ -1007,7 +1793,8 @@ static HRESULT WINAPI MimeBody_CopyTo(
                              IMimeBody* iface,
                              IMimeBody* pBody)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%p) stub\n", This, pBody);
     return E_NOTIMPL;
 }
 
@@ -1015,7 +1802,8 @@ static HRESULT WINAPI MimeBody_GetTransmitInfo(
                                       IMimeBody* iface,
                                       LPTRANSMITINFO pTransmitInfo)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%p) stub\n", This, pTransmitInfo);
     return E_NOTIMPL;
 }
 
@@ -1024,7 +1812,8 @@ static HRESULT WINAPI MimeBody_SaveToFile(
                                  ENCODINGTYPE ietEncoding,
                                  LPCSTR pszFilePath)
 {
-    FIXME("stub\n");
+    MimeBody *This = impl_from_IMimeBody(iface);
+    FIXME("(%p)->(%d, %s) stub\n", This, ietEncoding, debugstr_a(pszFilePath));
     return E_NOTIMPL;
 }
 
@@ -1035,6 +1824,9 @@ static HRESULT WINAPI MimeBody_GetHandle(
     MimeBody *This = impl_from_IMimeBody(iface);
     TRACE("(%p)->(%p)\n", iface, phBody);
 
+    if(!phBody)
+        return E_INVALIDARG;
+
     *phBody = This->handle;
     return This->handle ? S_OK : MIME_E_NO_DATA;
 }
@@ -1084,343 +1876,71 @@ static IMimeBodyVtbl body_vtbl =
     MimeBody_GetTransmitInfo,
     MimeBody_SaveToFile,
     MimeBody_GetHandle
-};
-
-static HRESULT MimeBody_set_offsets(MimeBody *body, const BODYOFFSETS *offsets)
-{
-    TRACE("setting offsets to %d, %d, %d, %d\n", offsets->cbBoundaryStart,
-          offsets->cbHeaderStart, offsets->cbBodyStart, offsets->cbBodyEnd);
-
-    body->body_offsets = *offsets;
-    return S_OK;
-}
-
-#define FIRST_CUSTOM_PROP_ID 0x100
-
-HRESULT MimeBody_create(IUnknown *outer, void **obj)
-{
-    MimeBody *This;
-    BODYOFFSETS body_offsets;
-
-    *obj = NULL;
-
-    if(outer) return CLASS_E_NOAGGREGATION;
-
-    This = HeapAlloc(GetProcessHeap(), 0, sizeof(*This));
-    if (!This) return E_OUTOFMEMORY;
-
-    This->lpVtbl = &body_vtbl;
-    This->refs = 1;
-    This->handle = NULL;
-    list_init(&This->headers);
-    list_init(&This->new_props);
-    This->next_prop_id = FIRST_CUSTOM_PROP_ID;
-    This->content_pri_type = NULL;
-    This->content_sub_type = NULL;
-    This->encoding = IET_7BIT;
-    This->data = NULL;
-    This->data_iid = IID_NULL;
-
-    body_offsets.cbBoundaryStart = body_offsets.cbHeaderStart = 0;
-    body_offsets.cbBodyStart     = body_offsets.cbBodyEnd     = 0;
-    MimeBody_set_offsets(This, &body_offsets);
-
-    *obj = &This->lpVtbl;
-    return S_OK;
-}
-
-typedef struct
-{
-    IStreamVtbl *lpVtbl;
-    LONG refs;
-
-    IStream *base;
-    ULARGE_INTEGER pos, start, length;
-} sub_stream_t;
-
-static inline sub_stream_t *impl_from_IStream( IStream *iface )
-{
-    return (sub_stream_t *)((char*)iface - FIELD_OFFSET(sub_stream_t, lpVtbl));
-}
-
-static HRESULT WINAPI sub_stream_QueryInterface(
-        IStream* iface,
-        REFIID riid,
-        void **ppvObject)
-{
-    sub_stream_t *This = impl_from_IStream(iface);
-
-    TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppvObject);
-    *ppvObject = NULL;
-
-    if(IsEqualIID(riid, &IID_IUnknown) ||
-       IsEqualIID(riid, &IID_ISequentialStream) ||
-       IsEqualIID(riid, &IID_IStream))
-    {
-        IStream_AddRef(iface);
-        *ppvObject = iface;
-        return S_OK;
-    }
-    return E_NOINTERFACE;
-}
-
-static ULONG WINAPI sub_stream_AddRef(
-         IStream* iface)
-{
-    sub_stream_t *This = impl_from_IStream(iface);
-
-    TRACE("(%p)\n", This);
-    return InterlockedIncrement(&This->refs);
-}
-
-static ULONG WINAPI  sub_stream_Release(
-        IStream* iface)
-{
-    sub_stream_t *This = impl_from_IStream(iface);
-    LONG refs;
-
-    TRACE("(%p)\n", This);
-    refs = InterlockedDecrement(&This->refs);
-    if(!refs)
-    {
-        IStream_Release(This->base);
-        HeapFree(GetProcessHeap(), 0, This);
-    }
-    return refs;
-}
-
-static HRESULT WINAPI sub_stream_Read(
-        IStream* iface,
-        void *pv,
-        ULONG cb,
-        ULONG *pcbRead)
-{
-    sub_stream_t *This = impl_from_IStream(iface);
-    HRESULT hr;
-    ULARGE_INTEGER base_pos;
-    LARGE_INTEGER tmp_pos;
-
-    TRACE("(%p, %d, %p)\n", pv, cb, pcbRead);
-
-    tmp_pos.QuadPart = 0;
-    IStream_Seek(This->base, tmp_pos, STREAM_SEEK_CUR, &base_pos);
-    tmp_pos.QuadPart = This->pos.QuadPart + This->start.QuadPart;
-    IStream_Seek(This->base, tmp_pos, STREAM_SEEK_SET, NULL);
-
-    if(This->pos.QuadPart + cb > This->length.QuadPart)
-        cb = This->length.QuadPart - This->pos.QuadPart;
-
-    hr = IStream_Read(This->base, pv, cb, pcbRead);
-
-    This->pos.QuadPart += *pcbRead;
-
-    tmp_pos.QuadPart = base_pos.QuadPart;
-    IStream_Seek(This->base, tmp_pos, STREAM_SEEK_SET, NULL);
-
-    return hr;
-}
-
-static HRESULT WINAPI sub_stream_Write(
-        IStream* iface,
-        const void *pv,
-        ULONG cb,
-        ULONG *pcbWritten)
-{
-    FIXME("stub\n");
-    return E_NOTIMPL;
-}
-
-static HRESULT WINAPI sub_stream_Seek(
-        IStream* iface,
-        LARGE_INTEGER dlibMove,
-        DWORD dwOrigin,
-        ULARGE_INTEGER *plibNewPosition)
-{
-    sub_stream_t *This = impl_from_IStream(iface);
-    LARGE_INTEGER new_pos;
-
-    TRACE("(%08x.%08x, %x, %p)\n", dlibMove.u.HighPart, dlibMove.u.LowPart, dwOrigin, plibNewPosition);
-
-    switch(dwOrigin)
-    {
-    case STREAM_SEEK_SET:
-        new_pos = dlibMove;
-        break;
-    case STREAM_SEEK_CUR:
-        new_pos.QuadPart = This->pos.QuadPart + dlibMove.QuadPart;
-        break;
-    case STREAM_SEEK_END:
-        new_pos.QuadPart = This->length.QuadPart + dlibMove.QuadPart;
-        break;
-    default:
-        return STG_E_INVALIDFUNCTION;
-    }
-
-    if(new_pos.QuadPart < 0) new_pos.QuadPart = 0;
-    else if(new_pos.QuadPart > This->length.QuadPart) new_pos.QuadPart = This->length.QuadPart;
-
-    This->pos.QuadPart = new_pos.QuadPart;
-
-    if(plibNewPosition) *plibNewPosition = This->pos;
-    return S_OK;
-}
-
-static HRESULT WINAPI sub_stream_SetSize(
-        IStream* iface,
-        ULARGE_INTEGER libNewSize)
-{
-    FIXME("stub\n");
-    return E_NOTIMPL;
-}
-
-static HRESULT WINAPI sub_stream_CopyTo(
-        IStream* iface,
-        IStream *pstm,
-        ULARGE_INTEGER cb,
-        ULARGE_INTEGER *pcbRead,
-        ULARGE_INTEGER *pcbWritten)
-{
-    HRESULT        hr = S_OK;
-    BYTE           tmpBuffer[128];
-    ULONG          bytesRead, bytesWritten, copySize;
-    ULARGE_INTEGER totalBytesRead;
-    ULARGE_INTEGER totalBytesWritten;
-
-    TRACE("(%p)->(%p, %d, %p, %p)\n", iface, pstm, cb.u.LowPart, pcbRead, pcbWritten);
-
-    totalBytesRead.QuadPart = 0;
-    totalBytesWritten.QuadPart = 0;
-
-    while ( cb.QuadPart > 0 )
-    {
-        if ( cb.QuadPart >= sizeof(tmpBuffer) )
-            copySize = sizeof(tmpBuffer);
-        else
-            copySize = cb.u.LowPart;
-
-        hr = IStream_Read(iface, tmpBuffer, copySize, &bytesRead);
-        if (FAILED(hr)) break;
-
-        totalBytesRead.QuadPart += bytesRead;
-
-        if (bytesRead)
-        {
-            hr = IStream_Write(pstm, tmpBuffer, bytesRead, &bytesWritten);
-            if (FAILED(hr)) break;
-            totalBytesWritten.QuadPart += bytesWritten;
-        }
-
-        if (bytesRead != copySize)
-            cb.QuadPart = 0;
-        else
-            cb.QuadPart -= bytesRead;
-    }
-
-    if (pcbRead) pcbRead->QuadPart = totalBytesRead.QuadPart;
-    if (pcbWritten) pcbWritten->QuadPart = totalBytesWritten.QuadPart;
-
-    return hr;
-}
-
-static HRESULT WINAPI sub_stream_Commit(
-        IStream* iface,
-        DWORD grfCommitFlags)
-{
-    FIXME("stub\n");
-    return E_NOTIMPL;
-}
-
-static HRESULT WINAPI sub_stream_Revert(
-        IStream* iface)
-{
-    FIXME("stub\n");
-    return E_NOTIMPL;
-}
-
-static HRESULT WINAPI sub_stream_LockRegion(
-        IStream* iface,
-        ULARGE_INTEGER libOffset,
-        ULARGE_INTEGER cb,
-        DWORD dwLockType)
-{
-    FIXME("stub\n");
-    return E_NOTIMPL;
-}
-
-static HRESULT WINAPI sub_stream_UnlockRegion(
-        IStream* iface,
-        ULARGE_INTEGER libOffset,
-        ULARGE_INTEGER cb,
-        DWORD dwLockType)
-{
-    FIXME("stub\n");
-    return E_NOTIMPL;
-}
+};
 
-static HRESULT WINAPI sub_stream_Stat(
-        IStream* iface,
-        STATSTG *pstatstg,
-        DWORD grfStatFlag)
+static HRESULT MimeBody_set_offsets(MimeBody *body, const BODYOFFSETS *offsets)
 {
-    sub_stream_t *This = impl_from_IStream(iface);
-    FIXME("(%p)->(%p, %08x)\n", This, pstatstg, grfStatFlag);
-    memset(pstatstg, 0, sizeof(*pstatstg));
-    pstatstg->cbSize = This->length;
-    return S_OK;
-}
+    TRACE("setting offsets to %d, %d, %d, %d\n", offsets->cbBoundaryStart,
+          offsets->cbHeaderStart, offsets->cbBodyStart, offsets->cbBodyEnd);
 
-static HRESULT WINAPI sub_stream_Clone(
-        IStream* iface,
-        IStream **ppstm)
-{
-    FIXME("stub\n");
-    return E_NOTIMPL;
+    body->body_offsets = *offsets;
+    return S_OK;
 }
 
-static struct IStreamVtbl sub_stream_vtbl =
-{
-    sub_stream_QueryInterface,
-    sub_stream_AddRef,
-    sub_stream_Release,
-    sub_stream_Read,
-    sub_stream_Write,
-    sub_stream_Seek,
-    sub_stream_SetSize,
-    sub_stream_CopyTo,
-    sub_stream_Commit,
-    sub_stream_Revert,
-    sub_stream_LockRegion,
-    sub_stream_UnlockRegion,
-    sub_stream_Stat,
-    sub_stream_Clone
-};
+#define FIRST_CUSTOM_PROP_ID 0x100
 
-static HRESULT create_sub_stream(IStream *stream, ULARGE_INTEGER start, ULARGE_INTEGER length, IStream **out)
+static MimeBody *mimebody_create(void)
 {
-    sub_stream_t *This;
+    MimeBody *This;
+    BODYOFFSETS body_offsets;
 
-    *out = NULL;
     This = HeapAlloc(GetProcessHeap(), 0, sizeof(*This));
-    if(!This) return E_OUTOFMEMORY;
+    if (!This)
+        return NULL;
 
-    This->lpVtbl = &sub_stream_vtbl;
-    This->refs = 1;
-    This->start = start;
-    This->length = length;
-    This->pos.QuadPart = 0;
-    IStream_AddRef(stream);
-    This->base = stream;
+    This->IMimeBody_iface.lpVtbl = &body_vtbl;
+    This->ref = 1;
+    This->handle = NULL;
+    list_init(&This->headers);
+    list_init(&This->new_props);
+    This->next_prop_id = FIRST_CUSTOM_PROP_ID;
+    This->content_pri_type = NULL;
+    This->content_sub_type = NULL;
+    This->encoding = IET_7BIT;
+    This->data = NULL;
+    This->data_iid = IID_NULL;
 
-    *out = (IStream*)&This->lpVtbl;
-    return S_OK;
+    body_offsets.cbBoundaryStart = body_offsets.cbHeaderStart = 0;
+    body_offsets.cbBodyStart     = body_offsets.cbBodyEnd     = 0;
+    MimeBody_set_offsets(This, &body_offsets);
+
+    return This;
 }
 
+HRESULT MimeBody_create(IUnknown *outer, void **ppv)
+{
+    MimeBody *mb;
+
+    if(outer)
+        return CLASS_E_NOAGGREGATION;
+
+    if ((mb = mimebody_create()))
+    {
+        *ppv = &mb->IMimeBody_iface;
+        return S_OK;
+    }
+    else
+    {
+        *ppv = NULL;
+        return E_OUTOFMEMORY;
+    }
+}
 
 typedef struct body_t
 {
     struct list entry;
     DWORD index;
-    IMimeBody *mime_body;
+    MimeBody *mime_body;
 
     struct body_t *parent;
     struct list children;
@@ -1428,15 +1948,19 @@ typedef struct body_t
 
 typedef struct MimeMessage
 {
-    const IMimeMessageVtbl *lpVtbl;
-
-    LONG refs;
+    IMimeMessage IMimeMessage_iface;
+    LONG ref;
     IStream *stream;
 
     struct list body_tree;
     DWORD next_index;
 } MimeMessage;
 
+static inline MimeMessage *impl_from_IMimeMessage(IMimeMessage *iface)
+{
+    return CONTAINING_RECORD(iface, MimeMessage, IMimeMessage_iface);
+}
+
 static HRESULT WINAPI MimeMessage_QueryInterface(IMimeMessage *iface, REFIID riid, void **ppv)
 {
     TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv);
@@ -1448,7 +1972,7 @@ static HRESULT WINAPI MimeMessage_QueryInterface(IMimeMessage *iface, REFIID rii
         IsEqualIID(riid, &IID_IMimeMessage))
     {
         *ppv = iface;
-        IUnknown_AddRef(iface);
+        IMimeMessage_AddRef(iface);
         return S_OK;
     }
 
@@ -1459,9 +1983,12 @@ static HRESULT WINAPI MimeMessage_QueryInterface(IMimeMessage *iface, REFIID rii
 
 static ULONG WINAPI MimeMessage_AddRef(IMimeMessage *iface)
 {
-    MimeMessage *This = (MimeMessage *)iface;
-    TRACE("(%p)->()\n", iface);
-    return InterlockedIncrement(&This->refs);
+    MimeMessage *This = impl_from_IMimeMessage(iface);
+    ULONG ref = InterlockedIncrement(&This->ref);
+
+    TRACE("(%p) ref=%d\n", This, ref);
+
+    return ref;
 }
 
 static void empty_body_list(struct list *list)
@@ -1471,20 +1998,19 @@ static void empty_body_list(struct list *list)
     {
         empty_body_list(&body->children);
         list_remove(&body->entry);
-        IMimeBody_Release(body->mime_body);
+        IMimeBody_Release(&body->mime_body->IMimeBody_iface);
         HeapFree(GetProcessHeap(), 0, body);
     }
 }
 
 static ULONG WINAPI MimeMessage_Release(IMimeMessage *iface)
 {
-    MimeMessage *This = (MimeMessage *)iface;
-    ULONG refs;
+    MimeMessage *This = impl_from_IMimeMessage(iface);
+    ULONG ref = InterlockedDecrement(&This->ref);
 
-    TRACE("(%p)->()\n", iface);
+    TRACE("(%p) ref=%d\n", This, ref);
 
-    refs = InterlockedDecrement(&This->refs);
-    if (!refs)
+    if (!ref)
     {
         empty_body_list(&This->body_tree);
 
@@ -1492,7 +2018,7 @@ static ULONG WINAPI MimeMessage_Release(IMimeMessage *iface)
         HeapFree(GetProcessHeap(), 0, This);
     }
 
-    return refs;
+    return ref;
 }
 
 /*** IPersist methods ***/
@@ -1512,7 +2038,7 @@ static HRESULT WINAPI MimeMessage_IsDirty(
     return E_NOTIMPL;
 }
 
-static body_t *new_body_entry(IMimeBody *mime_body, DWORD index, body_t *parent)
+static body_t *new_body_entry(MimeBody *mime_body, DWORD index, body_t *parent)
 {
     body_t *body = HeapAlloc(GetProcessHeap(), 0, sizeof(*body));
     if(body)
@@ -1521,6 +2047,8 @@ static body_t *new_body_entry(IMimeBody *mime_body, DWORD index, body_t *parent)
         body->index = index;
         list_init(&body->children);
         body->parent = parent;
+
+        mime_body->handle = UlongToHandle(body->index);
     }
     return body;
 }
@@ -1534,18 +2062,16 @@ typedef struct
 static HRESULT create_body_offset_list(IStream *stm, const char *boundary, struct list *body_offsets)
 {
     HRESULT hr;
-    DWORD read;
+    DWORD read, boundary_start;
     int boundary_len = strlen(boundary);
-    char *buf, *nl_boundary, *ptr, *overlap;
+    char *buf, *ptr, *overlap;
     DWORD start = 0, overlap_no;
     offset_entry_t *cur_body = NULL;
+    BOOL is_first_line = TRUE;
     ULARGE_INTEGER cur;
     LARGE_INTEGER zero;
 
     list_init(body_offsets);
-    nl_boundary = HeapAlloc(GetProcessHeap(), 0, 4 + boundary_len + 1);
-    memcpy(nl_boundary, "\r\n--", 4);
-    memcpy(nl_boundary + 4, boundary, boundary_len + 1);
 
     overlap_no = boundary_len + 5;
 
@@ -1562,90 +2088,103 @@ static HRESULT create_body_offset_list(IStream *stm, const char *boundary, struc
         overlap[read] = '\0';
 
         ptr = buf;
-        do {
-            ptr = strstr(ptr, nl_boundary);
-            if(ptr)
-            {
-                DWORD boundary_start = start + ptr - buf;
-                char *end = ptr + boundary_len + 4;
-
-                if(*end == '\0' || *(end + 1) == '\0')
+        while(1) {
+            if(is_first_line) {
+                is_first_line = FALSE;
+            }else {
+                ptr = strstr(ptr, "\r\n");
+                if(!ptr)
                     break;
+                ptr += 2;
+            }
+
+            boundary_start = start + ptr - buf;
+
+            if(*ptr == '-' && *(ptr + 1) == '-' && !memcmp(ptr + 2, boundary, boundary_len)) {
+                ptr += boundary_len + 2;
 
-                if(*end == '\r' && *(end + 1) == '\n')
+                if(*ptr == '\r' && *(ptr + 1) == '\n')
                 {
+                    ptr += 2;
                     if(cur_body)
                     {
-                        cur_body->offsets.cbBodyEnd = boundary_start;
+                        cur_body->offsets.cbBodyEnd = boundary_start - 2;
                         list_add_tail(body_offsets, &cur_body->entry);
                     }
                     cur_body = HeapAlloc(GetProcessHeap(), 0, sizeof(*cur_body));
-                    cur_body->offsets.cbBoundaryStart = boundary_start + 2; /* doesn't including the leading \r\n */
-                    cur_body->offsets.cbHeaderStart = boundary_start + boundary_len + 6;
+                    cur_body->offsets.cbBoundaryStart = boundary_start;
+                    cur_body->offsets.cbHeaderStart = start + ptr - buf;
                 }
-                else if(*end == '-' && *(end + 1) == '-')
+                else if(*ptr == '-' && *(ptr + 1) == '-')
                 {
                     if(cur_body)
                     {
-                        cur_body->offsets.cbBodyEnd = boundary_start;
+                        cur_body->offsets.cbBodyEnd = boundary_start - 2;
                         list_add_tail(body_offsets, &cur_body->entry);
                         goto end;
                     }
                 }
-                ptr = end + 2;
             }
-        } while(ptr);
+        }
 
         if(overlap == buf) /* 1st iteration */
         {
-            memcpy(buf, buf + PARSER_BUF_SIZE - overlap_no, overlap_no);
+            memmove(buf, buf + PARSER_BUF_SIZE - overlap_no, overlap_no);
             overlap = buf + overlap_no;
             start += read - overlap_no;
         }
         else
         {
-            memcpy(buf, buf + PARSER_BUF_SIZE, overlap_no);
+            memmove(buf, buf + PARSER_BUF_SIZE, overlap_no);
             start += read;
         }
     } while(1);
 
 end:
-    HeapFree(GetProcessHeap(), 0, nl_boundary);
     HeapFree(GetProcessHeap(), 0, buf);
     return hr;
 }
 
 static body_t *create_sub_body(MimeMessage *msg, IStream *pStm, BODYOFFSETS *offset, body_t *parent)
 {
-    IMimeBody *mime_body;
+    ULARGE_INTEGER start, length;
+    MimeBody *mime_body;
     HRESULT hr;
     body_t *body;
-    ULARGE_INTEGER cur;
-    LARGE_INTEGER zero;
+    LARGE_INTEGER pos;
+
+    pos.QuadPart = offset->cbHeaderStart;
+    IStream_Seek(pStm, pos, STREAM_SEEK_SET, NULL);
+
+    mime_body = mimebody_create();
+    IMimeBody_Load(&mime_body->IMimeBody_iface, pStm);
+
+    pos.QuadPart = 0;
+    hr = IStream_Seek(pStm, pos, STREAM_SEEK_CUR, &start);
+    offset->cbBodyStart = start.QuadPart;
+    if (parent) MimeBody_set_offsets(mime_body, offset);
+
+    length.QuadPart = offset->cbBodyEnd - offset->cbBodyStart;
+    create_sub_stream(pStm, start, length, (IStream**)&mime_body->data);
+    mime_body->data_iid = IID_IStream;
 
-    MimeBody_create(NULL, (void**)&mime_body);
-    IMimeBody_Load(mime_body, pStm);
-    zero.QuadPart = 0;
-    hr = IStream_Seek(pStm, zero, STREAM_SEEK_CUR, &cur);
-    offset->cbBodyStart = cur.u.LowPart + offset->cbHeaderStart;
-    if(parent) MimeBody_set_offsets(impl_from_IMimeBody(mime_body), offset);
-    IMimeBody_SetData(mime_body, IET_BINARY, NULL, NULL, &IID_IStream, pStm);
     body = new_body_entry(mime_body, msg->next_index++, parent);
 
-    if(IMimeBody_IsContentType(mime_body, "multipart", NULL) == S_OK)
+    if(IMimeBody_IsContentType(&mime_body->IMimeBody_iface, "multipart", NULL) == S_OK)
     {
         MIMEPARAMINFO *param_info;
         ULONG count, i;
         IMimeAllocator *alloc;
 
-        hr = IMimeBody_GetParameters(mime_body, "Content-Type", &count, &param_info);
+        hr = IMimeBody_GetParameters(&mime_body->IMimeBody_iface, "Content-Type", &count,
+                &param_info);
         if(hr != S_OK || count == 0) return body;
 
         MimeOleGetAllocator(&alloc);
 
         for(i = 0; i < count; i++)
         {
-            if(!strcasecmp(param_info[i].pszName, "boundary"))
+            if(!lstrcmpiA(param_info[i].pszName, "boundary"))
             {
                 struct list offset_list;
                 offset_entry_t *cur, *cursor2;
@@ -1653,14 +2192,8 @@ static body_t *create_sub_body(MimeMessage *msg, IStream *pStm, BODYOFFSETS *off
                 LIST_FOR_EACH_ENTRY_SAFE(cur, cursor2, &offset_list, offset_entry_t, entry)
                 {
                     body_t *sub_body;
-                    IStream *sub_stream;
-                    ULARGE_INTEGER start, length;
-
-                    start.QuadPart = cur->offsets.cbHeaderStart;
-                    length.QuadPart = cur->offsets.cbBodyEnd - cur->offsets.cbHeaderStart;
-                    create_sub_stream(pStm, start, length, &sub_stream);
-                    sub_body = create_sub_body(msg, sub_stream, &cur->offsets, body);
-                    IStream_Release(sub_stream);
+
+                    sub_body = create_sub_body(msg, pStm, &cur->offsets, body);
                     list_add_tail(&body->children, &sub_body->entry);
                     list_remove(&cur->entry);
                     HeapFree(GetProcessHeap(), 0, cur);
@@ -1674,11 +2207,9 @@ static body_t *create_sub_body(MimeMessage *msg, IStream *pStm, BODYOFFSETS *off
     return body;
 }
 
-static HRESULT WINAPI MimeMessage_Load(
-    IMimeMessage *iface,
-    LPSTREAM pStm)
+static HRESULT WINAPI MimeMessage_Load(IMimeMessage *iface, IStream *pStm)
 {
-    MimeMessage *This = (MimeMessage *)iface;
+    MimeMessage *This = impl_from_IMimeMessage(iface);
     body_t *root_body;
     BODYOFFSETS offsets;
     ULARGE_INTEGER cur;
@@ -1692,6 +2223,8 @@ static HRESULT WINAPI MimeMessage_Load(
         return E_FAIL;
     }
 
+    empty_body_list(&This->body_tree);
+
     IStream_AddRef(pStm);
     This->stream = pStm;
     offsets.cbBoundaryStart = offsets.cbHeaderStart = 0;
@@ -1702,17 +2235,14 @@ static HRESULT WINAPI MimeMessage_Load(
     zero.QuadPart = 0;
     IStream_Seek(pStm, zero, STREAM_SEEK_END, &cur);
     offsets.cbBodyEnd = cur.u.LowPart;
-    MimeBody_set_offsets(impl_from_IMimeBody(root_body->mime_body), &offsets);
+    MimeBody_set_offsets(root_body->mime_body, &offsets);
 
     list_add_head(&This->body_tree, &root_body->entry);
 
     return S_OK;
 }
 
-static HRESULT WINAPI MimeMessage_Save(
-    IMimeMessage *iface,
-    LPSTREAM pStm,
-    BOOL fClearDirty)
+static HRESULT WINAPI MimeMessage_Save(IMimeMessage *iface, IStream *pStm, BOOL fClearDirty)
 {
     FIXME("(%p)->(%p, %s)\n", iface, pStm, fClearDirty ? "TRUE" : "FALSE");
     return E_NOTIMPL;
@@ -1734,12 +2264,11 @@ static HRESULT WINAPI MimeMessage_InitNew(
 }
 
 /*** IMimeMessageTree methods ***/
-static HRESULT WINAPI MimeMessage_GetMessageSource(
-    IMimeMessage *iface,
-    IStream **ppStream,
-    DWORD dwFlags)
+static HRESULT WINAPI MimeMessage_GetMessageSource(IMimeMessage *iface, IStream **ppStream,
+        DWORD dwFlags)
 {
-    MimeMessage *This = (MimeMessage *)iface;
+    MimeMessage *This = impl_from_IMimeMessage(iface);
+
     FIXME("(%p)->(%p, 0x%x)\n", iface, ppStream, dwFlags);
 
     IStream_AddRef(This->stream);
@@ -1787,7 +2316,7 @@ static HRESULT WINAPI MimeMessage_Commit(
     DWORD dwFlags)
 {
     FIXME("(%p)->(0x%x)\n", iface, dwFlags);
-    return E_NOTIMPL;
+    return S_OK;
 }
 
 
@@ -1822,13 +2351,10 @@ static HRESULT find_body(struct list *list, HBODY hbody, body_t **body)
     return S_FALSE;
 }
 
-static HRESULT WINAPI MimeMessage_BindToObject(
-    IMimeMessage *iface,
-    const HBODY hBody,
-    REFIID riid,
-    void **ppvObject)
+static HRESULT WINAPI MimeMessage_BindToObject(IMimeMessage *iface, const HBODY hBody, REFIID riid,
+        void **ppvObject)
 {
-    MimeMessage *This = (MimeMessage *)iface;
+    MimeMessage *This = impl_from_IMimeMessage(iface);
     HRESULT hr;
     body_t *body;
 
@@ -1840,8 +2366,8 @@ static HRESULT WINAPI MimeMessage_BindToObject(
 
     if(IsEqualIID(riid, &IID_IMimeBody))
     {
-        IMimeBody_AddRef(body->mime_body);
-        *ppvObject = body->mime_body;
+        IMimeBody_AddRef(&body->mime_body->IMimeBody_iface);
+        *ppvObject = &body->mime_body->IMimeBody_iface;
         return S_OK;
     }
 
@@ -1878,7 +2404,10 @@ static HRESULT get_body(MimeMessage *msg, BODYLOCATION location, HBODY pivot, bo
         switch(location)
         {
         case IBL_PARENT:
-            *out = body->parent;
+            if(body->parent)
+                *out = body->parent;
+            else
+                hr = MIME_E_NOT_FOUND;
             break;
 
         case IBL_FIRST:
@@ -1933,18 +2462,20 @@ static HRESULT WINAPI MimeMessage_InsertBody(
     return E_NOTIMPL;
 }
 
-static HRESULT WINAPI MimeMessage_GetBody(
-    IMimeMessage *iface,
-    BODYLOCATION location,
-    HBODY hPivot,
-    LPHBODY phBody)
+static HRESULT WINAPI MimeMessage_GetBody(IMimeMessage *iface, BODYLOCATION location, HBODY hPivot,
+        HBODY *phBody)
 {
-    MimeMessage *This = (MimeMessage *)iface;
+    MimeMessage *This = impl_from_IMimeMessage(iface);
     body_t *body;
     HRESULT hr;
 
     TRACE("(%p)->(%d, %p, %p)\n", iface, location, hPivot, phBody);
 
+    if(!phBody)
+        return E_INVALIDARG;
+
+    *phBody = NULL;
+
     hr = get_body(This, location, hPivot, &body);
 
     if(hr == S_OK) *phBody = UlongToHandle(body->index);
@@ -1981,14 +2512,11 @@ static void count_children(body_t *body, boolean recurse, ULONG *count)
     }
 }
 
-static HRESULT WINAPI MimeMessage_CountBodies(
-    IMimeMessage *iface,
-    HBODY hParent,
-    boolean fRecurse,
-    ULONG *pcBodies)
+static HRESULT WINAPI MimeMessage_CountBodies(IMimeMessage *iface, HBODY hParent, boolean fRecurse,
+        ULONG *pcBodies)
 {
     HRESULT hr;
-    MimeMessage *This = (MimeMessage *)iface;
+    MimeMessage *This = impl_from_IMimeMessage(iface);
     body_t *body;
 
     TRACE("(%p)->(%p, %s, %p)\n", iface, hParent, fRecurse ? "TRUE" : "FALSE", pcBodies);
@@ -2002,9 +2530,8 @@ static HRESULT WINAPI MimeMessage_CountBodies(
     return S_OK;
 }
 
-static HRESULT find_next(IMimeMessage *msg, body_t *body, LPFINDBODY find, HBODY *out)
+static HRESULT find_next(MimeMessage *This, body_t *body, FINDBODY *find, HBODY *out)
 {
-    MimeMessage *This = (MimeMessage *)msg;
     struct list *ptr;
     HBODY next;
 
@@ -2024,7 +2551,8 @@ static HRESULT find_next(IMimeMessage *msg, body_t *body, LPFINDBODY find, HBODY
         body = LIST_ENTRY( ptr, body_t, entry );
         next = UlongToHandle( body->index );
         find->dwReserved = body->index;
-        if (IMimeBody_IsContentType(body->mime_body, find->pszPriType, find->pszSubType) == S_OK)
+        if (IMimeBody_IsContentType(&body->mime_body->IMimeBody_iface, find->pszPriType,
+                    find->pszSubType) == S_OK)
         {
             *out = next;
             return S_OK;
@@ -2033,23 +2561,19 @@ static HRESULT find_next(IMimeMessage *msg, body_t *body, LPFINDBODY find, HBODY
     return MIME_E_NOT_FOUND;
 }
 
-static HRESULT WINAPI MimeMessage_FindFirst(
-    IMimeMessage *iface,
-    LPFINDBODY pFindBody,
-    LPHBODY phBody)
+static HRESULT WINAPI MimeMessage_FindFirst(IMimeMessage *iface, FINDBODY *pFindBody, HBODY *phBody)
 {
+    MimeMessage *This = impl_from_IMimeMessage(iface);
+
     TRACE("(%p)->(%p, %p)\n", iface, pFindBody, phBody);
 
     pFindBody->dwReserved = 0;
-    return find_next( iface, NULL, pFindBody, phBody );
+    return find_next(This, NULL, pFindBody, phBody);
 }
 
-static HRESULT WINAPI MimeMessage_FindNext(
-    IMimeMessage *iface,
-    LPFINDBODY pFindBody,
-    LPHBODY phBody)
+static HRESULT WINAPI MimeMessage_FindNext(IMimeMessage *iface, FINDBODY *pFindBody, HBODY *phBody)
 {
-    MimeMessage *This = (MimeMessage *)iface;
+    MimeMessage *This = impl_from_IMimeMessage(iface);
     body_t *body;
     HRESULT hr;
 
@@ -2057,7 +2581,7 @@ static HRESULT WINAPI MimeMessage_FindNext(
 
     hr = find_body( &This->body_tree, UlongToHandle( pFindBody->dwReserved ), &body );
     if (hr != S_OK) return MIME_E_NOT_FOUND;
-    return find_next( iface, body, pFindBody, phBody );
+    return find_next(This, body, pFindBody, phBody);
 }
 
 static HRESULT WINAPI MimeMessage_ResolveURL(
@@ -2203,27 +2727,41 @@ static HRESULT WINAPI MimeMessage_SetOption(
     const TYPEDID oid,
     LPCPROPVARIANT pValue)
 {
-    HRESULT hr = E_NOTIMPL;
+    HRESULT hr = S_OK;
     TRACE("(%p)->(%08x, %p)\n", iface, oid, pValue);
 
+    /* Message ID is checked before type.
+     *  OID 0x4D -> 0x56 and 0x58 aren't defined but will filtered out later.
+     */
+    if(TYPEDID_ID(oid) < TYPEDID_ID(OID_ALLOW_8BIT_HEADER) || TYPEDID_ID(oid) > TYPEDID_ID(OID_SECURITY_2KEY_CERT_BAG_64))
+    {
+        WARN("oid (%08x) out of range\n", oid);
+        return MIME_E_INVALID_OPTION_ID;
+    }
+
     if(pValue->vt != TYPEDID_TYPE(oid))
     {
         WARN("Called with vartype %04x and oid %08x\n", pValue->vt, oid);
-        return E_INVALIDARG;
+        return S_OK;
     }
 
     switch(oid)
     {
     case OID_HIDE_TNEF_ATTACHMENTS:
         FIXME("OID_HIDE_TNEF_ATTACHMENTS (value %d): ignoring\n", pValue->u.boolVal);
-        hr = S_OK;
         break;
     case OID_SHOW_MACBINARY:
         FIXME("OID_SHOW_MACBINARY (value %d): ignoring\n", pValue->u.boolVal);
-        hr = S_OK;
+        break;
+    case OID_SAVEBODY_KEEPBOUNDARY:
+        FIXME("OID_SAVEBODY_KEEPBOUNDARY (value %d): ignoring\n", pValue->u.boolVal);
+        break;
+    case OID_CLEANUP_TREE_ON_SAVE:
+        FIXME("OID_CLEANUP_TREE_ON_SAVE (value %d): ignoring\n", pValue->u.boolVal);
         break;
     default:
         FIXME("Unhandled oid %08x\n", oid);
+        hr = MIME_E_INVALID_OPTION_ID;
     }
 
     return hr;
@@ -2547,6 +3085,8 @@ static const IMimeMessageVtbl MimeMessageVtbl =
 HRESULT MimeMessage_create(IUnknown *outer, void **obj)
 {
     MimeMessage *This;
+    MimeBody *mime_body;
+    body_t *root_body;
 
     TRACE("(%p, %p)\n", outer, obj);
 
@@ -2561,13 +3101,17 @@ HRESULT MimeMessage_create(IUnknown *outer, void **obj)
     This = HeapAlloc(GetProcessHeap(), 0, sizeof(*This));
     if (!This) return E_OUTOFMEMORY;
 
-    This->lpVtbl = &MimeMessageVtbl;
-    This->refs = 1;
+    This->IMimeMessage_iface.lpVtbl = &MimeMessageVtbl;
+    This->ref = 1;
     This->stream = NULL;
     list_init(&This->body_tree);
     This->next_index = 1;
 
-    *obj = &This->lpVtbl;
+    mime_body = mimebody_create();
+    root_body = new_body_entry(mime_body, This->next_index++, NULL);
+    list_add_head(&This->body_tree, &root_body->entry);
+
+    *obj = &This->IMimeMessage_iface;
     return S_OK;
 }
 
@@ -2603,54 +3147,53 @@ HRESULT WINAPI MimeOleCreateVirtualStream(IStream **ppStream)
 
 typedef struct MimeSecurity
 {
-    const IMimeSecurityVtbl *lpVtbl;
-
-    LONG refs;
+    IMimeSecurity IMimeSecurity_iface;
+    LONG ref;
 } MimeSecurity;
 
-static HRESULT WINAPI MimeSecurity_QueryInterface(
-        IMimeSecurity* iface,
-        REFIID riid,
-        void** obj)
+static inline MimeSecurity *impl_from_IMimeSecurity(IMimeSecurity *iface)
 {
-    TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), obj);
+    return CONTAINING_RECORD(iface, MimeSecurity, IMimeSecurity_iface);
+}
+
+static HRESULT WINAPI MimeSecurity_QueryInterface(IMimeSecurity *iface, REFIID riid, void **ppv)
+{
+    TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv);
 
     if (IsEqualIID(riid, &IID_IUnknown) ||
         IsEqualIID(riid, &IID_IMimeSecurity))
     {
-        *obj = iface;
-        IUnknown_AddRef(iface);
+        *ppv = iface;
+        IMimeSecurity_AddRef(iface);
         return S_OK;
     }
 
     FIXME("no interface for %s\n", debugstr_guid(riid));
-    *obj = NULL;
+    *ppv = NULL;
     return E_NOINTERFACE;
 }
 
-static ULONG WINAPI MimeSecurity_AddRef(
-        IMimeSecurity* iface)
+static ULONG WINAPI MimeSecurity_AddRef(IMimeSecurity *iface)
 {
-    MimeSecurity *This = (MimeSecurity *)iface;
-    TRACE("(%p)->()\n", iface);
-    return InterlockedIncrement(&This->refs);
+    MimeSecurity *This = impl_from_IMimeSecurity(iface);
+    LONG ref = InterlockedIncrement(&This->ref);
+
+    TRACE("(%p) ref=%d\n", This, ref);
+
+    return ref;
 }
 
-static ULONG WINAPI MimeSecurity_Release(
-        IMimeSecurity* iface)
+static ULONG WINAPI MimeSecurity_Release(IMimeSecurity *iface)
 {
-    MimeSecurity *This = (MimeSecurity *)iface;
-    ULONG refs;
+    MimeSecurity *This = impl_from_IMimeSecurity(iface);
+    LONG ref = InterlockedDecrement(&This->ref);
 
-    TRACE("(%p)->()\n", iface);
+    TRACE("(%p) ref=%d\n", This, ref);
 
-    refs = InterlockedDecrement(&This->refs);
-    if (!refs)
-    {
+    if (!ref)
         HeapFree(GetProcessHeap(), 0, This);
-    }
 
-    return refs;
+    return ref;
 }
 
 static HRESULT WINAPI MimeSecurity_InitNew(
@@ -2775,10 +3318,10 @@ HRESULT MimeSecurity_create(IUnknown *outer, void **obj)
     This = HeapAlloc(GetProcessHeap(), 0, sizeof(*This));
     if (!This) return E_OUTOFMEMORY;
 
-    This->lpVtbl = &MimeSecurityVtbl;
-    This->refs = 1;
+    This->IMimeSecurity_iface.lpVtbl = &MimeSecurityVtbl;
+    This->ref = 1;
 
-    *obj = &This->lpVtbl;
+    *obj = &This->IMimeSecurity_iface;
     return S_OK;
 }
 
@@ -2790,11 +3333,6 @@ HRESULT WINAPI MimeOleCreateSecurity(IMimeSecurity **ppSecurity)
     return MimeSecurity_create(NULL, (void **)ppSecurity);
 }
 
-typedef struct
-{
-    IMimeAllocatorVtbl *lpVtbl;
-} MimeAllocator;
-
 static HRESULT WINAPI MimeAlloc_QueryInterface(
         IMimeAllocator* iface,
         REFIID riid,
@@ -2807,7 +3345,7 @@ static HRESULT WINAPI MimeAlloc_QueryInterface(
         IsEqualIID(riid, &IID_IMimeAllocator))
     {
         *obj = iface;
-        IUnknown_AddRef(iface);
+        IMimeAllocator_AddRef(iface);
         return S_OK;
     }
 
@@ -2830,7 +3368,7 @@ static ULONG WINAPI MimeAlloc_Release(
 
 static LPVOID WINAPI MimeAlloc_Alloc(
         IMimeAllocator* iface,
-        ULONG cb)
+        SIZE_T cb)
 {
     return CoTaskMemAlloc(cb);
 }
@@ -2838,7 +3376,7 @@ static LPVOID WINAPI MimeAlloc_Alloc(
 static LPVOID WINAPI MimeAlloc_Realloc(
         IMimeAllocator* iface,
         LPVOID pv,
-        ULONG cb)
+        SIZE_T cb)
 {
     return CoTaskMemRealloc(pv, cb);
 }
@@ -2850,7 +3388,7 @@ static void WINAPI MimeAlloc_Free(
     CoTaskMemFree(pv);
 }
 
-static ULONG WINAPI MimeAlloc_GetSize(
+static SIZE_T WINAPI MimeAlloc_GetSize(
         IMimeAllocator* iface,
         LPVOID pv)
 {
@@ -2976,7 +3514,7 @@ static IMimeAllocatorVtbl mime_alloc_vtbl =
     MimeAlloc_PropVariantClear
 };
 
-static MimeAllocator mime_allocator =
+static IMimeAllocator mime_allocator =
 {
     &mime_alloc_vtbl
 };
@@ -3003,3 +3541,195 @@ HRESULT VirtualStream_create(IUnknown *outer, void **obj)
 
     return MimeOleCreateVirtualStream((IStream **)obj);
 }
+
+/* IMimePropertySchema Interface */
+static HRESULT WINAPI propschema_QueryInterface(IMimePropertySchema *iface, REFIID riid, void **out)
+{
+    propschema *This = impl_from_IMimePropertySchema(iface);
+    TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), out);
+
+    *out = NULL;
+
+    if (IsEqualIID(riid, &IID_IUnknown) ||
+        IsEqualIID(riid, &IID_IMimePropertySchema))
+    {
+        *out = iface;
+    }
+    else
+    {
+        FIXME("no interface for %s\n", debugstr_guid(riid));
+        return E_NOINTERFACE;
+    }
+
+    IMimePropertySchema_AddRef(iface);
+    return S_OK;
+}
+
+static ULONG WINAPI propschema_AddRef(IMimePropertySchema *iface)
+{
+    propschema *This = impl_from_IMimePropertySchema(iface);
+    LONG ref = InterlockedIncrement(&This->ref);
+
+    TRACE("(%p) ref=%d\n", This, ref);
+
+    return ref;
+}
+
+static ULONG WINAPI propschema_Release(IMimePropertySchema *iface)
+{
+    propschema *This = impl_from_IMimePropertySchema(iface);
+    LONG ref = InterlockedDecrement(&This->ref);
+
+    TRACE("(%p) ref=%d\n", This, ref);
+
+    if (!ref)
+    {
+        HeapFree(GetProcessHeap(), 0, This);
+    }
+
+    return ref;
+}
+
+static HRESULT WINAPI propschema_RegisterProperty(IMimePropertySchema *iface, const char *name, DWORD flags,
+        DWORD rownumber, VARTYPE vtdefault, DWORD *propid)
+{
+    propschema *This = impl_from_IMimePropertySchema(iface);
+    FIXME("(%p)->(%s, %x, %d, %d, %p) stub\n", This, debugstr_a(name), flags, rownumber, vtdefault, propid);
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI propschema_ModifyProperty(IMimePropertySchema *iface, const char *name, DWORD flags,
+        DWORD rownumber, VARTYPE vtdefault)
+{
+    propschema *This = impl_from_IMimePropertySchema(iface);
+    FIXME("(%p)->(%s, %x, %d, %d) stub\n", This, debugstr_a(name), flags, rownumber, vtdefault);
+    return S_OK;
+}
+
+static HRESULT WINAPI propschema_GetPropertyId(IMimePropertySchema *iface, const char *name, DWORD *propid)
+{
+    propschema *This = impl_from_IMimePropertySchema(iface);
+    FIXME("(%p)->(%s, %p) stub\n", This, debugstr_a(name), propid);
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI propschema_GetPropertyName(IMimePropertySchema *iface, DWORD propid, char **name)
+{
+    propschema *This = impl_from_IMimePropertySchema(iface);
+    FIXME("(%p)->(%d, %p) stub\n", This, propid, name);
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI propschema_RegisterAddressType(IMimePropertySchema *iface, const char *name, DWORD *adrtype)
+{
+    propschema *This = impl_from_IMimePropertySchema(iface);
+    FIXME("(%p)->(%s, %p) stub\n", This, debugstr_a(name), adrtype);
+    return E_NOTIMPL;
+}
+
+static IMimePropertySchemaVtbl prop_schema_vtbl =
+{
+    propschema_QueryInterface,
+    propschema_AddRef,
+    propschema_Release,
+    propschema_RegisterProperty,
+    propschema_ModifyProperty,
+    propschema_GetPropertyId,
+    propschema_GetPropertyName,
+    propschema_RegisterAddressType
+};
+
+
+HRESULT WINAPI MimeOleGetPropertySchema(IMimePropertySchema **schema)
+{
+    propschema *This;
+
+    TRACE("(%p) stub\n", schema);
+
+    This = HeapAlloc(GetProcessHeap(), 0, sizeof(*This));
+    if (!This)
+        return E_OUTOFMEMORY;
+
+    This->IMimePropertySchema_iface.lpVtbl = &prop_schema_vtbl;
+    This->ref = 1;
+
+    *schema = &This->IMimePropertySchema_iface;
+
+    return S_OK;
+}
+
+HRESULT WINAPI MimeGetAddressFormatW(REFIID riid, void *object, DWORD addr_type,
+       ADDRESSFORMAT addr_format, WCHAR **address)
+{
+    FIXME("(%s, %p, %d, %d, %p) stub\n", debugstr_guid(riid), object, addr_type, addr_format, address);
+
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI mime_obj_QueryInterface(IUnknown *iface, REFIID riid, void **ppv)
+{
+    FIXME("(%s %p)\n", debugstr_guid(riid), ppv);
+    *ppv = NULL;
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI mime_obj_AddRef(IUnknown *iface)
+{
+    TRACE("\n");
+    return 2;
+}
+
+static ULONG WINAPI mime_obj_Release(IUnknown *iface)
+{
+    TRACE("\n");
+    return 1;
+}
+
+static const IUnknownVtbl mime_obj_vtbl = {
+    mime_obj_QueryInterface,
+    mime_obj_AddRef,
+    mime_obj_Release
+};
+
+static IUnknown mime_obj = { &mime_obj_vtbl };
+
+HRESULT WINAPI MimeOleObjectFromMoniker(BINDF bindf, IMoniker *moniker, IBindCtx *binding,
+       REFIID riid, void **out, IMoniker **moniker_new)
+{
+    WCHAR *display_name, *mhtml_url;
+    size_t len;
+    HRESULT hres;
+
+    static const WCHAR mhtml_prefixW[] = {'m','h','t','m','l',':'};
+
+    WARN("(0x%08x, %p, %p, %s, %p, %p) semi-stub\n", bindf, moniker, binding, debugstr_guid(riid), out, moniker_new);
+
+    if(!IsEqualGUID(&IID_IUnknown, riid)) {
+        FIXME("Unsupported riid %s\n", debugstr_guid(riid));
+        return E_NOINTERFACE;
+    }
+
+    hres = IMoniker_GetDisplayName(moniker, NULL, NULL, &display_name);
+    if(FAILED(hres))
+        return hres;
+
+    TRACE("display name %s\n", debugstr_w(display_name));
+
+    len = lstrlenW(display_name);
+    mhtml_url = heap_alloc((len+1)*sizeof(WCHAR) + sizeof(mhtml_prefixW));
+    if(!mhtml_url)
+        return E_OUTOFMEMORY;
+
+    memcpy(mhtml_url, mhtml_prefixW, sizeof(mhtml_prefixW));
+    lstrcpyW(mhtml_url + ARRAY_SIZE(mhtml_prefixW), display_name);
+    HeapFree(GetProcessHeap(), 0, display_name);
+
+    hres = CreateURLMoniker(NULL, mhtml_url, moniker_new);
+    heap_free(mhtml_url);
+    if(FAILED(hres))
+        return hres;
+
+    /* FIXME: We most likely should start binding here and return something more meaningful as mime object. */
+    *out = &mime_obj;
+    return S_OK;
+}