|
@@ -0,0 +1,853 @@
|
|
|
+/** @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;
|
|
|
+
|
|
|
+ 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;
|
|
|
+}
|