Files
UnrealEngine/Engine/Source/Runtime/Online/HTTPServer/Private/HttpConnectionRequestReadContext.cpp
2025-05-18 13:04:45 +08:00

344 lines
9.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "HttpConnectionRequestReadContext.h"
#include "HttpServerConstants.h"
#include "HttpServerConstantsPrivate.h"
#include "HttpServerRequest.h"
#include "HttpConnectionContext.h"
#include "IPAddress.h"
#include "PlatformHttp.h"
#include "Sockets.h"
#include "SocketSubsystem.h"
DEFINE_LOG_CATEGORY(LogHttpConnectionRequestReadContext)
FHttpConnectionRequestReadContext::FHttpConnectionRequestReadContext(FSocket* InSocket)
: Socket(InSocket)
{
}
void FHttpConnectionRequestReadContext::ResetContext()
{
Request = nullptr;
ElapsedIdleTime = 0.0f;
SecondsWaitingForReadableSocket = 0.f;
ErrorBuilder.Empty();
HeaderBytes.Empty();
IncomingRequestBodyBytesToRead = 0;
bParseHeaderComplete = false;
bParseBodyComplete = false;
}
EHttpConnectionContextState FHttpConnectionRequestReadContext::ReadStream(float DeltaTime)
{
ElapsedIdleTime += DeltaTime;
const uint32 ByteBufferSize = 1024 * 64; // 64k - safe value to remain under SCA limit (/analyze:stacksize 81940)
uint8 ByteBuffer[ByteBufferSize] = { 0 };
int32 BytesRead = 0;
if (!Socket->Recv(ByteBuffer, sizeof(ByteBuffer) - 1, BytesRead, ESocketReceiveFlags::None))
{
AddError(UE_HTTP_SERVER_ERROR_STR_SOCKET_RECV_FAILURE);
return EHttpConnectionContextState::Error;
}
if (0 == BytesRead)
{
return EHttpConnectionContextState::Continue;
}
else
{
UE_LOG(LogHttpConnectionRequestReadContext, Verbose,
TEXT("ElapsedIdleTime\t %f"), ElapsedIdleTime);
ElapsedIdleTime = 0.0f;
}
if (!bParseHeaderComplete)
{
if (!ParseHeader(ByteBuffer, BytesRead))
{
return EHttpConnectionContextState::Error;
}
}
else if (!bParseBodyComplete)
{
if (!ParseBody(ByteBuffer, BytesRead))
{
return EHttpConnectionContextState::Error;
}
}
// If parsing is complete, we are done
if (bParseHeaderComplete && bParseBodyComplete)
{
check(Request);
return EHttpConnectionContextState::Done;
}
return EHttpConnectionContextState::Continue;
}
bool FHttpConnectionRequestReadContext::ParseHeader(uint8* ByteBuffer, int32 BufferLen)
{
// Append this chunk to our header bytes
int PreviousHeaderBytesLen = HeaderBytes.Num();
HeaderBytes.Append(ByteBuffer, BufferLen);
// We are hunting for the header terminator sequence '\r\n\r\n'
// Start 3 bytes prior to this most recent appendage
int32 SearchIndex = FMath::Max(0, PreviousHeaderBytesLen - 3);
for ( ; SearchIndex < HeaderBytes.Num() - 3; ++SearchIndex)
{
// To find this sequence we will parse byte-by-byte in ANSICHAR
if (ANSICHAR(HeaderBytes[SearchIndex]) == '\r' &&
ANSICHAR(HeaderBytes[SearchIndex + 1]) == '\n' &&
ANSICHAR(HeaderBytes[SearchIndex + 2]) == '\r' &&
ANSICHAR(HeaderBytes[SearchIndex + 3]) == '\n')
{
// We found the terminator
bParseHeaderComplete = true;
// Remove any extra header bytes, but retain the first trailing \r\n
HeaderBytes.SetNum(SearchIndex + 2);
HeaderBytes.Add('\0');
// Build header string
FUTF8ToTCHAR WByteBuffer(reinterpret_cast<const ANSICHAR*>(HeaderBytes.GetData()), HeaderBytes.Num());
const FString IncomingRequestHeaderStr = FString::ConstructFromPtrSize(WByteBuffer.Get(), WByteBuffer.Length());
Request = BuildRequest(IncomingRequestHeaderStr);
if (!Request)
{
// Error if we cannot build a request object
return false;
}
if (!IsRequestValid(*Request.Get()))
{
// Error if request object is invalid
return false;
}
// Try to parse the rest of the request body here in the same frame
ParseContentLength(*Request.Get(), IncomingRequestBodyBytesToRead);
// base relative offset absolute offset
auto BodyBytesPtr = ByteBuffer + (SearchIndex + 4 - PreviousHeaderBytesLen);
auto BufferBytesRemaining = BufferLen - (BodyBytesPtr - ByteBuffer);
return ParseBody(BodyBytesPtr, BufferBytesRemaining);
}
}
// Keep parsing.
return true;
}
bool FHttpConnectionRequestReadContext::ParseBody(uint8* ByteBuffer, int32 BufferLen)
{
if (BufferLen > IncomingRequestBodyBytesToRead)
{
// Error - Sent data size exceeds expected
AddError(UE_HTTP_SERVER_ERROR_STR_MISMATCHED_CONTENT_LENGTH_BODY_TOO_LARGE, EHttpServerResponseCodes::BadRequest);
return false;
}
// Append bytes
if (BufferLen > 0)
{
Request->Body.Append(ByteBuffer, BufferLen);
IncomingRequestBodyBytesToRead -= BufferLen;
}
bParseBodyComplete = (0 == IncomingRequestBodyBytesToRead);
return true;
}
bool FHttpConnectionRequestReadContext::IsRequestValid(const FHttpServerRequest& InRequest)
{
int32 RequestContentLength;
bool bContentLengthSpecified = ParseContentLength(InRequest, RequestContentLength);
switch (InRequest.Verb)
{
case EHttpServerRequestVerbs::VERB_GET:
// Enforce content length missing or 0
if (bContentLengthSpecified && 0 != RequestContentLength)
{
AddError(UE_HTTP_SERVER_ERROR_STR_INVALID_CONTENT_LENGTH_HEADER, EHttpServerResponseCodes::BadRequest);
return false;
}
break;
case EHttpServerRequestVerbs::VERB_PUT:
case EHttpServerRequestVerbs::VERB_POST:
// Content length must be set
if (!bContentLengthSpecified)
{
AddError(UE_HTTP_SERVER_ERROR_STR_MISSING_CONTENT_LENGTH_HEADER, EHttpServerResponseCodes::LengthRequired);
return false;
}
// Content length must be valid
if(RequestContentLength < 0)
{
AddError(UE_HTTP_SERVER_ERROR_STR_INVALID_CONTENT_LENGTH_HEADER, EHttpServerResponseCodes::LengthRequired);
return false;
}
break;
}
return true;
}
bool FHttpConnectionRequestReadContext::ParseContentLength(const FHttpServerRequest& InRequest, int32& OutContentLength)
{
const TArray<FString>* ContentLengthValues = InRequest.Headers.Find(UE_HTTP_SERVER_HEADER_KEYS_CONTENT_LENGTH);
if (ContentLengthValues && ContentLengthValues->Num() > 0)
{
const FString& ContentLengthStr = (*ContentLengthValues)[0];
OutContentLength = FMath::Max(0, FCString::Atoi(*ContentLengthStr));
return true;
}
return false;
}
TSharedPtr<FHttpServerRequest> FHttpConnectionRequestReadContext::BuildRequest(const FString& RequestHeader)
{
if (IsEngineExitRequested())
{
return nullptr;
}
TArray<FString> ParsedHeader;
RequestHeader.ParseIntoArrayLines(ParsedHeader);
if (0 == ParsedHeader.Num())
{
AddError(UE_HTTP_SERVER_ERROR_STR_MISSING_REQUEST_HEADERS, EHttpServerResponseCodes::BadRequest);
return nullptr;
}
// Split HTTP Method Line (Path/Verb/Version)
TArray<FString> HttpMethodTokens;
const FString& HttpMethod = ParsedHeader[0];
HttpMethod.ParseIntoArrayWS(HttpMethodTokens);
if (HttpMethodTokens.Num() < 3)
{
AddError(UE_HTTP_SERVER_ERROR_STR_MALFORMED_REQUEST_HEADER, EHttpServerResponseCodes::BadRequest);
return nullptr;
}
Request = MakeShared<FHttpServerRequest>();
if (Socket)
{
if (ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM))
{
TSharedRef<FInternetAddr> RemoteAddress = SocketSubsystem->CreateInternetAddr();
if (Socket->GetPeerAddress(*RemoteAddress))
{
Request->PeerAddress = MoveTemp(RemoteAddress);
}
}
}
auto RequestVerb = HttpMethodTokens[0];
if (0 == RequestVerb.Compare(TEXT("GET"),
ESearchCase::IgnoreCase))
{
Request->Verb = EHttpServerRequestVerbs::VERB_GET;
}
else if (0 == RequestVerb.Compare(TEXT("POST"),
ESearchCase::IgnoreCase))
{
Request->Verb = EHttpServerRequestVerbs::VERB_POST;
}
else if (0 == RequestVerb.Compare(TEXT("PUT"),
ESearchCase::IgnoreCase))
{
Request->Verb = EHttpServerRequestVerbs::VERB_PUT;
}
else if (0 == RequestVerb.Compare(TEXT("DELETE"),
ESearchCase::IgnoreCase))
{
Request->Verb = EHttpServerRequestVerbs::VERB_DELETE;
}
else if (0 == RequestVerb.Compare(TEXT("PATCH"),
ESearchCase::IgnoreCase))
{
Request->Verb = EHttpServerRequestVerbs::VERB_PATCH;
}
else if (0 == RequestVerb.Compare(TEXT("OPTIONS"),
ESearchCase::IgnoreCase))
{
Request->Verb = EHttpServerRequestVerbs::VERB_OPTIONS;
}
else
{
// Unknown Verb
AddError(UE_HTTP_SERVER_ERROR_STR_UNKNOWN_REQUEST_VERB, EHttpServerResponseCodes::BadMethod);
return nullptr;
}
// Parse/store path+query params
auto RequestHttpPath = HttpMethodTokens[1].TrimStartAndEnd();
int32 QueryParamsIndex = 0;
if (RequestHttpPath.FindChar(TCHAR('?'), QueryParamsIndex))
{
FString QueryParamsStr = RequestHttpPath.Mid(QueryParamsIndex+1);
RequestHttpPath.MidInline(0, QueryParamsIndex, EAllowShrinking::No);
// Split query params
TArray<FString> QueryParamPairs;
QueryParamsStr.ParseIntoArray(QueryParamPairs, TEXT("&"), true);
for (const FString& QueryParamPair : QueryParamPairs)
{
int32 Equalsindex = 0;
if (QueryParamPair.FindChar(TCHAR('='), Equalsindex))
{
FString QueryParamKey = FGenericPlatformHttp::UrlDecode(QueryParamPair.Mid(0, Equalsindex));
FString QueryParamValue = FGenericPlatformHttp::UrlDecode(QueryParamPair.Mid(Equalsindex + 1));
Request->QueryParams.Emplace(MoveTemp(QueryParamKey), MoveTemp(QueryParamValue));
}
}
}
Request->RelativePath.SetPath(RequestHttpPath);
// Parse/store http version
const FString& RequestHttpVersion = HttpMethodTokens[2];
if(!HttpVersion::FromString(RequestHttpVersion, Request->HttpVersion))
{
AddError(UE_HTTP_SERVER_ERROR_STR_UNSUPPORTED_HTTP_VERSION, EHttpServerResponseCodes::VersionNotSup);
return nullptr;
}
// Parse/store headers
for (int i = 1; i < ParsedHeader.Num(); ++i)
{
const FString& HeaderLine = ParsedHeader[i];
int32 SplitIndex = 0;
if (HeaderLine.FindChar(TCHAR(':'), SplitIndex))
{
const auto& HeaderKey = HeaderLine.Mid(0, SplitIndex).TrimStartAndEnd().ToLower();
const auto& HeaderValuesStr = HeaderLine.Mid(SplitIndex + 1).TrimStartAndEnd();
TArray<FString> HeaderValues;
HeaderValuesStr.ParseIntoArray(HeaderValues, TEXT(","), true);
if (HeaderValues.Num() > 0)
{
TArray<FString>* ExistingHeaderValues = Request->Headers.Find(HeaderKey);
if (ExistingHeaderValues)
{
ExistingHeaderValues->Append(HeaderValues);
}
else
{
Request->Headers.Emplace(HeaderKey, MoveTemp(HeaderValues));
}
}
}
}
return Request;
}