| /** @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; | |
| } |