mirror of https://github.com/acidanthera/audk.git
1657 lines
51 KiB
C
1657 lines
51 KiB
C
/** @file
|
|
Implementation of EFI_HTTP_PROTOCOL protocol interfaces.
|
|
|
|
Copyright (c) 2015 - 2017, Intel Corporation. All rights reserved.<BR>
|
|
(C) Copyright 2015-2016 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->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 OPTIONAL
|
|
)
|
|
{
|
|
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) {
|
|
|
|
if (HttpConfigData->HttpVersion >= HttpVersionUnsupported) {
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
UINTN HostNameSize;
|
|
UINT16 RemotePort;
|
|
HTTP_PROTOCOL *HttpInstance;
|
|
BOOLEAN Configure;
|
|
BOOLEAN ReConfigure;
|
|
BOOLEAN TlsConfigure;
|
|
CHAR8 *RequestMsg;
|
|
CHAR8 *Url;
|
|
UINTN UrlLen;
|
|
CHAR16 *HostNameStr;
|
|
HTTP_TOKEN_WRAP *Wrap;
|
|
CHAR8 *FileUrl;
|
|
UINTN RequestMsgSize;
|
|
EFI_HANDLE ImageHandle;
|
|
|
|
//
|
|
// Initializations
|
|
//
|
|
Url = NULL;
|
|
UrlParser = NULL;
|
|
RemotePort = 0;
|
|
HostName = NULL;
|
|
RequestMsg = NULL;
|
|
HostNameStr = NULL;
|
|
Wrap = NULL;
|
|
FileUrl = NULL;
|
|
TlsConfigure = FALSE;
|
|
|
|
if ((This == NULL) || (Token == NULL)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
HttpMsg = Token->Message;
|
|
if (HttpMsg == NULL) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
Request = HttpMsg->Data.Request;
|
|
|
|
//
|
|
// Only support GET, HEAD, PATCH, PUT and POST method in current implementation.
|
|
//
|
|
if ((Request != NULL) && (Request->Method != HttpMethodGet) &&
|
|
(Request->Method != HttpMethodHead) && (Request->Method != HttpMethodPut) &&
|
|
(Request->Method != HttpMethodPost) && (Request->Method != HttpMethodPatch)) {
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
HttpInstance = HTTP_INSTANCE_FROM_PROTOCOL (This);
|
|
ASSERT (HttpInstance != NULL);
|
|
|
|
//
|
|
// Capture the method into HttpInstance.
|
|
//
|
|
if (Request != NULL) {
|
|
HttpInstance->Method = Request->Method;
|
|
}
|
|
|
|
if (HttpInstance->State < HTTP_STATE_HTTP_CONFIGED) {
|
|
return EFI_NOT_STARTED;
|
|
}
|
|
|
|
if (Request == NULL) {
|
|
//
|
|
// Request would be NULL only for PUT/POST/PATCH operation (in the current implementation)
|
|
//
|
|
if ((HttpInstance->Method != HttpMethodPut) &&
|
|
(HttpInstance->Method != HttpMethodPost) &&
|
|
(HttpInstance->Method != HttpMethodPatch)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// For PUT/POST/PATCH, we need to have the TCP already configured. Bail out if it is not!
|
|
//
|
|
if (HttpInstance->State < HTTP_STATE_TCP_CONFIGED) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// We need to have the Message Body for sending the HTTP message across in these cases.
|
|
//
|
|
if (HttpMsg->Body == NULL || HttpMsg->BodyLength == 0) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Use existing TCP instance to transmit the packet.
|
|
//
|
|
Configure = FALSE;
|
|
ReConfigure = FALSE;
|
|
} else {
|
|
//
|
|
// Check whether the token already existed.
|
|
//
|
|
if (EFI_ERROR (NetMapIterate (&HttpInstance->TxTokens, HttpTokenExist, Token))) {
|
|
return EFI_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
|
|
UnicodeStrToAsciiStrS (Request->Url, Url, UrlLen);
|
|
|
|
//
|
|
// From the information in Url, the HTTP instance will
|
|
// be able to determine whether to use http or https.
|
|
//
|
|
HttpInstance->UseHttps = IsHttpsUrl (Url);
|
|
|
|
//
|
|
// HTTP is disabled, return directly if the URI is not HTTPS.
|
|
//
|
|
if (!PcdGetBool (PcdAllowHttpConnections) && !(HttpInstance->UseHttps)) {
|
|
|
|
DEBUG ((EFI_D_ERROR, "EfiHttpRequest: HTTP is disabled.\n"));
|
|
|
|
return EFI_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// Check whether we need to create Tls child and open the TLS protocol.
|
|
//
|
|
if (HttpInstance->UseHttps && HttpInstance->TlsChildHandle == NULL) {
|
|
//
|
|
// Use TlsSb to create Tls child and open the TLS protocol.
|
|
//
|
|
if (HttpInstance->LocalAddressIsIPv6) {
|
|
ImageHandle = HttpInstance->Service->Ip6DriverBindingHandle;
|
|
} else {
|
|
ImageHandle = HttpInstance->Service->Ip4DriverBindingHandle;
|
|
}
|
|
|
|
HttpInstance->TlsChildHandle = TlsCreateChild (
|
|
ImageHandle,
|
|
&(HttpInstance->TlsSb),
|
|
&(HttpInstance->Tls),
|
|
&(HttpInstance->TlsConfiguration)
|
|
);
|
|
if (HttpInstance->TlsChildHandle == NULL) {
|
|
return EFI_DEVICE_ERROR;
|
|
}
|
|
|
|
TlsConfigure = TRUE;
|
|
}
|
|
|
|
UrlParser = NULL;
|
|
Status = HttpParseUrl (Url, (UINT32) AsciiStrLen (Url), FALSE, &UrlParser);
|
|
if (EFI_ERROR (Status)) {
|
|
goto Error1;
|
|
}
|
|
|
|
HostName = NULL;
|
|
Status = HttpUrlGetHostName (Url, UrlParser, &HostName);
|
|
if (EFI_ERROR (Status)) {
|
|
goto Error1;
|
|
}
|
|
|
|
Status = HttpUrlGetPort (Url, UrlParser, &RemotePort);
|
|
if (EFI_ERROR (Status)) {
|
|
if (HttpInstance->UseHttps) {
|
|
RemotePort = HTTPS_DEFAULT_PORT;
|
|
} else {
|
|
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) &&
|
|
(!HttpInstance->UseHttps || (HttpInstance->UseHttps &&
|
|
!TlsConfigure &&
|
|
HttpInstance->TlsSessionState == EfiTlsSessionDataTransferring))) {
|
|
//
|
|
// Host Name and port number of the request URL are the same with previous call to Request().
|
|
// If Https protocol used, the corresponding SessionState is EfiTlsSessionDataTransferring.
|
|
// 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)) {
|
|
HostNameSize = AsciiStrSize (HostName);
|
|
HostNameStr = AllocateZeroPool (HostNameSize * sizeof (CHAR16));
|
|
if (HostNameStr == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
goto Error1;
|
|
}
|
|
|
|
AsciiStrToUnicodeStrS (HostName, HostNameStr, HostNameSize);
|
|
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);
|
|
}
|
|
|
|
if (HttpInstance->UseHttps && !TlsConfigure) {
|
|
Status = TlsCloseSession (HttpInstance);
|
|
if (EFI_ERROR (Status)) {
|
|
goto Error1;
|
|
}
|
|
|
|
TlsCloseTxRxEvent (HttpInstance);
|
|
}
|
|
|
|
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;
|
|
if (Request != NULL) {
|
|
Wrap->TcpWrap.Method = Request->Method;
|
|
}
|
|
|
|
Status = HttpInitSession (
|
|
HttpInstance,
|
|
Wrap,
|
|
Configure || ReConfigure,
|
|
TlsConfigure
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
goto Error2;
|
|
}
|
|
|
|
if (!Configure && !ReConfigure && !TlsConfigure) {
|
|
//
|
|
// 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 (Url != NULL && *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;
|
|
}
|
|
}
|
|
|
|
Status = HttpGenRequestMessage (HttpMsg, FileUrl, &RequestMsg, &RequestMsgSize);
|
|
|
|
if (EFI_ERROR (Status) || NULL == RequestMsg) {
|
|
goto Error3;
|
|
}
|
|
|
|
ASSERT (RequestMsg != NULL);
|
|
|
|
//
|
|
// Every request we insert a TxToken and a response call would remove the TxToken.
|
|
// In cases of PUT/POST/PATCH, after an initial request-response pair, we would do a
|
|
// continuous request without a response call. So, in such cases, where Request
|
|
// structure is NULL, we would not insert a TxToken.
|
|
//
|
|
if (Request != NULL) {
|
|
Status = NetMapInsertTail (&HttpInstance->TxTokens, Token, Wrap);
|
|
if (EFI_ERROR (Status)) {
|
|
goto Error4;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Transmit the request message.
|
|
//
|
|
Status = HttpTransmitTcp (
|
|
HttpInstance,
|
|
Wrap,
|
|
(UINT8*) RequestMsg,
|
|
RequestMsgSize
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
goto Error5;
|
|
}
|
|
|
|
DispatchDpc ();
|
|
|
|
if (HostName != NULL) {
|
|
FreePool (HostName);
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
|
|
Error5:
|
|
//
|
|
// We would have inserted a TxToken only if Request structure is not NULL.
|
|
// Hence check before we do a remove in this error case.
|
|
//
|
|
if (Request != NULL) {
|
|
NetMapRemoveTail (&HttpInstance->TxTokens, NULL);
|
|
}
|
|
|
|
Error4:
|
|
if (RequestMsg != NULL) {
|
|
FreePool (RequestMsg);
|
|
}
|
|
|
|
Error3:
|
|
if (HttpInstance->UseHttps) {
|
|
TlsCloseSession (HttpInstance);
|
|
TlsCloseTxRxEvent (HttpInstance);
|
|
}
|
|
|
|
Error2:
|
|
HttpCloseConnection (HttpInstance);
|
|
|
|
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;
|
|
|
|
if (!HttpInstance->LocalAddressIsIPv6) {
|
|
if (Wrap->TcpWrap.Rx4Token.CompletionToken.Event != NULL) {
|
|
//
|
|
// Cancle the Token before close its Event.
|
|
//
|
|
HttpInstance->Tcp4->Cancel (HttpInstance->Tcp4, &Wrap->TcpWrap.Rx4Token.CompletionToken);
|
|
|
|
//
|
|
// Dispatch the DPC queued by the NotifyFunction of the canceled token's events.
|
|
//
|
|
DispatchDpc ();
|
|
}
|
|
} else {
|
|
if (Wrap->TcpWrap.Rx6Token.CompletionToken.Event != NULL) {
|
|
//
|
|
// Cancle the Token before close its Event.
|
|
//
|
|
HttpInstance->Tcp6->Cancel (HttpInstance->Tcp6, &Wrap->TcpWrap.Rx6Token.CompletionToken);
|
|
|
|
//
|
|
// Dispatch the DPC queued by the NotifyFunction of the canceled token's events.
|
|
//
|
|
DispatchDpc ();
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
if (!HttpInstance->UseHttps) {
|
|
//
|
|
// Then check the tokens queued by EfiHttpResponse(), except for Https.
|
|
//
|
|
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;
|
|
}
|
|
}
|
|
} else {
|
|
if (!HttpInstance->LocalAddressIsIPv6) {
|
|
HttpInstance->Tcp4->Cancel (HttpInstance->Tcp4, &HttpInstance->Tcp4TlsRxToken.CompletionToken);
|
|
} else {
|
|
HttpInstance->Tcp6->Cancel (HttpInstance->Tcp6, &HttpInstance->Tcp6TlsRxToken.CompletionToken);
|
|
}
|
|
}
|
|
|
|
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;
|
|
NET_FRAGMENT Fragment;
|
|
|
|
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;
|
|
ValueInItem = NULL;
|
|
Fragment.Len = 0;
|
|
Fragment.Bulk = NULL;
|
|
|
|
if (HttpMsg->Data.Response != NULL) {
|
|
//
|
|
// 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;
|
|
|
|
|
|
if (HttpInstance->TimeoutEvent == NULL) {
|
|
//
|
|
// Create TimeoutEvent for response
|
|
//
|
|
Status = gBS->CreateEvent (
|
|
EVT_TIMER,
|
|
TPL_CALLBACK,
|
|
NULL,
|
|
NULL,
|
|
&HttpInstance->TimeoutEvent
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
goto Error;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Start the timer, and wait Timeout seconds to receive the header packet.
|
|
//
|
|
Status = gBS->SetTimer (HttpInstance->TimeoutEvent, TimerRelative, HTTP_RESPONSE_TIMEOUT * TICKS_PER_SECOND);
|
|
if (EFI_ERROR (Status)) {
|
|
goto Error;
|
|
}
|
|
|
|
Status = HttpTcpReceiveHeader (HttpInstance, &SizeofHeaders, &BufferSize, HttpInstance->TimeoutEvent);
|
|
|
|
gBS->SetTimer (HttpInstance->TimeoutEvent, TimerCancel, 0);
|
|
|
|
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) {
|
|
Status = EFI_NOT_READY;
|
|
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) {
|
|
Status = EFI_NOT_READY;
|
|
goto Error;
|
|
}
|
|
|
|
//
|
|
// We could have response with just a HTTP message and no headers. For Example,
|
|
// "100 Continue". In such cases, we would not want to unnecessarily call a Parse
|
|
// method. A "\r\n" following Tmp string again would indicate an end. Compare and
|
|
// set SizeofHeaders to 0.
|
|
//
|
|
Tmp = Tmp + AsciiStrLen (HTTP_CRLF_STR);
|
|
if (CompareMem (Tmp, HTTP_CRLF_STR, AsciiStrLen (HTTP_CRLF_STR)) == 0) {
|
|
SizeofHeaders = 0;
|
|
} else {
|
|
SizeofHeaders = SizeofHeaders - (Tmp - HttpHeaders);
|
|
}
|
|
|
|
HttpMsg->Data.Response->StatusCode = HttpMappingToStatusCode (StatusCode);
|
|
HttpInstance->StatusCode = StatusCode;
|
|
|
|
Status = EFI_NOT_READY;
|
|
ValueInItem = NULL;
|
|
|
|
//
|
|
// In cases of PUT/POST/PATCH, after an initial request-response pair, we would do a
|
|
// continuous request without a response call. So, we would not do an insert of
|
|
// TxToken. After we have sent the complete file, we will call a response to get
|
|
// a final response from server. In such a case, we would not have any TxTokens.
|
|
// Hence, check that case before doing a NetMapRemoveHead.
|
|
//
|
|
if (!NetMapIsEmpty (&HttpInstance->TxTokens)) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (SizeofHeaders != 0) {
|
|
HeaderTmp = AllocateZeroPool (SizeofHeaders);
|
|
if (HeaderTmp == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
goto Error2;
|
|
}
|
|
|
|
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 Error2;
|
|
}
|
|
|
|
//
|
|
// 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 Error2;
|
|
}
|
|
|
|
FreePool (HttpHeaders);
|
|
HttpHeaders = NULL;
|
|
|
|
|
|
//
|
|
// Init message-body parser by header information.
|
|
//
|
|
Status = HttpInitMsgParser (
|
|
HttpInstance->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;
|
|
//
|
|
if (!HttpInstance->UseHttps) {
|
|
Status = HttpTcpReceiveBody (Wrap, HttpMsg);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
goto Error2;
|
|
}
|
|
|
|
} else {
|
|
if (HttpInstance->TimeoutEvent == NULL) {
|
|
//
|
|
// Create TimeoutEvent for response
|
|
//
|
|
Status = gBS->CreateEvent (
|
|
EVT_TIMER,
|
|
TPL_CALLBACK,
|
|
NULL,
|
|
NULL,
|
|
&HttpInstance->TimeoutEvent
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
goto Error2;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Start the timer, and wait Timeout seconds to receive the body packet.
|
|
//
|
|
Status = gBS->SetTimer (HttpInstance->TimeoutEvent, TimerRelative, HTTP_RESPONSE_TIMEOUT * TICKS_PER_SECOND);
|
|
if (EFI_ERROR (Status)) {
|
|
goto Error2;
|
|
}
|
|
|
|
Status = HttpsReceive (HttpInstance, &Fragment, HttpInstance->TimeoutEvent);
|
|
|
|
gBS->SetTimer (HttpInstance->TimeoutEvent, TimerCancel, 0);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
goto Error2;
|
|
}
|
|
|
|
//
|
|
// Check whether we receive a complete HTTP message.
|
|
//
|
|
Status = HttpParseMessageBody (
|
|
HttpInstance->MsgParser,
|
|
(UINTN) Fragment.Len,
|
|
(CHAR8 *) Fragment.Bulk
|
|
);
|
|
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;
|
|
}
|
|
|
|
//
|
|
// We receive part of header of next HTTP msg.
|
|
//
|
|
if (HttpInstance->NextMsg != NULL) {
|
|
HttpMsg->BodyLength = MIN ((UINTN) HttpInstance->NextMsg - (UINTN) Fragment.Bulk, HttpMsg->BodyLength);
|
|
CopyMem (HttpMsg->Body, Fragment.Bulk, HttpMsg->BodyLength);
|
|
|
|
HttpInstance->CacheLen = Fragment.Len - HttpMsg->BodyLength;
|
|
if (HttpInstance->CacheLen != 0) {
|
|
if (HttpInstance->CacheBody != NULL) {
|
|
FreePool (HttpInstance->CacheBody);
|
|
}
|
|
|
|
HttpInstance->CacheBody = AllocateZeroPool (HttpInstance->CacheLen);
|
|
if (HttpInstance->CacheBody == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
goto Error2;
|
|
}
|
|
|
|
CopyMem (HttpInstance->CacheBody, Fragment.Bulk + HttpMsg->BodyLength, HttpInstance->CacheLen);
|
|
HttpInstance->CacheOffset = 0;
|
|
|
|
HttpInstance->NextMsg = HttpInstance->CacheBody + ((UINTN) HttpInstance->NextMsg - (UINTN) (Fragment.Bulk + HttpMsg->BodyLength));
|
|
}
|
|
} else {
|
|
HttpMsg->BodyLength = MIN (Fragment.Len, (UINT32) HttpMsg->BodyLength);
|
|
CopyMem (HttpMsg->Body, Fragment.Bulk, HttpMsg->BodyLength);
|
|
HttpInstance->CacheLen = Fragment.Len - HttpMsg->BodyLength;
|
|
if (HttpInstance->CacheLen != 0) {
|
|
if (HttpInstance->CacheBody != NULL) {
|
|
FreePool (HttpInstance->CacheBody);
|
|
}
|
|
|
|
HttpInstance->CacheBody = AllocateZeroPool (HttpInstance->CacheLen);
|
|
if (HttpInstance->CacheBody == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
goto Error2;
|
|
}
|
|
|
|
CopyMem (HttpInstance->CacheBody, Fragment.Bulk + HttpMsg->BodyLength, HttpInstance->CacheLen);
|
|
HttpInstance->CacheOffset = 0;
|
|
}
|
|
}
|
|
|
|
if (Fragment.Bulk != NULL) {
|
|
FreePool (Fragment.Bulk);
|
|
Fragment.Bulk = NULL;
|
|
}
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
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:
|
|
if (ValueInItem != NULL) {
|
|
NetMapInsertHead (&HttpInstance->TxTokens, ValueInItem->HttpToken, ValueInItem);
|
|
}
|
|
|
|
Error:
|
|
Item = NetMapFindKey (&Wrap->HttpInstance->RxTokens, Wrap->HttpToken);
|
|
if (Item != NULL) {
|
|
NetMapRemoveItem (&Wrap->HttpInstance->RxTokens, Item, NULL);
|
|
}
|
|
|
|
if (!HttpInstance->UseHttps) {
|
|
HttpTcpTokenCleanup (Wrap);
|
|
} else {
|
|
FreePool (Wrap);
|
|
}
|
|
|
|
if (HttpHeaders != NULL) {
|
|
FreePool (HttpHeaders);
|
|
HttpHeaders = NULL;
|
|
}
|
|
|
|
if (Fragment.Bulk != NULL) {
|
|
FreePool (Fragment.Bulk);
|
|
Fragment.Bulk = NULL;
|
|
}
|
|
|
|
if (HttpMsg->Headers != NULL) {
|
|
FreePool (HttpMsg->Headers);
|
|
HttpMsg->Headers = NULL;
|
|
}
|
|
|
|
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;
|
|
|
|
//
|
|
// Notes: For Https, receive token wrapped in HTTP_TOKEN_WRAP is not used to
|
|
// receive the https response. A special TlsRxToken is used for receiving TLS
|
|
// related messages. It should be a blocking response.
|
|
//
|
|
if (!HttpInstance->UseHttps) {
|
|
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;
|
|
}
|