Delphi 文件上载失败,使用Indy发布且文件名包含希腊字符

Delphi 文件上载失败,使用Indy发布且文件名包含希腊字符,delphi,utf-8,indy10,delphi-xe3,idhttp,Delphi,Utf 8,Indy10,Delphi Xe3,Idhttp,我正在尝试向web服务实现POST。我需要发送一个类型为variable的文件(.docx,.pdf,.txt)以及JSON格式的字符串 我已成功发布文件,代码如下: procedure DoRequest; var Http: TIdHTTP; Params: TIdMultipartFormDataStream; RequestStream, ResponseStream: TStringStream; JRequest, JResponse: TJSONObject;

我正在尝试向web服务实现
POST
。我需要发送一个类型为variable的文件(
.docx
.pdf
.txt
)以及JSON格式的字符串

我已成功发布文件,代码如下:

procedure DoRequest;
var
  Http: TIdHTTP;
  Params: TIdMultipartFormDataStream;
  RequestStream, ResponseStream: TStringStream;
  JRequest, JResponse: TJSONObject;
  url: string;
begin
  url := 'some_custom_service'

  JRequest := TJSONObject.Create;
  JResponse := TJSONObject.Create;
  try
    JRequest.AddPair('Pair1', 'Value1');
    JRequest.AddPair('Pair2', 'Value2');
    JRequest.AddPair('Pair3', 'Value3');

    Http := TIdHTTP.Create(nil);           
    ResponseStream := TStringStream.Create;
    RequestStream := TStringStream.Create(UTF8Encode(JRequest.ToString));
    try   
      Params := TIdMultipartFormDataStream.Create;
      Params.AddFile('File', ceFileName.Text, '').ContentTransfer := '';
      Params.AddFormField('Json', 'application/json', '', RequestStream);

      Http.Post(url, Params, ResponseStream);
      JResponse := TJSONObject.ParseJSONValue(ResponseStream.DataString) as TJSONObject;
    finally    
      RequestStream.Free;
      ResponseStream.Free;
      Params.Free;
      Http.Free;
    end;
  finally
    JRequest.Free;
    JResponse.Free;
  end;
end;
当我试图发送一个文件名中包含希腊字符和空格的文件时,就会出现问题。有时失败,有时成功

经过大量研究,我注意到
POST
标题是由Indy的
TIdFormDataField
类使用
EncodeHeader()
函数编码的。当post失败时,与未拆分的成功post相比,标头中的编码文件名将被拆分

例如:

  • docx编码为
    =?UTF-8?B?zpxpgm65z4pphphm6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66zr8uZG9j?=''D#$A'=?UTF-8?B?eA===?,失败
  • docx
    编码为
    =?UTF-8?B?zpXPgM65z4PPhM6/ZRVORIDOTC66Z4DOC65ZRTOC+Fz4TOuc66LmRvY3g=?=
    ,成功
  • docx
    编码为
    =?UTF-8?B?zpXPgM65z4PPhM6/ZRVORIDOTC66Z4DOC65ZRTOC+Fz4TOuc66?=.docx
    ,失败
我已尝试更改文件名的编码、
AddFile()
过程的
AContentType
ContentTransfer
,但这些都没有改变行为,并且在拆分编码的文件名时仍然会出错

这是某种错误,还是我遗漏了什么

我的代码适用于除上述情况之外的所有情况

我将Delphi XE3与Indy10一起使用。

EncodeHeader()
确实存在一些Unicode字符串的已知问题:

基本上,MIME编码的单词长度不能超过75个字符,所以长文本会被拆分。但是,在编码长Unicode字符串时,任何给定的Unicode字符都可以使用1个或多个字节进行字符集编码,
EncodeHeader()
仍无法避免将两个单独字节之间的多字节字符错误地拆分为单独的编码字(这是非法的,并且受到MIME规范的明确禁止)

然而,在你们的例子中并不是这样

在您的第一个示例中,
太长,无法作为单个MIME字进行编码,因此它被分成
子串,然后分别进行编码这在长文本MIME中是合法的(尽管你可能期望Indy将文本拆分为
“εππστττλή”
“εκπαιΔεετττκο.doc”
,或者甚至
“εκπππισττλλολήdoc”
“εκπππιΔειεεεετττοdocοdoc”
。。“ΕπισττολήεκπαιΔειτικ.docx”。如果服务器没有这样做,那么它的解码器中就有一个缺陷(可能它的解码方式是
“Επι∑ττολήεκπαιΔειτκοdoc x”

在第二个示例中,
“ΕπιστολήεκπαιΔεγτικ.docx”
足够短,可以编码为单个MIME字

在第三个示例中,
在第二个空格(而不是第一个空格)上拆分为
子串,只需对第一个子串进行编码这在MIME中是合法的。解码时,解码文本将与以下未编码文本连接,保留它们之间的空白,从而再次产生
“Επι∑ττολήεκπαιΔεΓτικ.docx”
。如果服务器没有这样做,那么它的解码器中就有一个缺陷(可能它的解码方式是
'πι∑τολθεκπαιΔειτκ.docx'

如果您通过Indy的MIME头编码器/解码器运行这些示例文件名,它们会正确解码:

var
  s: String;
begin
  s := EncodeHeader('Επιστολή εκπαιδευτικο.docx', '', 'B', 'UTF-8');
  ShowMessage(s); // '=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66zr8uZG9j?='#13#10' =?UTF-8?B?eA==?='
  s := DecodeHeader(s);
  ShowMessage(s); // 'Επιστολή εκπαιδευτικο.docx'

  s := EncodeHeader('Επιστολή εκπαιδευτικ.docx', '', 'B', 'UTF-8');
  ShowMessage(s); // '=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66LmRvY3g=?='
  s := DecodeHeader(s);
  ShowMessage(s); // 'Επιστολή εκπαιδευτικ.docx' 

  s := EncodeHeader('Επιστολή εκπαιδευτικ .docx', '', 'B', 'UTF-8');
  ShowMessage(s); // '=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66?= .docx' 
  s := DecodeHeader(s);
  ShowMessage(s); // 'Επιστολή εκπαιδευτικ .docx'
end;
因此,问题似乎在于服务器端解码,而不是Indy的客户端编码

也就是说,如果您使用的是Indy 10的最新版本(2011年11月或更高版本),
TIdFormDataField
有一个
HeaderEncoding
属性,在Unicode环境中默认为
'B'
(base64)。但是,拆分逻辑也会影响
'Q'
(引用可打印),因此这可能对您有用,也可能对您无效(但您可以尝试):


只需注意,如果服务器服务器不期望文件名的文件名是原始UTF-8字节的原始UTF-8字节,如果服务器服务器不期望文件名的文件名是原始UTF-8字节的原始UTF-8字节,那么你可能仍然会遇到问题,你可能仍然会遇到问题(即,《代码>《代码》的《代码》的《你可能仍然可能仍然会遇到问题(即,《代码》的《代码》的《你可能仍然可能仍然可能仍然可能仍然会遇到问题(即,即,即,<《即,<《即,<《即,<《即,<《代码>是,<《代码>在《代码》的《代码>被解释解释解释解释为<码>的《代码》被解释为《代码>的《2020666年年年年6年年年6月6月6月6月6日日)的《6日日日日日)的《本本本本本本(例如,docx’
).

非常感谢@Remy的回答和所有解释。我已与服务器所有者联系,我们将尝试一起调试它。与此同时,我尝试了第二种解决方法(8位),并且工作得非常出色。
with Params.AddFile('File', ceFileName.Text, '') do
begin
  ContentTransfer := '';
  HeaderEncoding := 'Q'; // <--- here
  HeaderCharSet := 'utf-8';
end;
with Params.AddFile('File', ceFileName.Text, '') do
begin
  ContentTransfer := '';
  HeaderEncoding := '8'; // <--- here
  HeaderCharSet := 'utf-8';
end;