| /** @file | |
| Implementation of EFI_HTTP_PROTOCOL protocol interfaces. | |
| Copyright (c) 2015 - 2016, Intel Corporation. All rights reserved.<BR> | |
| (C) Copyright 2015 Hewlett Packard Enterprise Development LP<BR> | |
| This program and the accompanying materials | |
| are licensed and made available under the terms and conditions of the BSD License | |
| which accompanies this distribution. The full text of the license may be found at | |
| http://opensource.org/licenses/bsd-license.php. | |
| THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
| **/ | |
| #include "HttpDriver.h" | |
| EFI_HTTP_PROTOCOL mEfiHttpTemplate = { | |
| EfiHttpGetModeData, | |
| EfiHttpConfigure, | |
| EfiHttpRequest, | |
| EfiHttpCancel, | |
| EfiHttpResponse, | |
| EfiHttpPoll | |
| }; | |
| /** | |
| Returns the operational parameters for the current HTTP child instance. | |
| The GetModeData() function is used to read the current mode data (operational | |
| parameters) for this HTTP protocol instance. | |
| @param[in] This Pointer to EFI_HTTP_PROTOCOL instance. | |
| @param[out] HttpConfigData Point to buffer for operational parameters of this | |
| HTTP instance. | |
| @retval EFI_SUCCESS Operation succeeded. | |
| @retval EFI_INVALID_PARAMETER One or more of the following conditions is TRUE: | |
| This is NULL. | |
| HttpConfigData is NULL. | |
| HttpInstance->LocalAddressIsIPv6 is FALSE and | |
| HttpConfigData->IPv4Node is NULL. | |
| HttpInstance->LocalAddressIsIPv6 is TRUE and | |
| HttpConfigData->IPv6Node is NULL. | |
| @retval EFI_NOT_STARTED This EFI HTTP Protocol instance has not been started. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EfiHttpGetModeData ( | |
| IN EFI_HTTP_PROTOCOL *This, | |
| OUT EFI_HTTP_CONFIG_DATA *HttpConfigData | |
| ) | |
| { | |
| HTTP_PROTOCOL *HttpInstance; | |
| // | |
| // Check input parameters. | |
| // | |
| if ((This == NULL) || (HttpConfigData == NULL)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| HttpInstance = HTTP_INSTANCE_FROM_PROTOCOL (This); | |
| ASSERT (HttpInstance != NULL); | |
| if ((HttpInstance->LocalAddressIsIPv6 && HttpConfigData->AccessPoint.IPv6Node == NULL) || | |
| (!HttpInstance->LocalAddressIsIPv6 && HttpConfigData->AccessPoint.IPv4Node == NULL)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| if (HttpInstance->State < HTTP_STATE_HTTP_CONFIGED) { | |
| return EFI_NOT_STARTED; | |
| } | |
| HttpConfigData->HttpVersion = HttpInstance->HttpVersion; | |
| HttpConfigData->TimeOutMillisec = HttpInstance->TimeOutMillisec; | |
| HttpConfigData->LocalAddressIsIPv6 = HttpInstance->LocalAddressIsIPv6; | |
| if (HttpInstance->LocalAddressIsIPv6) { | |
| CopyMem ( | |
| HttpConfigData->AccessPoint.IPv6Node, | |
| &HttpInstance->Ipv6Node, | |
| sizeof (HttpInstance->Ipv6Node) | |
| ); | |
| } else { | |
| CopyMem ( | |
| HttpConfigData->AccessPoint.IPv4Node, | |
| &HttpInstance->IPv4Node, | |
| sizeof (HttpInstance->IPv4Node) | |
| ); | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Initialize or brutally reset the operational parameters for this EFI HTTP instance. | |
| The Configure() function does the following: | |
| When HttpConfigData is not NULL Initialize this EFI HTTP instance by configuring | |
| timeout, local address, port, etc. | |
| When HttpConfigData is NULL, reset this EFI HTTP instance by closing all active | |
| connections with remote hosts, canceling all asynchronous tokens, and flush request | |
| and response buffers without informing the appropriate hosts. | |
| No other EFI HTTP function can be executed by this instance until the Configure() | |
| function is executed and returns successfully. | |
| @param[in] This Pointer to EFI_HTTP_PROTOCOL instance. | |
| @param[in] HttpConfigData Pointer to the configure data to configure the instance. | |
| @retval EFI_SUCCESS Operation succeeded. | |
| @retval EFI_INVALID_PARAMETER One or more of the following conditions is TRUE: | |
| This is NULL. | |
| HttpConfigData is NULL. | |
| HttpConfigData->LocalAddressIsIPv6 is FALSE and | |
| HttpConfigData->IPv4Node is NULL. | |
| HttpConfigData->LocalAddressIsIPv6 is TRUE and | |
| HttpConfigData->IPv6Node is NULL. | |
| @retval EFI_ALREADY_STARTED Reinitialize this HTTP instance without calling | |
| Configure() with NULL to reset it. | |
| @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. | |
| @retval EFI_OUT_OF_RESOURCES Could not allocate enough system resources when | |
| executing Configure(). | |
| @retval EFI_UNSUPPORTED One or more options in HttpConfigData are not supported | |
| in the implementation. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EfiHttpConfigure ( | |
| IN EFI_HTTP_PROTOCOL *This, | |
| IN EFI_HTTP_CONFIG_DATA *HttpConfigData | |
| ) | |
| { | |
| HTTP_PROTOCOL *HttpInstance; | |
| EFI_STATUS Status; | |
| // | |
| // Check input parameters. | |
| // | |
| if (This == NULL || | |
| HttpConfigData == NULL || | |
| ((HttpConfigData->LocalAddressIsIPv6 && HttpConfigData->AccessPoint.IPv6Node == NULL) || | |
| (!HttpConfigData->LocalAddressIsIPv6 && HttpConfigData->AccessPoint.IPv4Node == NULL))) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| HttpInstance = HTTP_INSTANCE_FROM_PROTOCOL (This); | |
| ASSERT (HttpInstance != NULL && HttpInstance->Service != NULL); | |
| if (HttpConfigData != NULL) { | |
| // | |
| // Now configure this HTTP instance. | |
| // | |
| if (HttpInstance->State != HTTP_STATE_UNCONFIGED) { | |
| return EFI_ALREADY_STARTED; | |
| } | |
| HttpInstance->HttpVersion = HttpConfigData->HttpVersion; | |
| HttpInstance->TimeOutMillisec = HttpConfigData->TimeOutMillisec; | |
| HttpInstance->LocalAddressIsIPv6 = HttpConfigData->LocalAddressIsIPv6; | |
| if (HttpConfigData->LocalAddressIsIPv6) { | |
| CopyMem ( | |
| &HttpInstance->Ipv6Node, | |
| HttpConfigData->AccessPoint.IPv6Node, | |
| sizeof (HttpInstance->Ipv6Node) | |
| ); | |
| } else { | |
| CopyMem ( | |
| &HttpInstance->IPv4Node, | |
| HttpConfigData->AccessPoint.IPv4Node, | |
| sizeof (HttpInstance->IPv4Node) | |
| ); | |
| } | |
| // | |
| // Creat Tcp child | |
| // | |
| Status = HttpInitProtocol (HttpInstance, HttpInstance->LocalAddressIsIPv6); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| HttpInstance->State = HTTP_STATE_HTTP_CONFIGED; | |
| return EFI_SUCCESS; | |
| } else { | |
| // | |
| // Reset all the resources related to HttpInsance. | |
| // | |
| HttpCleanProtocol (HttpInstance); | |
| HttpInstance->State = HTTP_STATE_UNCONFIGED; | |
| return EFI_SUCCESS; | |
| } | |
| } | |
| /** | |
| The Request() function queues an HTTP request to this HTTP instance. | |
| Similar to Transmit() function in the EFI TCP driver. When the HTTP request is sent | |
| successfully, or if there is an error, Status in token will be updated and Event will | |
| be signaled. | |
| @param[in] This Pointer to EFI_HTTP_PROTOCOL instance. | |
| @param[in] Token Pointer to storage containing HTTP request token. | |
| @retval EFI_SUCCESS Outgoing data was processed. | |
| @retval EFI_NOT_STARTED This EFI HTTP Protocol instance has not been started. | |
| @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. | |
| @retval EFI_TIMEOUT Data was dropped out of the transmit or receive queue. | |
| @retval EFI_OUT_OF_RESOURCES Could not allocate enough system resources. | |
| @retval EFI_UNSUPPORTED The HTTP method is not supported in current | |
| implementation. | |
| @retval EFI_INVALID_PARAMETER One or more of the following conditions is TRUE: | |
| This is NULL. | |
| Token is NULL. | |
| Token->Message is NULL. | |
| Token->Message->Body is not NULL, | |
| Token->Message->BodyLength is non-zero, and | |
| Token->Message->Data is NULL, but a previous call to | |
| Request()has not been completed successfully. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EfiHttpRequest ( | |
| IN EFI_HTTP_PROTOCOL *This, | |
| IN EFI_HTTP_TOKEN *Token | |
| ) | |
| { | |
| EFI_HTTP_MESSAGE *HttpMsg; | |
| EFI_HTTP_REQUEST_DATA *Request; | |
| VOID *UrlParser; | |
| EFI_STATUS Status; | |
| CHAR8 *HostName; | |
| UINT16 RemotePort; | |
| HTTP_PROTOCOL *HttpInstance; | |
| BOOLEAN Configure; | |
| BOOLEAN ReConfigure; | |
| CHAR8 *RequestStr; | |
| CHAR8 *Url; | |
| UINTN UrlLen; | |
| CHAR16 *HostNameStr; | |
| HTTP_TOKEN_WRAP *Wrap; | |
| CHAR8 *FileUrl; | |
| if ((This == NULL) || (Token == NULL)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| HttpMsg = Token->Message; | |
| if ((HttpMsg == NULL) || (HttpMsg->Headers == NULL)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| // | |
| // Current implementation does not support POST/PUT method. | |
| // If future version supports these two methods, Request could be NULL for a special case that to send large amounts | |
| // of data. For this case, the implementation need check whether previous call to Request() has been completed or not. | |
| // | |
| // | |
| Request = HttpMsg->Data.Request; | |
| if ((Request == NULL) || (Request->Url == NULL)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| // | |
| // Only support GET and HEAD method in current implementation. | |
| // | |
| if ((Request->Method != HttpMethodGet) && (Request->Method != HttpMethodHead)) { | |
| return EFI_UNSUPPORTED; | |
| } | |
| HttpInstance = HTTP_INSTANCE_FROM_PROTOCOL (This); | |
| ASSERT (HttpInstance != NULL); | |
| if (HttpInstance->State < HTTP_STATE_HTTP_CONFIGED) { | |
| return EFI_NOT_STARTED; | |
| } | |
| // | |
| // Check whether the token already existed. | |
| // | |
| if (EFI_ERROR (NetMapIterate (&HttpInstance->TxTokens, HttpTokenExist, Token))) { | |
| return EFI_ACCESS_DENIED; | |
| } | |
| HostName = NULL; | |
| Wrap = NULL; | |
| HostNameStr = NULL; | |
| // | |
| // Parse the URI of the remote host. | |
| // | |
| Url = HttpInstance->Url; | |
| UrlLen = StrLen (Request->Url) + 1; | |
| if (UrlLen > HTTP_URL_BUFFER_LEN) { | |
| Url = AllocateZeroPool (UrlLen); | |
| if (Url == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| FreePool (HttpInstance->Url); | |
| HttpInstance->Url = Url; | |
| } | |
| UnicodeStrToAsciiStr (Request->Url, Url); | |
| UrlParser = NULL; | |
| Status = HttpParseUrl (Url, (UINT32) AsciiStrLen (Url), FALSE, &UrlParser); | |
| if (EFI_ERROR (Status)) { | |
| goto Error1; | |
| } | |
| RequestStr = NULL; | |
| HostName = NULL; | |
| Status = HttpUrlGetHostName (Url, UrlParser, &HostName); | |
| if (EFI_ERROR (Status)) { | |
| goto Error1; | |
| } | |
| Status = HttpUrlGetPort (Url, UrlParser, &RemotePort); | |
| if (EFI_ERROR (Status)) { | |
| RemotePort = HTTP_DEFAULT_PORT; | |
| } | |
| // | |
| // If Configure is TRUE, it indicates the first time to call Request(); | |
| // If ReConfigure is TRUE, it indicates the request URL is not same | |
| // with the previous call to Request(); | |
| // | |
| Configure = TRUE; | |
| ReConfigure = TRUE; | |
| if (HttpInstance->RemoteHost == NULL) { | |
| // | |
| // Request() is called the first time. | |
| // | |
| ReConfigure = FALSE; | |
| } else { | |
| if ((HttpInstance->RemotePort == RemotePort) && | |
| (AsciiStrCmp (HttpInstance->RemoteHost, HostName) == 0)) { | |
| // | |
| // Host Name and port number of the request URL are the same with previous call to Request(). | |
| // Check whether previous TCP packet sent out. | |
| // | |
| if (EFI_ERROR (NetMapIterate (&HttpInstance->TxTokens, HttpTcpNotReady, NULL))) { | |
| // | |
| // Wrap the HTTP token in HTTP_TOKEN_WRAP | |
| // | |
| Wrap = AllocateZeroPool (sizeof (HTTP_TOKEN_WRAP)); | |
| if (Wrap == NULL) { | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto Error1; | |
| } | |
| Wrap->HttpToken = Token; | |
| Wrap->HttpInstance = HttpInstance; | |
| Status = HttpCreateTcpTxEvent (Wrap); | |
| if (EFI_ERROR (Status)) { | |
| goto Error1; | |
| } | |
| Status = NetMapInsertTail (&HttpInstance->TxTokens, Token, Wrap); | |
| if (EFI_ERROR (Status)) { | |
| goto Error1; | |
| } | |
| Wrap->TcpWrap.Method = Request->Method; | |
| FreePool (HostName); | |
| // | |
| // Queue the HTTP token and return. | |
| // | |
| return EFI_SUCCESS; | |
| } else { | |
| // | |
| // Use existing TCP instance to transmit the packet. | |
| // | |
| Configure = FALSE; | |
| ReConfigure = FALSE; | |
| } | |
| } else { | |
| // | |
| // Need close existing TCP instance and create a new TCP instance for data transmit. | |
| // | |
| if (HttpInstance->RemoteHost != NULL) { | |
| FreePool (HttpInstance->RemoteHost); | |
| HttpInstance->RemoteHost = NULL; | |
| HttpInstance->RemotePort = 0; | |
| } | |
| } | |
| } | |
| if (Configure) { | |
| // | |
| // Parse Url for IPv4 or IPv6 address, if failed, perform DNS resolution. | |
| // | |
| if (!HttpInstance->LocalAddressIsIPv6) { | |
| Status = NetLibAsciiStrToIp4 (HostName, &HttpInstance->RemoteAddr); | |
| } else { | |
| Status = HttpUrlGetIp6 (Url, UrlParser, &HttpInstance->RemoteIpv6Addr); | |
| } | |
| if (EFI_ERROR (Status)) { | |
| HostNameStr = AllocateZeroPool ((AsciiStrLen (HostName) + 1) * sizeof (CHAR16)); | |
| if (HostNameStr == NULL) { | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto Error1; | |
| } | |
| AsciiStrToUnicodeStr (HostName, HostNameStr); | |
| if (!HttpInstance->LocalAddressIsIPv6) { | |
| Status = HttpDns4 (HttpInstance, HostNameStr, &HttpInstance->RemoteAddr); | |
| } else { | |
| Status = HttpDns6 (HttpInstance, HostNameStr, &HttpInstance->RemoteIpv6Addr); | |
| } | |
| FreePool (HostNameStr); | |
| if (EFI_ERROR (Status)) { | |
| goto Error1; | |
| } | |
| } | |
| // | |
| // Save the RemotePort and RemoteHost. | |
| // | |
| ASSERT (HttpInstance->RemoteHost == NULL); | |
| HttpInstance->RemotePort = RemotePort; | |
| HttpInstance->RemoteHost = HostName; | |
| HostName = NULL; | |
| } | |
| if (ReConfigure) { | |
| // | |
| // The request URL is different from previous calls to Request(), close existing TCP instance. | |
| // | |
| if (!HttpInstance->LocalAddressIsIPv6) { | |
| ASSERT (HttpInstance->Tcp4 != NULL); | |
| } else { | |
| ASSERT (HttpInstance->Tcp6 != NULL); | |
| } | |
| HttpCloseConnection (HttpInstance); | |
| EfiHttpCancel (This, NULL); | |
| } | |
| // | |
| // Wrap the HTTP token in HTTP_TOKEN_WRAP | |
| // | |
| Wrap = AllocateZeroPool (sizeof (HTTP_TOKEN_WRAP)); | |
| if (Wrap == NULL) { | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto Error1; | |
| } | |
| Wrap->HttpToken = Token; | |
| Wrap->HttpInstance = HttpInstance; | |
| Wrap->TcpWrap.Method = Request->Method; | |
| Status = HttpInitTcp (HttpInstance, Wrap, Configure); | |
| if (EFI_ERROR (Status)) { | |
| goto Error2; | |
| } | |
| if (!Configure) { | |
| // | |
| // For the new HTTP token, create TX TCP token events. | |
| // | |
| Status = HttpCreateTcpTxEvent (Wrap); | |
| if (EFI_ERROR (Status)) { | |
| goto Error1; | |
| } | |
| } | |
| // | |
| // Create request message. | |
| // | |
| FileUrl = Url; | |
| if (*FileUrl != '/') { | |
| // | |
| // Convert the absolute-URI to the absolute-path | |
| // | |
| while (*FileUrl != ':') { | |
| FileUrl++; | |
| } | |
| if ((*(FileUrl+1) == '/') && (*(FileUrl+2) == '/')) { | |
| FileUrl += 3; | |
| while (*FileUrl != '/') { | |
| FileUrl++; | |
| } | |
| } else { | |
| Status = EFI_INVALID_PARAMETER; | |
| goto Error3; | |
| } | |
| } | |
| RequestStr = HttpGenRequestString (HttpInstance, HttpMsg, FileUrl); | |
| if (RequestStr == NULL) { | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto Error3; | |
| } | |
| Status = NetMapInsertTail (&HttpInstance->TxTokens, Token, Wrap); | |
| if (EFI_ERROR (Status)) { | |
| goto Error4; | |
| } | |
| // | |
| // Transmit the request message. | |
| // | |
| Status = HttpTransmitTcp ( | |
| HttpInstance, | |
| Wrap, | |
| (UINT8*) RequestStr, | |
| AsciiStrLen (RequestStr) | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| goto Error5; | |
| } | |
| DispatchDpc (); | |
| if (HostName != NULL) { | |
| FreePool (HostName); | |
| } | |
| return EFI_SUCCESS; | |
| Error5: | |
| NetMapRemoveTail (&HttpInstance->TxTokens, NULL); | |
| Error4: | |
| if (RequestStr != NULL) { | |
| FreePool (RequestStr); | |
| } | |
| Error3: | |
| HttpCloseConnection (HttpInstance); | |
| Error2: | |
| HttpCloseTcpConnCloseEvent (HttpInstance); | |
| if (NULL != Wrap->TcpWrap.Tx4Token.CompletionToken.Event) { | |
| gBS->CloseEvent (Wrap->TcpWrap.Tx4Token.CompletionToken.Event); | |
| Wrap->TcpWrap.Tx4Token.CompletionToken.Event = NULL; | |
| } | |
| if (NULL != Wrap->TcpWrap.Tx6Token.CompletionToken.Event) { | |
| gBS->CloseEvent (Wrap->TcpWrap.Tx6Token.CompletionToken.Event); | |
| Wrap->TcpWrap.Tx6Token.CompletionToken.Event = NULL; | |
| } | |
| Error1: | |
| if (HostName != NULL) { | |
| FreePool (HostName); | |
| } | |
| if (Wrap != NULL) { | |
| FreePool (Wrap); | |
| } | |
| if (UrlParser!= NULL) { | |
| HttpUrlFreeParser (UrlParser); | |
| } | |
| return Status; | |
| } | |
| /** | |
| Cancel a user's Token. | |
| @param[in] Map The HTTP instance's token queue. | |
| @param[in] Item Object container for one HTTP token and token's wrap. | |
| @param[in] Context The user's token to cancel. | |
| @retval EFI_SUCCESS Continue to check the next Item. | |
| @retval EFI_ABORTED The user's Token (Token != NULL) is cancelled. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| HttpCancelTokens ( | |
| IN NET_MAP *Map, | |
| IN NET_MAP_ITEM *Item, | |
| IN VOID *Context | |
| ) | |
| { | |
| EFI_HTTP_TOKEN *Token; | |
| HTTP_TOKEN_WRAP *Wrap; | |
| HTTP_PROTOCOL *HttpInstance; | |
| Token = (EFI_HTTP_TOKEN *) Context; | |
| // | |
| // Return EFI_SUCCESS to check the next item in the map if | |
| // this one doesn't match. | |
| // | |
| if ((Token != NULL) && (Token != Item->Key)) { | |
| return EFI_SUCCESS; | |
| } | |
| Wrap = (HTTP_TOKEN_WRAP *) Item->Value; | |
| ASSERT (Wrap != NULL); | |
| HttpInstance = Wrap->HttpInstance; | |
| // | |
| // Free resources. | |
| // | |
| NetMapRemoveItem (Map, Item, NULL); | |
| if (!HttpInstance->LocalAddressIsIPv6) { | |
| if (Wrap->TcpWrap.Tx4Token.CompletionToken.Event != NULL) { | |
| gBS->CloseEvent (Wrap->TcpWrap.Tx4Token.CompletionToken.Event); | |
| } | |
| if (Wrap->TcpWrap.Rx4Token.CompletionToken.Event != NULL) { | |
| gBS->CloseEvent (Wrap->TcpWrap.Rx4Token.CompletionToken.Event); | |
| } | |
| if (Wrap->TcpWrap.Rx4Token.Packet.RxData->FragmentTable[0].FragmentBuffer != NULL) { | |
| FreePool (Wrap->TcpWrap.Rx4Token.Packet.RxData->FragmentTable[0].FragmentBuffer); | |
| } | |
| } else { | |
| if (Wrap->TcpWrap.Tx6Token.CompletionToken.Event != NULL) { | |
| gBS->CloseEvent (Wrap->TcpWrap.Tx6Token.CompletionToken.Event); | |
| } | |
| if (Wrap->TcpWrap.Rx6Token.CompletionToken.Event != NULL) { | |
| gBS->CloseEvent (Wrap->TcpWrap.Rx6Token.CompletionToken.Event); | |
| } | |
| if (Wrap->TcpWrap.Rx6Token.Packet.RxData->FragmentTable[0].FragmentBuffer != NULL) { | |
| FreePool (Wrap->TcpWrap.Rx6Token.Packet.RxData->FragmentTable[0].FragmentBuffer); | |
| } | |
| } | |
| FreePool (Wrap); | |
| // | |
| // If only one item is to be cancel, return EFI_ABORTED to stop | |
| // iterating the map any more. | |
| // | |
| if (Token != NULL) { | |
| return EFI_ABORTED; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Cancel the user's receive/transmit request. It is the worker function of | |
| EfiHttpCancel API. If a matching token is found, it will call HttpCancelTokens to cancel the | |
| token. | |
| @param[in] HttpInstance Pointer to HTTP_PROTOCOL structure. | |
| @param[in] Token The token to cancel. If NULL, all token will be | |
| cancelled. | |
| @retval EFI_SUCCESS The token is cancelled. | |
| @retval EFI_NOT_FOUND The asynchronous request or response token is not found. | |
| @retval Others Other error as indicated. | |
| **/ | |
| EFI_STATUS | |
| HttpCancel ( | |
| IN HTTP_PROTOCOL *HttpInstance, | |
| IN EFI_HTTP_TOKEN *Token | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| // | |
| // First check the tokens queued by EfiHttpRequest(). | |
| // | |
| Status = NetMapIterate (&HttpInstance->TxTokens, HttpCancelTokens, Token); | |
| if (EFI_ERROR (Status)) { | |
| if (Token != NULL) { | |
| if (Status == EFI_ABORTED) { | |
| return EFI_SUCCESS; | |
| } | |
| } else { | |
| return Status; | |
| } | |
| } | |
| // | |
| // Then check the tokens queued by EfiHttpResponse(). | |
| // | |
| Status = NetMapIterate (&HttpInstance->RxTokens, HttpCancelTokens, Token); | |
| if (EFI_ERROR (Status)) { | |
| if (Token != NULL) { | |
| if (Status == EFI_ABORTED) { | |
| return EFI_SUCCESS; | |
| } else { | |
| return EFI_NOT_FOUND; | |
| } | |
| } else { | |
| return Status; | |
| } | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Abort an asynchronous HTTP request or response token. | |
| The Cancel() function aborts a pending HTTP request or response transaction. If | |
| Token is not NULL and the token is in transmit or receive queues when it is being | |
| cancelled, its Token->Status will be set to EFI_ABORTED and then Token->Event will | |
| be signaled. If the token is not in one of the queues, which usually means that the | |
| asynchronous operation has completed, EFI_NOT_FOUND is returned. If Token is NULL, | |
| all asynchronous tokens issued by Request() or Response() will be aborted. | |
| @param[in] This Pointer to EFI_HTTP_PROTOCOL instance. | |
| @param[in] Token Point to storage containing HTTP request or response | |
| token. | |
| @retval EFI_SUCCESS Request and Response queues are successfully flushed. | |
| @retval EFI_INVALID_PARAMETER This is NULL. | |
| @retval EFI_NOT_STARTED This instance hasn't been configured. | |
| @retval EFI_NOT_FOUND The asynchronous request or response token is not | |
| found. | |
| @retval EFI_UNSUPPORTED The implementation does not support this function. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EfiHttpCancel ( | |
| IN EFI_HTTP_PROTOCOL *This, | |
| IN EFI_HTTP_TOKEN *Token | |
| ) | |
| { | |
| HTTP_PROTOCOL *HttpInstance; | |
| if (This == NULL) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| HttpInstance = HTTP_INSTANCE_FROM_PROTOCOL (This); | |
| ASSERT (HttpInstance != NULL); | |
| if (HttpInstance->State != HTTP_STATE_TCP_CONNECTED) { | |
| return EFI_NOT_STARTED; | |
| } | |
| return HttpCancel (HttpInstance, Token); | |
| } | |
| /** | |
| A callback function to intercept events during message parser. | |
| This function will be invoked during HttpParseMessageBody() with various events type. An error | |
| return status of the callback function will cause the HttpParseMessageBody() aborted. | |
| @param[in] EventType Event type of this callback call. | |
| @param[in] Data A pointer to data buffer. | |
| @param[in] Length Length in bytes of the Data. | |
| @param[in] Context Callback context set by HttpInitMsgParser(). | |
| @retval EFI_SUCCESS Continue to parser the message body. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| HttpBodyParserCallback ( | |
| IN HTTP_BODY_PARSE_EVENT EventType, | |
| IN CHAR8 *Data, | |
| IN UINTN Length, | |
| IN VOID *Context | |
| ) | |
| { | |
| HTTP_TOKEN_WRAP *Wrap; | |
| UINTN BodyLength; | |
| CHAR8 *Body; | |
| if (EventType != BodyParseEventOnComplete) { | |
| return EFI_SUCCESS; | |
| } | |
| if (Data == NULL || Length != 0 || Context == NULL) { | |
| return EFI_SUCCESS; | |
| } | |
| Wrap = (HTTP_TOKEN_WRAP *) Context; | |
| Body = Wrap->HttpToken->Message->Body; | |
| BodyLength = Wrap->HttpToken->Message->BodyLength; | |
| if (Data < Body + BodyLength) { | |
| Wrap->HttpInstance->NextMsg = Data; | |
| } else { | |
| Wrap->HttpInstance->NextMsg = NULL; | |
| } | |
| // | |
| // Free Tx4Token or Tx6Token since already received corrsponding HTTP response. | |
| // | |
| FreePool (Wrap); | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| The work function of EfiHttpResponse(). | |
| @param[in] Wrap Pointer to HTTP token's wrap data. | |
| @retval EFI_SUCCESS Allocation succeeded. | |
| @retval EFI_OUT_OF_RESOURCES Failed to complete the opration due to lack of resources. | |
| @retval EFI_NOT_READY Can't find a corresponding Tx4Token/Tx6Token or | |
| the EFI_HTTP_UTILITIES_PROTOCOL is not available. | |
| **/ | |
| EFI_STATUS | |
| HttpResponseWorker ( | |
| IN HTTP_TOKEN_WRAP *Wrap | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_HTTP_MESSAGE *HttpMsg; | |
| CHAR8 *EndofHeader; | |
| CHAR8 *HttpHeaders; | |
| UINTN SizeofHeaders; | |
| UINTN BufferSize; | |
| UINTN StatusCode; | |
| CHAR8 *Tmp; | |
| CHAR8 *HeaderTmp; | |
| CHAR8 *StatusCodeStr; | |
| UINTN BodyLen; | |
| HTTP_PROTOCOL *HttpInstance; | |
| EFI_HTTP_TOKEN *Token; | |
| NET_MAP_ITEM *Item; | |
| HTTP_TOKEN_WRAP *ValueInItem; | |
| UINTN HdrLen; | |
| if (Wrap == NULL || Wrap->HttpInstance == NULL) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| HttpInstance = Wrap->HttpInstance; | |
| Token = Wrap->HttpToken; | |
| HttpMsg = Token->Message; | |
| HttpInstance->EndofHeader = NULL; | |
| HttpInstance->HttpHeaders = NULL; | |
| HttpMsg->Headers = NULL; | |
| HttpHeaders = NULL; | |
| SizeofHeaders = 0; | |
| BufferSize = 0; | |
| EndofHeader = NULL; | |
| if (HttpMsg->Data.Response != NULL) { | |
| // | |
| // Need receive the HTTP headers, prepare buffer. | |
| // | |
| Status = HttpCreateTcpRxEventForHeader (HttpInstance); | |
| if (EFI_ERROR (Status)) { | |
| goto Error; | |
| } | |
| // | |
| // Check whether we have cached header from previous call. | |
| // | |
| if ((HttpInstance->CacheBody != NULL) && (HttpInstance->NextMsg != NULL)) { | |
| // | |
| // The data is stored at [NextMsg, CacheBody + CacheLen]. | |
| // | |
| HdrLen = HttpInstance->CacheBody + HttpInstance->CacheLen - HttpInstance->NextMsg; | |
| HttpHeaders = AllocateZeroPool (HdrLen); | |
| if (HttpHeaders == NULL) { | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto Error; | |
| } | |
| CopyMem (HttpHeaders, HttpInstance->NextMsg, HdrLen); | |
| FreePool (HttpInstance->CacheBody); | |
| HttpInstance->CacheBody = NULL; | |
| HttpInstance->NextMsg = NULL; | |
| HttpInstance->CacheOffset = 0; | |
| SizeofHeaders = HdrLen; | |
| BufferSize = HttpInstance->CacheLen; | |
| // | |
| // Check whether we cached the whole HTTP headers. | |
| // | |
| EndofHeader = AsciiStrStr (HttpHeaders, HTTP_END_OF_HDR_STR); | |
| } | |
| HttpInstance->EndofHeader = &EndofHeader; | |
| HttpInstance->HttpHeaders = &HttpHeaders; | |
| Status = HttpTcpReceiveHeader (HttpInstance, &SizeofHeaders, &BufferSize); | |
| if (EFI_ERROR (Status)) { | |
| goto Error; | |
| } | |
| ASSERT (HttpHeaders != NULL); | |
| // | |
| // Cache the part of body. | |
| // | |
| BodyLen = BufferSize - (EndofHeader - HttpHeaders); | |
| if (BodyLen > 0) { | |
| if (HttpInstance->CacheBody != NULL) { | |
| FreePool (HttpInstance->CacheBody); | |
| } | |
| HttpInstance->CacheBody = AllocateZeroPool (BodyLen); | |
| if (HttpInstance->CacheBody == NULL) { | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto Error; | |
| } | |
| CopyMem (HttpInstance->CacheBody, EndofHeader, BodyLen); | |
| HttpInstance->CacheLen = BodyLen; | |
| } | |
| // | |
| // Search for Status Code. | |
| // | |
| StatusCodeStr = HttpHeaders + AsciiStrLen (HTTP_VERSION_STR) + 1; | |
| if (StatusCodeStr == NULL) { | |
| goto Error; | |
| } | |
| StatusCode = AsciiStrDecimalToUintn (StatusCodeStr); | |
| // | |
| // Remove the first line of HTTP message, e.g. "HTTP/1.1 200 OK\r\n". | |
| // | |
| Tmp = AsciiStrStr (HttpHeaders, HTTP_CRLF_STR); | |
| if (Tmp == NULL) { | |
| goto Error; | |
| } | |
| Tmp = Tmp + AsciiStrLen (HTTP_CRLF_STR); | |
| SizeofHeaders = SizeofHeaders - (Tmp - HttpHeaders); | |
| HeaderTmp = AllocateZeroPool (SizeofHeaders); | |
| if (HeaderTmp == NULL) { | |
| goto Error; | |
| } | |
| CopyMem (HeaderTmp, Tmp, SizeofHeaders); | |
| FreePool (HttpHeaders); | |
| HttpHeaders = HeaderTmp; | |
| // | |
| // Check whether the EFI_HTTP_UTILITIES_PROTOCOL is available. | |
| // | |
| if (mHttpUtilities == NULL) { | |
| Status = EFI_NOT_READY; | |
| goto Error; | |
| } | |
| // | |
| // Parse the HTTP header into array of key/value pairs. | |
| // | |
| Status = mHttpUtilities->Parse ( | |
| mHttpUtilities, | |
| HttpHeaders, | |
| SizeofHeaders, | |
| &HttpMsg->Headers, | |
| &HttpMsg->HeaderCount | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| goto Error; | |
| } | |
| FreePool (HttpHeaders); | |
| HttpHeaders = NULL; | |
| HttpMsg->Data.Response->StatusCode = HttpMappingToStatusCode (StatusCode); | |
| HttpInstance->StatusCode = StatusCode; | |
| // | |
| // Init message-body parser by header information. | |
| // | |
| Status = EFI_NOT_READY; | |
| ValueInItem = NULL; | |
| NetMapRemoveHead (&HttpInstance->TxTokens, (VOID**) &ValueInItem); | |
| if (ValueInItem == NULL) { | |
| goto Error; | |
| } | |
| // | |
| // The first Tx Token not transmitted yet, insert back and return error. | |
| // | |
| if (!ValueInItem->TcpWrap.IsTxDone) { | |
| goto Error2; | |
| } | |
| Status = HttpInitMsgParser ( | |
| ValueInItem->TcpWrap.Method, | |
| HttpMsg->Data.Response->StatusCode, | |
| HttpMsg->HeaderCount, | |
| HttpMsg->Headers, | |
| HttpBodyParserCallback, | |
| (VOID *) ValueInItem, | |
| &HttpInstance->MsgParser | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| goto Error2; | |
| } | |
| // | |
| // Check whether we received a complete HTTP message. | |
| // | |
| if (HttpInstance->CacheBody != NULL) { | |
| Status = HttpParseMessageBody (HttpInstance->MsgParser, HttpInstance->CacheLen, HttpInstance->CacheBody); | |
| if (EFI_ERROR (Status)) { | |
| goto Error2; | |
| } | |
| if (HttpIsMessageComplete (HttpInstance->MsgParser)) { | |
| // | |
| // Free the MsgParse since we already have a full HTTP message. | |
| // | |
| HttpFreeMsgParser (HttpInstance->MsgParser); | |
| HttpInstance->MsgParser = NULL; | |
| } | |
| } | |
| if ((HttpMsg->Body == NULL) || (HttpMsg->BodyLength == 0)) { | |
| Status = EFI_SUCCESS; | |
| goto Exit; | |
| } | |
| } | |
| // | |
| // Receive the response body. | |
| // | |
| BodyLen = 0; | |
| // | |
| // First check whether we cached some data. | |
| // | |
| if (HttpInstance->CacheBody != NULL) { | |
| // | |
| // Calculate the length of the cached data. | |
| // | |
| if (HttpInstance->NextMsg != NULL) { | |
| // | |
| // We have a cached HTTP message which includes a part of HTTP header of next message. | |
| // | |
| BodyLen = HttpInstance->NextMsg - (HttpInstance->CacheBody + HttpInstance->CacheOffset); | |
| } else { | |
| BodyLen = HttpInstance->CacheLen - HttpInstance->CacheOffset; | |
| } | |
| if (BodyLen > 0) { | |
| // | |
| // We have some cached data. Just copy the data and return. | |
| // | |
| if (HttpMsg->BodyLength < BodyLen) { | |
| CopyMem (HttpMsg->Body, HttpInstance->CacheBody + HttpInstance->CacheOffset, HttpMsg->BodyLength); | |
| HttpInstance->CacheOffset = HttpInstance->CacheOffset + HttpMsg->BodyLength; | |
| } else { | |
| // | |
| // Copy all cached data out. | |
| // | |
| CopyMem (HttpMsg->Body, HttpInstance->CacheBody + HttpInstance->CacheOffset, BodyLen); | |
| HttpInstance->CacheOffset = BodyLen + HttpInstance->CacheOffset; | |
| HttpMsg->BodyLength = BodyLen; | |
| if (HttpInstance->NextMsg == NULL) { | |
| // | |
| // There is no HTTP header of next message. Just free the cache buffer. | |
| // | |
| FreePool (HttpInstance->CacheBody); | |
| HttpInstance->CacheBody = NULL; | |
| HttpInstance->NextMsg = NULL; | |
| HttpInstance->CacheOffset = 0; | |
| } | |
| } | |
| // | |
| // Return since we aready received required data. | |
| // | |
| Status = EFI_SUCCESS; | |
| goto Exit; | |
| } | |
| if (BodyLen == 0 && HttpInstance->MsgParser == NULL) { | |
| // | |
| // We received a complete HTTP message, and we don't have more data to return to caller. | |
| // | |
| HttpMsg->BodyLength = 0; | |
| Status = EFI_SUCCESS; | |
| goto Exit; | |
| } | |
| } | |
| ASSERT (HttpInstance->MsgParser != NULL); | |
| // | |
| // We still need receive more data when there is no cache data and MsgParser is not NULL; | |
| // | |
| Status = HttpTcpReceiveBody (Wrap, HttpMsg); | |
| if (EFI_ERROR (Status)) { | |
| goto Error; | |
| } | |
| return Status; | |
| Exit: | |
| Item = NetMapFindKey (&Wrap->HttpInstance->RxTokens, Wrap->HttpToken); | |
| if (Item != NULL) { | |
| NetMapRemoveItem (&Wrap->HttpInstance->RxTokens, Item, NULL); | |
| } | |
| if (HttpInstance->StatusCode >= HTTP_ERROR_OR_NOT_SUPPORT_STATUS_CODE) { | |
| Token->Status = EFI_HTTP_ERROR; | |
| } else { | |
| Token->Status = Status; | |
| } | |
| gBS->SignalEvent (Token->Event); | |
| HttpCloseTcpRxEvent (Wrap); | |
| FreePool (Wrap); | |
| return Status; | |
| Error2: | |
| NetMapInsertHead (&HttpInstance->TxTokens, ValueInItem->HttpToken, ValueInItem); | |
| Error: | |
| HttpTcpTokenCleanup (Wrap); | |
| if (HttpHeaders != NULL) { | |
| FreePool (HttpHeaders); | |
| } | |
| if (HttpMsg->Headers != NULL) { | |
| FreePool (HttpMsg->Headers); | |
| } | |
| if (HttpInstance->CacheBody != NULL) { | |
| FreePool (HttpInstance->CacheBody); | |
| HttpInstance->CacheBody = NULL; | |
| } | |
| if (HttpInstance->StatusCode >= HTTP_ERROR_OR_NOT_SUPPORT_STATUS_CODE) { | |
| Token->Status = EFI_HTTP_ERROR; | |
| } else { | |
| Token->Status = Status; | |
| } | |
| gBS->SignalEvent (Token->Event); | |
| return Status; | |
| } | |
| /** | |
| The Response() function queues an HTTP response to this HTTP instance, similar to | |
| Receive() function in the EFI TCP driver. When the HTTP response is received successfully, | |
| or if there is an error, Status in token will be updated and Event will be signaled. | |
| The HTTP driver will queue a receive token to the underlying TCP instance. When data | |
| is received in the underlying TCP instance, the data will be parsed and Token will | |
| be populated with the response data. If the data received from the remote host | |
| contains an incomplete or invalid HTTP header, the HTTP driver will continue waiting | |
| (asynchronously) for more data to be sent from the remote host before signaling | |
| Event in Token. | |
| It is the responsibility of the caller to allocate a buffer for Body and specify the | |
| size in BodyLength. If the remote host provides a response that contains a content | |
| body, up to BodyLength bytes will be copied from the receive buffer into Body and | |
| BodyLength will be updated with the amount of bytes received and copied to Body. This | |
| allows the client to download a large file in chunks instead of into one contiguous | |
| block of memory. Similar to HTTP request, if Body is not NULL and BodyLength is | |
| non-zero and all other fields are NULL or 0, the HTTP driver will queue a receive | |
| token to underlying TCP instance. If data arrives in the receive buffer, up to | |
| BodyLength bytes of data will be copied to Body. The HTTP driver will then update | |
| BodyLength with the amount of bytes received and copied to Body. | |
| If the HTTP driver does not have an open underlying TCP connection with the host | |
| specified in the response URL, Request() will return EFI_ACCESS_DENIED. This is | |
| consistent with RFC 2616 recommendation that HTTP clients should attempt to maintain | |
| an open TCP connection between client and host. | |
| @param[in] This Pointer to EFI_HTTP_PROTOCOL instance. | |
| @param[in] Token Pointer to storage containing HTTP response token. | |
| @retval EFI_SUCCESS Allocation succeeded. | |
| @retval EFI_NOT_STARTED This EFI HTTP Protocol instance has not been | |
| initialized. | |
| @retval EFI_INVALID_PARAMETER One or more of the following conditions is TRUE: | |
| This is NULL. | |
| Token is NULL. | |
| Token->Message->Headers is NULL. | |
| Token->Message is NULL. | |
| Token->Message->Body is not NULL, | |
| Token->Message->BodyLength is non-zero, and | |
| Token->Message->Data is NULL, but a previous call to | |
| Response() has not been completed successfully. | |
| @retval EFI_OUT_OF_RESOURCES Could not allocate enough system resources. | |
| @retval EFI_ACCESS_DENIED An open TCP connection is not present with the host | |
| specified by response URL. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EfiHttpResponse ( | |
| IN EFI_HTTP_PROTOCOL *This, | |
| IN EFI_HTTP_TOKEN *Token | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_HTTP_MESSAGE *HttpMsg; | |
| HTTP_PROTOCOL *HttpInstance; | |
| HTTP_TOKEN_WRAP *Wrap; | |
| if ((This == NULL) || (Token == NULL)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| HttpMsg = Token->Message; | |
| if (HttpMsg == NULL) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| HttpInstance = HTTP_INSTANCE_FROM_PROTOCOL (This); | |
| ASSERT (HttpInstance != NULL); | |
| if (HttpInstance->State != HTTP_STATE_TCP_CONNECTED) { | |
| return EFI_NOT_STARTED; | |
| } | |
| // | |
| // Check whether the token already existed. | |
| // | |
| if (EFI_ERROR (NetMapIterate (&HttpInstance->RxTokens, HttpTokenExist, Token))) { | |
| return EFI_ACCESS_DENIED; | |
| } | |
| Wrap = AllocateZeroPool (sizeof (HTTP_TOKEN_WRAP)); | |
| if (Wrap == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| Wrap->HttpInstance = HttpInstance; | |
| Wrap->HttpToken = Token; | |
| Status = HttpCreateTcpRxEvent (Wrap); | |
| if (EFI_ERROR (Status)) { | |
| goto Error; | |
| } | |
| Status = NetMapInsertTail (&HttpInstance->RxTokens, Token, Wrap); | |
| if (EFI_ERROR (Status)) { | |
| goto Error; | |
| } | |
| // | |
| // If already have pending RxTokens, return directly. | |
| // | |
| if (NetMapGetCount (&HttpInstance->RxTokens) > 1) { | |
| return EFI_SUCCESS; | |
| } | |
| return HttpResponseWorker (Wrap); | |
| Error: | |
| if (Wrap != NULL) { | |
| if (Wrap->TcpWrap.Rx4Token.CompletionToken.Event != NULL) { | |
| gBS->CloseEvent (Wrap->TcpWrap.Rx4Token.CompletionToken.Event); | |
| } | |
| if (Wrap->TcpWrap.Rx6Token.CompletionToken.Event != NULL) { | |
| gBS->CloseEvent (Wrap->TcpWrap.Rx6Token.CompletionToken.Event); | |
| } | |
| FreePool (Wrap); | |
| } | |
| return Status; | |
| } | |
| /** | |
| The Poll() function can be used by network drivers and applications to increase the | |
| rate that data packets are moved between the communication devices and the transmit | |
| and receive queues. | |
| In some systems, the periodic timer event in the managed network driver may not poll | |
| the underlying communications device fast enough to transmit and/or receive all data | |
| packets without missing incoming packets or dropping outgoing packets. Drivers and | |
| applications that are experiencing packet loss should try calling the Poll() function | |
| more often. | |
| @param[in] This Pointer to EFI_HTTP_PROTOCOL instance. | |
| @retval EFI_SUCCESS Incoming or outgoing data was processed. | |
| @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. | |
| @retval EFI_INVALID_PARAMETER This is NULL. | |
| @retval EFI_NOT_READY No incoming or outgoing data is processed. | |
| @retval EFI_NOT_STARTED This EFI HTTP Protocol instance has not been started. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EfiHttpPoll ( | |
| IN EFI_HTTP_PROTOCOL *This | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| HTTP_PROTOCOL *HttpInstance; | |
| if (This == NULL) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| HttpInstance = HTTP_INSTANCE_FROM_PROTOCOL (This); | |
| ASSERT (HttpInstance != NULL); | |
| if (HttpInstance->State != HTTP_STATE_TCP_CONNECTED) { | |
| return EFI_NOT_STARTED; | |
| } | |
| if (HttpInstance->LocalAddressIsIPv6) { | |
| if (HttpInstance->Tcp6 == NULL) { | |
| return EFI_NOT_STARTED; | |
| } | |
| Status = HttpInstance->Tcp6->Poll (HttpInstance->Tcp6); | |
| } else { | |
| if (HttpInstance->Tcp4 == NULL) { | |
| return EFI_NOT_STARTED; | |
| } | |
| Status = HttpInstance->Tcp4->Poll (HttpInstance->Tcp4); | |
| } | |
| DispatchDpc (); | |
| return Status; | |
| } |