2017-02-22 62 views
0

我正在尝试将POST应用于Web服务。我需要发送一个类型可变的文件(.docx.pdf,.txt)以及一个JSON格式的字符串。当使用Indy发布并且文件名包含希腊字符时,文件上传失败

我曾设法与类似下面的代码成功发布文件:

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标头使用EncodeHeader()函数由Indy的TIdFormDataField类编码。当帖子失败时,标题中的编码文件名将被拆分,而不是拆分的成功帖子。

例如:

  • Επιστολή εκπαιδευτικο.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,失败。

我试图要改变文件名的编码,该AddFile()程序的AContentTypeContentTransfer,但这些都不改变行为,我仍然得到错误,当编码的文件名是分裂的。

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

我的代码适用于除上述情况外的每种情况。

我使用Delphi XE3与Indy10。

回答

1

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

EncodeHeader() needs to take codeunits into account when splitting data between adjacent encoded-words

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

但是,这不是你的例子中发生的事情。

在您的第一个示例中,'Επιστολή εκπαιδευτικο.docx'太长,无法编码为单个MIME字,因此会拆分为'Επιστολή εκπαιδευτικο.doc'子字符串,然后将其分别编码。 对于长文本,这在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月或更高版本),TIdFormDataFieldHeaderEncoding属性在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')。

+0

非常感谢@Remy的回答和所有的解释。我已与服务器所有者联系,我们将尝试一起调试它。与此同时,我尝试了第二种解决方法(8位),并像魅力一样工作。 – stmpakir