[M-git] Mahogany sources repository. branch master updated. v0.67-855-geb1e403d

vadz via Mahogany-cvsupdates <[email protected]> Mon, 10 Mar 2025 16:54:08 +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  eb1e403d031263ca26c36b5cb70aa45ea627d6dc (commit)
       via  222cf9d471588118c52c88b056e68d38ceba1e24 (commit)
       via  e28b1ef23247786e5148c01a59a6314b1da5653a (commit)
       via  c018ad921fa10ea741947733c2c1fd9405d45285 (commit)
       via  6fd769a0e2121e792d0b54f15d96a28ab4af3056 (commit)
       via  ca0e027d908f09506bfeb169f8df10972a7a1c66 (commit)
       via  d4a702454b34ff4774c427fe9b4896a1bcb43e6a (commit)
      from  9d1f084e02fa0258705884adba16c90f59d8e5d6 (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 eb1e403d031263ca26c36b5cb70aa45ea627d6dc
Author: Vadim Zeitlin <[email protected]>
Date:   Thu Feb 6 18:58:22 2025 +0100

    Fix decoding RFC 2047 words broken between character boundaries
    
    Concatenate the bytes encoded using RFC 2047 encoding first and then
    convert them from the specified encoding to ensure that we can do it
    successfully even when bytes of the same characters are separated in
    different encoded words.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index 9bde4aa4..e2e8c799 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -117,6 +117,13 @@ MIME::Encoding MIME::GetEncodingForFontEncoding(wxFontEncoding enc)
 // decoding
 // ----------------------------------------------------------------------------
 
+// Local wrapper around public function taking char* and length.
+static
+String DecodeString(const std::string& s, wxFontEncoding enc)
+{
+   return MIME::DecodeText(s.data(), s.length(), enc);
+}
+
 /*
    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
@@ -135,10 +142,25 @@ String DecodeHeaderOnce(const String& in, wxFontEncoding *pEncoding)
    // we don't enforce the sanity checks on charset and encoding - should we?
    // const char *specials = "()<>@,;:\\\"[].?=";
 
-   // there can be words in different encodings inside the same header so this
-   // variable doesn't really make sense but in practice only one encoding will
-   // be used in the entire header
-   wxFontEncoding encodingHeader = wxFONTENCODING_SYSTEM;
+   // encoding of the previous word or wxFONTENCODING_SYSTEM if it wasn't
+   // encoded (or there wasn't any previous word at all yet)
+   wxFontEncoding encodingLastWord = wxFONTENCODING_SYSTEM;
+
+   // the not yet decoded text using encodingLastWord, we'll convert it from
+   // this encoding all at once when we can be sure that there is nothing
+   // following it any more
+   //
+   // notice that this is more than just an optimization: RFC 2047 encoding can
+   // separate bytes that are part of the same multibyte characters, e.g. if
+   // the string is sufficiently long and needs to be wrapped it's perfectly
+   // possible that the leading byte of UTF-8 encoding is part of one encoded
+   // word while the rest of them are in the other one and so converting each
+   // of them from UTF-8 on their own wouldn't work, but combining them and
+   // only converting both at once would
+   std::string textLastWord;
+
+   if ( pEncoding )
+      *pEncoding = wxFONTENCODING_SYSTEM;
 
    // if the header starts with an encoded word, preceding whitespace must be
    // ignored, so the flag must be set to true initially
@@ -197,19 +219,20 @@ String DecodeHeaderOnce(const String& in, wxFontEncoding *pEncoding)
             wxLogDebug(_T("Unrecognized charset name \"%s\""), csName.mb_str());
          }
 
-         // this is not a problem in Unicode build
-#if !wxUSE_UNICODE
-         if ( encodingHeader != wxFONTENCODING_SYSTEM &&
-               encodingHeader != encodingWord )
+         if ( encodingWord != encodingLastWord )
          {
-            // this is a bug (well, missing feature) in ANSI build of Mahogany
-            wxLogDebug(_T("This header contains encoded words with different ")
-                       _T("encodings and won't be rendered correctly."));
-         }
-#endif // !wxUSE_UNICODE
+            if ( encodingLastWord != wxFONTENCODING_SYSTEM )
+            {
+               // The last word must be complete now, decode it.
+               out += DecodeString(textLastWord, encodingLastWord);
+            }
 
-         encodingHeader = encodingWord;
+            encodingLastWord = encodingWord;
+            textLastWord.clear();
 
+            if ( pEncoding )
+               *pEncoding = encodingWord;
+         }
 
          // get the encoding in RFC 2047 sense
          enum
@@ -269,14 +292,16 @@ String DecodeHeaderOnce(const String& in, wxFontEncoding *pEncoding)
          p += 2; // skip "Q?" or "B?"
 
          // get the encoded text
+         std::string encWord;
          bool hasUnderscore = false;
-         const wxString::const_iterator encTextStart = p;
          while ( p != last && (*p != '?' || *(p + 1) != '=') )
          {
             // this is needed for QP hack below
             if ( *p == '_' )
                hasUnderscore = true;
 
+            encWord += *p;
+
             ++p;
          }
 
@@ -289,23 +314,19 @@ String DecodeHeaderOnce(const String& in, wxFontEncoding *pEncoding)
             break;
          }
 
-         // convert the encoded word to char[] buffer for c-client
-         wxCharBuffer encWord(wxString(encTextStart, p).To8BitData());
-
          // skip '=' following '?'
          ++p;
 
-         String textDecoded;
-         if ( encWord )
+         if ( !encWord.empty() )
          {
-            const unsigned long lenEncWord = strlen(encWord);
+            const unsigned long lenEncWord = encWord.length();
 
             // now decode the text using c-client functions
             unsigned long len;
             void *text;
             if ( enc2047 == Encoding_Base64 )
             {
-               text = rfc822_base64(UCHAR_CCAST(encWord), lenEncWord, &len);
+               text = rfc822_base64(UCHAR_CCAST(encWord.data()), lenEncWord, &len);
             }
             else // QP
             {
@@ -316,74 +337,26 @@ String DecodeHeaderOnce(const String& in, wxFontEncoding *pEncoding)
                // standard-conforming
                if ( hasUnderscore )
                {
-                  for ( char *pc = encWord.data(); *pc; ++pc )
+                  for ( auto& c : encWord )
                   {
-                     if ( *pc == '_' )
-                        *pc = ' ';
+                     if ( c == '_' )
+                        c = ' ';
                   }
                }
 
-               text = rfc822_qprint(UCHAR_CCAST(encWord), lenEncWord, &len);
+               text = rfc822_qprint(UCHAR_CCAST(encWord.data()), lenEncWord, &len);
             }
 
             if ( text )
             {
                const char * const ctext = static_cast<char *>(text);
 
-               if ( encodingWord == wxFONTENCODING_DEFAULT )
-               {
-                  // CharsetToEncoding() returns this for US-ASCII but
-                  // wxCSConv() doesn't accept it, so handle it manually (we
-                  // also avoid wxString::FromAscii() because it asserts if the
-                  // string contains non-ASCII characters, but this can happen,
-                  // after all we're using untrusted input).
-                  textDecoded.reserve(len);
-                  for (unsigned long n = 0; n < len; ++n)
-                  {
-                     const unsigned char c = ctext[n];
-                     if ( c >= 0x80 )
-                     {
-                        wxLogDebug(wxS("Invalid character 0x%x "
-                                       "in ASCII-encoded word \"%s\""),
-                                   c, in);
-                     }
-                     else
-                     {
-                        textDecoded += static_cast<char>(c);
-                     }
-                  }
-               }
-               else // real conversion needed
-               {
-                  textDecoded = wxString(ctext, wxCSConv(encodingWord), len);
-               }
+               textLastWord.append(ctext, ctext + len);
 
                fs_give(&text);
             }
          }
 
-         if ( textDecoded.empty() )
-         {
-            // if decoding failed it is probably better to show undecoded
-            // text than nothing at all
-            textDecoded = wxString(encWordStart, p + 1);
-         }
-
-         // normally we leave the (8 bit) string as is and remember its
-         // encoding so that we may choose the font for displaying it
-         // correctly, but in case of UTF-7/8 we really need to transform it
-         // here as we don't have any UTF-7/8 fonts, so we should display a
-         // different string
-#if !wxUSE_UNICODE
-         if ( encodingHeader == wxFONTENCODING_UTF7 ||
-                  encodingHeader == wxFONTENCODING_UTF8 )
-         {
-            encodingHeader = ConvertUTFToMB(&textDecoded, encodingHeader);
-         }
-#endif // !wxUSE_UNICODE
-
-         out += textDecoded;
-
          // forget the space before this encoded word, it must be ignored
          space.clear();
          maybeBetweenEncodedWords = true;
@@ -398,6 +371,15 @@ String DecodeHeaderOnce(const String& in, wxFontEncoding *pEncoding)
       }
       else // just another normal char
       {
+         // the last word won't be continued any more, so decode it, if any
+         if ( encodingLastWord != wxFONTENCODING_SYSTEM )
+         {
+            out += DecodeString(textLastWord, encodingLastWord);
+
+            encodingLastWord = wxFONTENCODING_SYSTEM;
+            textLastWord.clear();
+         }
+
          // if we got any delayed whitespace (see above), flush it now
          if ( !space.empty() )
          {
@@ -411,8 +393,9 @@ String DecodeHeaderOnce(const String& in, wxFontEncoding *pEncoding)
       }
    }
 
-   if ( pEncoding )
-      *pEncoding = encodingHeader;
+   // decode anything that remains
+   if ( encodingLastWord != wxFONTENCODING_SYSTEM )
+      out += DecodeString(textLastWord, encodingLastWord);
 
    return out;
 }
@@ -670,15 +653,15 @@ wxCharBuffer MIME::EncodeHeader(const String& in, wxFontEncoding enc)
 
 String MIME::DecodeText(const char *p, size_t len, wxFontEncoding enc)
 {
-   // Special case of using UTF-8: it often happens that a message encoded in
-   // UTF-8 has some trailer with Latin-1 appended to it. In this case, try to
-   // recover as much of UTF-8 text as possible.
-   if ( enc == wxFONTENCODING_UTF8 )
-   {
-      return String(p, wxMBConvUTF8(wxMBConvUTF8::MAP_INVALID_UTF8_TO_PUA), len);
-   }
-   else
-   {
-      return String(p, wxCSConv(enc), len);
-   }
+   // Use always successful conversion from UTF-8 as fallback because it's
+   // better to return some garbage (which could, hopefully, contain readable
+   // parts of text) than nothing at all.
+   String s;
+   if ( enc != wxFONTENCODING_UTF8 )
+      s = String(p, wxCSConv(enc), len);
+
+   if ( s.empty() )
+      s = String(p, wxMBConvUTF8(wxMBConvUTF8::MAP_INVALID_UTF8_TO_PUA), len);
+
+   return s;
 }

commit 222cf9d471588118c52c88b056e68d38ceba1e24
Author: Vadim Zeitlin <[email protected]>
Date:   Thu Feb 6 02:54:37 2025 +0100

    Stop QP-encoding header words separately
    
    This doesn't make much sense, ASCII words are readable in QP-encoded
    form too (this is the rationale for having QP encoding in the first
    place) and doing it broke header wrapping as the code for merging
    adjacent encoded words completely disregarded the line length.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index 2e5b793f..9bde4aa4 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -665,72 +665,7 @@ wxCharBuffer MIME::EncodeHeader(const String& in, wxFontEncoding enc)
       csName = _T("UNKNOWN");
    }
 
-
-   String headerEnc;
-   headerEnc.reserve(2*in.length());
-
-   // for QP we encode each header word separately as some might not need being
-   // encoded at all and the header remains more readable if we don't encode
-   // them unnecessarily, 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 if necessary, taking into account one
-      // added complication: white space between 2 consecutive encoded words is
-      // ignored during decoding, so we must encode 2 consecutive words both of
-      // which need encoding as one single encoded word or the space between
-      // them would be lost
-      bool lastWordEncoded = false;
-      const wxArrayString words(wxStringTokenize(in));
-      const size_t count = words.size();
-      for ( size_t n = 0; n < count; ++n )
-      {
-         const wxString& word = words[n];
-         if ( NeedsEncoding(word) )
-         {
-            const String wordEnc = EncodeText(word, enc, enc2047, csName);
-
-            if ( lastWordEncoded )
-            {
-               // we need to merge the 2 consecutive encoded words together: we
-               // do it by removing "?=" suffix from the previous word, adding
-               // a space and remove the "=?charset?Q?" prefix from this word
-               ASSERT_MSG( headerEnc.length() > 7, "bad QP-encoded last word" );
-               headerEnc.RemoveLast(2); // "?="
-
-               headerEnc += '_'; // space can be represented like this in QP
-
-               const size_t posText = wordEnc.find("?Q?");
-               ASSERT_MSG( posText != String::npos, "bad QP-encoded word" );
-               headerEnc += wordEnc.substr(posText + 3);
-            }
-            else // last word not encoded, just append this one
-            {
-               if ( !headerEnc.empty() )
-                  headerEnc += ' ';
-
-               headerEnc += wordEnc;
-            }
-
-            lastWordEncoded = true;
-         }
-         else // this word doesn't need to be encoded, simply append it
-         {
-            if ( !headerEnc.empty() )
-               headerEnc += ' ';
-
-            headerEnc += word;
-
-            lastWordEncoded = false;
-         }
-      }
-   }
-   else // MIME::Encoding_Base64
-   {
-      headerEnc = EncodeText(in, enc, enc2047, csName);
-   }
-
-   return headerEnc.ToAscii();
+   return EncodeText(in, enc, enc2047, csName).ToAscii();
 }
 
 String MIME::DecodeText(const char *p, size_t len, wxFontEncoding enc)

commit e28b1ef23247786e5148c01a59a6314b1da5653a
Author: Vadim Zeitlin <[email protected]>
Date:   Thu Feb 6 02:44:48 2025 +0100

    Use more readable encoding for spaces in QP-encoded headers
    
    Use "_" rather than "=20", not only it's more readable, but it's also
    more compact.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index c3bb176c..2e5b793f 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -520,7 +520,7 @@ EncodeText(const String& in,
 
             // normal characters stand for themselves in QP, the encoded ones
             // take 3 positions (=XX)
-            lenRemaining -= (NeedsEncodingInHeader(c) || strchr(" =?", c))
+            lenRemaining -= (NeedsEncodingInHeader(c) || strchr("=?", c))
                               ? 3 : 1;
 
             if ( lenRemaining <= 0 )
@@ -591,7 +591,7 @@ EncodeText(const String& in,
             switch ( *p )
             {
                case ' ':
-                  encword2 += _T("=20");
+                  encword2 += '_'; // More readable than =20
                   break;
 
                case '\t':

commit c018ad921fa10ea741947733c2c1fd9405d45285
Author: Vadim Zeitlin <[email protected]>
Date:   Thu Feb 6 02:24:12 2025 +0100

    Fix bug in QP-encoding over long words
    
    Due to confusion between signed and unsigned chars, lenRemaining wasn't
    computed correctly and we passed rfc822_8bit() words that were long
    enough that it wrapped them by using "=\r\n", which completely mangled
    the generated header.
    
    Fix this by using unsigned chars.
    
    Also simplify the loop over the string by using range for.

diff --git a/src/mail/MimeDecode.cpp b/src/mail/MimeDecode.cpp
index 99427e57..c3bb176c 100644
--- a/src/mail/MimeDecode.cpp
+++ b/src/mail/MimeDecode.cpp
@@ -449,9 +449,9 @@ 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(wxUChar c)
+static inline bool NeedsEncodingInHeader(unsigned char c)
 {
-   return  c >= 127 || iscntrl(c);
+   return c < 20 || c >= 127;
 }
 
 // return true if the string contains any characters which must be encoded
@@ -459,26 +459,19 @@ 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 ( in.find(_T("=?")) != wxString::npos )
+      return true;
 
-      if ( p == end )
-      {
-         // string has only valid chars, don't encode
-         return false;
-      }
+   // only encode the strings which contain the characters unallowed in RFC
+   // 822 headers
+   for ( auto c : in )
+   {
+      if ( !c.IsAscii() || NeedsEncodingInHeader(c.GetValue()) )
+         return true;
    }
 
-   return true;
+   // string has only valid chars, don't encode
+   return false;
 }
 
 // encode the given text unconditionally, i.e. without checking if it must be
@@ -503,7 +496,7 @@ EncodeText(const String& in,
    String out;
    out.reserve(csName.length() + strlen(buf) + 7 /* for =?...?X?...?= */);
 
-   const char *s = buf;
+   auto *s = reinterpret_cast<const unsigned char*>(buf.data());
    while ( *s )
    {
       // if we wrapped, insert a line break
@@ -523,11 +516,11 @@ EncodeText(const String& in,
       {
          for ( ; s[len]; len++ )
          {
-            const char c = s[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(" \t=?", c))
+            lenRemaining -= (NeedsEncodingInHeader(c) || strchr(" =?", c))
                               ? 3 : 1;
 
             if ( lenRemaining <= 0 )
@@ -560,7 +553,7 @@ EncodeText(const String& in,
       }
 
       // do encode this word
-      unsigned char *text = (unsigned char *)s; // cast for cclient
+      unsigned char *text = const_cast<unsigned char*>(s); // cast for cclient
 
       // length of the encoded text and the text itself
       unsigned long lenEnc;

commit 6fd769a0e2121e792d0b54f15d96a28ab4af3056
Author: Vadim Zeitlin <[email protected]>
Date:   Thu Feb 6 02:23:13 2025 +0100

    Don't freeze wxFolderListCtrl while changing it
    
    This doesn't seem necessary with wxGTK neither (it was already disabled
    with wxMSW) and sometimes results in problems due to thawing a control
    which hadn't been frozen, so avoid this by simply not doing it at all.

diff --git a/src/gui/wxFolderView.cpp b/src/gui/wxFolderView.cpp
index 20013982..dc07c221 100644
--- a/src/gui/wxFolderView.cpp
+++ b/src/gui/wxFolderView.cpp
@@ -1577,15 +1577,6 @@ void wxFolderListCtrl::OnFolderChange()
 
       InvalidateCache();
    }
-
-   // freezing the control under MSW actually results in more flicker, not
-   // less: Thaw() repaints it, but it's also repained when we give it the
-   // focus so it's painted at least twice, while without Freeze/Thaw() it's
-   // still painted correctly and only once
-#ifndef __WXMSW__
-   // wait until we get the headers
-   Freeze();
-#endif // __WXMSW__
 }
 
 void wxFolderListCtrl::UpdateColumnWidths()
@@ -2377,11 +2368,6 @@ void wxFolderListCtrl::UpdateListing(HeaderInfoList *headers)
       SetListing(headers);
 
       m_FolderView->SelectInitialMessage();
-
-#ifndef __WXMSW__
-      // we can redraw now
-      Thaw();
-#endif // __WXMSW__
    }
 }
 

commit ca0e027d908f09506bfeb169f8df10972a7a1c66
Author: Vadim Zeitlin <[email protected]>
Date:   Fri Jan 31 01:40:29 2025 +0100

    Make default colour for "flagged" messages brighter
    
    The default value of "purple" has changed to be the same as in the CSS
    after 1c06d46a9f (Use CSS colour values in wxColourDatabase by default,
    2024-12-13) and is now not noticeable enough for such an "important"
    colour, so change it to "magenta" which is definitely noticeable.

diff --git a/include/Moptions.h b/include/Moptions.h
index c00ad4ce..8a33a1d3 100644
--- a/include/Moptions.h
+++ b/include/Moptions.h
@@ -2004,7 +2004,7 @@ extern const MOption MP_OPTION_ORIGIN_INHERITED;
 /// colour for unread messages
 #define   MP_FVIEW_UNREADCOLOUR_DEFVAL      "blue"
 /// colour for flagged messages
-#define   MP_FVIEW_FLAGGEDCOLOUR_DEFVAL      "purple"
+#define   MP_FVIEW_FLAGGEDCOLOUR_DEFVAL      "magenta"
 /// automatically select next unread message after finishing the current one
 #define MP_FVIEW_AUTONEXT_UNREAD_MSG_DEFVAL 1L
 /// automatically select next unread folder after finishing the current one

commit d4a702454b34ff4774c427fe9b4896a1bcb43e6a
Author: Vadim Zeitlin <[email protected]>
Date:   Fri Jan 31 01:32:53 2025 +0100

    Fix wrong wxPLURAL usage
    
    This never worked, i.e. never translated the messages, but also stopped
    compiling with the latest wx, so fix it now.

diff --git a/src/gui/wxMainFrame.cpp b/src/gui/wxMainFrame.cpp
index 0c34b2d6..8f39b421 100644
--- a/src/gui/wxMainFrame.cpp
+++ b/src/gui/wxMainFrame.cpp
@@ -1283,8 +1283,8 @@ void wxMainFrame::OnPowerSuspended(wxPowerEvent& WXUNUSED(event))
    {
       const unsigned numFolders = m_foldersToResume.size();
       wxLogStatus(
-         wxPLURAL(_("Closed %u folder which will be reopened on resume."),
-                  _("Closed %u folders which will be reopened on resume."),
+         wxPLURAL("Closed %u folder which will be reopened on resume.",
+                  "Closed %u folders which will be reopened on resume.",
                   numFolders),
          numFolders
       );
@@ -1305,8 +1305,8 @@ void wxMainFrame::OnPowerResume(wxPowerEvent& WXUNUSED(event))
    foldersToResume.swap(m_foldersToResume);
 
    const unsigned numFolders = foldersToResume.size();
-   wxLogStatus(wxPLURAL(_("Reopening %u folder on system resume"),
-                        _("Reopening %u folders on system resume"),
+   wxLogStatus(wxPLURAL("Reopening %u folder on system resume",
+                        "Reopening %u folders on system resume",
                         numFolders),
                numFolders);
 

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

Summary of changes:
 include/Moptions.h       |   2 +-
 src/gui/wxFolderView.cpp |  14 ---
 src/gui/wxMainFrame.cpp  |   8 +-
 src/mail/MimeDecode.cpp  | 267 ++++++++++++++++-------------------------------
 4 files changed, 94 insertions(+), 197 deletions(-)


hooks/post-receive
-- 
Mahogany sources repository.