当使用 Indy 发布且文件名包含希腊字符时,文件上传失败
File upload fails, when posting with Indy and filename contains Greek characters
我正在尝试对 Web 服务实施 POST
。我需要发送一个类型为变量(.docx
、.pdf
、.txt
)的文件以及一个 JSON 格式的字符串。
我已经成功 post 文件,代码类似于以下内容:
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
header 是由 Indy 的 TIdFormDataField
class 使用 EncodeHeader()
函数编码的。当 post 失败时, header 中的编码文件名被拆分,而成功的 post 则没有拆分。
例如:
Επιστολή εκπαιδευτικο.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()
过程的 AContentType
和 ContentTransfer
,但是 none 改变了行为,当编码文件名被拆分时我仍然会出错。
这是某种错误,还是我遗漏了什么?
我的代码适用于除我上面描述的情况之外的所有情况。
我正在使用 Delphi XE3 和 Indy10。
EncodeHeader()
确实有一些关于 Unicode 字符串的已知问题:
基本上,MIME-encoded 单词的长度不能超过 75 个字符,因此长文本会被拆分。但是当编码一个长的 Unicode 字符串时,任何给定的 Unicode 字符可能 charset-encoded 使用 1 个或多个字节,并且 EncodeHeader()
还不能避免错误地将两个单独字节之间的 multi-byte 字符分割成单独的编码字(这是非法的,并且被 MIME 规范的 RFC 2047 明确禁止)。
但是,这不是您的示例中发生的情况。
在您的第一个示例中,'Επιστολή εκπαιδευτικο.docx'
太长而无法编码为单个 MIME 字,因此它被拆分为 'Επιστολή εκπαιδευτικο.doc'
'x'
子字符串,然后单独编码。 对于长文本,这在 MIME 中是合法的(尽管您可能希望 Indy 将文本拆分为 'Επιστολή'
' εκπαιδευτικο.doc'
,甚至 'Επιστολή'
' εκπαιδευτικο'
'.doc'
。这可能会在未来的版本中出现)。仅由空格分隔的相邻 MIME 单词意味着在解码时连接在一起而不分隔空格,从而再次生成 'Επιστολή εκπαιδευτικο.docx'
。如果服务器没有这样做,则它的解码器存在缺陷(也许它正在解码为 'Επιστολή εκπαιδευτικο.doc x'
?)。
在您的第二个示例中,'Επιστολή εκπαιδευτικ.docx'
足够短,可以编码为单个 MIME 字。
在您的第三个示例中,'Επιστολή εκπαιδευτικ .docx'
在第二个空格(不是第一个)上被拆分为 'Επιστολή εκπαιδευτικ'
' .docx'
个子字符串,并且只需要对第一个子字符串进行编码。 这在 MIME 中是合法的。解码时,解码后的文本将与以下未编码的文本连接起来,保留它们之间的空格,从而再次生成 'Επιστολή εκπαιδευτικ .docx'
。如果服务器没有这样做,则它的解码器存在缺陷(也许它正在解码为 'Επιστολή εκπαιδευτικ.docx'
?)。
如果您 运行 通过 Indy 的 MIME header encoder/decoder 这些示例文件名,它们会正确解码:
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
属性,默认为 'B'
(base64) 在 Unicode 环境中。但是,拆分逻辑也会影响 'Q'
(quoted-printable),因此这可能对您有用,也可能对您不起作用(但您可以尝试):
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'
,例如)。
我正在尝试对 Web 服务实施 POST
。我需要发送一个类型为变量(.docx
、.pdf
、.txt
)的文件以及一个 JSON 格式的字符串。
我已经成功 post 文件,代码类似于以下内容:
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
header 是由 Indy 的 TIdFormDataField
class 使用 EncodeHeader()
函数编码的。当 post 失败时, header 中的编码文件名被拆分,而成功的 post 则没有拆分。
例如:
Επιστολή εκπαιδευτικο.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()
过程的 AContentType
和 ContentTransfer
,但是 none 改变了行为,当编码文件名被拆分时我仍然会出错。
这是某种错误,还是我遗漏了什么?
我的代码适用于除我上面描述的情况之外的所有情况。
我正在使用 Delphi XE3 和 Indy10。
EncodeHeader()
确实有一些关于 Unicode 字符串的已知问题:
基本上,MIME-encoded 单词的长度不能超过 75 个字符,因此长文本会被拆分。但是当编码一个长的 Unicode 字符串时,任何给定的 Unicode 字符可能 charset-encoded 使用 1 个或多个字节,并且 EncodeHeader()
还不能避免错误地将两个单独字节之间的 multi-byte 字符分割成单独的编码字(这是非法的,并且被 MIME 规范的 RFC 2047 明确禁止)。
但是,这不是您的示例中发生的情况。
在您的第一个示例中,'Επιστολή εκπαιδευτικο.docx'
太长而无法编码为单个 MIME 字,因此它被拆分为 'Επιστολή εκπαιδευτικο.doc'
'x'
子字符串,然后单独编码。 对于长文本,这在 MIME 中是合法的(尽管您可能希望 Indy 将文本拆分为 'Επιστολή'
' εκπαιδευτικο.doc'
,甚至 'Επιστολή'
' εκπαιδευτικο'
'.doc'
。这可能会在未来的版本中出现)。仅由空格分隔的相邻 MIME 单词意味着在解码时连接在一起而不分隔空格,从而再次生成 'Επιστολή εκπαιδευτικο.docx'
。如果服务器没有这样做,则它的解码器存在缺陷(也许它正在解码为 'Επιστολή εκπαιδευτικο.doc x'
?)。
在您的第二个示例中,'Επιστολή εκπαιδευτικ.docx'
足够短,可以编码为单个 MIME 字。
在您的第三个示例中,'Επιστολή εκπαιδευτικ .docx'
在第二个空格(不是第一个)上被拆分为 'Επιστολή εκπαιδευτικ'
' .docx'
个子字符串,并且只需要对第一个子字符串进行编码。 这在 MIME 中是合法的。解码时,解码后的文本将与以下未编码的文本连接起来,保留它们之间的空格,从而再次生成 'Επιστολή εκπαιδευτικ .docx'
。如果服务器没有这样做,则它的解码器存在缺陷(也许它正在解码为 'Επιστολή εκπαιδευτικ.docx'
?)。
如果您 运行 通过 Indy 的 MIME header encoder/decoder 这些示例文件名,它们会正确解码:
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
属性,默认为 'B'
(base64) 在 Unicode 环境中。但是,拆分逻辑也会影响 'Q'
(quoted-printable),因此这可能对您有用,也可能对您不起作用(但您可以尝试):
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'
,例如)。