/** @file | |
Http IO Helper Library. | |
(C) Copyright 2020 Hewlett-Packard Development Company, L.P.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <Uefi.h> | |
#include <Protocol/Http.h> | |
#include <Library/BaseLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/HttpIoLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/PrintLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
/** | |
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; | |
} |