我有一個基于 TIdHTTPServer 的 Web 服務器。它建于德爾福悉尼。從我收到以下多部分/表單資料發布流的網頁:
-----------------------------16857441221270830881532229640
Content-Disposition: form-data; name="d"
83AAAFUaVVs4Q07z
-----------------------------16857441221270830881532229640
Content-Disposition: form-data; name="dir"
Upload
-----------------------------16857441221270830881532229640
Content-Disposition: form-data; name="file_name"; filename="??esk?ˇ te??ka.png"
Content-Type: image/png
PNG_DATA
-----------------------------16857441221270830881532229640--
問題是未正確接收文本部分。我閱讀了Multipart/Form-Data Requests的Indy MIME 解碼回傳尾隨 CR/LF并將傳輸編碼更改為 8bit 這有助于正確接收檔案,但接收到的檔案名仍然是錯誤的(dir 應該是Uploadfilename 應該是?eská te?ka.png)。
d=83AAAFUaVVs4Q07z
dir=UploadW
??esk?? te??ka.png 75
為了演示這個問題,我將我的代碼簡化為一個控制臺應用程式(請注意 MIME.txt 檔案包含與上面發布流中相同的檔案):
program MIMEMultiPartTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.Classes, System.SysUtils,
IdGlobal, IdCoder, IdMessage, IdMessageCoder, IdGlobalProtocols, IdCoderMIME, IdMessageCoderMIME,
IdCoderQuotedPrintable, IdCoderBinHex4;
procedure ProcessAttachmentPart(var Decoder: TIdMessageDecoder; var MsgEnd: Boolean);
var
MS: TMemoryStream;
Name: string;
Value: string;
NewDecoder: TIdMessageDecoder;
begin
MS := TMemoryStream.Create;
try
// http://stackoverflow.com/questions/27257577/indy-mime-decoding-of-multipart-form-data-requests-returns-trailing-cr-lf
TIdMessageDecoderMIME(Decoder).Headers.Values['Content-Transfer-Encoding'] := '8bit';
TIdMessageDecoderMIME(Decoder).BodyEncoded := False;
NewDecoder := Decoder.ReadBody(MS, MsgEnd);
MS.Position := 0; // nutne?
if Decoder.Filename <> EmptyStr then // je to atachment
begin
try
Writeln(Decoder.Filename ' ' IntToStr(MS.Size));
except
FreeAndNil(NewDecoder);
Writeln('Error processing MIME');
end;
end
else // je to parametr
begin
Name := ExtractHeaderSubItem(Decoder.Headers.Text, 'name', QuoteHTTP);
if Name <> EmptyStr then
begin
Value := string(PAnsiChar(MS.Memory));
try
Writeln(Name '=' Value);
except
FreeAndNil(NewDecoder);
Writeln('Error processing MIME');
end;
end;
end;
Decoder.Free;
Decoder := NewDecoder;
finally
MS.Free;
end;
end;
function ProcessMultiPart(const ContentType: string; Stream: TStream): Boolean;
var
Boundary: string;
BoundaryStart: string;
BoundaryEnd: string;
Decoder: TIdMessageDecoder;
Line: string;
BoundaryFound: Boolean;
IsStartBoundary: Boolean;
MsgEnd: Boolean;
begin
Result := False;
Boundary := ExtractHeaderSubItem('multipart/form-data; boundary=---------------------------16857441221270830881532229640', 'boundary', QuoteHTTP);
if Boundary <> EmptyStr then
begin
BoundaryStart := '--' Boundary;
BoundaryEnd := BoundaryStart '--';
Decoder := TIdMessageDecoderMIME.Create(nil);
try
TIdMessageDecoderMIME(Decoder).MIMEBoundary := Boundary;
Decoder.SourceStream := Stream;
Decoder.FreeSourceStream := False;
BoundaryFound := False;
IsStartBoundary := False;
repeat
Line := ReadLnFromStream(Stream, -1, True);
if Line = BoundaryStart then
begin
BoundaryFound := True;
IsStartBoundary := True;
end
else
begin
if Line = BoundaryEnd then
BoundaryFound := True;
end;
until BoundaryFound;
if BoundaryFound and IsStartBoundary then
begin
MsgEnd := False;
repeat
TIdMessageDecoderMIME(Decoder).MIMEBoundary := Boundary;
Decoder.SourceStream := Stream;
Decoder.FreeSourceStream := False;
Decoder.ReadHeader;
case Decoder.PartType of
mcptText,
mcptAttachment:
begin
ProcessAttachmentPart(Decoder, MsgEnd);
end;
mcptIgnore:
begin
Decoder.Free;
Decoder := TIdMessageDecoderMIME.Create(nil);
end;
mcptEOF:
begin
Decoder.Free;
MsgEnd := True;
end;
end;
until (Decoder = nil) or MsgEnd;
Result := True;
end
finally
Decoder.Free;
end;
end;
end;
var
Stream: TMemoryStream;
begin
Stream := TMemoryStream.Create;
try
Stream.LoadFromFile('MIME.txt');
ProcessMultiPart('multipart/form-data; boundary=---------------------------16857441221270830881532229640', Stream);
finally
Stream.Free;
end;
Readln;
end.
有人可以幫助我我的代碼有什么問題嗎?謝謝你。
uj5u.com熱心網友回復:
您對ExtractHeaderSubItem()in 的呼叫ProcessMultiPart()是錯誤的,它需要傳入ContentType字串引數,而不是硬編碼的字串文字。
您對ExtractHeaderSubItem()in 的呼叫ProcessAttachmentPart()也是錯誤的,它只需要傳入Content-Disposition標題的內容,而不是整個Headers.Text. ExtractHeaderSubItem()設計為一次僅對 1 個標頭進行操作。
關于dirMIME部分,其原因身體資料最終成為'UploadW'代替的'Upload',因為你不采取MS.Size分配時考慮MS.Memory到您的Value字串。該TMemoryStream資料不是以空值終止!因此,您將需要使用SetString()而不是:=運算子,例如:
var
Value: AnsiString;
...
SetString(Value, PAnsiChar(MS.Memory), MS.Size);
關于Decoder.FileName,該值根本不受Content-Transfer-Encoding標題的影響。MIME 標頭根本不允許未編碼的Unicode 字符。目前,根據RFC 7578 第 5.1.3 節,Indy 的 MIME 解碼器支持標頭中 Unicode 字符的RFC2047樣式編碼,但您的流資料未使用該格式。它看起來像你的資料是使用原始UTF-8八位組1(其中5.1.3也提到作為一個可能的編碼,但解碼器目前不找)。因此,您可能必須根據需要自己手動提取和解碼原始檔案。如果你知道的總是會被編碼為UTF-8,你可以嘗試設定Indy的全球filenamefilenameIdGlobal.GIdDefaultTextEncoding變數為encUTF8(默認為encASCII),然后Decoder.FileName 應該是準確的。但是,這是一個全域設定,因此可能會在 Indy 的其他地方產生不需要的副作用,具體取決于背景關系和資料。因此,我建議改為設定GIdDefaultTextEncoding為enc8Bit,以便將不需要的副作用最小化,并且Decoder.FileName將包含原始原始位元組(僅擴展到 16 位字符)。這樣,您可以filename通過簡單地將Decoder.FileName原樣傳遞給 來恢復原始位元組IndyTextEncoding_8Bit.GetBytes(),然后根據需要對它們進行解碼(例如使用IndyTextEncoding_UTF8.GetString(),在驗證位元組是有效的 UTF-8 之后)。
1:然而,??esk?ˇ te??ka.png不是正確的 UTF-8 形式?eská te?ka.png,看起來資料可能已經過雙重編碼,?eská te?ka.png即被 UTF-8 編碼,然后產生的位元組又被 UTF-8 編碼
uj5u.com熱心網友回復:
如今,該filename引數僅應出于后備原因添加,而filename*應添加以明確說明檔案名具有哪種文本編碼。否則每個客戶只能猜測和假設。這可能會出錯。
RFC 5987 §3.2定義了該
filename*引數的格式:charset'[language]'value-chars...然而:
charset可以是UTF-8或ISO-8859-1或任何 MIME 字符集...語言是可選的。
RFC 6266 §4.3定義了
filename*應該使用的,并在§5 中提供了示例:Content-Disposition: attachment; filename="EURO rates"; filename*=utf-8''
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/371178.html
