1046 lines
25 KiB
C++
1046 lines
25 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
namespace UE::IoStore::HTTP
|
|
{
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
struct FMessageOffsets
|
|
{
|
|
uint8 StatusCode;
|
|
uint8 Message;
|
|
uint16 Headers;
|
|
};
|
|
|
|
static int32 ParseMessage(FAnsiStringView Message, FMessageOffsets& Out)
|
|
{
|
|
const FAnsiStringView Protocol("HTTP/1.1 ");
|
|
|
|
// Check there's enough data
|
|
if (Message.Len() < Protocol.Len() + 1) // "+1" accounts for at least one digit
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
const char* Cursor = Message.GetData();
|
|
|
|
// Check for the expected protocol
|
|
if (FAnsiStringView(Cursor, 9) != Protocol)
|
|
{
|
|
return -1;
|
|
}
|
|
int32 i = Protocol.Len();
|
|
|
|
// Trim left and tightly reject anything adventurous
|
|
for (int n = 32; i < n && Cursor[i] == ' '; ++i);
|
|
Out.StatusCode = uint8(i);
|
|
|
|
// At least one status line digit. (Note to self; expect exactly three)
|
|
for (int n = 32; i < n && uint32(Cursor[i] - 0x30) <= 9; ++i);
|
|
if (uint32(i - Out.StatusCode - 1) > 32)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// Trim left
|
|
for (int n = 32; i < n && Cursor[i] == ' '; ++i);
|
|
Out.Message = uint8(i);
|
|
|
|
// Extra conservative length allowance
|
|
if (i > 32)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// Find \r\n
|
|
for (; Cursor[i] != '\r'; ++i)
|
|
{
|
|
if (i >= 2048)
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
if (Cursor[i + 1] != '\n')
|
|
{
|
|
return -1;
|
|
}
|
|
Out.Headers = uint16(i + 2);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
template <typename LambdaType>
|
|
static void EnumerateHeaders(FAnsiStringView Headers, LambdaType&& Lambda)
|
|
{
|
|
// NB. here we are assuming that we will be dealing with servers that will
|
|
// not be returning headers with "obsolete line folding".
|
|
|
|
auto IsOws = [] (int32 c) { return (c == ' ') | (c == '\t'); };
|
|
|
|
const char* Cursor = Headers.GetData();
|
|
const char* End = Cursor + Headers.Len();
|
|
do
|
|
{
|
|
int32 ColonIndex = 0;
|
|
for (; Cursor + ColonIndex < End; ++ColonIndex)
|
|
{
|
|
if (Cursor[ColonIndex] == ':')
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
Cursor += ColonIndex;
|
|
|
|
const char* Right = Cursor + 1;
|
|
while (Right + 1 < End)
|
|
{
|
|
if (Right[0] != '\r' || Right[1] != '\n')
|
|
{
|
|
Right += 1 + (Right[1] != '\r');
|
|
continue;
|
|
}
|
|
|
|
FAnsiStringView Name(Cursor - ColonIndex, ColonIndex);
|
|
|
|
const char* Left = Cursor + 1;
|
|
for (; IsOws(Left[0]); ++Left);
|
|
|
|
Cursor = Right;
|
|
for (; Cursor > Left + 1 && IsOws(Cursor[-1]); --Cursor);
|
|
|
|
FAnsiStringView Value (Left, int32(ptrdiff_t(Cursor - Left)));
|
|
|
|
if (!Lambda(Name, Value))
|
|
{
|
|
Right = End;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
Cursor = Right + 2;
|
|
} while (Cursor < End);
|
|
}
|
|
|
|
|
|
|
|
namespace DetailOne
|
|
{
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
class FBase
|
|
{
|
|
public:
|
|
FBase();
|
|
|
|
protected:
|
|
using FMutableSection = FBuffer::FMutableSection;
|
|
|
|
const char* GetData() const;
|
|
uint32 GetSize() const;
|
|
FMutableSection GetMutableFree(uint32 MinSize, uint32 PageSize=256);
|
|
void AdvanceUsed(uint32 Delta);
|
|
|
|
private:
|
|
FBuffer Buffer;
|
|
char Data[256];
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FBase::FBase()
|
|
: Buffer(Data, sizeof(Data)) // -V670
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
const char* FBase::GetData() const
|
|
{
|
|
return Buffer.GetData();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
uint32 FBase::GetSize() const
|
|
{
|
|
return Buffer.GetSize();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FBase::FMutableSection FBase::GetMutableFree(uint32 MinSize, uint32 PageSize)
|
|
{
|
|
return Buffer.GetMutableFree(MinSize, PageSize);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void FBase::AdvanceUsed(uint32 Delta)
|
|
{
|
|
return Buffer.AdvanceUsed(Delta);
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
class FRequest
|
|
: public FBase
|
|
{
|
|
public:
|
|
using FBase::FBase;
|
|
|
|
void Begin(FAnsiStringView Host, FAnsiStringView Method, FAnsiStringView Path);
|
|
void AddHeader(FAnsiStringView Key, FAnsiStringView Value);
|
|
FTransactId End(bool bKeepAlive);
|
|
FOutcome TrySendRequest(FTlsPeer& Peer);
|
|
|
|
private:
|
|
FRequest& operator << (FAnsiStringView Value);
|
|
uint16 HeaderLeft;
|
|
uint16 AlreadySent = 0;
|
|
int16 HeaderRight = -1;
|
|
int8 MethodLength = -1;
|
|
uint8 _Unnused;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void FRequest::Begin(FAnsiStringView Host, FAnsiStringView Method, FAnsiStringView Path)
|
|
{
|
|
check(MethodLength < 0);
|
|
AlreadySent = uint16(GetSize());
|
|
*this << Method << " " << Path << " HTTP/1.1" "\r\n";
|
|
MethodLength = int8(Method.Len());
|
|
HeaderLeft = uint16(GetSize());
|
|
|
|
AddHeader("Host", Host);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void FRequest::AddHeader(FAnsiStringView Key, FAnsiStringView Value)
|
|
{
|
|
check(HeaderRight < 0);
|
|
*this << Key << ":" << Value << "\r\n";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FTransactId FRequest::End(bool bKeepAlive)
|
|
{
|
|
// HTTP/1.1 is persistent by default thus "Connection" header isn't required
|
|
// unless we want to opt in to a single transaction.
|
|
if (!bKeepAlive)
|
|
{
|
|
AddHeader("Connection", "close");
|
|
}
|
|
|
|
*this << "\r\n";
|
|
HeaderRight = int16(GetSize());
|
|
return 1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FRequest& FRequest::operator << (FAnsiStringView Value)
|
|
{
|
|
uint32 Length = uint32(Value.Len());
|
|
FBuffer::FMutableSection Section = GetMutableFree(Length);
|
|
::memcpy(Section.Data, Value.GetData(), Length);
|
|
AdvanceUsed(Length);
|
|
return *this;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FOutcome FRequest::TrySendRequest(FTlsPeer& Peer)
|
|
{
|
|
const char* SendData = GetData();
|
|
int32 SendSize = GetSize();
|
|
|
|
SendData += AlreadySent;
|
|
SendSize -= AlreadySent;
|
|
check(SendSize > 0);
|
|
|
|
FOutcome Outcome = Peer.Send(SendData, SendSize);
|
|
if (!Outcome.IsOk())
|
|
{
|
|
return Outcome;
|
|
}
|
|
|
|
int32 Result = Outcome.GetResult();
|
|
AlreadySent += uint16(Result);
|
|
if (AlreadySent == GetSize())
|
|
{
|
|
return FOutcome::Ok();
|
|
}
|
|
|
|
check(AlreadySent < GetSize());
|
|
return FOutcome::Waiting();
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
class FStatusHeaders
|
|
: public FRequest
|
|
{
|
|
public:
|
|
using FRequest::FRequest;
|
|
|
|
FOutcome TryRecvResponse(FTlsPeer& Peer);
|
|
bool IsKeepAlive() const;
|
|
uint32 GetStatusCode() const;
|
|
FAnsiStringView GetStatusMessage() const;
|
|
int64 GetContentLength() const;
|
|
bool IsChunked() const;
|
|
void ReadHeaders(FResponse::FHeaderSink Sink) const;
|
|
|
|
protected:
|
|
const char* GetMessageRight() const;
|
|
|
|
private:
|
|
FOutcome Parse();
|
|
int64 ContentLength = -1;
|
|
FMessageOffsets Offsets = {};
|
|
int16 MessageLeft = -1;
|
|
int16 MessageRight = -1;
|
|
uint16 StatusCode = 0;
|
|
uint8 bKeepAlive : 1;
|
|
uint8 bChunked : 1;
|
|
uint8 _Unused : 7;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
bool FStatusHeaders::IsKeepAlive() const
|
|
{
|
|
return (MessageRight > 0) ? bKeepAlive : true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
uint32 FStatusHeaders::GetStatusCode() const
|
|
{
|
|
check(StatusCode >= 100);
|
|
return StatusCode;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FAnsiStringView FStatusHeaders::GetStatusMessage() const
|
|
{
|
|
check(Offsets.Message > 0);
|
|
const char* Cursor = GetData() + MessageLeft;
|
|
return FAnsiStringView(Cursor + Offsets.Message, Offsets.Headers - Offsets.Message);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int64 FStatusHeaders::GetContentLength() const
|
|
{
|
|
check(Offsets.Headers > 0);
|
|
return ContentLength;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
bool FStatusHeaders::IsChunked() const
|
|
{
|
|
check(MessageRight > 0);
|
|
return bChunked;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void FStatusHeaders::ReadHeaders(FResponse::FHeaderSink Sink) const
|
|
{
|
|
uint32 Offset = MessageLeft + Offsets.Headers;
|
|
uint32 Length = MessageRight - Offset - 2; // "-2" trims off '\r\n' that signals end of headers
|
|
const char* Cursor = GetData() + Offset;
|
|
FAnsiStringView Headers(Cursor, Length);
|
|
UE::IoStore::HTTP::EnumerateHeaders(Headers, Sink);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
const char* FStatusHeaders::GetMessageRight() const
|
|
{
|
|
return GetData() + MessageRight;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FOutcome FStatusHeaders::TryRecvResponse(FTlsPeer& Peer)
|
|
{
|
|
auto FindMessageTerminal = [] (const char* Cursor, int32 Length)
|
|
{
|
|
for (int32 i = 4; i <= Length; ++i)
|
|
{
|
|
uint32 Candidate;
|
|
::memcpy(&Candidate, Cursor + i - 4, sizeof(Candidate));
|
|
if (Candidate == 0x0a0d0a0d)
|
|
{
|
|
return i;
|
|
}
|
|
|
|
i += (Cursor[i - 1] > 0x0d) ? 3 : 0;
|
|
}
|
|
|
|
return -1;
|
|
};
|
|
|
|
if (MessageLeft < 0)
|
|
{
|
|
MessageLeft = int16(GetSize());
|
|
bKeepAlive = true;
|
|
bChunked = false;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
static const uint32 PageSize = 256;
|
|
static const uint32 MaxHeaderSize = 8 << 10;
|
|
|
|
auto [Dest, DestSize] = GetMutableFree(0, PageSize);
|
|
FOutcome Outcome = Peer.Recv(Dest, DestSize);
|
|
if (!Outcome.IsOk())
|
|
{
|
|
return Outcome;
|
|
}
|
|
|
|
int32 Result = Outcome.GetResult();
|
|
AdvanceUsed(Result);
|
|
|
|
// Rewind a little to cover cases where the terminal is fragmented across
|
|
// recv() calls
|
|
uint32 DestBias = 0;
|
|
if (Dest - 3 >= GetData() + MessageLeft)
|
|
{
|
|
Dest -= (DestBias = 3);
|
|
}
|
|
|
|
int32 MessageEnd = FindMessageTerminal(Dest, Result + DestBias);
|
|
if (MessageEnd < 0)
|
|
{
|
|
if (GetSize() - MessageLeft > MaxHeaderSize)
|
|
{
|
|
return FOutcome::Error("Headers have grown larger than expected");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
MessageRight = int16(ptrdiff_t(Dest + MessageEnd - GetData()));
|
|
return Parse();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FOutcome FStatusHeaders::Parse()
|
|
{
|
|
const char* Cursor = GetData() + MessageLeft;
|
|
FAnsiStringView MessageView(Cursor, MessageRight - MessageLeft);
|
|
|
|
if (ParseMessage(MessageView, Offsets) < 0)
|
|
{
|
|
return FOutcome::Error("Failed to parse message status");
|
|
}
|
|
|
|
FAnsiStringView StatusCodeView(
|
|
Cursor + Offsets.StatusCode,
|
|
Offsets.Message - Offsets.StatusCode
|
|
);
|
|
StatusCode = uint16(CrudeToInt(StatusCodeView));
|
|
if (!IsStatusCodeOk(StatusCode))
|
|
{
|
|
return FOutcome::Error("Invalid status code", StatusCode);
|
|
}
|
|
|
|
static const uint32 Flag_Length = 1 << 0;
|
|
static const uint32 Flag_XferEnc = 1 << 1;
|
|
static const uint32 Flag_Connection = 1 << 2;
|
|
static const uint32 Flag_All = 0x07;
|
|
uint32 Flags = 0;
|
|
|
|
ReadHeaders(
|
|
[this, &Flags] (FAnsiStringView Name, FAnsiStringView Value) mutable
|
|
{
|
|
// todo; may need smarter value handling; ;/, separated options & key-value pairs (ex. in rfc2068)
|
|
|
|
if (Name.Equals("Content-Length", ESearchCase::IgnoreCase))
|
|
{
|
|
ContentLength = int32(CrudeToInt(Value));
|
|
Flags |= Flag_Length;
|
|
}
|
|
|
|
else if (Name.Equals("Transfer-Encoding", ESearchCase::IgnoreCase))
|
|
{
|
|
bChunked = Value.Equals("chunked", ESearchCase::IgnoreCase);
|
|
Flags |= Flag_XferEnc;
|
|
}
|
|
|
|
else if (Name.Equals("Connection", ESearchCase::IgnoreCase))
|
|
{
|
|
bKeepAlive = !Value.Equals("close", ESearchCase::IgnoreCase);
|
|
Flags |= Flag_Connection;
|
|
}
|
|
|
|
return Flags != Flag_All;
|
|
}
|
|
);
|
|
|
|
if (Flags & Flag_XferEnc)
|
|
{
|
|
if (!bChunked) return FOutcome::Error("Unsupported Transfer-Encoding");
|
|
if (Flags & Flag_Length) return FOutcome::Error("Chunked yet with length");
|
|
ContentLength = -1;
|
|
}
|
|
else if (uint32 Overshoot = GetSize() - MessageRight; Flags & Flag_Length)
|
|
{
|
|
if (ContentLength < 0) return FOutcome::Error("Invalid Content-Length field", uint32(ContentLength));
|
|
if (Overshoot > ContentLength) return FOutcome::Error("More data received that expected");
|
|
}
|
|
else if (Overshoot)
|
|
{
|
|
return FOutcome::Error("Received content when none was expected");
|
|
}
|
|
else
|
|
{
|
|
ContentLength = 0;
|
|
}
|
|
|
|
if (IsContentless(StatusCode))
|
|
{
|
|
ContentLength = 0;
|
|
bChunked = false;
|
|
}
|
|
|
|
return FOutcome::Ok();
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
class FBody
|
|
: public FStatusHeaders
|
|
{
|
|
public:
|
|
using FStatusHeaders::FStatusHeaders;
|
|
|
|
int64 GetRemaining() const;
|
|
FOutcome TryRecv(FMutableMemoryView Dest, FTlsPeer& Peer);
|
|
|
|
private:
|
|
enum class EState : uint8 { Init, Recv, Prologue, Chunk, Epilogue, Done };
|
|
|
|
FOutcome Gather(FMutableMemoryView Dest, FTlsPeer& Peer);
|
|
FOutcome Init(FMutableMemoryView Dest, FTlsPeer& Peer);
|
|
FOutcome Recv(FMutableMemoryView Dest, FTlsPeer& Peer);
|
|
FOutcome Prologue(FMutableMemoryView Dest, FTlsPeer& Peer);
|
|
FOutcome Chunk(FMutableMemoryView Dest, FTlsPeer& Peer);
|
|
FOutcome Epilogue(FMutableMemoryView Dest, FTlsPeer& Peer);
|
|
FMemoryView Overspill;
|
|
FMutableMemoryView Scratch;
|
|
int64 Remaining = 0;
|
|
EState State = EState::Init;
|
|
|
|
enum
|
|
{
|
|
HeaderBufSize = 32,
|
|
CrLfLength = 2,
|
|
EndOfXfer = -1,
|
|
};
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int64 FBody::GetRemaining() const
|
|
{
|
|
return (State == EState::Recv) ? Remaining : -1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FOutcome FBody::TryRecv(FMutableMemoryView Dest, FTlsPeer& Peer)
|
|
{
|
|
if (Dest.GetSize() == 0)
|
|
{
|
|
return FOutcome::Error("Empty destination");
|
|
}
|
|
|
|
FOutcome Outcome = FOutcome::None();
|
|
|
|
switch (State)
|
|
{
|
|
case EState::Init: Outcome = Init(Dest, Peer); break;
|
|
case EState::Recv: Outcome = Recv(Dest, Peer); break;
|
|
default: Outcome = Gather(Dest, Peer); break;
|
|
case EState::Done: Outcome = FOutcome::Error("Transaction is complete"); break;
|
|
}
|
|
|
|
if (Outcome.IsOk())
|
|
{
|
|
State = EState::Done;
|
|
}
|
|
|
|
return Outcome;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FOutcome FBody::Gather(FMutableMemoryView Dest, FTlsPeer& Peer)
|
|
{
|
|
FOutcome Outcome = FOutcome::None();
|
|
|
|
bool bDone = false;
|
|
int32 Size = 0;
|
|
do
|
|
{
|
|
switch (State)
|
|
{
|
|
case EState::Prologue: Outcome = Prologue(Dest, Peer); break;
|
|
case EState::Chunk: Outcome = Chunk(Dest, Peer); break;
|
|
case EState::Epilogue: Outcome = Epilogue(Dest, Peer); break;
|
|
default: Outcome = FOutcome::Error("Unexpected try-chunked state"); break;
|
|
}
|
|
|
|
if (Outcome.IsError())
|
|
{
|
|
return Outcome;
|
|
}
|
|
|
|
bDone = Outcome.IsOk();
|
|
int32 Result = Outcome.GetResult();
|
|
if (Result == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
Size += Result;
|
|
Dest = Dest.Mid(Result);
|
|
}
|
|
while (!Dest.IsEmpty());
|
|
|
|
return bDone ? FOutcome::Ok(Size) : FOutcome::Waiting(Size);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FOutcome FBody::Init(FMutableMemoryView Dest, FTlsPeer& Peer)
|
|
{
|
|
const char* DataEnd = GetData() + GetSize();
|
|
const char* MessageEnd = GetMessageRight();
|
|
uint32 AlreadyReceived = uint32(ptrdiff_t(DataEnd - MessageEnd));
|
|
if (AlreadyReceived < HeaderBufSize)
|
|
{
|
|
GetMutableFree(HeaderBufSize, HeaderBufSize);
|
|
MessageEnd = GetMessageRight();
|
|
}
|
|
|
|
if (AlreadyReceived)
|
|
{
|
|
Overspill = { (const uint8*)MessageEnd, AlreadyReceived };
|
|
}
|
|
|
|
Scratch = { (uint8*)MessageEnd, HeaderBufSize };
|
|
|
|
if (IsChunked())
|
|
{
|
|
check(Remaining == 0);
|
|
State = EState::Prologue;
|
|
}
|
|
else
|
|
{
|
|
Remaining = GetContentLength();
|
|
check(Remaining > 0);
|
|
State = EState::Recv;
|
|
}
|
|
|
|
return TryRecv(Dest, Peer);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FOutcome FBody::Recv(FMutableMemoryView Dest, FTlsPeer& Peer)
|
|
{
|
|
int64 OverspillSize = Overspill.GetSize();
|
|
if (OverspillSize != 0)
|
|
{
|
|
int64 Size = FMath::Min<int64>(Dest.GetSize(), OverspillSize);
|
|
::memcpy(Dest.GetData(), Overspill.GetData(), Size);
|
|
Dest = Dest.Mid(Size);
|
|
Overspill = Overspill.Mid(Size);
|
|
Remaining -= Size;
|
|
if (Remaining == 0 || Dest.IsEmpty())
|
|
{
|
|
int32 Result = int32(Size);
|
|
return Remaining ? FOutcome::WaitBuffer(Result) : FOutcome::Ok(Result);
|
|
}
|
|
}
|
|
|
|
auto* Cursor = (char*)(Dest.GetData());
|
|
uint32 Size = uint32(FMath::Min<int64>(Dest.GetSize(), Remaining));
|
|
FOutcome Outcome = Peer.Recv(Cursor, Size);
|
|
if (Outcome.IsError())
|
|
{
|
|
return Outcome;
|
|
}
|
|
|
|
if (Outcome.IsWaiting())
|
|
{
|
|
check(Outcome.GetResult() == 0);
|
|
return FOutcome::Waiting(int32(OverspillSize));
|
|
}
|
|
|
|
int32 Result = Outcome.GetResult();
|
|
Remaining -= Result;
|
|
if (Remaining < 0)
|
|
{
|
|
return FOutcome::Error("Unexpectedly received too much", int32(Remaining));
|
|
}
|
|
|
|
Result += int32(OverspillSize);
|
|
|
|
if (Remaining == 0)
|
|
{
|
|
return FOutcome::Ok(Result);
|
|
}
|
|
|
|
if (Result == Dest.GetSize())
|
|
{
|
|
return FOutcome::WaitBuffer(Result);
|
|
}
|
|
|
|
return FOutcome::Waiting(Result);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FOutcome FBody::Prologue(FMutableMemoryView Dest, FTlsPeer& Peer)
|
|
{
|
|
auto FindHeader = [] (const char* Cursor, int32 Size)
|
|
{
|
|
// Base-16 size
|
|
int32 HexDigLen = 0;
|
|
for (; HexDigLen < Size; ++HexDigLen)
|
|
{
|
|
uint32 c = Cursor[HexDigLen];
|
|
if ((c - '0' >= 10) & (c - 'a' >= 6) & (c - 'A' >= 6))
|
|
{
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
int32 Delta = Size - HexDigLen;
|
|
|
|
if (HexDigLen >= HeaderBufSize - 2) return FOutcome::Error("Chunk size too large");
|
|
if (HexDigLen == 0) return FOutcome::Error("Invalid chunk header");
|
|
if (Delta < 1) return FOutcome::Waiting();
|
|
if (Cursor[HexDigLen] != '\r') return FOutcome::Error("Chunk extensions are not supported (ERREXT)");
|
|
if (Delta < 2) return FOutcome::Waiting();
|
|
if (Cursor[HexDigLen + 1] != '\n') return FOutcome::Error("Invalid chunk CRLF");
|
|
|
|
return FOutcome::Ok(HexDigLen + 2);
|
|
};
|
|
|
|
const char* Header = nullptr;
|
|
FOutcome Outcome = FOutcome::None();
|
|
int32 HeaderLength = 0;
|
|
|
|
uint32 OverspillSize = uint32(Overspill.GetSize());
|
|
if (OverspillSize != 0)
|
|
{
|
|
// We try all the overspill first - we don't know how much we're expecting
|
|
// and overspill could contain both header and all data (thus further recvs
|
|
// could falsely hang us up).
|
|
Header = (char*)Overspill.GetData();
|
|
|
|
Outcome = FindHeader(Header, OverspillSize);
|
|
if (Outcome.IsError())
|
|
{
|
|
return Outcome;
|
|
}
|
|
|
|
HeaderLength = Outcome.GetResult();
|
|
Overspill = Overspill.Mid(HeaderLength);
|
|
}
|
|
|
|
if (HeaderLength == 0)
|
|
{
|
|
FMutableMemoryView View = Scratch;
|
|
|
|
if (OverspillSize != 0)
|
|
{
|
|
if (OverspillSize > uint32(View.GetSize()))
|
|
{
|
|
return FOutcome::Error("Unexpectedly large overspill");
|
|
}
|
|
std::memmove(View.GetData(), Overspill.GetData(), OverspillSize);
|
|
View = View.Mid(OverspillSize);
|
|
}
|
|
|
|
if (!View.IsEmpty())
|
|
{
|
|
char* Cursor = (char*)View.GetData();
|
|
Outcome = Peer.Recv(Cursor, uint32(View.GetSize()));
|
|
if (!Outcome.IsOk())
|
|
{
|
|
return Outcome;
|
|
}
|
|
View = Scratch.Left(OverspillSize + Outcome.GetResult());
|
|
}
|
|
|
|
char* Cursor = (char*)View.GetData();
|
|
Outcome = FindHeader(Cursor, uint32(View.GetSize()));
|
|
if (!Outcome.IsOk())
|
|
{
|
|
return Outcome;
|
|
}
|
|
|
|
check(View.GetData() == Scratch.GetData());
|
|
Header = (char*)Scratch.GetData();
|
|
HeaderLength = Outcome.GetResult();
|
|
Overspill = View.Mid(HeaderLength);
|
|
}
|
|
|
|
// Read chunk size
|
|
Header += Remaining;
|
|
int64 ChunkSize = CrudeToInt<16>(FAnsiStringView(Header, HeaderLength));
|
|
if (ChunkSize < 0) return FOutcome::Error("Unparsable chunk size");
|
|
if (ChunkSize > 16 << 20) return FOutcome::Error("Unacceptable chunk size");
|
|
|
|
if (ChunkSize == 0)
|
|
{
|
|
Remaining = EndOfXfer;
|
|
::memset(Scratch.GetData(), 0, CrLfLength);
|
|
State = EState::Epilogue;
|
|
return Epilogue(Dest, Peer);
|
|
}
|
|
|
|
Remaining = ChunkSize;
|
|
State = EState::Chunk;
|
|
return Chunk(Dest, Peer);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FOutcome FBody::Chunk(FMutableMemoryView Dest, FTlsPeer& Peer)
|
|
{
|
|
check(Remaining != 0);
|
|
|
|
Dest = Dest.Left(Remaining);
|
|
FOutcome Outcome = Recv(Dest, Peer);
|
|
if (!Outcome.IsOk())
|
|
{
|
|
return Outcome;
|
|
}
|
|
|
|
check(Remaining == 0);
|
|
::memset(Scratch.GetData(), 0, CrLfLength);
|
|
State = EState::Epilogue;
|
|
|
|
int32 Result = Outcome.GetResult();
|
|
return FOutcome::WaitBuffer(Result);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FOutcome FBody::Epilogue(FMutableMemoryView Dest, FTlsPeer& Peer)
|
|
{
|
|
auto* Cursor = (uint8*)(Scratch.GetData());
|
|
|
|
FMutableMemoryView CrLfBuffer(Cursor, CrLfLength);
|
|
if (Cursor[0] != 0)
|
|
{
|
|
CrLfBuffer = { Cursor + 1, 1 };
|
|
}
|
|
|
|
check(Remaining <= 0);
|
|
|
|
int64 RemainingCache = Remaining;
|
|
Remaining = CrLfBuffer.GetSize();
|
|
FOutcome Outcome = Recv(CrLfBuffer, Peer);
|
|
if (!Outcome.IsOk())
|
|
{
|
|
Remaining = RemainingCache;
|
|
return Outcome;
|
|
}
|
|
|
|
if (Cursor[0] != '\r' || Cursor[1] != '\n')
|
|
{
|
|
return FOutcome::Error("Trailing headers are not supported (ERRTRAIL)");
|
|
}
|
|
|
|
if (RemainingCache > EndOfXfer)
|
|
{
|
|
State = EState::Prologue;
|
|
return Prologue(Dest, Peer);
|
|
}
|
|
|
|
return FOutcome::Ok();
|
|
}
|
|
|
|
} // namespace DetailOne
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
class FTransactionOne
|
|
: public DetailOne::FBody
|
|
{
|
|
public:
|
|
using DetailOne::FBody::FBody;
|
|
};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FOutcome HandshakeOne(FTlsPeer&, void*)
|
|
{
|
|
return FOutcome::Ok();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FOutcome TickOne(FTlsPeer&, void*)
|
|
{
|
|
return FOutcome::Ok(1);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void GoAwayOne(FTlsPeer&, void*)
|
|
{
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
class FTransaction
|
|
{
|
|
public:
|
|
#define TR_METHOD(x, ...) \
|
|
template <typename... Arg> auto x(Arg&&... Args) __VA_ARGS__ { \
|
|
if (auto Self = UPTRINT(this); Self & 1) \
|
|
return ((FTransactionTwo*)(Self ^ 1))->x(Forward<Arg>(Args)...); \
|
|
return ((FTransactionOne*)this)->x(Forward<Arg>(Args)...); \
|
|
}
|
|
TR_METHOD(Begin)
|
|
TR_METHOD(AddHeader)
|
|
TR_METHOD(End)
|
|
TR_METHOD(TrySendRequest)
|
|
TR_METHOD(TryRecvResponse)
|
|
TR_METHOD(IsKeepAlive, const)
|
|
TR_METHOD(GetStatusCode, const)
|
|
TR_METHOD(GetStatusMessage, const)
|
|
TR_METHOD(GetContentLength, const)
|
|
TR_METHOD(IsChunked, const)
|
|
TR_METHOD(ReadHeaders, const)
|
|
TR_METHOD(GetRemaining, const)
|
|
TR_METHOD(TryRecv)
|
|
#undef TR_METHOD
|
|
|
|
private:
|
|
friend class FTransactRef;
|
|
|
|
void Destruct()
|
|
{
|
|
if (auto Self = UPTRINT(this); Self & 1)
|
|
{
|
|
void* Addr = (void*)(Self ^ 1);
|
|
((FTransactionTwo*)Addr)->~FTransactionTwo();
|
|
FMemory::Free(Addr);
|
|
return;
|
|
}
|
|
|
|
((FTransactionOne*)this)->~FTransactionOne();
|
|
FMemory::Free(this);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
class FTransactRef
|
|
{
|
|
public:
|
|
FTransactRef() = default;
|
|
~FTransactRef();
|
|
FTransactRef(UPTRINT Ptr, int32 Ver);
|
|
FTransactRef(FTransactRef&&) = delete;
|
|
FTransactRef(const FTransactRef&) = delete;
|
|
void operator = (const FTransactRef& Rhs) = delete;
|
|
void operator = (FTransactRef&& Rhs);
|
|
const FTransaction* operator -> () const;
|
|
FTransaction* operator -> ();
|
|
bool IsValid() const;
|
|
|
|
private:
|
|
const FTransaction* Get() const;
|
|
FTransaction* Get();
|
|
UPTRINT Self = 0;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FTransactRef::FTransactRef(UPTRINT Ptr, int32 Ver)
|
|
: Self(Ptr | (Ver == 2))
|
|
{
|
|
check((Ptr & 1) == 0);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void FTransactRef::operator = (FTransactRef&& Rhs)
|
|
{
|
|
Swap(Self, Rhs.Self);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
const FTransaction* FTransactRef::operator -> () const
|
|
{
|
|
return Get();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FTransaction* FTransactRef::operator -> ()
|
|
{
|
|
return Get();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
bool FTransactRef::IsValid() const
|
|
{
|
|
return Get() != nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
const FTransaction* FTransactRef::Get() const
|
|
{
|
|
return (FTransaction*)Self;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FTransaction* FTransactRef::Get()
|
|
{
|
|
return (FTransaction*)Self;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FTransactRef::~FTransactRef()
|
|
{
|
|
if (Self == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Get()->Destruct();
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FTransactRef CreateTransactOne(void*&)
|
|
{
|
|
void* Ptr = FMemory::Malloc(sizeof(FTransactionOne), alignof(FTransactionOne));
|
|
new (Ptr) FTransactionOne();
|
|
return FTransactRef(UPTRINT(Ptr), 1);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FTransactRef CreateTransactTwo(void* PeerData)
|
|
{
|
|
#if IAS_HTTP_WITH_TWO
|
|
using namespace DetailTwo;
|
|
|
|
auto* Driver = (FDriverNg*)PeerData;
|
|
|
|
void* Ptr = FMemory::Malloc(sizeof(FTransactionTwo), alignof(FTransactionTwo));
|
|
new (Ptr) FTransactionTwo(*Driver);
|
|
|
|
return FTransactRef(UPTRINT(Ptr), 2);
|
|
#else
|
|
return FTransactRef();
|
|
#endif
|
|
}
|
|
|
|
} // namespace UE::IoStore::HTTP
|