繁体   English   中英

文件上传失败,当使用 Indy 发布并且文件名包含希腊字符时

[英]File upload fails, when posting with Indy and filename contains Greek characters

我正在尝试对 Web 服务实施POST 我需要发送一个类型为可变的文件( .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()函数编码的。 当发布失败时,与未拆分的成功发布相比,标头中的编码文件名被拆分。

例如 :

  • Επιστολή εκπαιδευτικο.docx被编码为=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66zr8uZG9j?='#$D#$A' =?UTF-8?B?eA==?=
  • Επιστολή εκπαιδευτικ.docx被编码为=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66LmRvY3g=?= ,它成功了。
  • Επιστολή εκπαιδευτικ .docx被编码为=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66?= .docx ,这失败了。

我曾试图改变文件名的编码,该AContentType中的AddFile()方法,以及ContentTransfer我,但没有这些改变的行为,并仍然出现错误时编码的文件名是分裂的。

这是某种错误,还是我错过了什么?

我的代码适用于除我上面描述的那些情况之外的所有情况。

我正在使用带有 Indy10 的 Delphi XE3。

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

EncodeHeader() 在相​​邻编码字之间拆分数据时需要考虑代码单元

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

但是,这不是您的示例中发生的情况。

在您的第一个示例中, 'Επιστολή εκπαιδευτικο.docx'太长而无法编码为单个 MIME 单词,因此它被拆分为'Επιστολή εκπαιδευτικο.doc' 'x'子字符串,然后单独编码。 这在 MIME 中对于长文本是合法的(尽管您可能希望 Indy 将文本拆分为'Επιστολή' ' εκπαιδευτικο.doc' ,或者甚至是'Επιστολή' ' εκπαιδευτικο' '.doc' 。这可能是一种可能性。未来版本)。 仅由空格分隔的相邻 MIME 词意味着在解码时将连接在一起而不分隔空格,从而再次产生'Επιστολή εκπαιδευτικο.docx' 如果服务器没有这样做,则它的解码器存在缺陷(也许它正在解码为'Επιστολή εκπαιδευτικο.doc x' ?)。

在您的第二个示例中, 'Επιστολή εκπαιδευτικ.docx'足够短,可以编码为单个 MIME 单词。

在你的第三个例子中, 'Επιστολή εκπαιδευτικ .docx'在第二个空格(不是第一个)上'Επιστολή εκπαιδευτικ'分为'Επιστολή εκπαιδευτικ' ' .docx'子字符串,并且只需要对第一个子字符串进行编码。 这在 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' (引用可打印),因此这可能对您有用,也可能对您不起作用(但您可以尝试):

with Params.AddFile('File', ceFileName.Text, '') do
begin
  ContentTransfer := '';
  HeaderEncoding := 'Q'; // <--- here
  HeaderCharSet := 'utf-8';
end;

否则,解决方法可能是将值更改为'8' (8 位),这会有效地禁用 MIME 编码(但不是字符集编码):

with Params.AddFile('File', ceFileName.Text, '') do
begin
  ContentTransfer := '';
  HeaderEncoding := '8'; // <--- here
  HeaderCharSet := 'utf-8';
end;

请注意,如果服务器不希望文件名使用原始 UTF-8 字节,您可能仍然会遇到问题(即, 'Επιστολή εκπαιδευτικο.docx'被解释为'Επιστολή εκπαιδευτικο.docx' )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM