Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 64 additions & 6 deletions Libraries/SimpleServer/dwsHTTPSysServer.pas
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ implementation

const
cDefaultMaxInputCountLength = 10*1024*1024; // 10 MB
// QuBES patch #1333: default cumulative cap for bodies of unknown length (chunked)
cDefaultMaxChunkedInputLength = 100*1024*1024; // 100 MB

var
vWsaDataOnce : TWSADATA;
Expand Down Expand Up @@ -627,6 +629,7 @@ constructor THttpApi2Server.CreateClone(From : THttpApi2Server);
end;
FServerEvents := From.FServerEvents;
FURLRewriter := From.URLRewriter;
FMaxInputCountLength := From.FMaxInputCountLength; // QuBES patch #1333
end;

// SendStaticFile
Expand Down Expand Up @@ -1250,17 +1253,67 @@ procedure THttpApi2Server.RegisterCompress(aFunction : THttpSocketCompress);
//
function THttpApi2Server.GetRequestContentBody(request : PHTTP_REQUEST_V2; response : PHTTP_RESPONSE_V2;
var inContent : RawByteString) : Boolean;
const
cChunkedReadBufferSize = 65536; // 64 KB per ReceiveRequestEntityBody call
var
inContentLength, inContentLengthRead, bytesRead : Cardinal;
inContentLength, inContentLengthRead, bytesRead, capacity : Cardinal;
bufRead : PAnsiChar;
flags, errCode : Integer;
i, n : Integer;
compressRec : PHttpSocketCompressRec;
contentEncoding : PHTTP_KNOWN_HEADER;
transferEncoding : RawByteString;
chunked : Boolean;
inputCap, maxBodyLength : Cardinal;
begin
if Win32MajorVersion>5 then // speed optimization for Vista+
flags := HTTP_RECEIVE_REQUEST_ENTITY_BODY_FLAG_FILL_BUFFER
else flags := 0;
with request^.Headers.KnownHeaders[reqContentLength] do
inContentLength := GetCardinal(pRawValue, pRawValue+RawValueLength);
if inContentLength<>0 then begin
// QuBES patch #1333: chunked transfer-encoding support
// http.sys de-chunks natively, the body just has to be read until ERROR_HANDLE_EOF
// without assuming a known Content-Length
with request^.Headers.KnownHeaders[reqTransferEncoding] do
SetString(transferEncoding, pRawValue, RawValueLength);
chunked := (transferEncoding<>'') and (Pos('chunked', LowerCase(String(transferEncoding)))>0);
if chunked or (inContentLength=0) then begin
// unknown body length: cumulative size cap enforced while reading
if MaxInputCountLength>0 then
inputCap := MaxInputCountLength
else inputCap := cDefaultMaxChunkedInputLength;
maxBodyLength := inputCap;
inContentLengthRead := 0;
capacity := cChunkedReadBufferSize;
SetLength(inContent, capacity);
repeat
// grow geometrically to avoid O(n^2) reallocations on large bodies
if capacity-inContentLengthRead < cChunkedReadBufferSize then begin
capacity := capacity*2;
SetLength(inContent, capacity);
end;
bufRead := PAnsiChar(Pointer(inContent))+inContentLengthRead;
bytesRead := 0;
errCode := HttpAPI.ReceiveRequestEntityBody(FReqQueue, request^.RequestId, flags,
bufRead, cChunkedReadBufferSize, bytesRead);
inc(inContentLengthRead, bytesRead); // ERROR_HANDLE_EOF may carry final bytes
if inContentLengthRead>inputCap then begin
// send the 413 first so the client gets a clean status, then cancel to stop
// the remaining (unread) body from flooding the connection
SendError(request, response, 413, 'Payload Too Large');
HttpAPI.CancelHttpRequest(FReqQueue, request^.RequestId, nil);
Exit(False);
end;
if (errCode=NO_ERROR) and (bytesRead=0) then
break; // no progress means end of body
until errCode<>NO_ERROR;
if (errCode<>ERROR_HANDLE_EOF) and (errCode<>NO_ERROR) then begin
SendError(request, response, 406, SysErrorMessage(errCode));
Exit(False);
end;
SetLength(inContent, inContentLengthRead);
end else begin
maxBodyLength := MaxInputCountLength; // 0 = historical unbounded behavior
if (MaxInputCountLength>0) and (inContentLength>MaxInputCountLength) then begin
// ideally SendError(412, 'Content-Length too large');
// but if we don't cancel the request, it'll eat all the
Expand All @@ -1273,9 +1326,6 @@ function THttpApi2Server.GetRequestContentBody(request : PHTTP_REQUEST_V2; respo
inContentLengthRead := 0;
repeat
bytesRead := 0;
if Win32MajorVersion>5 then // speed optimization for Vista+
flags := HTTP_RECEIVE_REQUEST_ENTITY_BODY_FLAG_FILL_BUFFER
else flags := 0;
errCode := HttpAPI.ReceiveRequestEntityBody(FReqQueue, request^.RequestId, flags,
bufRead, inContentLength-inContentLengthRead, bytesRead);
inc(inContentLengthRead, BytesRead);
Expand All @@ -1293,7 +1343,9 @@ function THttpApi2Server.GetRequestContentBody(request : PHTTP_REQUEST_V2; respo
SendError(request, response, 406, SysErrorMessage(errCode));
Exit(False);
end;
// attempt to decompress if necessary
end;
// attempt to decompress if necessary (QuBES patch #1333: common to both read paths)
if inContent<>'' then begin
contentEncoding:=@request^.Headers.KnownHeaders[reqContentEncoding];
n:=contentEncoding.RawValueLength;
if n>0 then begin
Expand All @@ -1302,6 +1354,12 @@ function THttpApi2Server.GetRequestContentBody(request : PHTTP_REQUEST_V2; respo
if (compressRec.NameLength=n)
and CompareMem(Pointer(compressRec.Name), contentEncoding.pRawValue, n) then begin
compressRec.Func(inContent, false); // uncompress
// QuBES patch #1333: guard against decompression bombs -- the cap above
// only bounds the compressed bytes read off the wire, not the output
if (maxBodyLength>0) and (Cardinal(Length(inContent))>maxBodyLength) then begin
SendError(request, response, 413, 'Payload Too Large');
Exit(False);
end;
break;
end;
end;
Expand Down