【问题标题】:Encounter encoding problem while copying an email from Thunderbird via Delphi using native messaging使用本机消息通过 Delphi 从 Thunderbird 复制电子邮件时遇到编码问题
【发布时间】:2021-02-09 23:50:55
【问题描述】:

我正在使用本地消息传递 (following the ping pong example in python) 在 Thunderbird 中编写一个插件,以调用 Delphi 程序将电子邮件复制为本地“.eml”文件。我面临的问题似乎是编码。此外,生成的文件在文件的开头和结尾包含双引号 ("") 以及转义的双引号 (\")。我只想有一个 1 对 1 的副本,而不是更改其内容。

邮件内容示例:

"test"
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ
éöàäèüâêû 

但是,在文件中,它看起来更像这样:

\"test\"
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“â€â€¢â€“—˜™š›œžŸ
éöà äèüâêû

我可能已经找到了问题所在,这里对此进行了解释:

https://www.i18nqa.com/debug/utf8-debug.html

但是,我真的不知道如何调整我的代码来解决这个问题。

感谢您的帮助!

这是我的 background.js:

async function main() {
        messenger.menus.create({
            contexts : ["message_list"],
            id: "copy@mail.lu",
            onclick : passMsg,
            title: messenger.i18n.getMessage("lang.menuTitle")
        });
    }
    
    async function passMsg(OnClickData) {
        if (OnClickData.selectedMessages && OnClickData.selectedMessages.messages.length > 0) {
            let MessageHeader = OnClickData.selectedMessages.messages[0];
            let raw = await messenger.messages.getRaw(MessageHeader.id);
            let port=browser.runtime.connectNative("copymail");
    
            port.onMessage.addListener((message) => {
              port.disconnect();
            });
    
            port.postMessage(raw);
        } else {
            console.log("No message selected");
        }
    }
    main();

这是我的 Delphi 代码:

procedure WriteSTDInputToFile(const Filename: String);
    var
       Buffer:    array [0 .. 3] of Byte;
       msgLen:    LongInt;
       msg:       UTF8String;
       myFile:    TextFile;
       StdIn:     THandleStream;
       jsonValue: TJSONValue;
    begin
       StdIn  := THandleStream.Create(GetStdHandle(STD_INPUT_HANDLE));
    
       try
          msgLen    := 0;
          if StdIn.Read(Buffer, SizeOf(msgLen)) > 0 then
             msgLen := PLongInt(@Buffer)^;
    
          if msgLen > 0 then
          begin
             SetLength(msg, msgLen);
             StdIn.Read(PUTF8Char(msg)^, msgLen);
    
             if msg <> '' then
             begin
                AssignFile(myFile, Filename, CP_UTF8);
                ReWrite(myFile);
    
                jsonValue := TJSONObject.ParseJSONValue(msg);
    
                try
                   write(myFile, UTF8Encode(jsonValue.ToString));
                finally
                   jsonValue.Free;
                end;
    
                CloseFile(myFile);
    
             end;
          end;
    
       finally
          if Assigned(StdIn) then
             StdIn.Free;
       end;
    
    end;

生成的文件内容:

"X-MDAV-Result: clean
X-MDAV-Processed: mail.test.lu, Wed, 28 Oct 2020 08:13:22 +0100
X-Spam-Processed: mail.test.lu, Wed, 28 Oct 2020 08:13:22 +0100
Return-path: <copy@mail.lu>
X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on MAIL01E
X-Spam-Level: 
X-Spam-Status: No, score=0.7 required=10.0 tests=HTML_MESSAGE,MPART_ALT_DIFF
    shortcircuit=no autolearn=disabled version=3.4.2
Authentication-Results: test.lu;
    auth=pass (plain) smtp.auth=ascholtes@test.lu
Received: from [172.16.17.35] [(172.16.17.35)] by test.lu (172.31.3.6) with ESMTPSA id md50033234892.msg; 
    Wed, 28 Oct 2020 08:13:21 +0100
X-MDRemoteIP: 172.16.17.35
X-MDArrival-Date: Wed, 28 Oct 2020 08:13:21 +0100
X-Authenticated-Sender: ascholtes@test.lu
X-Rcpt-To: copy@mail.lu
X-MDRcpt-To: copy@mail.lu
X-Return-Path: copy@mail.lu
X-Envelope-From: copy@mail.lu
X-MDaemon-Deliver-To: ascholtes@test.lu
To: Ayuth Scholtes <copy@mail.lu>
From: Ayuth Scholtes <copy@mail.lu>
Subject: Test
Organization: CISS
Message-ID: <7eb36f7c-a7af-c272-c189-eded642c3e1c@test.lu>
Date: Wed, 28 Oct 2020 08:13:21 +0100
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101
 Thunderbird/68.10.0
MIME-Version: 1.0
Content-Type: multipart/alternative;
 boundary=\"------------6068A746223BB2C9F1771938\"
Content-Language: lb-LU

This is a multi-part message in MIME format.
--------------6068A746223BB2C9F1771938
Content-Type: text/plain; charset=utf-8; format=flowed
Content-Transfer-Encoding: 8bit

|\"test\" â¬âÆââ¦â â¡Ëâ°Å â¹ÅŽâââââ¢ââËâ¢Å¡âºÅžŸ éöàäèüâêû|


--------------6068A746223BB2C9F1771938
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: 8bit

<html>
  <head>

    <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">
  </head>
  <body>
    <pre class=\"lang-pascal s-code-block hljs delphi\"><code>\"test\"
â¬âÆââ¦â â¡Ëâ°Å â¹ÅŽâââââ¢ââËâ¢Å¡âºÅžŸ
éöàäèüâêû</code></pre>
  </body>
</html>

--------------6068A746223BB2C9F1771938--
"

【问题讨论】:

  • 欢迎来到 StackOverflow!我不明白,在您的问题中,.eml 文件和 JSON 之间的关系是什么。您能否编辑您的消息以显示您从 StdIn 读取的测试消息数据是什么?
  • 您希望通过 STD_INPUT_HANDLE 收到什么?您是否尝试将其保存到文件并检查了内容?也许 Json 消息不正确...
  • 谢谢!我发布了文件的结果。我希望收到保存的东西,就像我通过“另存为..”在 Thunderbird 中手动将邮件保存到 .eml 文件中一样。我相信命令 postMessage(raw) 正在发送 JSON 格式的消息。 link。原始数据通过命令 getRaw 检索。 link

标签: json delphi encoding thunderbird chrome-native-messaging


【解决方案1】:

首先我要说的是,您在使用 native messaging 在 Web 扩展(Thunderbird 插件)和本机应用程序之间传输数据方面做得很好。理解和设置它并不容易,但是您设法传输了所需的数据,但您在问题中描述了一些小故障。

...生成的文件在文件的开头和结尾包含双引号 (") 以及转义的双引号 (\")

在附加组件中,您将获取原始电子邮件数据作为字符串 - console.log(typeof raw) 提供 string,然后您将其传递给 port.postMessage。尽管文档说它需要 JSON 对象来表示要发送的消息,但根据某些标准,它似乎接受单个字符串值 valid JSON。在 Delphi 代码中,您通过STDIN 接收消息并使用TJSONObject.ParseJSONValue 将其解析为TJSONValue。它实际上会创建TJSONString 的实例。您可以通过检查jsonValue.ClassName 的值来验证这一点。当您使用 jsonValue.ToString 时会出现引号问题,它返回的字符串的引号版本与您在解析之前的版本基本相同。使用Value 属性返回原始字符串值。

单独使用jsonValue.Value 不会帮助您解决编码问题。您从电子邮件客户端获得的原始消息数据位于EML format 中。它符合RFC-822,这意味着它是 ASCII 编码的,但它可以包含任意编码的消息部分(请参阅您自己的示例 EML)。由于您只想将 EML 文件保存为不考虑任何编码,因此最好传输 EML 的原始字节,但这不是 Javascript 和本机消息 API 支持的开箱即用。因此,我建议您将 Base64 编码的数据字符串发送到本机应用程序,然后将其解码为可以直接写入磁盘的原始字节。

在附加使用函数btoa中将原始消息数据编码为Base64字符串:

port.postMessage(btoa(raw));

要在本机应用程序中接收消息,您可以执行以下操作:

uses
  System.SysUtils, System.Classes, System.IOUtils, System.JSON, System.NetEncoding, Winapi.Windows;

procedure WriteSTDInputToFile(const FileName: string);
var
  StdIn: THandleStream;
  MsgLen: Cardinal;
  Data: TBytes;
  JSONValue: TJSONValue;
begin
  StdIn := THandleStream.Create(GetStdHandle(STD_INPUT_HANDLE));
  try
    StdIn.ReadBuffer(MsgLen, SizeOf(MsgLen));
    SetLength(Data, MsgLen);
    StdIn.ReadBuffer(Data, MsgLen);
    JSONValue := TJSONObject.ParseJSONValue(Data, 0);
    Data := TNetEncoding.Base64.DecodeStringToBytes(JSONValue.Value);
    TFile.WriteAllBytes(FileName, Data);
  finally
    StdIn.Free;
  end;
end;

注意对原始代码的一些改进:

  • Cardinal 类型用于MsgLen。该协议定义输入的前 4 个字节表示消息长度,以字节表示,表示为 32 位无符号整数。 Cardinal 是 Delphi 对此类值的原生类型,或者您也可以使用 UInt32 别名。
  • 我使用ReadBuffer 方法而不是Read 从STDIN 读取,这会使程序在某些意外情况下崩溃。理想情况下,您应该处理这种情况,通过 STDOUT 发送错误消息作为响应,并在插件中处理响应。
  • 我不会将传统的 I/O 例程与流混合在一起。我什至没有使用流在我的代码中写入输出文件。多亏了来自Syste.IOUtilsFile.WriteAllBytes,创建文件是单行的。
  • 我不检查if Assigned(StdIn) then StdIn.Free;。这就是 Free 已经为您做的事情。

知道传入的消息是带引号的 Base64 编码字符串,就可以省略 JSON 处理,因此代码变为:

procedure WriteSTDInputToFile(const FileName: string);
var
  StdIn: THandleStream;
  MsgLen: Cardinal;
  Msg: RawByteString;
  Data: TBytes;
begin
  StdIn := THandleStream.Create(GetStdHandle(STD_INPUT_HANDLE));
  try
    StdIn.ReadBuffer(MsgLen, SizeOf(MsgLen));
    StdIn.Seek(1, soFromCurrent); { skip double quote }
    SetLength(Msg, MsgLen - 2); { minus leading and trailing double quotes }
    StdIn.ReadBuffer(Msg[Low(Msg)], MsgLen);
    Data := TNetEncoding.Base64.DecodeStringToBytes(UTF8ToString(Msg));
    TFile.WriteAllBytes(FileName, Data);
  finally
    StdIn.Free;
  end;
end;

【讨论】:

  • 太棒了!那完成了工作!非常感谢您的帮助,也非常感谢您提供的解释!
猜你喜欢
  • 2010-11-01
  • 1970-01-01
  • 2014-06-02
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
  • 2016-07-09
  • 2015-10-19
  • 2017-09-21
相关资源
最近更新 更多