/** @file
Http IO Helper Library.
(C) Copyright 2020 Hewlett-Packard Development Company, L.P.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#include
#include
#include
#include
#include
/**
Notify the callback function when an event is triggered.
@param[in] Context The opaque parameter to the function.
**/
VOID
EFIAPI
HttpIoNotifyDpc (
IN VOID *Context
)
{
*((BOOLEAN *)Context) = TRUE;
}
/**
Request HttpIoNotifyDpc as a DPC at TPL_CALLBACK.
@param[in] Event The event signaled.
@param[in] Context The opaque parameter to the function.
**/
VOID
EFIAPI
HttpIoNotify (
IN EFI_EVENT Event,
IN VOID *Context
)
{
//
// Request HttpIoNotifyDpc as a DPC at TPL_CALLBACK
//
QueueDpc (TPL_CALLBACK, HttpIoNotifyDpc, Context);
}
/**
Destroy the HTTP_IO and release the resources.
@param[in] HttpIo The HTTP_IO which wraps the HTTP service to be destroyed.
**/
VOID
HttpIoDestroyIo (
IN HTTP_IO *HttpIo
)
{
EFI_HTTP_PROTOCOL *Http;
EFI_EVENT Event;
if (HttpIo == NULL) {
return;
}
Event = HttpIo->ReqToken.Event;
if (Event != NULL) {
gBS->CloseEvent (Event);
}
Event = HttpIo->RspToken.Event;
if (Event != NULL) {
gBS->CloseEvent (Event);
}
Event = HttpIo->TimeoutEvent;
if (Event != NULL) {
gBS->CloseEvent (Event);
}
Http = HttpIo->Http;
if (Http != NULL) {
Http->Configure (Http, NULL);
gBS->CloseProtocol (
HttpIo->Handle,
&gEfiHttpProtocolGuid,
HttpIo->Image,
HttpIo->Controller
);
}
NetLibDestroyServiceChild (
HttpIo->Controller,
HttpIo->Image,
&gEfiHttpServiceBindingProtocolGuid,
HttpIo->Handle
);
}
/**
Create a HTTP_IO to access the HTTP service. It will create and configure
a HTTP child handle.
@param[in] Image The handle of the driver image.
@param[in] Controller The handle of the controller.
@param[in] IpVersion IP_VERSION_4 or IP_VERSION_6.
@param[in] ConfigData The HTTP_IO configuration data ,
NULL means not to configure the HTTP child.
@param[in] Callback Callback function which will be invoked when specified
HTTP_IO_CALLBACK_EVENT happened.
@param[in] Context The Context data which will be passed to the Callback function.
@param[out] HttpIo The HTTP_IO.
@retval EFI_SUCCESS The HTTP_IO is created and configured.
@retval EFI_INVALID_PARAMETER One or more parameters are invalid.
@retval EFI_UNSUPPORTED One or more of the control options are not
supported in the implementation.
@retval EFI_OUT_OF_RESOURCES Failed to allocate memory.
@retval Others Failed to create the HTTP_IO or configure it.
**/
EFI_STATUS
HttpIoCreateIo (
IN EFI_HANDLE Image,
IN EFI_HANDLE Controller,
IN UINT8 IpVersion,
IN HTTP_IO_CONFIG_DATA *ConfigData OPTIONAL,
IN HTTP_IO_CALLBACK Callback,
IN VOID *Context,
OUT HTTP_IO *HttpIo
)
{
EFI_STATUS Status;
EFI_HTTP_CONFIG_DATA HttpConfigData;
EFI_HTTPv4_ACCESS_POINT Http4AccessPoint;
EFI_HTTPv6_ACCESS_POINT Http6AccessPoint;
EFI_HTTP_PROTOCOL *Http;
EFI_EVENT Event;
if ((Image == NULL) || (Controller == NULL) || (HttpIo == NULL)) {
return EFI_INVALID_PARAMETER;
}
if ((IpVersion != IP_VERSION_4) && (IpVersion != IP_VERSION_6)) {
return EFI_UNSUPPORTED;
}
ZeroMem (HttpIo, sizeof (HTTP_IO));
ZeroMem (&HttpConfigData, sizeof (EFI_HTTP_CONFIG_DATA));
//
// Create the HTTP child instance and get the HTTP protocol.
//
Status = NetLibCreateServiceChild (
Controller,
Image,
&gEfiHttpServiceBindingProtocolGuid,
&HttpIo->Handle
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = gBS->OpenProtocol (
HttpIo->Handle,
&gEfiHttpProtocolGuid,
(VOID **)&Http,
Image,
Controller,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status) || (Http == NULL)) {
goto ON_ERROR;
}
//
// Init the configuration data and configure the HTTP child.
//
HttpIo->Image = Image;
HttpIo->Controller = Controller;
HttpIo->IpVersion = IpVersion;
HttpIo->Http = Http;
HttpIo->Callback = Callback;
HttpIo->Context = Context;
HttpIo->Timeout = PcdGet32 (PcdHttpIoTimeout);
if (ConfigData != NULL) {
if (HttpIo->IpVersion == IP_VERSION_4) {
HttpConfigData.LocalAddressIsIPv6 = FALSE;
HttpConfigData.HttpVersion = ConfigData->Config4.HttpVersion;
HttpConfigData.TimeOutMillisec = ConfigData->Config4.RequestTimeOut;
Http4AccessPoint.UseDefaultAddress = ConfigData->Config4.UseDefaultAddress;
Http4AccessPoint.LocalPort = ConfigData->Config4.LocalPort;
IP4_COPY_ADDRESS (&Http4AccessPoint.LocalAddress, &ConfigData->Config4.LocalIp);
IP4_COPY_ADDRESS (&Http4AccessPoint.LocalSubnet, &ConfigData->Config4.SubnetMask);
HttpConfigData.AccessPoint.IPv4Node = &Http4AccessPoint;
} else {
HttpConfigData.LocalAddressIsIPv6 = TRUE;
HttpConfigData.HttpVersion = ConfigData->Config6.HttpVersion;
HttpConfigData.TimeOutMillisec = ConfigData->Config6.RequestTimeOut;
Http6AccessPoint.LocalPort = ConfigData->Config6.LocalPort;
IP6_COPY_ADDRESS (&Http6AccessPoint.LocalAddress, &ConfigData->Config6.LocalIp);
HttpConfigData.AccessPoint.IPv6Node = &Http6AccessPoint;
}
Status = Http->Configure (Http, &HttpConfigData);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
}
//
// Create events for variuos asynchronous operations.
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
HttpIoNotify,
&HttpIo->IsTxDone,
&Event
);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
HttpIo->ReqToken.Event = Event;
HttpIo->ReqToken.Message = &HttpIo->ReqMessage;
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
HttpIoNotify,
&HttpIo->IsRxDone,
&Event
);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
HttpIo->RspToken.Event = Event;
HttpIo->RspToken.Message = &HttpIo->RspMessage;
//
// Create TimeoutEvent for response
//
Status = gBS->CreateEvent (
EVT_TIMER,
TPL_CALLBACK,
NULL,
NULL,
&Event
);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
HttpIo->TimeoutEvent = Event;
return EFI_SUCCESS;
ON_ERROR:
HttpIoDestroyIo (HttpIo);
return Status;
}
/**
Synchronously send a HTTP REQUEST message to the server.
@param[in] HttpIo The HttpIo wrapping the HTTP service.
@param[in] Request A pointer to storage such data as URL and HTTP method.
@param[in] HeaderCount Number of HTTP header structures in Headers list.
@param[in] Headers Array containing list of HTTP headers.
@param[in] BodyLength Length in bytes of the HTTP body.
@param[in] Body Body associated with the HTTP request.
@retval EFI_SUCCESS The HTTP request is trasmitted.
@retval EFI_INVALID_PARAMETER One or more parameters are invalid.
@retval EFI_OUT_OF_RESOURCES Failed to allocate memory.
@retval EFI_DEVICE_ERROR An unexpected network or system error occurred.
@retval Others Other errors as indicated.
**/
EFI_STATUS
HttpIoSendRequest (
IN HTTP_IO *HttpIo,
IN EFI_HTTP_REQUEST_DATA *Request,
IN UINTN HeaderCount,
IN EFI_HTTP_HEADER *Headers,
IN UINTN BodyLength,
IN VOID *Body
)
{
EFI_STATUS Status;
EFI_HTTP_PROTOCOL *Http;
if ((HttpIo == NULL) || (HttpIo->Http == NULL)) {
return EFI_INVALID_PARAMETER;
}
HttpIo->ReqToken.Status = EFI_NOT_READY;
HttpIo->ReqToken.Message->Data.Request = Request;
HttpIo->ReqToken.Message->HeaderCount = HeaderCount;
HttpIo->ReqToken.Message->Headers = Headers;
HttpIo->ReqToken.Message->BodyLength = BodyLength;
HttpIo->ReqToken.Message->Body = Body;
if (HttpIo->Callback != NULL) {
Status = HttpIo->Callback (
HttpIoRequest,
HttpIo->ReqToken.Message,
HttpIo->Context
);
if (EFI_ERROR (Status)) {
return Status;
}
}
//
// Queue the request token to HTTP instances.
//
Http = HttpIo->Http;
HttpIo->IsTxDone = FALSE;
Status = Http->Request (
Http,
&HttpIo->ReqToken
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Poll the network until transmit finish.
//
while (!HttpIo->IsTxDone) {
Http->Poll (Http);
}
return HttpIo->ReqToken.Status;
}
/**
Synchronously receive a HTTP RESPONSE message from the server.
@param[in] HttpIo The HttpIo wrapping the HTTP service.
@param[in] RecvMsgHeader TRUE to receive a new HTTP response (from message header).
FALSE to continue receive the previous response message.
@param[out] ResponseData Point to a wrapper of the received response data.
@retval EFI_SUCCESS The HTTP response is received.
@retval EFI_INVALID_PARAMETER One or more parameters are invalid.
@retval EFI_OUT_OF_RESOURCES Failed to allocate memory.
@retval EFI_DEVICE_ERROR An unexpected network or system error occurred.
@retval Others Other errors as indicated.
**/
EFI_STATUS
HttpIoRecvResponse (
IN HTTP_IO *HttpIo,
IN BOOLEAN RecvMsgHeader,
OUT HTTP_IO_RESPONSE_DATA *ResponseData
)
{
EFI_STATUS Status;
EFI_HTTP_PROTOCOL *Http;
if ((HttpIo == NULL) || (HttpIo->Http == NULL) || (ResponseData == NULL)) {
return EFI_INVALID_PARAMETER;
}
//
// Queue the response token to HTTP instances.
//
HttpIo->RspToken.Status = EFI_NOT_READY;
if (RecvMsgHeader) {
HttpIo->RspToken.Message->Data.Response = &ResponseData->Response;
} else {
HttpIo->RspToken.Message->Data.Response = NULL;
}
HttpIo->RspToken.Message->HeaderCount = 0;
HttpIo->RspToken.Message->Headers = NULL;
HttpIo->RspToken.Message->BodyLength = ResponseData->BodyLength;
HttpIo->RspToken.Message->Body = ResponseData->Body;
Http = HttpIo->Http;
HttpIo->IsRxDone = FALSE;
//
// Start the timer, and wait Timeout seconds to receive the header packet.
//
Status = gBS->SetTimer (HttpIo->TimeoutEvent, TimerRelative, HttpIo->Timeout * TICKS_PER_MS);
if (EFI_ERROR (Status)) {
return Status;
}
Status = Http->Response (
Http,
&HttpIo->RspToken
);
if (EFI_ERROR (Status)) {
//
// Remove timeout timer from the event list.
//
gBS->SetTimer (HttpIo->TimeoutEvent, TimerCancel, 0);
return Status;
}
//
// Poll the network until receive finish.
//
while (!HttpIo->IsRxDone && EFI_ERROR (gBS->CheckEvent (HttpIo->TimeoutEvent))) {
Http->Poll (Http);
}
//
// Remove timeout timer from the event list.
//
gBS->SetTimer (HttpIo->TimeoutEvent, TimerCancel, 0);
if (!HttpIo->IsRxDone) {
//
// Timeout occurs, cancel the response token.
//
Http->Cancel (Http, &HttpIo->RspToken);
Status = EFI_TIMEOUT;
return Status;
} else {
HttpIo->IsRxDone = FALSE;
}
if ((HttpIo->Callback != NULL) &&
((HttpIo->RspToken.Status == EFI_SUCCESS) || (HttpIo->RspToken.Status == EFI_HTTP_ERROR)))
{
Status = HttpIo->Callback (
HttpIoResponse,
HttpIo->RspToken.Message,
HttpIo->Context
);
if (EFI_ERROR (Status)) {
return Status;
}
}
//
// Store the received data into the wrapper.
//
ResponseData->Status = HttpIo->RspToken.Status;
ResponseData->HeaderCount = HttpIo->RspToken.Message->HeaderCount;
ResponseData->Headers = HttpIo->RspToken.Message->Headers;
ResponseData->BodyLength = HttpIo->RspToken.Message->BodyLength;
return Status;
}
/**
Get the value of the content length if there is a "Content-Length" header.
@param[in] HeaderCount Number of HTTP header structures in Headers.
@param[in] Headers Array containing list of HTTP headers.
@param[out] ContentLength Pointer to save the value of the content length.
@retval EFI_SUCCESS Successfully get the content length.
@retval EFI_NOT_FOUND No "Content-Length" header in the Headers.
**/
EFI_STATUS
HttpIoGetContentLength (
IN UINTN HeaderCount,
IN EFI_HTTP_HEADER *Headers,
OUT UINTN *ContentLength
)
{
EFI_HTTP_HEADER *Header;
Header = HttpFindHeader (HeaderCount, Headers, HTTP_HEADER_CONTENT_LENGTH);
if (Header == NULL) {
return EFI_NOT_FOUND;
}
return AsciiStrDecimalToUintnS (Header->FieldValue, (CHAR8 **)NULL, ContentLength);
}
/**
Send HTTP request in chunks.
@param[in] HttpIo The HttpIo wrapping the HTTP service.
@param[in] SendChunkProcess Pointer to current chunk process status.
@param[in] RequestMessage Request to send.
@retval EFI_SUCCESS Successfully to send chunk data according to SendChunkProcess.
@retval Other Other errors.
**/
EFI_STATUS
HttpIoSendChunkedTransfer (
IN HTTP_IO *HttpIo,
IN HTTP_IO_SEND_CHUNK_PROCESS *SendChunkProcess,
IN EFI_HTTP_MESSAGE *RequestMessage
)
{
EFI_STATUS Status;
EFI_HTTP_HEADER *NewHeaders;
EFI_HTTP_HEADER *ContentLengthHeader;
UINTN AddNewHeader;
UINTN HeaderCount;
CHAR8 *MessageBody;
UINTN MessageBodyLength;
UINTN ChunkLength;
CHAR8 ChunkLengthStr[HTTP_IO_CHUNK_SIZE_STRING_LEN];
EFI_HTTP_REQUEST_DATA *SentRequestData;
AddNewHeader = 0;
NewHeaders = NULL;
MessageBody = NULL;
ContentLengthHeader = NULL;
MessageBodyLength = 0;
switch (*SendChunkProcess) {
case HttpIoSendChunkHeaderZeroContent:
ContentLengthHeader = HttpFindHeader (RequestMessage->HeaderCount, RequestMessage->Headers, HTTP_HEADER_CONTENT_LENGTH);
if (ContentLengthHeader == NULL) {
AddNewHeader = 1;
}
NewHeaders = AllocateZeroPool ((RequestMessage->HeaderCount + AddNewHeader) * sizeof (EFI_HTTP_HEADER));
CopyMem ((VOID *)NewHeaders, (VOID *)RequestMessage->Headers, RequestMessage->HeaderCount * sizeof (EFI_HTTP_HEADER));
if (AddNewHeader == 0) {
//
// Override content-length to Transfer-Encoding.
//
ContentLengthHeader = HttpFindHeader (RequestMessage->HeaderCount, NewHeaders, HTTP_HEADER_CONTENT_LENGTH);
ContentLengthHeader->FieldName = NULL;
ContentLengthHeader->FieldValue = NULL;
} else {
ContentLengthHeader = NewHeaders + RequestMessage->HeaderCount;
}
HttpSetFieldNameAndValue (ContentLengthHeader, HTTP_HEADER_TRANSFER_ENCODING, HTTP_HEADER_TRANSFER_ENCODING_CHUNKED);
HeaderCount = RequestMessage->HeaderCount + AddNewHeader;
MessageBodyLength = 0;
MessageBody = NULL;
SentRequestData = RequestMessage->Data.Request;
break;
case HttpIoSendChunkContent:
HeaderCount = 0;
NewHeaders = NULL;
SentRequestData = NULL;
if (RequestMessage->BodyLength > HTTP_IO_MAX_SEND_PAYLOAD) {
MessageBodyLength = HTTP_IO_MAX_SEND_PAYLOAD;
} else {
MessageBodyLength = RequestMessage->BodyLength;
}
AsciiSPrint (
ChunkLengthStr,
HTTP_IO_CHUNK_SIZE_STRING_LEN,
"%x%c%c",
MessageBodyLength,
CHUNKED_TRANSFER_CODING_CR,
CHUNKED_TRANSFER_CODING_LF
);
ChunkLength = AsciiStrLen (ChunkLengthStr);
MessageBody = AllocatePool (ChunkLength + MessageBodyLength + 2);
if (MessageBody == NULL) {
DEBUG ((DEBUG_ERROR, "Not enough memory for chunk transfer\n"));
return EFI_OUT_OF_RESOURCES;
}
//
// Build up the chunk transfer paylaod.
//
CopyMem (MessageBody, ChunkLengthStr, ChunkLength);
CopyMem (MessageBody + ChunkLength, RequestMessage->Body, MessageBodyLength);
*(MessageBody + ChunkLength + MessageBodyLength) = CHUNKED_TRANSFER_CODING_CR;
*(MessageBody + ChunkLength + MessageBodyLength + 1) = CHUNKED_TRANSFER_CODING_LF;
//
// Change variables for the next chunk trasnfer.
//
RequestMessage->BodyLength -= MessageBodyLength;
RequestMessage->Body = (VOID *)((CHAR8 *)RequestMessage->Body + MessageBodyLength);
MessageBodyLength += (ChunkLength + 2);
if (RequestMessage->BodyLength == 0) {
*SendChunkProcess = HttpIoSendChunkEndChunk;
}
break;
case HttpIoSendChunkEndChunk:
HeaderCount = 0;
NewHeaders = NULL;
SentRequestData = NULL;
AsciiSPrint (
ChunkLengthStr,
HTTP_IO_CHUNK_SIZE_STRING_LEN,
"0%c%c%c%c",
CHUNKED_TRANSFER_CODING_CR,
CHUNKED_TRANSFER_CODING_LF,
CHUNKED_TRANSFER_CODING_CR,
CHUNKED_TRANSFER_CODING_LF
);
MessageBody = AllocatePool (AsciiStrLen (ChunkLengthStr));
if (MessageBody == NULL) {
DEBUG ((DEBUG_ERROR, "Not enough memory for the end chunk transfer\n"));
return EFI_OUT_OF_RESOURCES;
}
CopyMem (MessageBody, ChunkLengthStr, AsciiStrLen (ChunkLengthStr));
MessageBodyLength = AsciiStrLen (ChunkLengthStr);
*SendChunkProcess = HttpIoSendChunkFinish;
break;
default:
return EFI_INVALID_PARAMETER;
}
Status = HttpIoSendRequest (
HttpIo,
SentRequestData,
HeaderCount,
NewHeaders,
MessageBodyLength,
MessageBody
);
if (ContentLengthHeader != NULL) {
if (ContentLengthHeader->FieldName != NULL) {
FreePool (ContentLengthHeader->FieldName);
}
if (ContentLengthHeader->FieldValue != NULL) {
FreePool (ContentLengthHeader->FieldValue);
}
}
if (NewHeaders != NULL) {
FreePool (NewHeaders);
}
if (MessageBody != NULL) {
FreePool (MessageBody);
}
return Status;
}
/**
Synchronously receive a HTTP RESPONSE message from the server.
@param[in] HttpIo The HttpIo wrapping the HTTP service.
@param[in] HeaderCount Number of headers in Headers.
@param[in] Headers Array containing list of HTTP headers.
@param[out] ChunkListHead A pointer to receive list head
of chunked data. Caller has to
release memory of ChunkListHead
and all list entries.
@param[out] ContentLength Total content length
@retval EFI_SUCCESS The HTTP chunked transfer is received.
@retval EFI_NOT_FOUND No chunked transfer coding header found.
@retval EFI_OUT_OF_RESOURCES Failed to allocate memory.
@retval EFI_INVALID_PARAMETER Improper parameters.
@retval Others Other errors as indicated.
**/
EFI_STATUS
HttpIoGetChunkedTransferContent (
IN HTTP_IO *HttpIo,
IN UINTN HeaderCount,
IN EFI_HTTP_HEADER *Headers,
OUT LIST_ENTRY **ChunkListHead,
OUT UINTN *ContentLength
)
{
EFI_HTTP_HEADER *Header;
CHAR8 ChunkSizeAscii[256];
EFI_STATUS Status;
UINTN Index;
HTTP_IO_RESPONSE_DATA ResponseData;
UINTN TotalLength;
UINTN MaxTotalLength;
LIST_ENTRY *HttpChunks;
HTTP_IO_CHUNKS *ThisChunk;
LIST_ENTRY *ThisListEntry;
if ((ChunkListHead == NULL) || (ContentLength == NULL)) {
return EFI_INVALID_PARAMETER;
}
*ContentLength = 0;
Header = HttpFindHeader (HeaderCount, Headers, HTTP_HEADER_TRANSFER_ENCODING);
if (Header == NULL) {
return EFI_NOT_FOUND;
}
if (AsciiStrCmp (Header->FieldValue, HTTP_HEADER_TRANSFER_ENCODING_CHUNKED) != 0) {
return EFI_NOT_FOUND;
}
//
// Loop to get all chunks.
//
TotalLength = 0;
MaxTotalLength = PcdGet32 (PcdMaxHttpChunkTransfer);
HttpChunks = (LIST_ENTRY *)AllocateZeroPool (sizeof (LIST_ENTRY));
if (HttpChunks == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto ExitDeleteChunks;
}
InitializeListHead (HttpChunks);
DEBUG ((DEBUG_INFO, " Chunked transfer\n"));
while (TRUE) {
ZeroMem ((VOID *)&ResponseData, sizeof (HTTP_IO_RESPONSE_DATA));
ResponseData.BodyLength = HTTP_IO_CHUNKED_TRANSFER_CODING_DATA_LENGTH;
ResponseData.Body = ChunkSizeAscii;
Status = HttpIoRecvResponse (
HttpIo,
FALSE,
&ResponseData
);
if (EFI_ERROR (Status)) {
goto ExitDeleteChunks;
}
//
// Decoding Chunked Transfer Coding.
// Only decode chunk-size and last chunk.
//
DEBUG ((DEBUG_INFO, " Chunk HTTP Response StatusCode - %d\n", ResponseData.Response.StatusCode));
//
// Break if this is last chunk.
//
if (ChunkSizeAscii[0] == CHUNKED_TRANSFER_CODING_LAST_CHUNK) {
//
// Check if this is a valid Last-Chunk.
//
if ((ChunkSizeAscii[1] != CHUNKED_TRANSFER_CODING_CR) ||
(ChunkSizeAscii[2] != CHUNKED_TRANSFER_CODING_LF)
)
{
DEBUG ((DEBUG_ERROR, " This is an invalid Last-chunk\n"));
Status = EFI_INVALID_PARAMETER;
goto ExitDeleteChunks;
}
Status = EFI_SUCCESS;
DEBUG ((DEBUG_INFO, " Last-chunk\n"));
ThisChunk = (HTTP_IO_CHUNKS *)AllocateZeroPool (sizeof (HTTP_IO_CHUNKS));
if (ThisChunk == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto ExitDeleteChunks;
}
InitializeListHead (&ThisChunk->NextChunk);
ThisChunk->Length = ResponseData.BodyLength - 1 - 2; // Minus sizeof '0' and CRLF.
ThisChunk->Data = (CHAR8 *)AllocatePool (ThisChunk->Length);
if (ThisChunk->Data == NULL) {
FreePool ((UINT8 *)ThisChunk);
Status = EFI_OUT_OF_RESOURCES;
goto ExitDeleteChunks;
}
CopyMem ((UINT8 *)ThisChunk->Data, (UINT8 *)ResponseData.Body + 1, ThisChunk->Length);
TotalLength += ThisChunk->Length;
InsertTailList (HttpChunks, &ThisChunk->NextChunk);
break;
}
//
// Get the chunk length
//
Index = 0;
while ((ChunkSizeAscii[Index] != CHUNKED_TRANSFER_CODING_EXTENSION_SEPARATOR) &&
(ChunkSizeAscii[Index] != (CHAR8)CHUNKED_TRANSFER_CODING_CR) &&
(Index != HTTP_IO_CHUNKED_TRANSFER_CODING_DATA_LENGTH))
{
Index++;
}
if (Index == HTTP_IO_CHUNKED_TRANSFER_CODING_DATA_LENGTH) {
Status = EFI_NOT_FOUND;
goto ExitDeleteChunks;
}
ChunkSizeAscii[Index] = 0;
AsciiStrHexToUintnS (ChunkSizeAscii, NULL, ContentLength);
DEBUG ((DEBUG_INFO, " Length of this chunk %d\n", *ContentLength));
//
// Receive the data;
//
ThisChunk = (HTTP_IO_CHUNKS *)AllocateZeroPool (sizeof (HTTP_IO_CHUNKS));
if (ThisChunk == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto ExitDeleteChunks;
}
ResponseData.BodyLength = *ContentLength;
ResponseData.Body = (CHAR8 *)AllocatePool (*ContentLength);
if (ResponseData.Body == NULL) {
FreePool (ThisChunk);
Status = EFI_OUT_OF_RESOURCES;
goto ExitDeleteChunks;
}
InitializeListHead (&ThisChunk->NextChunk);
ThisChunk->Length = *ContentLength;
ThisChunk->Data = ResponseData.Body;
InsertTailList (HttpChunks, &ThisChunk->NextChunk);
Status = HttpIoRecvResponse (
HttpIo,
FALSE,
&ResponseData
);
if (EFI_ERROR (Status)) {
goto ExitDeleteChunks;
}
//
// Read CRLF
//
ZeroMem ((VOID *)&ResponseData, sizeof (HTTP_IO_RESPONSE_DATA));
ResponseData.BodyLength = 2;
ResponseData.Body = ChunkSizeAscii;
Status = HttpIoRecvResponse (
HttpIo,
FALSE,
&ResponseData
);
if (EFI_ERROR (Status)) {
goto ExitDeleteChunks;
}
//
// Verify the end of chunk payload.
//
if ((ChunkSizeAscii[0] != CHUNKED_TRANSFER_CODING_CR) ||
(ChunkSizeAscii[1] != CHUNKED_TRANSFER_CODING_LF)
)
{
DEBUG ((DEBUG_ERROR, " This is an invalid End-of-chunk notation.\n"));
goto ExitDeleteChunks;
}
TotalLength += *ContentLength;
if (TotalLength > MaxTotalLength) {
DEBUG ((DEBUG_ERROR, " Total chunk transfer payload exceeds the size defined by PcdMaxHttpChunkTransfer.\n"));
goto ExitDeleteChunks;
}
}
*ContentLength = TotalLength;
*ChunkListHead = HttpChunks;
DEBUG ((DEBUG_INFO, " Total of lengh of chunks :%d\n", TotalLength));
return EFI_SUCCESS;
ExitDeleteChunks:
if (HttpChunks != NULL) {
while (!IsListEmpty (HttpChunks)) {
ThisListEntry = GetFirstNode (HttpChunks);
RemoveEntryList (ThisListEntry);
ThisChunk = (HTTP_IO_CHUNKS *)ThisListEntry;
if (ThisChunk->Data != NULL) {
FreePool (ThisChunk->Data);
}
FreePool (ThisListEntry);
}
FreePool (HttpChunks);
}
return Status;
}