[M-git] Mahogany sources repository. branch master updated. v0.67-877-gbcc0c9d1

vadz via Mahogany-cvsupdates <[email protected]> Fri, 21 Mar 2025 03:59:40 +0000
Newsgroups gmane.mail.mahogany.cvs
Message-ID <[email protected]>
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Mahogany sources repository.".

The branch, master has been updated
       via  bcc0c9d101a040236ffe7d16c659956ebc0eb536 (commit)
       via  1ce4e762a220d22008d95c29033b475dc41bbf11 (commit)
       via  583336819f11ab5e23bddfff15be73b6daa35854 (commit)
       via  570b1608a0d04a22ea2d106e53ebbe9dcbc82c37 (commit)
       via  7517e90b2e31776448ff881a3cfaa1435f55a6ce (commit)
       via  50790426be9c783e4edea4a2e3ecc807efc90594 (commit)
       via  044f6c4bc0c2a405f81617a9abd67929b9cd0077 (commit)
       via  ba821e338687c1602fc536c3db591c10ca281a8a (commit)
       via  f14cf32b94dd6d98b4ccf4bd52f1e787bceab2a3 (commit)
       via  9f4b95a7555bf07860a2286f1f04b66885e0b71a (commit)
       via  ba1921ea80e18ca17d4978c5a57da36625c7233f (commit)
       via  3d1f4cf9b4ac17f17b49f49145d51e3c0c131d4a (commit)
       via  1ac798f306170815921c6d482d668cf27ede5e88 (commit)
       via  868edf0e0f3facab75414106cbc04f2b27ed05b9 (commit)
       via  409d6974435db410434831eed0ef2d561fcb6569 (commit)
       via  6a5486de8057dc3e64036d61c67e769ccae2f8a6 (commit)
       via  06d5b206e24809152471268a618bd0283722e4ee (commit)
       via  64372212fc20adc244123679f96a2f5612b147d0 (commit)
       via  610297bac03abfb00321bd71289c82067f51a858 (commit)
       via  43f8634ada42e8e998e9962c7f1fdea7adb4a07e (commit)
       via  a1e6cff79899ea701e8e6e033618a622d501a674 (commit)
       via  5bc4dbcf83c197a1ede8eac399b55d6a1ecb50dc (commit)
      from  eb1e403d031263ca26c36b5cb70aa45ea627d6dc (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit bcc0c9d101a040236ffe7d16c659956ebc0eb536
Author: Vadim Zeitlin <[email protected]>
Date:   Fri Mar 21 04:56:49 2025 +0100

    Strip literal CR LF from the internal header representation
    
    Having CR LF in the "References:" header broke its value in the outgoing
    messages after recent changes to MIME::EncodeHeader() which resulted in
    not just throwing away all whitespace characters in the header when
    encoding it.
    
    It's not completely clear if AddHeaderEntry() should be given strings
    with CR LF or if they should be stripped at some higher level, but for
    now do it here to at least ensure that the program generates correct
    outgoing messages.

diff --git a/src/gui/wxComposeView.cpp b/src/gui/wxComposeView.cpp
index f49cec7d..736bd151 100644
--- a/src/gui/wxComposeView.cpp
+++ b/src/gui/wxComposeView.cpp
@@ -5375,8 +5375,26 @@ void wxComposeView::OnSendThreadDone(wxThreadEvent& evt)
 }
 
 void
-wxComposeView::AddHeaderEntry(const String& name, const String& value)
+wxComposeView::AddHeaderEntry(const String& name, const String& valueOrig)
 {
+   // We may need to unfold the header if it contains line folds, we don't want
+   // to have them in the logical header value that we store internally.
+   String value;
+   for ( size_t pos = 0;; )
+   {
+      auto eol = valueOrig.find("\r\n ", pos);
+      if ( eol == String::npos )
+      {
+         value.append(valueOrig, pos, String::npos);
+         break;
+      }
+
+      value.append(valueOrig, pos, eol - pos);
+      value += ' ';
+
+      pos = eol + 3;
+   }
+
    // first check if we don't already have a header with this name
    const StringList::iterator end = m_extraHeadersNames.end();
    for ( StringList::iterator i = m_extraHeadersNames.begin(),

commit 1ce4e762a220d22008d95c29033b475dc41bbf11
Author: Vadim Zeitlin <[email protected]>
Date:   Fri Mar 21 04:18:10 2025 +0100

    Default encoding to UTF-8 in the MIME encoding test
    
    This is by far the most common.

diff --git a/tests/mime/decode.cpp b/tests/mime/decode.cpp
index 168d4b03..04c9ce77 100644
--- a/tests/mime/decode.cpp
+++ b/tests/mime/decode.cpp
@@ -337,7 +337,7 @@ int main()
     {
         const char *encoded;
         const char *utf8;
-        wxFontEncoding enc;
+        wxFontEncoding enc = wxFONTENCODING_UTF8;
         const char *encodedAlt = nullptr;
     } data[] =
     {
@@ -365,8 +365,7 @@ int main()
         },
         {
             "=?UTF-8?Q?Ludovic_P=C3=A9net?=",
-            "Ludovic P\303\251net",
-            wxFONTENCODING_UTF8
+            "Ludovic P\303\251net"
         },
 
         {
@@ -380,8 +379,7 @@ int main()
 
         {
             "=?UTF-8?Q?2006_=D0=92_=D0=A6_2007?=",
-            "2006 \xD0\x92 \xD0\xA6 2007",
-            wxFONTENCODING_UTF8
+            "2006 \xD0\x92 \xD0\xA6 2007"
         },
 
         {

commit 583336819f11ab5e23bddfff15be73b6daa35854
Author: Vadim Zeitlin <[email protected]>
Date:   Fri Mar 21 04:10:50 2025 +0100

    Decide which RFC 2047 encoding to use based on input contents
    
    Use Base64 if it's going to be more compact and only use QP if there are
    relatively few characters to encode.
    
    This requires modifying the test to accept Base64 encoding of QP-encoded
    strings.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index f56b613d..93100d69 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -432,26 +432,6 @@ static inline bool NeedsEncodingInHeader(unsigned c)
    return c < 20 || 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 )
-      return true;
-
-   // only encode the strings which contain the characters unallowed in RFC
-   // 822 headers
-   for ( auto c : in )
-   {
-      if ( NeedsEncodingInHeader(c.GetValue()) )
-         return true;
-   }
-
-   // string has only valid chars, don't encode
-   return false;
-}
-
 // Some constants used by the encoding functions below.
 static constexpr size_t RFC2047_MAXWORD_LEN = 75;
 static constexpr size_t MIME_WORD_OVERHEAD = 7; // =?...?X?...?=
@@ -609,7 +589,35 @@ EncodeTextBase64(const char* in, const std::string& csName)
 
 std::string MIME::EncodeHeader(const String& in, wxFontEncoding enc)
 {
-   if ( !NeedsEncoding(in) )
+   // First check if this text needs to be encoded at all.
+   size_t countNeedsEncoding = 0;
+   for ( auto c : in )
+   {
+      if ( NeedsEncodingInHeader(c.GetValue()) )
+         countNeedsEncoding++;
+   }
+
+   // We arbitrarily decide that if more than 1/4 of the string needs to be
+   // encoded, we'll encode it using Base64, otherwise we'll use QP to keep it
+   // roughly readable even in the encoded form.
+   MIME::Encoding enc2047 = MIME::Encoding_Unknown;
+   if ( !countNeedsEncoding )
+   {
+      // if input contains "=?", encode it anyhow to avoid generating invalid
+      // encoded words
+      if ( in.find(_T("=?")) != wxString::npos )
+         enc2047 = MIME::Encoding_QuotedPrintable;
+   }
+   else if ( countNeedsEncoding >= in.length() / 4 )
+   {
+      enc2047 = MIME::Encoding_Base64;
+   }
+   else
+   {
+      enc2047 = MIME::Encoding_QuotedPrintable;
+   }
+
+   if ( enc2047 == MIME::Encoding_Unknown )
       return std::string(in.ToAscii());
 
    // If we were given an explicit encoding to use, check if we can use it, and
@@ -642,11 +650,11 @@ std::string MIME::EncodeHeader(const String& in, wxFontEncoding enc)
    }
 
    std::string out;
-   switch ( MIME::GetEncodingForFontEncoding(enc) )
+   switch ( enc2047 )
    {
       case MIME::Encoding_Unknown:
-         FAIL_MSG( "using unknown MIME encoding?" );
-         wxFALLTHROUGH;
+         FAIL_MSG( "unreachable" );
+         break;
 
       case MIME::Encoding_QuotedPrintable:
          out = EncodeTextQP(inbuf.data(), csName);
diff --git a/tests/mime/decode.cpp b/tests/mime/decode.cpp
index 6f6d1f42..168d4b03 100644
--- a/tests/mime/decode.cpp
+++ b/tests/mime/decode.cpp
@@ -338,6 +338,7 @@ int main()
         const char *encoded;
         const char *utf8;
         wxFontEncoding enc;
+        const char *encodedAlt = nullptr;
     } data[] =
     {
         {
@@ -373,7 +374,8 @@ int main()
             "  =?UTF-8?Q?=D0=B8=D0=BD?=",
             "\xD0\x92\xD0\xB0\xD0\xB4\xD0\xB8\xD0\xBC "
             "\xD0\xA6\xD0\xB5\xD0\xB9\xD1\x82\xD0\xBB\xD0\xB8\xD0\xBD",
-            wxFONTENCODING_UTF8
+            wxFONTENCODING_UTF8,
+            "=?UTF-8?B?0JLQsNC00LjQvCDQptC10LnRgtC70LjQvQ==?="
         },
 
         {
@@ -385,7 +387,8 @@ int main()
         {
             "=?UTF-8?Q?=D0=92_=D0=A6_2007?=",
             "\xD0\x92 \xD0\xA6 2007",
-            wxFONTENCODING_UTF8
+            wxFONTENCODING_UTF8,
+            "=?UTF-8?B?0JIg0KYgMjAwNw==?="
         },
     };
 
@@ -407,7 +410,7 @@ int main()
             continue;
 
         const std::string buf = MIME::EncodeHeader(s, d.enc);
-        if ( buf != d.encoded )
+        if ( buf != d.encoded && (!d.encodedAlt || buf != d.encodedAlt) )
         {
             printf("ERROR: encoding #%u: expected \"%s\", got \"%s\"\n",
                    n, d.encoded, buf.c_str());

commit 570b1608a0d04a22ea2d106e53ebbe9dcbc82c37
Author: Vadim Zeitlin <[email protected]>
Date:   Fri Mar 21 04:11:56 2025 +0100

    Remove redundant encoding enum declaration
    
    This is the same thing as MIME::Encoding.
    
    No real changes.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index 088ebac0..f56b613d 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -235,12 +235,7 @@ String DecodeHeaderOnce(const String& in, wxFontEncoding *pEncoding)
          }
 
          // get the encoding in RFC 2047 sense
-         enum
-         {
-            Encoding_Unknown,
-            Encoding_Base64,
-            Encoding_QuotedPrintable
-         } enc2047 = Encoding_Unknown;
+         MIME::Encoding enc2047 = MIME::Encoding_Unknown;
 
          ++p; // skip '?'
 
@@ -256,14 +251,14 @@ String DecodeHeaderOnce(const String& in, wxFontEncoding *pEncoding)
             if ( *(p + 1) == '?' )
             {
                if ( *p == 'B' || *p == 'b' )
-                  enc2047 = Encoding_Base64;
+                  enc2047 = MIME::Encoding_Base64;
                else if ( *p == 'Q' || *p == 'q' )
-                  enc2047 = Encoding_QuotedPrintable;
+                  enc2047 = MIME::Encoding_QuotedPrintable;
             }
             //else: multi letter encoding unrecognized
          }
 
-         if ( enc2047 == Encoding_Unknown )
+         if ( enc2047 == MIME::Encoding_Unknown )
          {
             wxLogDebug(_T("Unrecognized header encoding in '%s'."), in.c_str());
 
@@ -324,7 +319,7 @@ String DecodeHeaderOnce(const String& in, wxFontEncoding *pEncoding)
             // now decode the text using c-client functions
             unsigned long len;
             void *text;
-            if ( enc2047 == Encoding_Base64 )
+            if ( enc2047 == MIME::Encoding_Base64 )
             {
                text = rfc822_base64(UCHAR_CCAST(encWord.data()), lenEncWord, &len);
             }

commit 7517e90b2e31776448ff881a3cfaa1435f55a6ce
Author: Vadim Zeitlin <[email protected]>
Date:   Fri Mar 21 04:01:11 2025 +0100

    Return std::string from EncodeHeader()
    
    This avoids extra conversions with wxString and using std::string in the
    interface is just generally cleaner than using wxCharBuffer.
    
    No real changes.

diff --git a/include/mail/MimeDecode.h b/include/mail/MimeDecode.h
index c20a3f0f..ce2a0fbc 100644
--- a/include/mail/MimeDecode.h
+++ b/include/mail/MimeDecode.h
@@ -66,7 +66,7 @@ String GetCharsetForFontEncoding(wxFontEncoding enc);
               use the encoding of the current locale
    @return the encoded text or NULL buffer if encoding failed
  */
-wxCharBuffer
+std::string
 EncodeHeader(const wxString& in, wxFontEncoding enc = wxFONTENCODING_SYSTEM);
 
 /**
diff --git a/src/gui/wxComposeView.cpp b/src/gui/wxComposeView.cpp
index 603d96f1..f49cec7d 100644
--- a/src/gui/wxComposeView.cpp
+++ b/src/gui/wxComposeView.cpp
@@ -5402,7 +5402,7 @@ wxComposeView::AddHeaderEntry(const String& name, const String& value)
 
    // if we didn't find it, add a new one
    m_extraHeadersNames.push_back(name.utf8_string());
-   m_extraHeadersValues.push_back(MIME::EncodeHeader(value).data());
+   m_extraHeadersValues.push_back(MIME::EncodeHeader(value));
 }
 
 bool wxComposeView::IsPGPSigningEnabled() const
diff --git a/src/mail/AddressCC.cpp b/src/mail/AddressCC.cpp
index fa69be51..577b412c 100644
--- a/src/mail/AddressCC.cpp
+++ b/src/mail/AddressCC.cpp
@@ -576,7 +576,7 @@ ParseAddressList(const String& address,
       {
          String personal = wxString::FromUTF8(adr2->personal);
          fs_give((void **)&adr2->personal);
-         adr2->personal = cpystr(MIME::EncodeHeader(personal, enc));
+         adr2->personal = cpystr(MIME::EncodeHeader(personal, enc).c_str());
       }
    }
 
diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index 197c6ef2..088ebac0 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -462,8 +462,8 @@ static constexpr size_t RFC2047_MAXWORD_LEN = 75;
 static constexpr size_t MIME_WORD_OVERHEAD = 7; // =?...?X?...?=
 
 // encode the text in the charset with the given name in QP
-static String
-EncodeTextQP(const char* in, const String& csName)
+static std::string
+EncodeTextQP(const char* in, const std::string& csName)
 {
    // encode the text splitting it in the chunks such that they will be no
    // longer than maximum length each
@@ -471,7 +471,7 @@ EncodeTextQP(const char* in, const String& csName)
 
    const char* const HEX_DIGITS = "0123456789ABCDEF";
 
-   String out;
+   std::string out;
    out.reserve(strlen(in) + overhead);
 
    // Each iteration of this loop corresponds to a physical line.
@@ -543,14 +543,14 @@ EncodeTextQP(const char* in, const String& csName)
 }
 
 // same as the function above but use Base64 encoding
-static String
-EncodeTextBase64(const char* in, const String& csName)
+static std::string
+EncodeTextBase64(const char* in, const std::string& csName)
 {
    const size_t overhead = MIME_WORD_OVERHEAD + csName.length();
 
    // encode the word splitting it in the chunks such that they will be no
    // longer than maximum length each
-   String out;
+   std::string out;
    out.reserve(strlen(in) + overhead);
 
    auto *s = reinterpret_cast<const unsigned char*>(in);
@@ -594,10 +594,14 @@ EncodeTextBase64(const char* in, const String& csName)
       }
 
       // put into string as we might want to do some more replacements...
-      String encword(wxString::FromAscii(CHAR_CAST(textEnc), lenEnc));
+      std::string encword(CHAR_CAST(textEnc), lenEnc);
 
       // append this word to the header
-      out << _T("=?") << csName << _T("?B?") << encword << _T("?=");
+      out += "=?";
+      out += csName;
+      out += "?B?";
+      out += encword;
+      out += "?=";
 
       fs_give((void **)&textEnc);
 
@@ -608,10 +612,10 @@ EncodeTextBase64(const char* in, const String& csName)
    return out;
 }
 
-wxCharBuffer MIME::EncodeHeader(const String& in, wxFontEncoding enc)
+std::string MIME::EncodeHeader(const String& in, wxFontEncoding enc)
 {
    if ( !NeedsEncoding(in) )
-      return in.ToAscii();
+      return std::string(in.ToAscii());
 
    // If we were given an explicit encoding to use, check if we can use it, and
    // if not fall back to the same UTF-8 (which can always be used) as we use
@@ -623,12 +627,12 @@ wxCharBuffer MIME::EncodeHeader(const String& in, wxFontEncoding enc)
    }
 
    // get the name of the charset to use
-   String csName = MIME::GetCharsetForFontEncoding(enc);
+   std::string csName = MIME::GetCharsetForFontEncoding(enc).utf8_string();
    if ( csName.empty() )
    {
       FAIL_MSG( _T("should have a valid charset name!") );
 
-      csName = _T("UNKNOWN");
+      csName = "UNKNOWN";
    }
 
    wxCharBuffer inbuf;
@@ -642,7 +646,7 @@ wxCharBuffer MIME::EncodeHeader(const String& in, wxFontEncoding enc)
       inbuf = in.utf8_str();
    }
 
-   String out;
+   std::string out;
    switch ( MIME::GetEncodingForFontEncoding(enc) )
    {
       case MIME::Encoding_Unknown:
@@ -658,7 +662,7 @@ wxCharBuffer MIME::EncodeHeader(const String& in, wxFontEncoding enc)
          break;
    }
 
-   return out.ToAscii();
+   return out;
 }
 
 String MIME::DecodeText(const char *p, size_t len, wxFontEncoding enc)
diff --git a/src/mail/SendMessageCC.cpp b/src/mail/SendMessageCC.cpp
index c223dc99..f915ffd3 100644
--- a/src/mail/SendMessageCC.cpp
+++ b/src/mail/SendMessageCC.cpp
@@ -835,12 +835,12 @@ SendMessageCC::SetSubject(const String& subject)
 
    // don't encode the headers of an existing message second time, we want to
    // preserve them as they are
-   wxCharBuffer buf;
+   std::string buf;
    if ( m_cloneOfExisting )
       buf = subject.ToAscii();
    else
       buf = MIME::EncodeHeader(subject, m_encHeaders);
-   m_Envelope->subject = cpystr(buf);
+   m_Envelope->subject = cpystr(buf.c_str());
 }
 
 void
@@ -1331,8 +1331,8 @@ SendMessageCC::Build(bool forStorage)
          continue;
       }
 
-      const wxCharBuffer value(MIME::EncodeHeader(i->m_value));
-      if ( !value )
+      const std::string value(MIME::EncodeHeader(i->m_value));
+      if ( value.empty() )
       {
          wxLogError(_("Invalid value \"%s\" for the custom header \"%s\""),
                     i->m_value.c_str(), i->m_name.c_str());
@@ -1349,7 +1349,7 @@ SendMessageCC::Build(bool forStorage)
       // Conversion to ASCII is safe because HeaderName::IsValid() would have
       // returned false if we had any non-ASCII characters in the name.
       m_headerNames[h] = strutil_strdup(i->m_name.ToAscii());
-      m_headerValues[h] = strutil_strdup(value);
+      m_headerValues[h] = strutil_strdup(value.c_str());
 
       h++;
    }
@@ -1586,7 +1586,7 @@ SendMessageCC::AddPart(MimeType::Primary type,
          }
 
          par->attribute = strdup(name.ToAscii());
-         par->value     = strdup(MIME::EncodeHeader(i->value));
+         par->value     = strdup(MIME::EncodeHeader(i->value).c_str());
          par->next      = lastpar;
          lastpar = par;
       }
@@ -1638,7 +1638,7 @@ SendMessageCC::AddPart(MimeType::Primary type,
       {
          PARAMETER *par = mail_newbody_parameter();
          par->attribute = strdup(i->name.ToAscii());
-         par->value     = strdup(MIME::EncodeHeader(i->value));
+         par->value     = strdup(MIME::EncodeHeader(i->value).c_str());
          par->next      = NULL;
          if(lastpar)
             lastpar->next = par;
diff --git a/tests/mime/decode.cpp b/tests/mime/decode.cpp
index 36d91829..6f6d1f42 100644
--- a/tests/mime/decode.cpp
+++ b/tests/mime/decode.cpp
@@ -406,11 +406,11 @@ int main()
         if ( d.enc == wxFONTENCODING_DEFAULT )
             continue;
 
-        const wxCharBuffer buf = MIME::EncodeHeader(s, d.enc);
-        if ( strcmp(buf, d.encoded) != 0 )
+        const std::string buf = MIME::EncodeHeader(s, d.enc);
+        if ( buf != d.encoded )
         {
             printf("ERROR: encoding #%u: expected \"%s\", got \"%s\"\n",
-                   n, d.encoded, (const char *)buf);
+                   n, d.encoded, buf.c_str());
         }
     }
 

commit 50790426be9c783e4edea4a2e3ecc807efc90594
Author: Vadim Zeitlin <[email protected]>
Date:   Fri Mar 21 03:47:52 2025 +0100

    Rewrite headers QP MIME encoding code once again
    
    The previous version was still wrong as it didn't handle spaces between
    words correctly any longer.
    
    Simplify things by always encoding, instead of deciding whether to
    encode for each word separately, this is not significantly less readable
    in the encoded form (if we even still care about this) but much simpler
    and more obviously correct.
    
    Also perform our own QP-encoding instead of using c-client function,
    it's so simple that it's much easier to do it like this.
    
    Update the tests to match the results of the encoding algorithm now.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index 46fddd47..197c6ef2 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -465,101 +465,78 @@ static constexpr size_t MIME_WORD_OVERHEAD = 7; // =?...?X?...?=
 static String
 EncodeTextQP(const char* in, const String& csName)
 {
-   // encode the word splitting it in the chunks such that they will be no
+   // encode the text splitting it in the chunks such that they will be no
    // longer than maximum length each
    const size_t overhead = MIME_WORD_OVERHEAD + csName.length();
 
+   const char* const HEX_DIGITS = "0123456789ABCDEF";
+
    String out;
    out.reserve(strlen(in) + overhead);
 
-   auto *s = reinterpret_cast<const unsigned char*>(in);
-   while ( *s )
+   // Each iteration of this loop corresponds to a physical line.
+   while ( *in )
    {
-      // if we wrapped, insert a line break
       if ( !out.empty() )
          out += "\r\n  ";
 
-      // how many characters may we put in this encoded word?
-      size_t len = 0;
+      out += "=?";
+      out += csName;
+      out += "?Q?";
 
-      // take into account the length of "=?charset?...?="
-      int lenRemaining = RFC2047_MAXWORD_LEN - overhead;
-
-      // for QP we need to examine all characters
-      for ( ; s[len]; len++ )
+      // Iterate over the characters that fit into the current line.
+      size_t lineLen = overhead;
+      for ( ;; ++in )
       {
-         const unsigned char c = s[len];
-
-         // normal characters stand for themselves in QP, the encoded ones
-         // take 3 positions (=XX)
-         lenRemaining -= (NeedsEncodingInHeader(c) || strchr("=?", c))
-                           ? 3 : 1;
+         const unsigned char c = *in;
 
-         if ( lenRemaining <= 0 )
-         {
-            // can't put any more chars into this word
+         if ( c == '\0' )
             break;
-         }
-      }
-
-      // do encode this word
-      unsigned char *text = const_cast<unsigned char*>(s); // cast for cclient
-
-      // length of the encoded text and the text itself
-      unsigned long lenEnc;
-      unsigned char *textEnc;
-
-      textEnc = rfc822_8bit(text, len, &lenEnc);
-
-      // put into string as we might want to do some more replacements...
-      String encword(wxString::FromAscii(CHAR_CAST(textEnc), lenEnc));
 
-      // 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
-      String encword2;
-      encword2.reserve(encword.length());
-
-      bool replaced = false;
-      for ( const wxChar *p = encword.c_str(); *p; p++ )
-      {
-         switch ( *p )
+         // If this character is 0, we must encode "c" instead.
+         unsigned char unencoded = '\0';
+         switch ( c )
          {
             case ' ':
-               encword2 += '_'; // More readable than =20
-               break;
-
-            case '\t':
-               encword2 += _T("=09");
+               // Encode spaces as underscores rather than =20, as this is more
+               // readable, even if both are allowed.
+               unencoded = '_';
                break;
 
+               // These characters need to be encoded in headers to avoid
+               // clashing with the encoded word syntax. In principle, we could
+               // let them remain unencoded if they don't occur in the same
+               // combination as in the encoded word syntax, but for now keep
+               // things simple and always encode them.
+            case '=':
             case '?':
-               encword2 += _T("=3F");
                break;
 
             default:
-               encword2 += *p;
-
-               // skip assignment to replaced below
-               continue;
+               if ( !NeedsEncodingInHeader(c) )
+                  unencoded = c;
+               break;
          }
 
-         replaced = true;
-      }
-
-      if ( replaced )
-      {
-         encword = encword2;
-      }
+         const auto lenNeeded = unencoded ? 1 : 3;
+         if ( lineLen + lenNeeded > RFC2047_MAXWORD_LEN )
+            break;
 
-      // append this word to the header
-      out << _T("=?") << csName << _T("?Q?") << encword << _T("?=");
+         if ( unencoded )
+         {
+            out += unencoded;
+         }
+         else
+         {
+            out += '=';
+            out += HEX_DIGITS[c >> 4];
+            out += HEX_DIGITS[c & 0xf];
+         }
 
-      fs_give((void **)&textEnc);
+         lineLen += lenNeeded;
+      }
 
-      // skip the already encoded part
-      s += len;
+      out += "?=";
    }
 
    return out;
diff --git a/tests/mime/decode.cpp b/tests/mime/decode.cpp
index 8473f719..36d91829 100644
--- a/tests/mime/decode.cpp
+++ b/tests/mime/decode.cpp
@@ -358,32 +358,32 @@ int main()
             wxFONTENCODING_KOI8
         },
         {
-            "Ludovic =?ISO-8859-1?Q?P=E9net?= <[email protected]>",
-            "Ludovic P\303\251net <[email protected]>",
+            "=?ISO-8859-1?Q?Ludovic_P=E9net?=",
+            "Ludovic P\303\251net",
             wxFONTENCODING_ISO8859_1
         },
         {
-            "Ludovic =?UTF-8?Q?P=C3=A9net?= <[email protected]>",
-            "Ludovic P\303\251net <[email protected]>",
+            "=?UTF-8?Q?Ludovic_P=C3=A9net?=",
+            "Ludovic P\303\251net",
             wxFONTENCODING_UTF8
         },
 
         {
-            "=?UTF-8?Q?=D0=92=D0=B0=D0=B4=D0=B8=D0=BC_"
-            "=D0=A6=D0=B5=D0=B9=D1=82=D0=BB=D0=B8=D0=BD?=",
+            "=?UTF-8?Q?=D0=92=D0=B0=D0=B4=D0=B8=D0=BC_=D0=A6=D0=B5=D0=B9=D1=82=D0=BB?=\r\n"
+            "  =?UTF-8?Q?=D0=B8=D0=BD?=",
             "\xD0\x92\xD0\xB0\xD0\xB4\xD0\xB8\xD0\xBC "
             "\xD0\xA6\xD0\xB5\xD0\xB9\xD1\x82\xD0\xBB\xD0\xB8\xD0\xBD",
             wxFONTENCODING_UTF8
         },
 
         {
-            "2006 =?UTF-8?Q?=D0=92_=D0=A6?= 2007",
+            "=?UTF-8?Q?2006_=D0=92_=D0=A6_2007?=",
             "2006 \xD0\x92 \xD0\xA6 2007",
             wxFONTENCODING_UTF8
         },
 
         {
-            "=?UTF-8?Q?=D0=92_=D0=A6?= 2007",
+            "=?UTF-8?Q?=D0=92_=D0=A6_2007?=",
             "\xD0\x92 \xD0\xA6 2007",
             wxFONTENCODING_UTF8
         },

commit 044f6c4bc0c2a405f81617a9abd67929b9cd0077
Author: Vadim Zeitlin <[email protected]>
Date:   Wed Mar 12 03:09:23 2025 +0100

    Simplify length calculation code in EncodeTextBase64()
    
    Also improve the comments.
    
    No real changes.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index 151f734c..46fddd47 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -583,23 +583,18 @@ EncodeTextBase64(const char* in, const String& csName)
       if ( !out.empty() )
          out += "\r\n  ";
 
-      // 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 - overhead;
-
       // rfc822_binary() splits lines after 60 characters so don't make
       // chunks longer than this as the base64-encoded headers can't have
       // EOLs in them
       static const int CCLIENT_MAX_BASE64_LEN = 60;
 
-      if ( lenRemaining > CCLIENT_MAX_BASE64_LEN )
-         lenRemaining = CCLIENT_MAX_BASE64_LEN;
+      // but if the charset name is sufficiently long, we may need to make them
+      // shorter than this
+      size_t lenRemaining = wxMin(CCLIENT_MAX_BASE64_LEN,
+                                  RFC2047_MAXWORD_LEN - overhead);
 
-      // we can calculate how many characters we may put into lenRemaining
-      // directly
-      len = (lenRemaining / 4) * 3;
+      // we can calculate how many characters we may put on one line directly
+      size_t len = (lenRemaining / 4) * 3;
 
       // but not more than what we have
       size_t lenMax = strlen(reinterpret_cast<const char*>(s));
@@ -613,9 +608,8 @@ EncodeTextBase64(const char* in, const String& csName)
 
       // length of the encoded text and the text itself
       unsigned long lenEnc;
-      unsigned char *textEnc;
+      unsigned char *textEnc = rfc822_binary(text, len, &lenEnc);
 
-      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

commit ba821e338687c1602fc536c3db591c10ca281a8a
Author: Vadim Zeitlin <[email protected]>
Date:   Wed Mar 12 03:00:24 2025 +0100

    Use constants in text encoding code instead of bare numbers
    
    Also fix an apparent bug when we used 5, instead of 7, when computing
    the remaining available space in the line.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index 62bc6865..151f734c 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -457,14 +457,20 @@ static bool NeedsEncoding(const String& in)
    return false;
 }
 
+// Some constants used by the encoding functions below.
+static constexpr size_t RFC2047_MAXWORD_LEN = 75;
+static constexpr size_t MIME_WORD_OVERHEAD = 7; // =?...?X?...?=
+
 // encode the text in the charset with the given name in QP
 static String
 EncodeTextQP(const char* in, const String& csName)
 {
    // encode the word splitting it in the chunks such that they will be no
-   // longer than 75 characters each
+   // longer than maximum length each
+   const size_t overhead = MIME_WORD_OVERHEAD + csName.length();
+
    String out;
-   out.reserve(csName.length() + strlen(in) + 7 /* for =?...?X?...?= */);
+   out.reserve(strlen(in) + overhead);
 
    auto *s = reinterpret_cast<const unsigned char*>(in);
    while ( *s )
@@ -473,13 +479,11 @@ EncodeTextQP(const char* in, const String& csName)
       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());
+      int lenRemaining = RFC2047_MAXWORD_LEN - overhead;
 
       // for QP we need to examine all characters
       for ( ; s[len]; len++ )
@@ -565,10 +569,12 @@ EncodeTextQP(const char* in, const String& csName)
 static String
 EncodeTextBase64(const char* in, const String& csName)
 {
+   const size_t overhead = MIME_WORD_OVERHEAD + csName.length();
+
    // encode the word splitting it in the chunks such that they will be no
-   // longer than 75 characters each
+   // longer than maximum length each
    String out;
-   out.reserve(csName.length() + strlen(in) + 7 /* for =?...?X?...?= */);
+   out.reserve(strlen(in) + overhead);
 
    auto *s = reinterpret_cast<const unsigned char*>(in);
    while ( *s )
@@ -577,13 +583,11 @@ EncodeTextBase64(const char* in, const String& csName)
       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());
+      int lenRemaining = RFC2047_MAXWORD_LEN - overhead;
 
       // rfc822_binary() splits lines after 60 characters so don't make
       // chunks longer than this as the base64-encoded headers can't have

commit f14cf32b94dd6d98b4ccf4bd52f1e787bceab2a3
Author: Vadim Zeitlin <[email protected]>
Date:   Wed Mar 12 02:51:56 2025 +0100

    Split EncodeText() into 2 functions for QP and Base64
    
    There are already a few differences in how the different encodings are
    handled and there will be even more of them soon, so don't try to handle
    both encodings in a single function, this is too complicated.
    
    No changes yet.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index 1e98b81c..62bc6865 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -457,13 +457,9 @@ static bool NeedsEncoding(const String& in)
    return false;
 }
 
-// encode the given text unconditionally, i.e. without checking if it must be
-// encoded (this is supposed to be done in the caller) and using the specified
-// encodings and charset (which are supposed to be detected by the caller too)
+// encode the text in the charset with the given name in QP
 static String
-EncodeText(const char* in,
-           MIME::Encoding enc2047,
-           const String& csName)
+EncodeTextQP(const char* in, const String& csName)
 {
    // encode the word splitting it in the chunks such that they will be no
    // longer than 75 characters each
@@ -486,43 +482,19 @@ EncodeText(const char* in,
       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++ )
       {
-         for ( ; s[len]; len++ )
-         {
-            const unsigned char c = s[len];
-
-            // normal characters stand for themselves in QP, the encoded ones
-            // take 3 positions (=XX)
-            lenRemaining -= (NeedsEncodingInHeader(c) || strchr("=?", c))
-                              ? 3 : 1;
-
-            if ( lenRemaining <= 0 )
-            {
-               // can't put any more chars into this word
-               break;
-            }
-         }
-      }
-      else // Base64
-      {
-         // rfc822_binary() splits lines after 60 characters so don't make
-         // chunks longer than this as the base64-encoded headers can't have
-         // EOLs in them
-         static const int CCLIENT_MAX_BASE64_LEN = 60;
+         const unsigned char c = s[len];
 
-         if ( lenRemaining > CCLIENT_MAX_BASE64_LEN )
-            lenRemaining = CCLIENT_MAX_BASE64_LEN;
+         // normal characters stand for themselves in QP, the encoded ones
+         // take 3 positions (=XX)
+         lenRemaining -= (NeedsEncodingInHeader(c) || strchr("=?", c))
+                           ? 3 : 1;
 
-         // we can calculate how many characters we may put into lenRemaining
-         // directly
-         len = (lenRemaining / 4) * 3;
-
-         // but not more than what we have
-         size_t lenMax = strlen(reinterpret_cast<const char*>(s));
-         if ( len > lenMax )
+         if ( lenRemaining <= 0 )
          {
-            len = lenMax;
+            // can't put any more chars into this word
+            break;
          }
       }
 
@@ -533,19 +505,7 @@ EncodeText(const char* in,
       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;
-            }
-      }
+      textEnc = rfc822_8bit(text, len, &lenEnc);
 
       // put into string as we might want to do some more replacements...
       String encword(wxString::FromAscii(CHAR_CAST(textEnc), lenEnc));
@@ -554,48 +514,115 @@ EncodeText(const char* in,
       // 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());
+      String encword2;
+      encword2.reserve(encword.length());
 
-         bool replaced = false;
-         for ( const wxChar *p = encword.c_str(); *p; p++ )
+      bool replaced = false;
+      for ( const wxChar *p = encword.c_str(); *p; p++ )
+      {
+         switch ( *p )
          {
-            switch ( *p )
-            {
-               case ' ':
-                  encword2 += '_'; // More readable than =20
-                  break;
-
-               case '\t':
-                  encword2 += _T("=09");
-                  break;
+            case ' ':
+               encword2 += '_'; // More readable than =20
+               break;
 
-               case '?':
-                  encword2 += _T("=3F");
-                  break;
+            case '\t':
+               encword2 += _T("=09");
+               break;
 
-               default:
-                  encword2 += *p;
+            case '?':
+               encword2 += _T("=3F");
+               break;
 
-                  // skip assignment to replaced below
-                  continue;
-            }
+            default:
+               encword2 += *p;
 
-            replaced = true;
+               // skip assignment to replaced below
+               continue;
          }
 
-         if ( replaced )
-         {
-            encword = encword2;
-         }
+         replaced = true;
+      }
+
+      if ( replaced )
+      {
+         encword = encword2;
       }
 
       // append this word to the header
-      out << _T("=?") << csName << _T('?') << (char)enc2047 << _T('?')
-          << encword
-          << _T("?=");
+      out << _T("=?") << csName << _T("?Q?") << encword << _T("?=");
+
+      fs_give((void **)&textEnc);
+
+      // skip the already encoded part
+      s += len;
+   }
+
+   return out;
+}
+
+// same as the function above but use Base64 encoding
+static String
+EncodeTextBase64(const char* in, const String& csName)
+{
+   // encode the word splitting it in the chunks such that they will be no
+   // longer than 75 characters each
+   String out;
+   out.reserve(csName.length() + strlen(in) + 7 /* for =?...?X?...?= */);
+
+   auto *s = reinterpret_cast<const unsigned char*>(in);
+   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());
+
+      // rfc822_binary() splits lines after 60 characters so don't make
+      // chunks longer than this as the base64-encoded headers can't have
+      // EOLs in them
+      static const int CCLIENT_MAX_BASE64_LEN = 60;
+
+      if ( lenRemaining > CCLIENT_MAX_BASE64_LEN )
+         lenRemaining = CCLIENT_MAX_BASE64_LEN;
+
+      // we can calculate how many characters we may put into lenRemaining
+      // directly
+      len = (lenRemaining / 4) * 3;
+
+      // but not more than what we have
+      size_t lenMax = strlen(reinterpret_cast<const char*>(s));
+      if ( len > lenMax )
+      {
+         len = lenMax;
+      }
+
+      // do encode this word
+      unsigned char *text = const_cast<unsigned char*>(s); // cast for cclient
+
+      // length of the encoded text and the text itself
+      unsigned long lenEnc;
+      unsigned char *textEnc;
+
+      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), lenEnc));
+
+      // append this word to the header
+      out << _T("=?") << csName << _T("?B?") << encword << _T("?=");
 
       fs_give((void **)&textEnc);
 
@@ -620,16 +647,6 @@ wxCharBuffer MIME::EncodeHeader(const String& in, wxFontEncoding enc)
       enc = wxFONTENCODING_UTF8;
    }
 
-   // get the encoding in RFC 2047 sense
-   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() )
@@ -650,7 +667,23 @@ wxCharBuffer MIME::EncodeHeader(const String& in, wxFontEncoding enc)
       inbuf = in.utf8_str();
    }
 
-   return EncodeText(inbuf.data(), enc2047, csName).ToAscii();
+   String out;
+   switch ( MIME::GetEncodingForFontEncoding(enc) )
+   {
+      case MIME::Encoding_Unknown:
+         FAIL_MSG( "using unknown MIME encoding?" );
+         wxFALLTHROUGH;
+
+      case MIME::Encoding_QuotedPrintable:
+         out = EncodeTextQP(inbuf.data(), csName);
+         break;
+
+      case MIME::Encoding_Base64:
+         out = EncodeTextBase64(inbuf.data(), csName);
+         break;
+   }
+
+   return out.ToAscii();
 }
 
 String MIME::DecodeText(const char *p, size_t len, wxFontEncoding enc)

commit 9f4b95a7555bf07860a2286f1f04b66885e0b71a
Author: Vadim Zeitlin <[email protected]>
Date:   Wed Mar 12 02:45:10 2025 +0100

    Remove unused wxFontEncoding parameter of EncodeText()
    
    Simplify the function a bit.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index 8b6b11bd..1e98b81c 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -462,7 +462,6 @@ static bool NeedsEncoding(const String& in)
 // encodings and charset (which are supposed to be detected by the caller too)
 static String
 EncodeText(const char* in,
-           wxFontEncoding enc,
            MIME::Encoding enc2047,
            const String& csName)
 {
@@ -651,7 +650,7 @@ wxCharBuffer MIME::EncodeHeader(const String& in, wxFontEncoding enc)
       inbuf = in.utf8_str();
    }
 
-   return EncodeText(inbuf.data(), enc, enc2047, csName).ToAscii();
+   return EncodeText(inbuf.data(), enc2047, csName).ToAscii();
 }
 
 String MIME::DecodeText(const char *p, size_t len, wxFontEncoding enc)

commit ba1921ea80e18ca17d4978c5a57da36625c7233f
Author: Vadim Zeitlin <[email protected]>
Date:   Wed Mar 12 02:43:13 2025 +0100

    Pass C string, not wxString, to EncodeText()
    
    Also avoid constructing wxCSConv() when using UTF-8, which is the most
    common use case.
    
    No real changes, just prepare for the upcoming refactoring.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index 7faeca1d..8b6b11bd 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -461,25 +461,17 @@ static bool NeedsEncoding(const String& in)
 // encoded (this is supposed to be done in the caller) and using the specified
 // encodings and charset (which are supposed to be detected by the caller too)
 static String
-EncodeText(const String& in,
+EncodeText(const char* in,
            wxFontEncoding enc,
            MIME::Encoding enc2047,
            const String& csName)
 {
    // 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?...?= */);
+   out.reserve(csName.length() + strlen(in) + 7 /* for =?...?X?...?= */);
 
-   auto *s = reinterpret_cast<const unsigned char*>(buf.data());
+   auto *s = reinterpret_cast<const unsigned char*>(in);
    while ( *s )
    {
       // if we wrapped, insert a line break
@@ -648,7 +640,18 @@ wxCharBuffer MIME::EncodeHeader(const String& in, wxFontEncoding enc)
       csName = _T("UNKNOWN");
    }
 
-   return EncodeText(in, enc, enc2047, csName).ToAscii();
+   wxCharBuffer inbuf;
+   if ( enc != wxFONTENCODING_UTF8 )
+      inbuf = in.mb_str(wxCSConv(enc));
+
+   if ( !inbuf )
+   {
+      // if the header can't be encoded using the given encoding, use UTF-8
+      // which always works
+      inbuf = in.utf8_str();
+   }
+
+   return EncodeText(inbuf.data(), enc, enc2047, csName).ToAscii();
 }
 
 String MIME::DecodeText(const char *p, size_t len, wxFontEncoding enc)

commit 3d1f4cf9b4ac17f17b49f49145d51e3c0c131d4a
Author: Vadim Zeitlin <[email protected]>
Date:   Wed Mar 12 01:27:05 2025 +0100

    Allow up to 6 characters when encoding headers using Base64
    
    The maximum length of the line shouldn't account for "\r\n", otherwise
    we only put 58 characters in each line instead of 60 as intended.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index 2dd19197..7faeca1d 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -525,7 +525,7 @@ EncodeText(const String& in,
 
          // we can calculate how many characters we may put into lenRemaining
          // directly
-         len = (lenRemaining / 4) * 3 - 2;
+         len = (lenRemaining / 4) * 3;
 
          // but not more than what we have
          size_t lenMax = strlen(reinterpret_cast<const char*>(s));

commit 1ac798f306170815921c6d482d668cf27ede5e88
Author: Vadim Zeitlin <[email protected]>
Date:   Wed Mar 12 01:17:18 2025 +0100

    Fix infinite loop in EncodeText() when using Base64
    
    We need to get the number of remaining bytes in the string, so use
    strlen() instead of using wxStrlen() which compiles without the cast but
    involves a conversion to wxString, which may fail, returning an empty
    string and hence encoding a "word" of 0 length.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index 9134fb4c..2dd19197 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -528,7 +528,7 @@ EncodeText(const String& in,
          len = (lenRemaining / 4) * 3 - 2;
 
          // but not more than what we have
-         size_t lenMax = wxStrlen(s);
+         size_t lenMax = strlen(reinterpret_cast<const char*>(s));
          if ( len > lenMax )
          {
             len = lenMax;

commit 868edf0e0f3facab75414106cbc04f2b27ed05b9
Author: Vadim Zeitlin <[email protected]>
Date:   Wed Mar 12 01:16:51 2025 +0100

    Stop using wxFONTENCODING_DEFAULT in MIME decoding test
    
    This doesn't make much sense and doesn't work.

diff --git a/tests/mime/decode.cpp b/tests/mime/decode.cpp
index 08f8245d..8473f719 100644
--- a/tests/mime/decode.cpp
+++ b/tests/mime/decode.cpp
@@ -387,12 +387,6 @@ int main()
             "\xD0\x92 \xD0\xA6 2007",
             wxFONTENCODING_UTF8
         },
-
-        {
-            "=?us-ascii?Q?Foo=20bar?=",
-            "Foo bar",
-            wxFONTENCODING_DEFAULT
-        },
     };
 
     int rc = EXIT_SUCCESS;

commit 409d6974435db410434831eed0ef2d561fcb6569
Author: Vadim Zeitlin <[email protected]>
Date:   Wed Mar 12 01:16:00 2025 +0100

    Suppress harmless warnings in code copied from c-client
    
    Fixing them would involve modifying the code which really shouldn't even
    be here at all.

diff --git a/tests/mime/decode.cpp b/tests/mime/decode.cpp
index 289f97dc..08f8245d 100644
--- a/tests/mime/decode.cpp
+++ b/tests/mime/decode.cpp
@@ -5,6 +5,8 @@ typedef wxString String;
 
 #include "mail/MimeDecode.h"
 
+wxGCC_WARNING_SUPPRESS(write-strings)
+
 extern "C" {
 
 void *fs_get (size_t size) { return malloc(size); }
@@ -325,6 +327,8 @@ unsigned char *rfc822_8bit (unsigned char *src,unsigned long srcl,
 }
 }
 
+wxGCC_WARNING_RESTORE(write-strings)
+
 int main()
 {
     wxInitializer init;

commit 6a5486de8057dc3e64036d61c67e769ccae2f8a6
Author: Vadim Zeitlin <[email protected]>
Date:   Wed Mar 12 01:08:02 2025 +0100

    Remove hard coded paths from MIME tests makefile

diff --git a/tests/mime/Makefile b/tests/mime/Makefile
index a7fb3635..81d952ad 100644
--- a/tests/mime/Makefile
+++ b/tests/mime/Makefile
@@ -1,6 +1,9 @@
-WX_CONFIG := /usr/local/src/build/wx-gtkud/wx-config
+WX_CONFIG := wx-config
+
+ifndef top_builddir
+$(error Define top_builddir to point to build directory on make command line)
+endif
 
-top_builddir := /home/zeitlin/build/M-gtkud
 top_srcdir := ../..
 
 CXXFLAGS := -I$(top_srcdir)/include `$(WX_CONFIG) --cxxflags` -g

commit 06d5b206e24809152471268a618bd0283722e4ee
Author: Vadim Zeitlin <[email protected]>
Date:   Wed Mar 12 00:54:44 2025 +0100

    Don't soft wrap "References" header value
    
    This is done when constructing the final message and there is no need to
    do it twice.

diff --git a/src/gui/wxComposeView.cpp b/src/gui/wxComposeView.cpp
index e04e6f18..603d96f1 100644
--- a/src/gui/wxComposeView.cpp
+++ b/src/gui/wxComposeView.cpp
@@ -5519,10 +5519,7 @@ bool wxComposeView::ConfigureInReplyTo()
                // if replacement failed (or if we had nothing to replace),
                // just add new message id
                if ( !ref.empty() )
-               {
-                  // continue "References" header on the next line
-                  ref += _T("\015\012 ");
-               }
+                  ref += ' ';
 
                ref += messageIdNew;
             }
diff --git a/src/mail/MailFolder.cpp b/src/mail/MailFolder.cpp
index e2a2f36b..4a597b6d 100644
--- a/src/mail/MailFolder.cpp
+++ b/src/mail/MailFolder.cpp
@@ -1117,10 +1117,7 @@ MailFolder::ReplyMessage(Message *msg,
    {
       String references = headersOrig[1].Trim(TRUE).Trim(FALSE);
       if ( !references.empty() )
-      {
-         // continue "References" header on the next line
-         references += _T("\015\012 ");
-      }
+         references += ' ';
       references += messageid;
 
       cv->AddHeaderEntry(_T("References"), references);

commit 64372212fc20adc244123679f96a2f5612b147d0
Author: Vadim Zeitlin <[email protected]>
Date:   Wed Mar 12 00:40:58 2025 +0100

    Compare header names case-insensitively
    
    Header names are not case-sensitive, so don't allow adding 2 headers
    differing in case only.

diff --git a/src/gui/wxComposeView.cpp b/src/gui/wxComposeView.cpp
index 60fc33d6..e04e6f18 100644
--- a/src/gui/wxComposeView.cpp
+++ b/src/gui/wxComposeView.cpp
@@ -5384,7 +5384,7 @@ wxComposeView::AddHeaderEntry(const String& name, const String& value)
          i != end;
          ++i, ++j )
    {
-      if ( *i == name )
+      if ( wxStricmp(*i, name) == 0 )
       {
          if ( value.empty() )
          {
@@ -5421,7 +5421,7 @@ bool wxComposeView::IsInReplyTo() const
                               end = m_extraHeadersNames.end();
    for ( i = m_extraHeadersNames.begin(); i != end; ++i )
    {
-      if ( *i == "In-Reply-To" )
+      if ( wxStricmp(*i, "In-Reply-To") == 0 )
          return true;
    }
 
@@ -5435,7 +5435,7 @@ bool wxComposeView::ConfigureInReplyTo()
    for ( i = m_extraHeadersNames.begin(),
          j = m_extraHeadersValues.begin(); i != end; ++i, ++j )
    {
-      if ( *i == "In-Reply-To" )
+      if ( wxStricmp(*i, "In-Reply-To") == 0 )
          break;
    }
 
@@ -5458,7 +5458,7 @@ bool wxComposeView::ConfigureInReplyTo()
             end = m_extraHeadersNames.end(),
             j = m_extraHeadersValues.begin() ; i != end; ++i, ++j )
       {
-         if ( *i == "References" )
+         if ( wxStricmp(*i, "References") == 0 )
          {
             String ref = *j;
             ref.Trim(true).Trim(false);
@@ -5508,7 +5508,7 @@ bool wxComposeView::ConfigureInReplyTo()
             end = m_extraHeadersNames.begin(),
             j = m_extraHeadersValues.begin(); i != end; ++i, ++j )
       {
-         if ( *i == "References" )
+         if ( wxStricmp(*i, "References") == 0 )
          {
             String ref = *j;
 

commit 610297bac03abfb00321bd71289c82067f51a858
Author: Vadim Zeitlin <[email protected]>
Date:   Wed Mar 12 00:39:45 2025 +0100

    Let NeedsEncodingInHeader() check for ASCII too
    
    We don't need to check whether a (Unicode) code point is an ASCII
    character before calling this function if we avoid the truncating
    conversion of its argument.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index e2e8c799..9134fb4c 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -432,7 +432,7 @@ String MIME::DecodeHeader(const String& in, wxFontEncoding *pEncoding)
 // 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(unsigned char c)
+static inline bool NeedsEncodingInHeader(unsigned c)
 {
    return c < 20 || c >= 127;
 }
@@ -449,7 +449,7 @@ static bool NeedsEncoding(const String& in)
    // 822 headers
    for ( auto c : in )
    {
-      if ( !c.IsAscii() || NeedsEncodingInHeader(c.GetValue()) )
+      if ( NeedsEncodingInHeader(c.GetValue()) )
          return true;
    }
 

commit 43f8634ada42e8e998e9962c7f1fdea7adb4a07e
Author: Vadim Zeitlin <[email protected]>
Date:   Tue Mar 11 23:22:21 2025 +0100

    Don't MIME-encode header names
    
    This was added in the apparently unrelated 5ca32298 (Replace all uses of
    obsolete kbList stuff with std::list., 2010-06-29) and doesn't make any
    sense.

diff --git a/src/gui/wxComposeView.cpp b/src/gui/wxComposeView.cpp
index 6a34407b..60fc33d6 100644
--- a/src/gui/wxComposeView.cpp
+++ b/src/gui/wxComposeView.cpp
@@ -5401,7 +5401,7 @@ wxComposeView::AddHeaderEntry(const String& name, const String& value)
    }
 
    // if we didn't find it, add a new one
-   m_extraHeadersNames.push_back(MIME::EncodeHeader(name).data());
+   m_extraHeadersNames.push_back(name.utf8_string());
    m_extraHeadersValues.push_back(MIME::EncodeHeader(value).data());
 }
 

commit a1e6cff79899ea701e8e6e033618a622d501a674
Author: Vadim Zeitlin <[email protected]>
Date:   Tue Mar 11 18:46:58 2025 +0100

    Reopen previously opened mailboxes on resume under Linux too
    
    Create a power resource blocker to be notified about suspend and resume
    under Linux and so be able to reopen (even remote) mailboxes.

diff --git a/include/gui/wxMainFrame.h b/include/gui/wxMainFrame.h
index 477599db..54e1d98d 100644
--- a/include/gui/wxMainFrame.h
+++ b/include/gui/wxMainFrame.h
@@ -128,6 +128,10 @@ protected:
 
 
 #ifdef wxHAS_POWER_EVENTS
+#if wxCHECK_VERSION(3, 3, 0)
+   wxPowerResourceBlocker m_powerDelaySleep;
+#endif // wx 3.3.0+
+
    // the list of folders which were opened when we were suspended
    typedef std::vector<MailFolder*> MailFolders;
    MailFolders m_foldersToResume;
diff --git a/src/gui/wxMainFrame.cpp b/src/gui/wxMainFrame.cpp
index 8f39b421..d55f1434 100644
--- a/src/gui/wxMainFrame.cpp
+++ b/src/gui/wxMainFrame.cpp
@@ -628,6 +628,14 @@ END_EVENT_TABLE()
 
 wxMainFrame::wxMainFrame(const String &iname, wxFrame *parent)
            : wxMFrame(iname,parent)
+#if wxCHECK_VERSION(3, 3, 0)
+             // We could wait until we open any network connections before
+             // doing this, but it does no real harm to initialize it
+             // immediately.
+             , m_powerDelaySleep(wxPOWER_RESOURCE_SYSTEM,
+                                 _("Close network connections"),
+                                 wxPOWER_DELAY)
+#endif // wx 3.3.0+
 {
    // init members
    m_searchData = NULL;

commit 5bc4dbcf83c197a1ede8eac399b55d6a1ecb50dc
Author: Vadim Zeitlin <[email protected]>
Date:   Tue Mar 11 18:45:56 2025 +0100

    Use CallAfter() instead of disconnecting event handler
    
    Simplify the code a bit by using CallAfter() instead of defining and
    connecting a handler which is only called once and then disconnects
    itself.
    
    No real changes.

diff --git a/src/gui/wxComposeView.cpp b/src/gui/wxComposeView.cpp
index 27beb3da..6a34407b 100644
--- a/src/gui/wxComposeView.cpp
+++ b/src/gui/wxComposeView.cpp
@@ -800,21 +800,13 @@ public:
 
       // AddHeaderEntry("In-Reply-To") is called after composer creation, so we
       // want to update our state a bit later
-      Connect(wxEVT_IDLE, wxIdleEventHandler(IsReplyButton::OnIdle));
+      CallAfter(&ToggleIconButton::Update);
    }
 
 private:
    virtual void DoHandleClick() { m_composer->ConfigureInReplyTo(); }
    virtual bool DoGetValue() const { return m_composer->IsInReplyTo(); }
 
-   void OnIdle(wxIdleEvent& /* event */)
-   {
-      Disconnect(wxID_ANY, wxEVT_IDLE,
-                     wxIdleEventHandler(IsReplyButton::OnIdle));
-
-      Update();
-   }
-
    DECLARE_NO_COPY_CLASS(IsReplyButton)
 };
 

-----------------------------------------------------------------------

Summary of changes:
 include/gui/wxMainFrame.h  |   4 +
 include/mail/MimeDecode.h  |   2 +-
 src/gui/wxComposeView.cpp  |  49 ++++---
 src/gui/wxMainFrame.cpp    |   8 ++
 src/mail/AddressCC.cpp     |   2 +-
 src/mail/MailFolder.cpp    |   5 +-
 src/mail/MimeDecode.cpp    | 333 ++++++++++++++++++++++++---------------------
 src/mail/SendMessageCC.cpp |  14 +-
 tests/mime/Makefile        |   7 +-
 tests/mime/decode.cpp      |  45 +++---
 10 files changed, 252 insertions(+), 217 deletions(-)


hooks/post-receive
-- 
Mahogany sources repository.