2017-02-22 64 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