Revision: 7344
http://mahogany.svn.sourceforge.net/mahogany/?rev=7344&view=rev
Author: vadz
Date: 2007-08-27 13:03:17 -0700 (Mon, 27 Aug 2007)
Log Message:
-----------
revised the code dealing with headers encoding and made sure it works in
Unicode build
Modified Paths:
--------------
trunk/M/include/Address.h
trunk/M/include/AddressCC.h
trunk/M/include/SendMessageCC.h
trunk/M/include/mail/MimeDecode.h
trunk/M/src/gui/wxComposeView.cpp
trunk/M/src/mail/Address.cpp
trunk/M/src/mail/AddressCC.cpp
trunk/M/src/mail/MailFolder.cpp
trunk/M/src/mail/MimeDecode.cpp
trunk/M/src/mail/SendMessageCC.cpp
trunk/M/tests/mime/decode.cpp
Modified: trunk/M/include/Address.h
===================================================================
--- trunk/M/include/Address.h 2007-08-26 12:07:41 UTC (rev 7343)
+++ trunk/M/include/Address.h 2007-08-27 20:03:17 UTC (rev 7344)
@@ -15,6 +15,8 @@
#include "MObject.h"
+#include <wx/fontenc.h>
+
class Profile;
// see near definition of this symbol in src/modules/PalmOS.cpp
@@ -94,6 +96,14 @@
static String BuildFullForm(const String& personal, const String& address);
/**
+ Returns the sender address from the given profile.
+
+ The address is constructed using the personal name, host name and default
+ domain (if necessary) options.
+ */
+ static String GetSenderAddress(Profile *profile);
+
+ /**
Returns true if the address matches any of the entries in the array.
Array entries may contain wildcards (? and *).
@@ -136,13 +146,18 @@
class AddressList : public MObjectRC
{
public:
- /// create the address list from string (may be empty)
+ /**
+ Create the address list from string.
+
+ @param address the string with the address
+ @param defhost the default host name to use for unqualified addresses
+ @param enc the encoding to use for non-ASCII characters if possible (if
+ not, UTF-8 is used, as with MIME::EncodeHeader())
+ */
static AddressList *Create(const String& address,
- const String& defhost = wxEmptyString);
+ const String& defhost = wxEmptyString,
+ wxFontEncoding enc = wxFONTENCODING_SYSTEM);
- /// create the "From" address using settings in this profile
- static AddressList *CreateFromAddress(Profile *profile);
-
/// get the first address in the list, return NULL if list is empty
virtual Address *GetFirst() const = 0;
Modified: trunk/M/include/AddressCC.h
===================================================================
--- trunk/M/include/AddressCC.h 2007-08-26 12:07:41 UTC (rev 7343)
+++ trunk/M/include/AddressCC.h 2007-08-27 20:03:17 UTC (rev 7344)
@@ -84,8 +84,9 @@
String m_addressHeader;
// these methods use our private ctor
- friend AddressList *AddressList::Create(const String&, const String&);
- friend AddressList *AddressList::CreateFromAddress(Profile *profile);
+ friend AddressList *AddressList::Create(const String& address,
+ const String& defHost,
+ wxFontEncoding enc);
MOBJECT_DEBUG(AddressListCC)
DECLARE_NO_COPY_CLASS(AddressListCC)
@@ -93,7 +94,9 @@
// wrapper around rfc822_parse_adrlist() c-client function
extern
-mail_address *ParseAddressList(const String& address, const String& defhost);
+mail_address *ParseAddressList(const String& address,
+ const String& defhost,
+ wxFontEncoding enc);
#endif // _ADDRESSCC_H_
Modified: trunk/M/include/SendMessageCC.h
===================================================================
--- trunk/M/include/SendMessageCC.h 2007-08-26 12:07:41 UTC (rev 7343)
+++ trunk/M/include/SendMessageCC.h 2007-08-27 20:03:17 UTC (rev 7344)
@@ -148,15 +148,6 @@
/// translate the (wxWin) encoding to (MIME) charset
String EncodingToCharset(wxFontEncoding enc);
- /// encode the string using m_encHeaders encoding
- String EncodeHeaderString(const String& header);
-
- /// encode the address field using m_encHeaders
- void EncodeAddress(struct mail_address *adr);
-
- /// encode all entries in the list of addresses
- void EncodeAddressList(struct mail_address *adr);
-
/// write the message using the specified writer function
bool WriteMessage(soutr_t writer, void *where);
Modified: trunk/M/include/mail/MimeDecode.h
===================================================================
--- trunk/M/include/mail/MimeDecode.h 2007-08-26 12:07:41 UTC (rev 7343)
+++ trunk/M/include/mail/MimeDecode.h 2007-08-27 20:03:17 UTC (rev 7344)
@@ -1,6 +1,6 @@
//////////////////////////////////////////////////////////////////////////////
// Project: M - cross platform e-mail GUI client
-// File name: mail/MimeDecode.h: functions for MIME words decoding
+// File name: mail/MimeDecode.h: functions for MIME words encoding/decoding
// Author: Vadim Zeitlin
// Created: 2007-07-29
// CVS-ID: $Id$
@@ -12,6 +12,7 @@
#define M_MAIL_MIMEDECODE_H
#include <wx/fontenc.h>
+#include <wx/buffer.h>
/**
Various MIME helpers.
@@ -20,6 +21,55 @@
{
/**
+ MIME encodings defined by RFC 2047.
+
+ NB: don't change the values of the enum elements, EncodeHeader() relies on
+ them being what they are!
+ */
+enum Encoding
+{
+ Encoding_Unknown,
+ Encoding_Base64 = 'B',
+ Encoding_QuotedPrintable = 'Q'
+};
+
+/**
+ Return the MIME encoding which should be preferrably used for the given font
+ encoding.
+
+ For encodings which use a lot of ASCII characters, QP MIME encoding is
+ preferred as it is more space efficient and results in more or less readable
+ headers. For the others, Base64 is used.
+
+ @param enc encoding not equal to wxFONTENCODING_SYSTEM or
+ wxFONTENCODING_DEFAULT
+ @return the corresponding MIME encoding or Encoding_Unknown if enc is
invalid
+ */
+Encoding GetEncodingForFontEncoding(wxFontEncoding enc);
+
+/**
+ Return the MIME charset corresponding to the given font encoding.
+
+ @param enc encoding not equal to wxFONTENCODING_SYSTEM or
+ wxFONTENCODING_DEFAULT
+ @return the charset or empty string if encoding is invalid
+ */
+String GetCharsetForFontEncoding(wxFontEncoding enc);
+
+/**
+ Encode a header containing special symbols using RFC 2047 mechanism.
+
+ @param in text containing arbitrary Unicode characters
+ @param enc suggestion for the encoding to use for encoding the input text,
+ another encoding (typically UTF-8) will be used if the input
+ can't be converted to the specified encoding; default means to
+ use the encoding of the current locale
+ @return the encoded text or NULL buffer if encoding failed
+ */
+wxCharBuffer
+EncodeHeader(const wxString& in, wxFontEncoding enc = wxFONTENCODING_SYSTEM);
+
+/**
RFC 2047 compliant message decoding.
All encoded words from the header are decoded, but only the encoding of the
Modified: trunk/M/src/gui/wxComposeView.cpp
===================================================================
--- trunk/M/src/gui/wxComposeView.cpp 2007-08-26 12:07:41 UTC (rev 7343)
+++ trunk/M/src/gui/wxComposeView.cpp 2007-08-27 20:03:17 UTC (rev 7344)
@@ -4932,19 +4932,13 @@
m_txtFrom->SetValue(from);
}
-/// sets From field using the current profile
+/// sets From field using the default value for it from the current profile
void
wxComposeView::SetDefaultFrom()
{
if ( m_txtFrom )
{
- AddressList_obj addrList(AddressList::CreateFromAddress(m_Profile));
-
- Address *addr = addrList->GetFirst();
- if ( addr )
- {
- SetFrom(addr->GetAddress());
- }
+ SetFrom(Address::GetSenderAddress(m_Profile));
}
}
Modified: trunk/M/src/mail/Address.cpp
===================================================================
--- trunk/M/src/mail/Address.cpp 2007-08-26 12:07:41 UTC (rev 7343)
+++ trunk/M/src/mail/Address.cpp 2007-08-27 20:03:17 UTC (rev 7344)
@@ -40,11 +40,13 @@
// options we use here
// ----------------------------------------------------------------------------
+extern const MOption MP_ADD_DEFAULT_HOSTNAME;
extern const MOption MP_EQUIV_ADDRESSES;
extern const MOption MP_FROM_REPLACE_ADDRESSES;
extern const MOption MP_FROM_ADDRESS;
extern const MOption MP_HOSTNAME;
extern const MOption MP_LIST_ADDRESSES;
+extern const MOption MP_PERSONALNAME;
// ============================================================================
// implementation
@@ -385,6 +387,29 @@
return false;
}
+/* static */
+String Address::GetSenderAddress(Profile *profile)
+{
+ String email(READ_CONFIG_TEXT(profile, MP_FROM_ADDRESS));
+
+ // check that the email address has the domain part
+ if ( email.find('@') == String::npos )
+ {
+ String host;
+ if ( READ_CONFIG(profile, MP_ADD_DEFAULT_HOSTNAME) )
+ {
+ host = READ_CONFIG_TEXT(profile, MP_HOSTNAME);
+ }
+
+ // append '@' even if host is empty: this tricks c-client into accepting
+ // addresses without host names instead of using a stupid
+ // MISSING.WHATEVER instead of the host part
+ email << '@' << host;
+ }
+
+ return BuildFullForm(READ_CONFIG(profile, MP_PERSONALNAME), email);
+}
+
// ----------------------------------------------------------------------------
// AddressList
// ----------------------------------------------------------------------------
Modified: trunk/M/src/mail/AddressCC.cpp
===================================================================
--- trunk/M/src/mail/AddressCC.cpp 2007-08-26 12:07:41 UTC (rev 7343)
+++ trunk/M/src/mail/AddressCC.cpp 2007-08-27 20:03:17 UTC (rev 7344)
@@ -27,17 +27,9 @@
#endif // USE_PCH
#include "AddressCC.h"
+#include "mail/MimeDecode.h"
// ----------------------------------------------------------------------------
-// options we use here
-// ----------------------------------------------------------------------------
-
-extern const MOption MP_ADD_DEFAULT_HOSTNAME;
-extern const MOption MP_FROM_ADDRESS;
-extern const MOption MP_HOSTNAME;
-extern const MOption MP_PERSONALNAME;
-
-// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------
@@ -249,56 +241,16 @@
}
/* static */
-AddressList *AddressList::CreateFromAddress(Profile *profile)
+AddressList *
+AddressList::Create(const String& address,
+ const String& defhost,
+ wxFontEncoding enc)
{
- // it is a bit difficult for From because we have 2 entries in config to
- // specify it (for historic reasons mainly, I don't think this is actually
- // useful) and so we must combine them together
-
- // set personal name
- ADDRESS *adr = mail_newaddr();
- adr->personal = cpystr(wxConvertWX2MB(READ_CONFIG_TEXT(profile,
MP_PERSONALNAME)));
-
- // set mailbox/host
- String email = READ_CONFIG(profile, MP_FROM_ADDRESS);
- size_t pos = email.find('@');
- if ( pos != String::npos )
- {
- adr->mailbox = cpystr(email.substr(0, pos).c_str());
- adr->host = cpystr(email.c_str() + pos + 1);
- }
- else // no '@'?
- {
- adr->mailbox = cpystr(wxConvertWX2MB(email));
-
- String host;
- if ( READ_CONFIG(profile, MP_ADD_DEFAULT_HOSTNAME) )
- {
- host = READ_CONFIG_TEXT(profile, MP_HOSTNAME);
- }
-
- if ( host.empty() )
- {
- // trick c-client into accepting addresses without host names
- // instead of using a stupid MISSING.WHATEVER instead of the host
- // part
- host = _T('@');
- }
-
- adr->host = cpystr(wxConvertWX2MB(host));
- }
-
- return new AddressListCC(adr);
-}
-
-/* static */
-AddressList *AddressList::Create(const String& address, const String& defhost)
-{
ADDRESS *adr = NULL;
if ( !address.empty() )
{
- adr = ParseAddressList(address, defhost);
+ adr = ParseAddressList(address, defhost, enc);
if ( !adr || adr->error )
{
@@ -598,20 +550,37 @@
return result;
}
-extern ADDRESS *ParseAddressList(const String& address, const String& defhost)
+extern ADDRESS *
+ParseAddressList(const String& address,
+ const String& defhost,
+ wxFontEncoding enc)
{
// NB: rfc822_parse_adrlist() modifies the string passed in, copy them!
- char *addressCopy = strdup(wxConvertWX2MB(RemoveEmptyListParts(address)));
+ // encode the header in UTF-8 as this never fails
+ wxCharBuffer addressBuf(RemoveEmptyListParts(address).utf8_str());
// use '@' to trick c-client into accepting addresses without host names
- char *defhostCopy = strdup(defhost.empty() ? "@" :
wxConvertWX2MB(defhost.c_str()));
+ wxCharBuffer defhostBuf;
+ if ( defhost.empty() )
+ defhostBuf = wxCharBuffer("@");
+ else
+ defhostBuf = defhost.ToAscii();
ADDRESS *adr = NULL;
- rfc822_parse_adrlist(&adr, addressCopy, defhostCopy);
+ rfc822_parse_adrlist(&adr, addressBuf.data(), defhostBuf.data());
- free(defhostCopy);
- free(addressCopy);
+ // encode the personal part of the header as it can contain non-ASCII
+ // characters
+ for ( ADDRESS *adr2 = adr; adr2; adr2 = adr2->next )
+ {
+ if ( adr2->personal )
+ {
+ String personal = wxString::FromUTF8(adr2->personal);
+ fs_give((void **)&adr2->personal);
+ adr2->personal = cpystr(MIME::EncodeHeader(personal, enc));
+ }
+ }
return adr;
}
Modified: trunk/M/src/mail/MailFolder.cpp
===================================================================
--- trunk/M/src/mail/MailFolder.cpp 2007-08-26 12:07:41 UTC (rev 7343)
+++ trunk/M/src/mail/MailFolder.cpp 2007-08-27 20:03:17 UTC (rev 7344)
@@ -63,6 +63,7 @@
extern const MOption MP_IMAPHOST;
extern const MOption MP_LIST_ADDRESSES;
extern const MOption MP_NNTPHOST;
+extern const MOption MP_PERSONALNAME;
extern const MOption MP_POPHOST;
extern const MOption MP_REPLY_COLLAPSE_PREFIX;
extern const MOption MP_REPLY_PREFIX;
@@ -1082,12 +1083,11 @@
if ( READ_CONFIG(profile, MP_SET_REPLY_STD_NAME) )
{
// use the standard personal name
- AddressList_obj
addrOwn(AddressList::CreateFromAddress(profile));
- addr = addrOwn->GetFirst();
- if ( addr )
- {
- from = Address::BuildFullForm(addr->GetName(), from);
- }
+ from = Address::BuildFullForm
+ (
+ READ_CONFIG(profile, MP_PERSONALNAME),
+ addr->GetEMail()
+ );
}
}
Modified: trunk/M/src/mail/MimeDecode.cpp
===================================================================
--- trunk/M/src/mail/MimeDecode.cpp 2007-08-26 12:07:41 UTC (rev 7343)
+++ trunk/M/src/mail/MimeDecode.cpp 2007-08-27 20:03:17 UTC (rev 7344)
@@ -25,6 +25,7 @@
#include "mail/MimeDecode.h"
#include <wx/fontmap.h>
+#include <wx/tokenzr.h>
// ----------------------------------------------------------------------------
// local helper functions
@@ -40,6 +41,75 @@
// implementation
// ============================================================================
+// ----------------------------------------------------------------------------
+// font encoding <-> MIME functions
+// ----------------------------------------------------------------------------
+
+String MIME::GetCharsetForFontEncoding(wxFontEncoding enc)
+{
+ // translate encoding to the charset
+ wxString cs;
+ if ( enc != wxFONTENCODING_SYSTEM && enc != wxFONTENCODING_DEFAULT )
+ {
+ cs = wxFontMapper::GetEncodingName(enc).Upper();
+ }
+
+ return cs;
+}
+
+MIME::Encoding MIME::GetEncodingForFontEncoding(wxFontEncoding enc)
+{
+ // QP should be used for the encodings which mostly overlap with US_ASCII,
+ // Base64 for the others - choose the encoding method
+ switch ( enc )
+ {
+ case wxFONTENCODING_ISO8859_1:
+ case wxFONTENCODING_ISO8859_2:
+ case wxFONTENCODING_ISO8859_3:
+ case wxFONTENCODING_ISO8859_4:
+ case wxFONTENCODING_ISO8859_9:
+ case wxFONTENCODING_ISO8859_10:
+ case wxFONTENCODING_ISO8859_13:
+ case wxFONTENCODING_ISO8859_14:
+ case wxFONTENCODING_ISO8859_15:
+
+ case wxFONTENCODING_CP1250:
+ case wxFONTENCODING_CP1252:
+ case wxFONTENCODING_CP1254:
+ case wxFONTENCODING_CP1257:
+
+ case wxFONTENCODING_UTF7:
+ case wxFONTENCODING_UTF8:
+ return Encoding_QuotedPrintable;
+
+
+ case wxFONTENCODING_ISO8859_5:
+ case wxFONTENCODING_ISO8859_6:
+ case wxFONTENCODING_ISO8859_7:
+ case wxFONTENCODING_ISO8859_8:
+ case wxFONTENCODING_ISO8859_11:
+ case wxFONTENCODING_ISO8859_12:
+
+ case wxFONTENCODING_CP1251:
+ case wxFONTENCODING_CP1253:
+ case wxFONTENCODING_CP1255:
+ case wxFONTENCODING_CP1256:
+
+ case wxFONTENCODING_KOI8:
+ return Encoding_Base64;
+
+ default:
+ FAIL_MSG( _T("unknown encoding") );
+
+ case wxFONTENCODING_SYSTEM:
+ return Encoding_Unknown;
+ }
+}
+
+// ----------------------------------------------------------------------------
+// decoding
+// ----------------------------------------------------------------------------
+
/*
See RFC 2047 for the description of the encodings used in the mail headers.
Briefly, "encoded words" can be inserted which have the form of
@@ -100,8 +170,8 @@
// pass false to prevent asking the user from here: we can be called
// during non-interactive operations and popping up a dialog for an
// unknown charset can be inappropriate
- const wxFontEncoding
- encodingWord = wxFontMapper::Get()->CharsetToEncoding(csName,
false);
+ const wxFontEncoding encodingWord = wxFontMapperBase::Get()->
+ CharsetToEncoding(csName,
false);
if ( encodingWord == wxFONTENCODING_SYSTEM )
{
@@ -313,3 +383,250 @@
return header;
}
+// ----------------------------------------------------------------------------
+// encoding
+// ----------------------------------------------------------------------------
+
+// returns true if the character must be encoded in a MIME header
+//
+// NB: we suppose that any special characters had been already escaped
+static inline bool NeedsEncodingInHeader(wxUChar c)
+{
+ return iscntrl(c) || c >= 127;
+}
+
+// return true if the string contains any characters which must be encoded
+static bool NeedsEncoding(const String& in)
+{
+ // if input contains "=?", encode it anyhow to avoid generating invalid
+ // encoded words
+ if ( in.find(_T("=?")) == wxString::npos )
+ {
+ // only encode the strings which contain the characters unallowed in RFC
+ // 822 headers
+ wxString::const_iterator p;
+ const wxString::const_iterator end = in.end();
+ for ( p = in.begin(); p != end; ++p )
+ {
+ if ( NeedsEncodingInHeader(*p) )
+ break;
+ }
+
+ if ( p == end )
+ {
+ // string has only valid chars, don't encode
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static String
+EncodeWord(const String& in,
+ wxFontEncoding enc,
+ MIME::Encoding enc2047,
+ const String& csName)
+{
+ if ( !NeedsEncoding(in) )
+ return in;
+
+
+ // encode the word splitting it in the chunks such that they will be no
+ // longer than 75 characters each
+ wxCharBuffer buf(in.mb_str(wxCSConv(enc)));
+ if ( !buf )
+ {
+ // if the header can't be encoded using the given encoding, use UTF-8
+ // which always works
+ buf = in.utf8_str();
+ }
+
+ String out;
+ out.reserve(csName.length() + strlen(buf) + 7 /* for =?...?X?...?= */);
+
+ const char *s = buf;
+ while ( *s )
+ {
+ // if we wrapped, insert a line break
+ if ( !out.empty() )
+ out += "\r\n ";
+
+ static const size_t RFC2047_MAXWORD_LEN = 75;
+
+ // how many characters may we put in this encoded word?
+ size_t len = 0;
+
+ // take into account the length of "=?charset?...?="
+ int lenRemaining = RFC2047_MAXWORD_LEN - (5 + csName.length());
+
+ // for QP we need to examine all characters
+ if ( enc2047 == MIME::Encoding_QuotedPrintable )
+ {
+ for ( ; s[len]; len++ )
+ {
+ const char c = s[len];
+
+ // normal characters stand for themselves in QP, the encoded ones
+ // take 3 positions (=XX)
+ lenRemaining -= (NeedsEncodingInHeader(c) || strchr(" \t=?", c))
+ ? 3 : 1;
+
+ if ( lenRemaining <= 0 )
+ {
+ // can't put any more chars into this word
+ break;
+ }
+ }
+ }
+ else // Base64
+ {
+ // we can calculate how many characters we may put into lenRemaining
+ // directly
+ len = (lenRemaining / 4) * 3 - 2;
+
+ // but not more than what we have
+ size_t lenMax = wxStrlen(s);
+ if ( len > lenMax )
+ {
+ len = lenMax;
+ }
+ }
+
+ // do encode this word
+ unsigned char *text = (unsigned char *)s; // cast for cclient
+
+ // length of the encoded text and the text itself
+ unsigned long lenEnc;
+ unsigned char *textEnc;
+
+ if ( enc2047 == MIME::Encoding_QuotedPrintable )
+ {
+ textEnc = rfc822_8bit(text, len, &lenEnc);
+ }
+ else // Encoding_Base64
+ {
+ textEnc = rfc822_binary(text, len, &lenEnc);
+ while ( textEnc[lenEnc - 2] == '\r' && textEnc[lenEnc - 1] == '\n'
)
+ {
+ // discard eol which we don't need in the header
+ lenEnc -= 2;
+ }
+ }
+
+ // put into string as we might want to do some more replacements...
+ String encword(wxString::FromAscii(CHAR_CAST(textEnc)
+#if wxCHECK_VERSION(2, 9, 0)
+ , lenEnc
+#endif
+ ));
+
+ // hack: rfc822_8bit() doesn't encode spaces normally but we must
+ // do it inside the headers
+ //
+ // we also have to encode '?'s in the headers which are not encoded by it
+ if ( enc2047 == MIME::Encoding_QuotedPrintable )
+ {
+ String encword2;
+ encword2.reserve(encword.length());
+
+ bool replaced = false;
+ for ( const wxChar *p = encword.c_str(); *p; p++ )
+ {
+ switch ( *p )
+ {
+ case ' ':
+ encword2 += _T("=20");
+ break;
+
+ case '\t':
+ encword2 += _T("=09");
+ break;
+
+ case '?':
+ encword2 += _T("=3F");
+ break;
+
+ default:
+ encword2 += *p;
+
+ // skip assignment to replaced below
+ continue;
+ }
+
+ replaced = true;
+ }
+
+ if ( replaced )
+ {
+ encword = encword2;
+ }
+ }
+
+ // append this word to the header
+ out << _T("=?") << csName << _T('?') << (char)enc2047 << _T('?')
+ << encword
+ << _T("?=");
+
+ fs_give((void **)&textEnc);
+
+ // skip the already encoded part
+ s += len;
+ }
+
+ return out;
+}
+
+wxCharBuffer MIME::EncodeHeader(const String& in, wxFontEncoding enc)
+{
+ if ( !NeedsEncoding(in) )
+ return in.ToAscii();
+
+ // get the encoding in RFC 2047 sense: choose the most reasonable one
+ if ( enc == wxFONTENCODING_SYSTEM )
+ enc = wxLocale::GetSystemEncoding();
+
+ MIME::Encoding enc2047 = MIME::GetEncodingForFontEncoding(enc);
+
+ if ( enc2047 == MIME::Encoding_Unknown )
+ {
+ FAIL_MSG( _T("should have valid MIME encoding") );
+
+ enc2047 = MIME::Encoding_QuotedPrintable;
+ }
+
+ // get the name of the charset to use
+ String csName = MIME::GetCharsetForFontEncoding(enc);
+ if ( csName.empty() )
+ {
+ FAIL_MSG( _T("should have a valid charset name!") );
+
+ csName = _T("UNKNOWN");
+ }
+
+
+ String headerEnc;
+ headerEnc.reserve(2*in.length());
+
+ // for QP we encode each header word separately so that the header remains
+ // readable, but for Base64 it's useless to do this as it's unreadable
+ // anyhow so we just encode everything at once
+ if ( enc2047 == MIME::Encoding_QuotedPrintable )
+ {
+ // encode each word of the header
+ const wxArrayString words(wxStringTokenize(in));
+ const size_t count = words.size();
+ for ( size_t n = 0; n < count; ++n )
+ {
+ headerEnc += EncodeWord(words[n], enc, enc2047, csName);
+ if ( n + 1 < count )
+ headerEnc += ' ';
+ }
+ }
+ else // MIME::Encoding_Base64
+ {
+ headerEnc = EncodeWord(in, enc, enc2047, csName);
+ }
+
+ return headerEnc.ToAscii();
+}
Modified: trunk/M/src/mail/SendMessageCC.cpp
===================================================================
--- trunk/M/src/mail/SendMessageCC.cpp 2007-08-26 12:07:41 UTC (rev 7343)
+++ trunk/M/src/mail/SendMessageCC.cpp 2007-08-27 20:03:17 UTC (rev 7344)
@@ -32,6 +32,7 @@
#include "Mversion.h"
#include "MailFolderCC.h"
+#include "mail/MimeDecode.h"
#include "LogCircle.h"
@@ -113,17 +114,6 @@
// constants
// ----------------------------------------------------------------------------
-// the encodings defined by RFC 2047
-//
-// NB: don't change the values of the enum elements, EncodeHeaderString()
-// relies on them being what they are!
-enum MimeEncoding
-{
- MimeEncoding_Unknown,
- MimeEncoding_Base64 = 'B',
- MimeEncoding_QuotedPrintable = 'Q'
-};
-
// trace mask for message sending/queuing operations
#define TRACE_SEND _T("send")
@@ -342,12 +332,7 @@
// set up default value for From (Reply-To is set in InitNew() as it isn't
// needed for the resent messages)
- AddressList_obj addrList(AddressList::CreateFromAddress(m_profile));
- Address *addrFrom = addrList->GetFirst();
- if ( addrFrom )
- {
- m_From = addrFrom->GetAddress();
- }
+ m_From = Address::GetSenderAddress(m_profile);
// remember the default hostname to use for addresses without host part
m_DefaultHost = READ_CONFIG_TEXT(profile, MP_HOSTNAME);
@@ -636,55 +621,6 @@
// SendMessageCC encodings
// ----------------------------------------------------------------------------
-static MimeEncoding GetMimeEncodingForFontEncoding(wxFontEncoding enc)
-{
- // QP should be used for the encodings which mostly overlap with US_ASCII,
- // Base64 for the others - choose the encoding method
- switch ( enc )
- {
- case wxFONTENCODING_ISO8859_1:
- case wxFONTENCODING_ISO8859_2:
- case wxFONTENCODING_ISO8859_3:
- case wxFONTENCODING_ISO8859_4:
- case wxFONTENCODING_ISO8859_9:
- case wxFONTENCODING_ISO8859_10:
- case wxFONTENCODING_ISO8859_13:
- case wxFONTENCODING_ISO8859_14:
- case wxFONTENCODING_ISO8859_15:
-
- case wxFONTENCODING_CP1250:
- case wxFONTENCODING_CP1252:
- case wxFONTENCODING_CP1254:
- case wxFONTENCODING_CP1257:
-
- case wxFONTENCODING_UTF7:
- case wxFONTENCODING_UTF8:
-
- return MimeEncoding_QuotedPrintable;
-
- case wxFONTENCODING_ISO8859_5:
- case wxFONTENCODING_ISO8859_6:
- case wxFONTENCODING_ISO8859_7:
- case wxFONTENCODING_ISO8859_8:
- case wxFONTENCODING_ISO8859_11:
- case wxFONTENCODING_ISO8859_12:
-
- case wxFONTENCODING_CP1251:
- case wxFONTENCODING_CP1253:
- case wxFONTENCODING_CP1255:
- case wxFONTENCODING_CP1256:
-
- case wxFONTENCODING_KOI8:
- return MimeEncoding_Base64;
-
- default:
- FAIL_MSG( _T("unknown encoding") );
-
- case wxFONTENCODING_SYSTEM:
- return MimeEncoding_Unknown;
- }
-}
-
// Check if text can be sent without encoding it (using QP or Base64): for
// this it must not contain 8bit chars and must not have too long lines
static bool NeedsToBeEncoded(const unsigned char *text)
@@ -724,239 +660,17 @@
m_encHeaders = enc;
}
-// returns true if the character must be encoded in an SMTP [address] header
-static inline bool NeedsEncodingInHeader(unsigned char c)
-{
- return iscntrl(c) || c >= 127;
-}
-
-String
-SendMessageCC::EncodeHeaderString(const String& header)
-{
- // if headers are already encoded, don't do anything
- if ( !m_encodeHeaders )
- return header;
-
- // if a header contains "=?", encode it anyhow to avoid generating invalid
- // encoded words
- if ( !wxStrstr(header, _T("=?")) )
- {
- // only encode the strings which contain the characters unallowed in RFC
- // 822 headers
- const unsigned char *p;
- for ( p = (const unsigned char *)header.c_str(); *p; p++ )
- {
- if ( NeedsEncodingInHeader(*p) )
- break;
- }
-
- if ( !*p )
- {
- // string has only valid chars, don't encode
- return header;
- }
- }
-
- // get the encoding in RFC 2047 sense: choose the most reasonable one
- wxFontEncoding enc = m_encHeaders == wxFONTENCODING_SYSTEM
- ? wxLocale::GetSystemEncoding()
- : m_encHeaders;
-
- MimeEncoding enc2047 = GetMimeEncodingForFontEncoding(enc);
-
- if ( enc2047 == MimeEncoding_Unknown )
- {
- FAIL_MSG( _T("should have valid MIME encoding") );
-
- enc2047 = MimeEncoding_QuotedPrintable;
- }
-
- // get the name of the charset to use
- String csName = EncodingToCharset(enc);
- if ( csName.empty() )
- {
- FAIL_MSG( _T("should have a valid charset name!") );
-
- csName = _T("UNKNOWN");
- }
-
- // the entire encoded header
- String headerEnc;
- headerEnc.reserve(csName.length() + 2*header.length() + 16);
-
- // encode the header splitting it in the chunks such that they will be no
- // longer than 75 characters each
- //
- // FIXME-Unicode: we shouldn't use a global encoding for headers any more,
- // we could mix different encoding inside the same header
- const wxCharBuffer buf(header.mb_str(wxCSConv(enc)));
- const char *s = buf;
- while ( *s )
- {
- // if this is not the first line, insert a line break
- if ( !headerEnc.empty() )
- {
- headerEnc << _T("\r\n ");
- }
-
- static const size_t RFC2047_MAXWORD_LEN = 75;
-
- // how many characters may we put in this encoded word?
- size_t len = 0;
-
- // take into account the length of "=?charset?...?="
- int lenRemaining = RFC2047_MAXWORD_LEN - (5 + csName.length());
-
- // for QP we need to examine all characters
- if ( enc2047 == MimeEncoding_QuotedPrintable )
- {
- for ( ; s[len]; len++ )
- {
- const char c = s[len];
-
- // normal characters stand for themselves in QP, the encoded ones
- // take 3 positions (=XX)
- lenRemaining -= (NeedsEncodingInHeader(c) || strchr(" \t=?", c))
- ? 3 : 1;
-
- if ( lenRemaining <= 0 )
- {
- // can't put any more chars into this word
- break;
- }
- }
- }
- else // Base64
- {
- // we can calculate how many characters we may put into lenRemaining
- // directly
- len = (lenRemaining / 4) * 3 - 2;
-
- // but not more than what we have
- size_t lenMax = wxStrlen(s);
- if ( len > lenMax )
- {
- len = lenMax;
- }
- }
-
- // do encode this word
- unsigned char *text = (unsigned char *)s; // cast for cclient
-
- // length of the encoded text and the text itself
- unsigned long lenEnc;
- unsigned char *textEnc;
-
- if ( enc2047 == MimeEncoding_QuotedPrintable )
- {
- textEnc = rfc822_8bit(text, len, &lenEnc);
- }
- else // MimeEncoding_Base64
- {
- textEnc = rfc822_binary(text, len, &lenEnc);
- while ( textEnc[lenEnc - 2] == '\r' && textEnc[lenEnc - 1] == '\n'
)
- {
- // discard eol which we don't need in the header
- lenEnc -= 2;
- }
- }
-
- // put into string as we might want to do some more replacements...
- String encword(wxString::FromAscii(CHAR_CAST(textEnc)
-#if wxCHECK_VERSION(2, 9, 0)
- , lenEnc
-#endif
- ));
-
- // hack: rfc822_8bit() doesn't encode spaces normally but we must
- // do it inside the headers
- //
- // we also have to encode '?'s in the headers which are not encoded by it
- if ( enc2047 == MimeEncoding_QuotedPrintable )
- {
- String encword2;
- encword2.reserve(encword.length());
-
- bool replaced = false;
- for ( const wxChar *p = encword.c_str(); *p; p++ )
- {
- switch ( *p )
- {
- case ' ':
- encword2 += _T("=20");
- break;
-
- case '\t':
- encword2 += _T("=09");
- break;
-
- case '?':
- encword2 += _T("=3F");
- break;
-
- default:
- encword2 += *p;
-
- // skip assignment to replaced below
- continue;
- }
-
- replaced = true;
- }
-
- if ( replaced )
- {
- encword = encword2;
- }
- }
-
- // append this word to the header
- headerEnc << _T("=?") << csName << _T('?') << (char)enc2047 << _T('?')
- << encword
- << _T("?=");
-
- fs_give((void **)&textEnc);
-
- // skip the already encoded part
- s += len;
- }
-
- return headerEnc;
-}
-
-// unlike EncodeHeaderString(), we should only encode the personal name part
of the
-// address headers
void
-SendMessageCC::EncodeAddress(struct mail_address *adr)
+SendMessageCC::SetSubject(const String& subject)
{
- if ( adr->personal )
- {
- char *tmp = adr->personal;
- adr->personal =
cpystr(wxConvertWX2MB(EncodeHeaderString(wxConvertMB2WX(tmp))));
-
- fs_give((void **)&tmp);
- }
-}
-
-void
-SendMessageCC::EncodeAddressList(struct mail_address *adr)
-{
- while ( adr )
- {
- EncodeAddress(adr);
-
- adr = adr->next;
- }
-}
-
-void
-SendMessageCC::SetSubject(const String &subject)
-{
if(m_Envelope->subject)
fs_give((void **)&m_Envelope->subject);
- String subj = EncodeHeaderString(subject);
- m_Envelope->subject = cpystr(wxConvertWX2MB(subj.c_str()));
+ // if headers are already encoded, don't do anything, they must be already
+ // in ASCII
+ wxCharBuffer buf(m_encodeHeaders ? MIME::EncodeHeader(subject, m_encHeaders)
+ : subject.ToAscii());
+ m_Envelope->subject = cpystr(buf);
}
void
@@ -1013,7 +727,7 @@
}
// parse into ADDRESS struct
- *pAdr = ParseAddressList(address, m_DefaultHost);
+ *pAdr = ParseAddressList(address, m_DefaultHost, m_encHeaders);
// finally filter out any invalid addressees
CheckAddressFieldForErrors(*pAdr);
@@ -1042,8 +756,6 @@
adr = adrNext;
}
-
- EncodeAddressList(adrStart);
}
void
@@ -1121,19 +833,6 @@
return true;
}
-String
-SendMessageCC::EncodingToCharset(wxFontEncoding enc)
-{
- // translate encoding to the charset
- wxString cs;
- if ( enc != wxFONTENCODING_SYSTEM && enc != wxFONTENCODING_DEFAULT )
- {
- cs = wxFontMapper::GetEncodingName(enc).Upper();
- }
-
- return cs;
-}
-
// ----------------------------------------------------------------------------
// methods to manage the extra headers
// ----------------------------------------------------------------------------
@@ -1507,12 +1206,12 @@
{
// some encodings should be encoded in QP as they typically contain
// only a small number of non printable characters while others
- // should be incoded in Base64 as almost all characters used in
them
+ // should be encoded in Base64 as almost all characters used in
them
// are outside basic Ascii set
- switch ( GetMimeEncodingForFontEncoding(enc) )
+ switch ( MIME::GetEncodingForFontEncoding(enc) )
{
- case MimeEncoding_Unknown:
- case MimeEncoding_QuotedPrintable:
+ case MIME::Encoding_Unknown:
+ case MIME::Encoding_QuotedPrintable:
// automatically translated to QP by c-client
bdy->encoding = ENC8BIT;
break;
@@ -1521,7 +1220,7 @@
FAIL_MSG( _T("unknown MIME encoding") );
// fall through
- case MimeEncoding_Base64:
+ case MIME::Encoding_Base64:
if ( m_Protocol == Prot_SMTP &&
READ_CONFIG_BOOL(m_profile, MP_SMTP_USE_8BIT) )
{
@@ -1594,7 +1293,7 @@
}
else // 8bit message
{
- cs = EncodingToCharset(enc);
+ cs = MIME::GetCharsetForFontEncoding(enc);
if ( cs.empty() )
{
cs = m_CharSet;
Modified: trunk/M/tests/mime/decode.cpp
===================================================================
--- trunk/M/tests/mime/decode.cpp 2007-08-26 12:07:41 UTC (rev 7343)
+++ trunk/M/tests/mime/decode.cpp 2007-08-27 20:03:17 UTC (rev 7344)
@@ -50,6 +50,54 @@
return ((c1 -= (isdigit (c1) ? '0' : ((c1 <= 'Z') ? 'A' : 'a') - 10)) << 4) +
(c2 - (isdigit (c2) ? '0' : ((c2 <= 'Z') ? 'A' : 'a') - 10));
}
+
+/* Convert binary contents to BASE64
+ * Accepts: source
+ * length of source
+ * pointer to return destination length
+ * Returns: destination as BASE64
+ */
+
+unsigned char *rfc822_binary (void *src,unsigned long srcl,unsigned long *len)
+{
+ unsigned char *ret,*d;
+ unsigned char *s = (unsigned char *) src;
+ char *v = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ unsigned long i = ((srcl + 2) / 3) * 4;
+ *len = i += 2 * ((i / 60) + 1);
+ d = ret = (unsigned char *) fs_get ((size_t) ++i);
+ /* process tuplets */
+ for (i = 0; srcl >= 3; s += 3, srcl -= 3) {
+ *d++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */
+ /* byte 2: low 2 bits (1), high 4 bits (2) */
+ *d++ = v[((s[0] << 4) + (s[1] >> 4)) & 0x3f];
+ /* byte 3: low 4 bits (2), high 2 bits (3) */
+ *d++ = v[((s[1] << 2) + (s[2] >> 6)) & 0x3f];
+ *d++ = v[s[2] & 0x3f]; /* byte 4: low 6 bits (3) */
+ if ((++i) == 15) { /* output 60 characters? */
+ i = 0; /* restart line break count, insert CRLF */
+ *d++ = '\015'; *d++ = '\012';
+ }
+ }
+ if (srcl) {
+ *d++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */
+ /* byte 2: low 2 bits (1), high 4 bits (2) */
+ *d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
+ /* byte 3: low 4 bits (2), high 2 bits (3) */
+ *d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) : 0)) & 0x3f] : '=';
+ /* byte 4: low 6 bits (3) */
+ *d++ = srcl ? v[s[2] & 0x3f] : '=';
+ if (srcl) srcl--; /* count third character if processed */
+ if ((++i) == 15) { /* output 60 characters? */
+ i = 0; /* restart line break count, insert CRLF */
+ *d++ = '\015'; *d++ = '\012';
+ }
+ }
+ *d++ = '\015'; *d++ = '\012'; /* insert final CRLF */
+ *d = '\0'; /* tie off string */
+ if (((unsigned long) (d - ret)) != *len) fatal ("rfc822_binary logic flaw");
+ return ret; /* return the resulting string */
+}
/* Convert QUOTED-PRINTABLE contents to 8BIT
* Accepts: source
* length of source
@@ -223,19 +271,120 @@
*d = '\0'; /* NUL terminate just in case */
return ret; /* return the string */
}
+
+/* Convert 8BIT contents to QUOTED-PRINTABLE
+ * Accepts: source
+ * length of source
+ * pointer to return destination length
+ * Returns: destination as quoted-printable text
+ */
+
+#define MAXL (size_t) 75 /* 76th position only used by continuation = */
+
+unsigned char *rfc822_8bit (unsigned char *src,unsigned long srcl,
+ unsigned long *len)
+{
+ unsigned long lp = 0;
+ unsigned char *ret = (unsigned char *)
+ fs_get ((size_t) (3*srcl + 3*(((3*srcl)/MAXL) + 1)));
+ unsigned char *d = ret;
+ char *hex = "0123456789ABCDEF";
+ unsigned char c;
+ while (srcl--) { /* for each character */
+ /* true line break? */
+ if (((c = *src++) == '\015') && (*src == '\012') && srcl) {
+ *d++ = '\015'; *d++ = *src++; srcl--;
+ lp = 0; /* reset line count */
+ }
+ else { /* not a line break */
+ /* quoting required? */
+ if (iscntrl (c) || (c == 0x7f) || (c & 0x80) || (c == '=') ||
+ ((c == ' ') && (*src == '\015'))) {
+ if ((lp += 3) > MAXL) { /* yes, would line overflow? */
+ *d++ = '='; *d++ = '\015'; *d++ = '\012';
+ lp = 3; /* set line count */
+ }
+ *d++ = '='; /* quote character */
+ *d++ = hex[c >> 4]; /* high order 4 bits */
+ *d++ = hex[c & 0xf]; /* low order 4 bits */
+ }
+ else { /* ordinary character */
+ if ((++lp) > MAXL) { /* would line overflow? */
+ *d++ = '='; *d++ = '\015'; *d++ = '\012';
+ lp = 1; /* set line count */
+ }
+ *d++ = c; /* ordinary character */
+ }
+ }
+ }
+ *d = '\0'; /* tie off destination */
+ *len = d - ret; /* calculate true size */
+ /* try to give some space back */
+ fs_resize ((void **) &ret,(size_t) *len + 1);
+ return ret;
}
+}
int main()
{
wxInitializer init;
- String s;
+ static const struct MimeTestData
+ {
+ const char *encoded;
+ const char *utf8;
+ wxFontEncoding enc;
+ } data[] =
+ {
+ {
+ "=?KOI8-R?B?79TXxdTZIM7BINfP0NLP09k=?=",
+ "\xd0\x9e\xd1\x82\xd0\xb2\xd0\xb5\xd1\x82\xd1\x8b\x20"
+ "\xd0\xbd\xd0\xb0\x20\xd0\xb2\xd0\xbe\xd0\xbf\xd1\x80"
+ "\xd0\xbe\xd1\x81\xd1\x8b",
+ wxFONTENCODING_KOI8
+ },
+ {
+ "=?KOI8-R?B?99jA1sHOyc4g68/O09TBztTJziBcKENvbnN0YW50a"
+ "W5lIFZ5dXpoYW5pblwp?=",
+ "\xd0\x92\xd1\x8c\xd1\x8e\xd0\xb6\xd0\xb0\xd0\xbd"
+ "\xd0\xb8\xd0\xbd\x20\xd0\x9a\xd0\xbe\xd0\xbd\xd1\x81"
+ "\xd1\x82\xd0\xb0\xd0\xbd\xd1\x82\xd0\xb8\xd0\xbd\x20"
+ "\x5c\x28\x43\x6f\x6e\x73\x74\x61\x6e\x74\x69\x6e\x65"
+ "\x20\x56\x79\x75\x7a\x68\x61\x6e\x69\x6e\x5c\x29",
+ wxFONTENCODING_KOI8
+ },
+ {
+ "Ludovic =?ISO-8859-1?Q?P=E9net?= <[EMAIL PROTECTED]>",
+ "Ludovic P\303\251net <[EMAIL PROTECTED]>",
+ wxFONTENCODING_ISO8859_1
+ },
+ {
+ "Ludovic =?UTF-8?Q?P=C3=A9net?= <[EMAIL PROTECTED]>",
+ "Ludovic P\303\251net <[EMAIL PROTECTED]>",
+ wxFONTENCODING_UTF8
+ },
+ };
- s = MIME::DecodeHeader("=?koi8-r?B?79TXxdTZIM7BINfP0NLP09k=?=");
- s =
MIME::DecodeHeader("=?koi8-r?B?99jA1sHOyc4g68/O09TBztTJziBcKENvbnN0YW50aW5lIFZ5dXpoYW5pblwp?=
<[EMAIL PROTECTED]>");
- s = MIME::DecodeHeader("Ludovic =?ISO-8859-1?Q?P=E9net?= <[EMAIL
PROTECTED]>");
- printf(s.utf8_str());
+ int rc = EXIT_SUCCESS;
+ for ( unsigned n = 0; n < WXSIZEOF(data); ++n )
+ {
+ const MimeTestData& d = data[n];
+ const wxString s = MIME::DecodeHeader(d.encoded);
+ if ( s != wxString::FromUTF8(d.utf8) )
+ {
+ printf("ERROR: decoding #%u: expected \"%s\", got \"%s\"\n",
+ n, d.utf8, (const char *)s.utf8_str());
+ rc = EXIT_FAILURE;
+ }
- return 0;
+ const wxCharBuffer buf = MIME::EncodeHeader(s, d.enc);
+ if ( strcmp(buf, d.encoded) != 0 )
+ {
+ printf("ERROR: encoding #%u: expected \"%s\", got \"%s\"\n",
+ n, d.encoded, (const char *)buf);
+ }
+ }
+
+ return rc;
}
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc.
Still grepping through log files to find problems? Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >> http://get.splunk.com/
_______________________________________________
Mahogany-cvsupdates mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mahogany-cvsupdates