- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
Indy 10.6 修订版 5128 似乎包含一项更改,该更改破坏了以下用于 HTTP 表单上传的代码。
接收到的数据在末尾包含两个附加字节,即 CR/LF 对。
通读 5127 和 5128 之间更改的代码行并没有找到根本原因。
我会在找到时间时尝试调试它并在此处发布结果(但也许有人更快)。
这是一个独立的演示应用程序,它在 http://127.0.0.1:8080
program IndyMultipartUploadDemo;
{$APPTYPE CONSOLE}
uses
IdHTTPServer, IdCustomHTTPServer, IdContext, IdSocketHandle, IdGlobal,
IdMessageCoder, IdGlobalProtocols, IdMessageCoderMIME, IdMultiPartFormData,
SysUtils, Classes;
type
TMimeHandler = procedure(var VDecoder: TIdMessageDecoder;
var VMsgEnd: Boolean; const Response: TIdHTTPResponseInfo) of object;
TMyServer = class(TIdHTTPServer)
private
procedure ProcessMimePart(var VDecoder: TIdMessageDecoder;
var VMsgEnd: Boolean; const Response: TIdHTTPResponseInfo);
function IsHeaderMediaType(const AHeaderLine, AMediaType: String): Boolean;
function MediaTypeMatches(const AValue, AMediaType: String): Boolean;
function GetUploadFolder: string;
procedure HandleMultipartUpload(Request: TIdHTTPRequestInfo; Response:
TIdHTTPResponseInfo; MimeHandler: TMimeHandler);
public
procedure InitComponent; override;
procedure DoCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); override;
end;
procedure Demo;
var
Server: TMyServer;
begin
ReportMemoryLeaksOnShutdown := True;
Server := TMyServer.Create;
try
try
Server.Active := True;
except
on E: Exception do
begin
WriteLn(E.ClassName + ' ' + E.Message);
end;
end;
WriteLn('Hit any key to terminate.');
ReadLn;
finally
Server.Free;
end;
end;
procedure TMyServer.InitComponent;
var
Binding: TIdSocketHandle;
begin
inherited;
Bindings.Clear;
Binding := Bindings.Add;
Binding.IP := '127.0.0.1';
Binding.Port := 8080;
KeepAlive := True;
end;
procedure TMyServer.DoCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
AResponseInfo.ContentType := 'text/html';
AResponseInfo.CharSet := 'UTF-8';
if ARequestInfo.CommandType = hcGET then
begin
AResponseInfo.ContentText :=
'<!DOCTYPE HTML>' + #13#10
+ '<html>' + #13#10
+ ' <head>' + #13#10
+ ' <title>Multipart Upload Example</title>' + #13#10
+ ' </head>' + #13#10
+ ' <body> ' + #13#10
+ ' <form enctype="multipart/form-data" method="post">' + #13#10
+ ' <fieldset>' + #13#10
+ ' <legend>Standard file upload</legend>' + #13#10
+ ' <label>File input</label>' + #13#10
+ ' <input type="file" class="input-file" name="upload" />' + #13#10
+ ' <button type="submit" class="btn btn-default">Upload</button>' + #13#10
+ ' </fieldset>' + #13#10
+ ' </form>' + #13#10
+ ' </body>' + #13#10
+ '</html>' + #13#10;
end
else
begin
if ARequestInfo.CommandType = hcPOST then
begin
if IsHeaderMediaType(ARequestInfo.ContentType, 'multipart/form-data') then
begin
HandleMultipartUpload(ARequestInfo, AResponseInfo, ProcessMimePart);
end;
end;
end;
end;
// based on code on the Indy and Winsock Forum articles
// http://forums2.atozed.com/viewtopic.php?f=7&t=10924
// http://embarcadero.newsgroups.archived.at/public.delphi.internet.winsock/201107/1107276163.html
procedure TMyServer.ProcessMimePart(var VDecoder: TIdMessageDecoder;
var VMsgEnd: Boolean; const Response: TIdHTTPResponseInfo);
var
LMStream: TMemoryStream;
LNewDecoder: TIdMessageDecoder;
UploadFile: string;
begin
LMStream := TMemoryStream.Create;
try
LNewDecoder := VDecoder.ReadBody(LMStream, VMsgEnd);
if VDecoder.Filename <> '' then
begin
try
LMStream.Position := 0;
Response.ContentText := Response.ContentText
+ Format('<p>%s %d bytes</p>' + #13#10,
[VDecoder.Filename, LMStream.Size]);
// write stream to upload folder
UploadFile := GetUploadFolder + VDecoder.Filename;
LMStream.SaveToFile(UploadFile);
Response.ContentText := Response.ContentText
+ '<p>' + UploadFile + ' written</p>';
except
LNewDecoder.Free;
raise;
end;
end;
VDecoder.Free;
VDecoder := LNewDecoder;
finally
LMStream.Free;
end;
end;
function TMyServer.IsHeaderMediaType(const AHeaderLine, AMediaType: String): Boolean;
begin
Result := MediaTypeMatches(ExtractHeaderItem(AHeaderLine), AMediaType);
end;
function TMyServer.MediaTypeMatches(const AValue, AMediaType: String): Boolean;
begin
if Pos('/', AMediaType) > 0 then begin
Result := TextIsSame(AValue, AMediaType);
end else begin
Result := TextStartsWith(AValue, AMediaType + '/');
end;
end;
function TMyServer.GetUploadFolder: string;
begin
Result := ExtractFilePath(ParamStr(0)) + 'upload\';
ForceDirectories(Result);
end;
procedure TMyServer.HandleMultipartUpload(Request: TIdHTTPRequestInfo;
Response: TIdHTTPResponseInfo; MimeHandler: TMimeHandler);
var
LBoundary, LBoundaryStart, LBoundaryEnd: string;
LDecoder: TIdMessageDecoder;
LLine: string;
LBoundaryFound, LIsStartBoundary, LMsgEnd: Boolean;
begin
LBoundary := ExtractHeaderSubItem(Request.ContentType, 'boundary',
QuoteHTTP);
if LBoundary = '' then
begin
Response.ResponseNo := 400;
Response.CloseConnection := True;
Response.WriteHeader;
Exit;
end;
LBoundaryStart := '--' + LBoundary;
LBoundaryEnd := LBoundaryStart + '--';
LDecoder := TIdMessageDecoderMIME.Create(nil);
try
TIdMessageDecoderMIME(LDecoder).MIMEBoundary := LBoundary;
LDecoder.SourceStream := Request.PostStream;
LDecoder.FreeSourceStream := False;
LBoundaryFound := False;
LIsStartBoundary := False;
repeat
LLine := ReadLnFromStream(Request.PostStream, -1, True);
if LLine = LBoundaryStart then
begin
LBoundaryFound := True;
LIsStartBoundary := True;
end
else if LLine = LBoundaryEnd then
begin
LBoundaryFound := True;
end;
until LBoundaryFound;
if (not LBoundaryFound) or (not LIsStartBoundary) then
begin
Response.ResponseNo := 400;
Response.CloseConnection := True;
Response.WriteHeader;
Exit;
end;
LMsgEnd := False;
repeat
TIdMessageDecoderMIME(LDecoder).MIMEBoundary := LBoundary;
LDecoder.SourceStream := Request.PostStream;
LDecoder.FreeSourceStream := False;
LDecoder.ReadHeader;
case LDecoder.PartType of
mcptText, mcptAttachment:
begin
MimeHandler(LDecoder, LMsgEnd, Response);
end;
mcptIgnore:
begin
LDecoder.Free;
LDecoder := TIdMessageDecoderMIME.Create(nil);
end;
mcptEOF:
begin
LDecoder.Free;
LMsgEnd := True;
end;
end;
until (LDecoder = nil) or LMsgEnd;
finally
LDecoder.Free;
end;
end;
begin
Demo;
end.
最佳答案
目前的SVN版本是5203,所以你的更新有点落后。
我在带有 IE11 的 XE2 中使用修订版 5203 按原样测试了您的代码。
我上传了一个测试.pas
文件,它在upload
文件夹中大了53个字节。我可以确认解码前的原始 PostStream
数据是正确的。
是的,我确实在文件末尾看到了一个额外的 CRLF,这与 TIdMessageDecoderMIME
解码非二进制非 base64/QP 编码数据的方式有关(您的示例就是这样做的不是)。它逐行读取数据,对每一行进行解码,在不使用二进制传输编码时,将解码后的行写入带有新换行符的目标流。该逻辑没有考虑到 MIME 边界前面的换行符属于边界,而不属于边界之前的数据。 MIME 规范对此非常明确,但 Indy 尚未将非 base64 数据考虑在内。
文件大小的其余差异都与非 ASCII 字符被转换为 $3F
字节序列有关,包括 UTF-8 BOM。这是因为 PostStream
数据在 TIdMessageDecoderMIME.ReadBody()
中被解码为 7 位 ASCII,因为没有 Content-Transfer-Encoding
header 与文件数据一起发送,因此由于 RFC 2045 第 6.1 节中的以下声明,Indy 默认为 ASCII:
"Content-Transfer-Encoding: 7BIT" is assumed if the Content-Transfer-Encoding header field is not present.
但是,第 6.4 节中的以下内容似乎与 6.1 相矛盾:
Any entity with an unrecognized Content-Transfer-Encoding must be treated as if it has a Content-Type of "application/octet-stream", regardless of what the Content-Type header field actually says.
ReadBody()
处理这两种情况,但是首先检查 6.1,因此 Indy 假设一个 7bit
编码,然后取消它对 6.4 的处理,因为 7bit
不是无法识别的编码。除非假设缺失的 Content-Transfer-Encoding
应该被视为无法识别的编码,而 Indy 目前不会这样做。
上传的实际Content-Type
是application/octet-stream
,这意味着8位编码。当我在应用第 6.1 节时更新 ReadBody()
以将 application/octet-stream
视为 8bit
而不是 7bit
,所有问题都消失了:
if LContentTransferEncoding = '' then begin
// RLebeau 04/08/2014: According to RFC 2045 Section 6.1:
// "Content-Transfer-Encoding: 7BIT" is assumed if the
// Content-Transfer-Encoding header field is not present."
if IsHeaderMediaType(LContentType, 'application/mac-binhex40') then begin {Do not Localize}
LContentTransferEncoding := 'binhex40'; {do not localize}
end
// START FIX!!
else if IsHeaderMediaType(LContentType, 'application/octet-stream') then begin {Do not Localize}
LContentTransferEncoding := '8bit'; {do not localize}
end
// END FIX!!
else begin
LContentTransferEncoding := '7bit'; {do not localize}
end;
end
上传的文件是正确的文件大小,字节被正确解码和写入,没有将非 ASCII 序列转换为 $3F
序列,并且文件末尾没有额外的 CRLF。
我将不得不进一步调查这个问题,看看是否有更好的方法来处理这种差异。我已经在 Indy 的问题跟踪器中为它开了票。同时,如果您修补您的 Indy 副本,您就有了解决方法。
关于delphi - 多部分/表单数据请求的 Indy MIME 解码返回尾随 CR/LF,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27257577/
考虑 this link来自 Bit Twiddling Hacks 网站。为了计算尾随位,使用了以下算法: unsigned int v; // 32-bit word input to
我正在开发一个在输出目录中创建多个文件的 makefile。为了创建这些文件,输出目录需要已经存在,否则文件创建失败。 这是一个演示我遇到的问题的最小示例: .PHONY: default defau
我试图让 TextField 的值以尾随对齐方式显示。 如您所见,值 34.3 以前导对齐方式显示。 我确信我遗漏了一些明显的东西,但我不知道是什么。有什么想法吗? @State private va
我想让我的 StackView 在屏幕上居中,并在每边(左和右)添加一个 20 的填充以获得屏幕边距: 但不考虑左边距: 结果如下: 如何处理右边距?对于其他类型的对象(不是 StackView),我
我们在我们的网站上使用漂亮的网址。几年前我有一个外部技术人员添加反向链接。他做得很好,但在一个案例中,他总是添加一个尾随空格字符的链接。 https://www.example.com/item/it
我正在设计一个带前缀的可变长度整数。 Rust 具有计算前导和尾随 1 和 0 的方法:https://doc.rust-lang.org/std/primitive.u64.html#method.
Passing a lambda to the last parameter In Kotlin, there is a convention that if the last parameter o
我需要正则表达式的帮助来从字符串中去除不需要的字符(在 Java 中)。我用 4 个正则表达式解决了这个问题。替换将被多次调用 [peeks: 50+ times/sec] 它并降低性能。但我认为它肯
如果我用 base64 编码一个由七个字符组成的字符串,例如abcdefg 与网站 https://www.base64encode.org/结果是 YWJjZGVmZw==。尾部的“==”字符被填充
尝试在客户端上使用 sprintf 打印出包含缓冲区的 char[]。最后一个字符是“\0”。这是我的服务器代码: char buffer[MAXDATASIZE]; char res[MAXDATA
我在 angular2 项目中遇到问题,我正在生成一个要通过电子邮件发送给用户的 URL。电子邮件中的 URL 需要包含一个特殊 ID,该 ID 在路由器中传递为: { 路径:somepath/:id
我正在尝试删除所有前导和尾随 在使用 PHP 的字符串中。 举个例子 Hello World This is a message... 我想回去 Hello World This is a mes
我有一个 JSON 输出,我想稍微修改一下: JSON={"type":"global", "elements":[ {"type":"car","model":"bmw", "element
继续阅读 ranges-v3 库,我意识到所有关于模板类型有效表达式的检查都有一个尾部 ", 42"表达式,我想知道这样做的目的是什么。例如: namespace concepts { con
我使用的是最新版本的 Xcode (11 Beta 16) 和 macOS (10.15 Beta 6) 我正在尝试创建两个 View 。从第一个 View ,您应该能够通过尾随导航栏项目导航到第二个
我在使用 SwiftUI 时遇到性能问题 List有大量数据。我创建了一个演示应用程序只是为了展示 500_000 String 的问题s 并显示其中一个的尾随 Action ,CPU 将在几秒钟内达
我正在使用 Blue River 的最新 docker 镜像,运行 docker-compose 启动它并在 Mura CMS 中部署一个站点包。 问题是,当我访问 URL 后面没有正斜杠的页面时,我
我是一名优秀的程序员,十分优秀!