1188 lines
31 KiB
C
1188 lines
31 KiB
C
/** @file
|
|
REST protocol implementation.
|
|
|
|
;******************************************************************************
|
|
;* Copyright (c) 2012 - 2020, Insyde Software Corp. All Rights Reserved.
|
|
;*
|
|
;* You may not reproduce, distribute, publish, display, perform, modify, adapt,
|
|
;* transmit, broadcast, present, recite, release, license or otherwise exploit
|
|
;* any part of this publication in any form, by any means, without the prior
|
|
;* written permission of Insyde Software Corporation.
|
|
;*
|
|
;******************************************************************************
|
|
*/
|
|
|
|
#include "RestDriver.h"
|
|
|
|
EFI_HTTP_HEADER mCommonHeader[] = {
|
|
{HTTP_HEADER_HOST, "localhost"},
|
|
{HTTP_HEADER_ACCEPT, "*/*"},
|
|
{HTTP_HEADER_USER_AGENT, "UefiRestProtocol/1.0"}
|
|
};
|
|
|
|
EFI_STATUS
|
|
RestHttpInput (
|
|
IN REST_HTTP_TOKEN *HttpToken,
|
|
IN BOOLEAN HeaderOnly
|
|
);
|
|
|
|
EFI_STATUS
|
|
RestHttpOutput (
|
|
IN REST_HTTP_TOKEN *HttpToken
|
|
);
|
|
|
|
/**
|
|
Check URL validation
|
|
|
|
TRUE : Valid
|
|
FALSE : Invalid
|
|
**/
|
|
BOOLEAN
|
|
IsURLValid (
|
|
UINT16 *URL
|
|
)
|
|
{
|
|
UINTN Index;
|
|
|
|
Index = 0;
|
|
while (URL[Index] != 0) {
|
|
/// Ascii charectors check, a-z, A-Z
|
|
if ((URL[Index] < 0x23) && (URL[Index] > 0x7E)) {
|
|
return FALSE;
|
|
}
|
|
switch (URL[Index]) {
|
|
case '<':
|
|
case '>':
|
|
case '\\':
|
|
case '^':
|
|
case '`':
|
|
case '{':
|
|
case '}':
|
|
return FALSE;
|
|
}
|
|
Index ++;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Update HOST name from HTTP request message.
|
|
//
|
|
EFI_STATUS
|
|
RestUpdateHttpHost (
|
|
OUT EFI_HTTP_MESSAGE *DestHttpMessage,
|
|
IN CHAR8 *HostName
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINTN Index;
|
|
|
|
Status = EFI_NOT_FOUND;
|
|
for (Index = 0; Index < DestHttpMessage->HeaderCount; Index++) {
|
|
if (AsciiStrCmp (DestHttpMessage->Headers[Index].FieldName, HTTP_HEADER_HOST) != 0) {
|
|
continue;
|
|
}
|
|
Status = EFI_SUCCESS;
|
|
DestHttpMessage->Headers[Index].FieldValue = HostName;
|
|
break;
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// This function will merge command HTTP headers and append http headers.
|
|
//
|
|
EFI_STATUS
|
|
RestMegerHttpHeaders (
|
|
IN EFI_HTTP_MESSAGE *AppendHeader,
|
|
OUT EFI_HTTP_MESSAGE *DestHttpMessage
|
|
)
|
|
{
|
|
UINTN AppendHeaderIndex;
|
|
UINTN AppendHeaderCounter;
|
|
UINTN CommonHeaderIndex;
|
|
UINTN CommonHeaderCounter;
|
|
UINTN RealHeaderIndex;
|
|
UINTN RealHeaderCounter;
|
|
EFI_HTTP_HEADER *AppendHttpHeaders;
|
|
EFI_HTTP_HEADER *HttpHeaders;
|
|
CHAR8 *FieldName;
|
|
|
|
//
|
|
// Calculate number of common header and copy common headers.
|
|
//
|
|
CommonHeaderCounter = sizeof (mCommonHeader) / sizeof (EFI_HTTP_HEADER);
|
|
AppendHeaderCounter = AppendHeader->HeaderCount;
|
|
AppendHttpHeaders = AppendHeader->Headers;
|
|
RealHeaderCounter = CommonHeaderCounter + AppendHeaderCounter;
|
|
HttpHeaders = AllocateZeroPool (sizeof (EFI_HTTP_HEADER) * RealHeaderCounter);
|
|
if (HttpHeaders == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// Append headers into HttpHeaders
|
|
// RealHeaderIndex push to end of CommonHeaders, to append the header not exist in common headers.
|
|
//
|
|
RealHeaderIndex = CommonHeaderCounter;
|
|
CopyMem (HttpHeaders, mCommonHeader, CommonHeaderCounter * sizeof (EFI_HTTP_HEADER));
|
|
for (AppendHeaderIndex = 0; AppendHeaderIndex < AppendHeaderCounter; AppendHeaderIndex++) {
|
|
FieldName = AppendHttpHeaders[AppendHeaderIndex].FieldName;
|
|
for (CommonHeaderIndex = 0; CommonHeaderIndex < CommonHeaderCounter; CommonHeaderIndex++) {
|
|
if (AsciiStrCmp (HttpHeaders[CommonHeaderIndex].FieldName, FieldName) != 0) {
|
|
continue;
|
|
}
|
|
HttpHeaders[CommonHeaderIndex].FieldValue = AppendHttpHeaders[AppendHeaderIndex].FieldValue;
|
|
break;
|
|
}
|
|
if (CommonHeaderIndex < CommonHeaderCounter) {
|
|
//
|
|
// Value overrided by AppendHttpHeaders.
|
|
//
|
|
continue;
|
|
}
|
|
//
|
|
// Insert append header to RealHeaders.
|
|
//
|
|
HttpHeaders[RealHeaderIndex].FieldName = AppendHttpHeaders[AppendHeaderIndex].FieldName;
|
|
HttpHeaders[RealHeaderIndex].FieldValue = AppendHttpHeaders[AppendHeaderIndex].FieldValue;
|
|
RealHeaderIndex++;
|
|
}
|
|
|
|
DestHttpMessage->HeaderCount = RealHeaderIndex;
|
|
DestHttpMessage->Headers = HttpHeaders;
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Callback to parsing received HTTP Package
|
|
//
|
|
EFI_STATUS
|
|
RestHttpParserCallback (
|
|
IN HTTP_BODY_PARSE_EVENT EventType,
|
|
IN CHAR8 *Data,
|
|
IN UINTN Length,
|
|
IN VOID *Context
|
|
)
|
|
{
|
|
|
|
//
|
|
// Chunk transfer
|
|
// This function must be review and debug.
|
|
//
|
|
REST_HTTP_TOKEN *HttpToken;
|
|
UINT8 *BufferPtr;
|
|
UINTN BufferOffser;
|
|
|
|
HttpToken = Context;
|
|
BufferOffser = HttpToken->ChunkDataLength;
|
|
if (EventType == BodyParseEventOnData) {
|
|
if (HttpToken->ChunkDataBuffer == NULL) {
|
|
BufferPtr = AllocateZeroPool (Length);
|
|
} else {
|
|
BufferPtr = ReallocatePool (HttpToken->ChunkDataLength, HttpToken->ChunkDataLength + Length, HttpToken->ChunkDataBuffer);
|
|
}
|
|
if (BufferPtr == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
CopyMem (BufferPtr + BufferOffser, Data, Length);
|
|
HttpToken->ChunkDataBuffer = BufferPtr;
|
|
HttpToken->ChunkDataLength += Length;
|
|
}
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// For DEBUG
|
|
//
|
|
EFI_STATUS
|
|
RestHttpStatusDump (
|
|
EFI_HTTP_MESSAGE *HttpMessage
|
|
)
|
|
{
|
|
UINTN Index;
|
|
|
|
if (!DEBUG_MODE) {
|
|
return EFI_NOT_READY;
|
|
}
|
|
|
|
AsciiPrint ("HTTP return Status : ", HttpMessage->Data.Response->StatusCode);
|
|
for (Index = 0; Index < HttpMessage->HeaderCount; Index++) {
|
|
AsciiPrint ("%a : ", HttpMessage->Headers[Index].FieldName);
|
|
AsciiPrint ("%a\n", HttpMessage->Headers[Index].FieldValue);
|
|
}
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
RestHttpHeaderDump (
|
|
EFI_HTTP_MESSAGE *HttpMessage
|
|
)
|
|
{
|
|
UINTN Index;
|
|
|
|
if (!DEBUG_MODE) {
|
|
return EFI_NOT_READY;
|
|
}
|
|
|
|
AsciiPrint ("HTTP header\n");
|
|
for (Index = 0; Index < HttpMessage->HeaderCount; Index++) {
|
|
AsciiPrint ("%a : ", HttpMessage->Headers[Index].FieldName);
|
|
AsciiPrint ("%a\n", HttpMessage->Headers[Index].FieldValue);
|
|
}
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
Notify the callback function when an event is triggered.
|
|
|
|
@param[in] Event The triggered event.
|
|
@param[in] Context The opaque parameter to the function.
|
|
|
|
**/
|
|
VOID
|
|
RestHttpClientCommonNotify (
|
|
IN EFI_EVENT Event,
|
|
IN VOID *Context
|
|
)
|
|
{
|
|
*((BOOLEAN *) Context) = TRUE;
|
|
}
|
|
|
|
EFI_STATUS
|
|
RestHttpGetDnsFromIp4Config2Data (
|
|
REST_HTTP_TOKEN *HttpToken
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINTN DnsServerCnt;
|
|
EFI_IPv4_ADDRESS *DnsServers;
|
|
EFI_IP4_CONFIG2_PROTOCOL *Ip4Config2;
|
|
|
|
Ip4Config2 = HttpToken->Ip4Config2;
|
|
DnsServers = NULL;
|
|
DnsServerCnt = 0;
|
|
|
|
Status = Ip4Config2->GetData (
|
|
Ip4Config2,
|
|
Ip4Config2DataTypeDnsServer,
|
|
&DnsServerCnt,
|
|
NULL
|
|
);
|
|
if (Status == EFI_BUFFER_TOO_SMALL) {
|
|
DnsServers = AllocateZeroPool (DnsServerCnt);
|
|
if (DnsServers == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
Status = Ip4Config2->GetData (
|
|
Ip4Config2,
|
|
Ip4Config2DataTypeDnsServer,
|
|
&DnsServerCnt,
|
|
DnsServers
|
|
);
|
|
}
|
|
if (EFI_ERROR (Status)) {
|
|
FreePool (DnsServers);
|
|
return Status;
|
|
}
|
|
|
|
HttpToken->DnsServerCounter = DnsServerCnt / sizeof (EFI_IPv4_ADDRESS);
|
|
HttpToken->DnsServerList = DnsServers;
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
RestHttpDns4Configure (
|
|
REST_HTTP_TOKEN *HttpToken
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_DNS4_PROTOCOL *Dns4;
|
|
EFI_DNS4_CONFIG_DATA Dns4ConfigData;
|
|
|
|
ZeroMem (&Dns4ConfigData, sizeof (EFI_DNS4_CONFIG_DATA));
|
|
Dns4ConfigData.DnsServerListCount = HttpToken->DnsServerCounter;
|
|
Dns4ConfigData.DnsServerList = HttpToken->DnsServerList;
|
|
Dns4ConfigData.EnableDnsCache = TRUE;
|
|
/// this setting should be review.
|
|
Dns4ConfigData.UseDefaultSetting = TRUE;
|
|
Dns4ConfigData.Protocol = EFI_IP_PROTO_UDP;
|
|
|
|
Dns4 = HttpToken->Dns4;
|
|
if (Dns4 == NULL) {
|
|
AsciiPrint ("DNS protocol is not exist!\n");
|
|
return EFI_NOT_READY;
|
|
}
|
|
Status = Dns4->Configure (Dns4, &Dns4ConfigData);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
RestHttpDns4TranslateAddress (
|
|
REST_HTTP_TOKEN *HttpToken
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_DNS4_PROTOCOL *Dns4;
|
|
EFI_DNS4_COMPLETION_TOKEN Dns4Token;
|
|
BOOLEAN Dns4IsDone;
|
|
UINT16 *HostName;
|
|
UINTN HostNameLength;
|
|
|
|
HostNameLength = AsciiStrLen (HttpToken->HostName);
|
|
HostName = AllocateZeroPool (HostNameLength * sizeof (UINT16));
|
|
if (HostName == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
ZeroMem (&Dns4Token, sizeof (EFI_DNS4_COMPLETION_TOKEN));
|
|
Status = gBS->CreateEvent (
|
|
EVT_NOTIFY_SIGNAL,
|
|
TPL_NOTIFY,
|
|
RestHttpClientCommonNotify,
|
|
&Dns4IsDone,
|
|
&Dns4Token.Event
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
|
|
//
|
|
// Start asynchronous name resolution.
|
|
//
|
|
Dns4 = HttpToken->Dns4;
|
|
Dns4Token.Status = EFI_NOT_READY;
|
|
Dns4IsDone = FALSE;
|
|
AsciiStrToUnicodeStrS (HttpToken->HostName, HostName, HostNameLength * sizeof (UINT16));
|
|
Status = Dns4->HostNameToIp (Dns4, HostName, &Dns4Token);
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
|
|
while (!Dns4IsDone) {
|
|
Dns4->Poll (Dns4);
|
|
}
|
|
|
|
//
|
|
// This section should be review for parsing DNS if it has IPv4 and IPv6 both.
|
|
// Name resolution is done, check result.
|
|
//
|
|
Status = Dns4Token.Status;
|
|
if (!EFI_ERROR (Status)) {
|
|
if (Dns4Token.RspData.H2AData == NULL) {
|
|
Status = EFI_DEVICE_ERROR;
|
|
goto EXIT;
|
|
}
|
|
if (Dns4Token.RspData.H2AData->IpCount == 0 || Dns4Token.RspData.H2AData->IpList == NULL) {
|
|
Status = EFI_DEVICE_ERROR;
|
|
goto EXIT;
|
|
}
|
|
//
|
|
// Just for a test with parsing URI.DomanName, it will not using IPv4 address yet.
|
|
//
|
|
//IP4_COPY_ADDRESS (IpAddress, Dns4Token.RspData.H2AData->IpList);
|
|
Status = EFI_SUCCESS;
|
|
}
|
|
|
|
EXIT:
|
|
if (EFI_ERROR (Status)) {
|
|
HttpToken->ReturnHttpStatusCode = HTTP_STATUS_503_SERVICE_UNAVAILABLE;
|
|
}
|
|
if (HostName != NULL) {
|
|
FreePool (HostName);
|
|
}
|
|
gBS->CloseEvent (Dns4Token.Event);
|
|
return Status;
|
|
}
|
|
|
|
///
|
|
/// This function will refer PCD to choice DNS capability in IPv4 or IPv6.
|
|
/// If there is no PCD setting in PCD database, it will be false to use IPv4.
|
|
///
|
|
VOID
|
|
RestResolveDnsPolicy (
|
|
OUT BOOLEAN *IsIPv6
|
|
)
|
|
{
|
|
VOID *IsIPv6Ptr;
|
|
UINT8 Policy;
|
|
|
|
/// Check PCD exist in PCD database.
|
|
*IsIPv6 = FALSE;
|
|
IsIPv6Ptr = PcdGetExPtr (&gInsydeTokenSpaceGuid, PcdH2ORestIPv6DnsPolicy);
|
|
if (IsIPv6Ptr == NULL) {
|
|
return;
|
|
}
|
|
|
|
Policy = PcdGetEx8 (&gInsydeTokenSpaceGuid, PcdH2ORestIPv6DnsPolicy);
|
|
if (Policy != 0) {
|
|
*IsIPv6 = TRUE;
|
|
}
|
|
return;
|
|
}
|
|
|
|
EFI_STATUS
|
|
RestCreateHttpClientToken (
|
|
IN REST_BINDING_INSTANCE *Instance,
|
|
OUT REST_HTTP_TOKEN **RestHttpToken
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
REST_HTTP_TOKEN *HttpToken;
|
|
|
|
HttpToken = (REST_HTTP_TOKEN *) AllocateZeroPool (sizeof (REST_HTTP_TOKEN));
|
|
if (HttpToken == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
HttpToken->HttpHandle = Instance->HttpHandle;
|
|
Status = gBS->HandleProtocol (
|
|
HttpToken->HttpHandle,
|
|
&gEfiHttpProtocolGuid,
|
|
(VOID **) &HttpToken->Http
|
|
);
|
|
if (EFI_ERROR(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
Status = NetLibCreateServiceChild (
|
|
Instance->ControllerHandle,
|
|
gImageHandle,
|
|
&gEfiDns4ServiceBindingProtocolGuid,
|
|
&HttpToken->Dns4Handle
|
|
);
|
|
if (EFI_ERROR(Status)) {
|
|
HttpToken->Dns4 = NULL;
|
|
} else {
|
|
Status = gBS->HandleProtocol (
|
|
HttpToken->Dns4Handle,
|
|
&gEfiDns4ProtocolGuid,
|
|
(VOID **) &HttpToken->Dns4
|
|
);
|
|
if (EFI_ERROR(Status)) {
|
|
HttpToken->Dns4 = NULL;
|
|
}
|
|
}
|
|
|
|
Status = gBS->HandleProtocol (
|
|
Instance->ControllerHandle,
|
|
&gEfiIp4Config2ProtocolGuid,
|
|
(VOID **) &HttpToken->Ip4Config2
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
HttpToken->Ip4Config2 = NULL;
|
|
}
|
|
|
|
HttpToken->IsIpv6 = FALSE;
|
|
Status = gBS->HandleProtocol (
|
|
Instance->ControllerHandle,
|
|
&gEfiIp6ConfigProtocolGuid,
|
|
(VOID **) &HttpToken->Ip6Config
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
HttpToken->Ip6Config = NULL;
|
|
} else {
|
|
RestResolveDnsPolicy (&HttpToken->IsIpv6);
|
|
}
|
|
|
|
*RestHttpToken = HttpToken;
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
RestHttpClientParsingURI (
|
|
IN OUT REST_HTTP_TOKEN *RestHttpToken
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
CHAR8 *URL;
|
|
UINTN URLLen;
|
|
VOID *UrlParser;
|
|
|
|
UrlParser = NULL;
|
|
|
|
if (!IsURLValid (RestHttpToken->HttpRequest->Url)) {
|
|
RestHttpToken->ReturnHttpStatusCode = HTTP_STATUS_400_BAD_REQUEST;
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Translate UNICODE to ASCII and parsing URL to check status of URL from HttpRequest
|
|
//
|
|
URLLen = StrLen (RestHttpToken->HttpRequest->Url) + 1;
|
|
URL = (CHAR8 *) AllocateZeroPool (URLLen);
|
|
if (URL == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
Status = UnicodeStrToAsciiStrS (RestHttpToken->HttpRequest->Url, URL, URLLen);
|
|
if (EFI_ERROR (Status)) {
|
|
goto FINISH;
|
|
}
|
|
|
|
Status = HttpParseUrl (
|
|
URL,
|
|
(UINT32) URLLen,
|
|
FALSE,
|
|
(VOID **) &UrlParser
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
RestHttpToken->ReturnHttpStatusCode = HTTP_STATUS_400_BAD_REQUEST;
|
|
Status = EFI_INVALID_PARAMETER;
|
|
goto FINISH;
|
|
}
|
|
|
|
//
|
|
// Get HostName.
|
|
// It might be those cases.
|
|
// IPv4 address 192.168.111.1
|
|
// IPv6 address [2048:1096::1]
|
|
// Domain name www.domain.com
|
|
//
|
|
RestHttpToken->HostName = NULL;
|
|
Status = HttpUrlGetHostName (URL, UrlParser, &RestHttpToken->HostName);
|
|
if (!EFI_ERROR (Status)) {
|
|
goto FINISH;
|
|
}
|
|
|
|
//
|
|
// Try to translate HostName to IPv4, if it success, means the HostName is IPv4 address type.
|
|
//
|
|
Status = HttpUrlGetIp4 (URL, UrlParser, &RestHttpToken->RemoteAddr);
|
|
if (!EFI_ERROR (Status)) {
|
|
goto FINISH;
|
|
}
|
|
|
|
//
|
|
// Try to translate HostName to IPv6, if it success, means the HostName is IPv6 address type.
|
|
//
|
|
Status = HttpUrlGetIp6 (URL, UrlParser, &RestHttpToken->RemoteAddr6);
|
|
if (!EFI_ERROR (Status)) {
|
|
RestHttpToken->IsIpv6 = TRUE;
|
|
goto FINISH;
|
|
}
|
|
|
|
//
|
|
// Domain name need to parsing by DNS.
|
|
// This function will not check status from DNS, so it RestDxe will choose IPv4 or IPv6 HTTP services.
|
|
//
|
|
|
|
//// DNS detect function disabled, enable this function after review and resolve problem of DNS.
|
|
//
|
|
// //
|
|
// // Get DNS addresses from Ip4Config2 protocol.
|
|
// //
|
|
// if ((RestHttpToken->Ip4Config2 == NULL) || (RestHttpToken->Dns4 == NULL)) {
|
|
// RestHttpToken->ReturnHttpStatusCode = HTTP_STATUS_503_SERVICE_UNAVAILABLE;
|
|
// Status = EFI_UNSUPPORTED;
|
|
// goto FINISH;
|
|
// }
|
|
//
|
|
//
|
|
// Status = RestHttpGetDnsFromIp4Config2Data (RestHttpToken);
|
|
// if (EFI_ERROR (Status)) {
|
|
// goto FINISH;
|
|
// }
|
|
//
|
|
// Status = RestHttpDns4Configure (RestHttpToken);
|
|
// if (EFI_ERROR (Status)) {
|
|
// goto FINISH;
|
|
// }
|
|
//
|
|
// Status = RestHttpDns4TranslateAddress (RestHttpToken);
|
|
// if (EFI_ERROR (Status)) {
|
|
// goto FINISH;
|
|
// }
|
|
|
|
FINISH:
|
|
if (UrlParser != NULL) {
|
|
HttpUrlFreeParser (UrlParser);
|
|
}
|
|
FreePool (URL);
|
|
return Status;
|
|
}
|
|
|
|
EFI_STATUS
|
|
RestDestroyHttpClientToken (
|
|
REST_BINDING_INSTANCE *Instance,
|
|
REST_HTTP_TOKEN *HttpToken
|
|
)
|
|
{
|
|
if (HttpToken == NULL) {
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
gBS->CloseEvent (HttpToken->TimeoutEvent);
|
|
if (HttpToken->HttpHandle != NULL) {
|
|
HttpToken->Http->Configure (HttpToken->Http, NULL);
|
|
// NetLibDestroyServiceChild (
|
|
// NicHandle,
|
|
// gImageHandle,
|
|
// &gEfiHttpServiceBindingProtocolGuid,
|
|
// HttpToken->HttpHandle
|
|
// );
|
|
}
|
|
|
|
if (HttpToken->Dns4Handle != NULL) {
|
|
NetLibDestroyServiceChild (
|
|
Instance->ControllerHandle,
|
|
gImageHandle,
|
|
&gEfiDns4ServiceBindingProtocolGuid,
|
|
HttpToken->Dns4Handle
|
|
);
|
|
}
|
|
if (HttpToken->DnsServerList != NULL) {
|
|
FreePool (HttpToken->DnsServerList);
|
|
}
|
|
FreePool (HttpToken);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
RestHttpClientConfigIpv4 (
|
|
IN REST_HTTP_TOKEN *HttpToken,
|
|
IN OUT EFI_HTTP_CONFIG_DATA *HttpConfigData
|
|
)
|
|
{
|
|
EFI_HTTPv4_ACCESS_POINT *Http4AccessPoint;
|
|
|
|
if (HttpToken->Ip4Config2 == NULL) {
|
|
return EFI_NOT_READY;
|
|
}
|
|
|
|
Http4AccessPoint = AllocateZeroPool (sizeof (EFI_HTTPv4_ACCESS_POINT));
|
|
if (Http4AccessPoint == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
Http4AccessPoint->UseDefaultAddress = TRUE;
|
|
HttpConfigData->AccessPoint.IPv4Node = Http4AccessPoint;
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
|
|
EFI_STATUS
|
|
RestHttpClientConfigIpv6 (
|
|
IN REST_HTTP_TOKEN *HttpToken,
|
|
IN OUT EFI_HTTP_CONFIG_DATA *HttpConfigData
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_HTTPv6_ACCESS_POINT *Http6AccessPoint;
|
|
EFI_IP6_CONFIG_PROTOCOL *Ip6Config;
|
|
UINTN DataSize;
|
|
EFI_IP6_CONFIG_INTERFACE_INFO *IfInfo;
|
|
|
|
if (HttpToken->Ip6Config == NULL) {
|
|
return EFI_NOT_READY;
|
|
}
|
|
|
|
IfInfo = NULL;
|
|
Http6AccessPoint = NULL;
|
|
Ip6Config = HttpToken->Ip6Config;
|
|
DataSize = 0;
|
|
Status = Ip6Config->GetData (
|
|
Ip6Config,
|
|
Ip6ConfigDataTypeInterfaceInfo,
|
|
&DataSize,
|
|
NULL
|
|
);
|
|
if (Status == EFI_BUFFER_TOO_SMALL) {
|
|
IfInfo = AllocateZeroPool (DataSize);
|
|
if (IfInfo == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
Status = Ip6Config->GetData (
|
|
Ip6Config,
|
|
Ip6ConfigDataTypeInterfaceInfo,
|
|
&DataSize,
|
|
IfInfo
|
|
);
|
|
}
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
|
|
if (IfInfo == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
goto EXIT;
|
|
}
|
|
|
|
Http6AccessPoint = AllocateZeroPool (sizeof (EFI_HTTPv6_ACCESS_POINT));
|
|
if (Http6AccessPoint == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
goto EXIT;
|
|
}
|
|
|
|
/// Get lastest address, expect that is not link-local address
|
|
CopyMem (&Http6AccessPoint->LocalAddress, &(IfInfo->AddressInfo[IfInfo->AddressInfoCount-1].Address), sizeof (EFI_IPv6_ADDRESS));
|
|
HttpConfigData->AccessPoint.IPv6Node = Http6AccessPoint;
|
|
|
|
EXIT:
|
|
if (IfInfo != NULL) {
|
|
FreePool (IfInfo);
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
EFI_STATUS
|
|
RestHttpClientConfig (
|
|
IN REST_HTTP_TOKEN *HttpToken
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_EVENT Event;
|
|
EFI_HTTP_CONFIG_DATA *HttpConfigData;
|
|
|
|
HttpConfigData = &HttpToken->HttpConfigData;
|
|
HttpConfigData->HttpVersion = HttpVersion11;
|
|
HttpConfigData->TimeOutMillisec = HTTP_RESPONSE_TIME_OUT;
|
|
|
|
HttpConfigData->LocalAddressIsIPv6 = HttpToken->IsIpv6;
|
|
if (!HttpToken->IsIpv6) {
|
|
Status = RestHttpClientConfigIpv4 (HttpToken, HttpConfigData);
|
|
} else {
|
|
Status = RestHttpClientConfigIpv6 (HttpToken, HttpConfigData);
|
|
}
|
|
if (EFI_ERROR (Status)) {
|
|
goto ON_ERROR;
|
|
}
|
|
|
|
Status = HttpToken->Http->Configure (HttpToken->Http, HttpConfigData);
|
|
if (EFI_ERROR (Status)) {
|
|
goto ON_ERROR;
|
|
}
|
|
|
|
//
|
|
// Create events for variuos asynchronous operations.
|
|
//
|
|
Event = NULL;
|
|
Status = gBS->CreateEvent (
|
|
EVT_NOTIFY_SIGNAL,
|
|
TPL_NOTIFY,
|
|
RestHttpClientCommonNotify,
|
|
&HttpToken->IsTxDone,
|
|
&Event
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
goto ON_ERROR;
|
|
}
|
|
HttpToken->RequestEvent = Event;
|
|
|
|
Event = NULL;
|
|
Status = gBS->CreateEvent (
|
|
EVT_NOTIFY_SIGNAL,
|
|
TPL_NOTIFY,
|
|
RestHttpClientCommonNotify,
|
|
&HttpToken->IsRxDone,
|
|
&Event
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
goto ON_ERROR;
|
|
}
|
|
HttpToken->ResponseEvent = Event;
|
|
|
|
Event = NULL;
|
|
Status = gBS->CreateEvent (
|
|
EVT_TIMER,
|
|
TPL_CALLBACK,
|
|
NULL,
|
|
NULL,
|
|
&Event
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
goto ON_ERROR;
|
|
}
|
|
HttpToken->TimeoutEvent = Event;
|
|
|
|
return EFI_SUCCESS;
|
|
|
|
ON_ERROR:
|
|
if (HttpConfigData->AccessPoint.IPv4Node != NULL) {
|
|
FreePool (HttpConfigData->AccessPoint.IPv4Node);
|
|
}
|
|
if (HttpConfigData->AccessPoint.IPv6Node != NULL) {
|
|
FreePool (HttpConfigData->AccessPoint.IPv6Node);
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
EFI_STATUS
|
|
RestHttpGetResponse (
|
|
IN REST_HTTP_TOKEN *HttpToken
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
BOOLEAN HeaderOnly;
|
|
VOID *Parser;
|
|
UINT8 *ContentBuffer;
|
|
UINT8 *Block;
|
|
UINTN BlockSize;
|
|
UINTN ContentLength;
|
|
UINTN ReceivedLength;
|
|
EFI_HTTP_MESSAGE *Response;
|
|
|
|
ContentBuffer = NULL;
|
|
HeaderOnly = (HttpToken->HttpRequest->Method == HttpMethodHead) ? TRUE : FALSE;
|
|
Response = &HttpToken->Response;
|
|
//
|
|
// Transmit HTTP request
|
|
//
|
|
Status = RestHttpOutput (HttpToken);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Receive HTTP header
|
|
//
|
|
Status = RestHttpInput (HttpToken, TRUE);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
Status = HttpInitMsgParser (
|
|
HttpToken->HttpRequest->Method,
|
|
HttpToken->ReturnHttpStatusCode,
|
|
Response->HeaderCount,
|
|
Response->Headers,
|
|
RestHttpParserCallback,
|
|
(VOID *) HttpToken,
|
|
&Parser
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// If "Content-Length" not in HTTP header, perhaps is identity transfer-coding.
|
|
//
|
|
Status = HttpGetEntityLength (Parser, &ContentLength);
|
|
if (!EFI_ERROR (Status)) {
|
|
HttpToken->IdentityMode = TRUE;
|
|
HttpToken->ContentLength = ContentLength;
|
|
}
|
|
|
|
if (HeaderOnly) {
|
|
goto EXIT;
|
|
}
|
|
|
|
//
|
|
// Start to receive HTTP body.
|
|
//
|
|
ReceivedLength = 0;
|
|
if (HttpToken->IdentityMode) {
|
|
ContentBuffer = AllocateZeroPool (ContentLength);
|
|
if (ContentBuffer == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
goto EXIT;
|
|
}
|
|
|
|
while (ReceivedLength < ContentLength) {
|
|
Response->Body = ContentBuffer + ReceivedLength;
|
|
if (ContentLength - ReceivedLength >= REST_BLOCK_SIZE) {
|
|
Response->BodyLength = REST_BLOCK_SIZE;
|
|
} else {
|
|
Response->BodyLength = ContentLength - ReceivedLength;
|
|
}
|
|
Status = RestHttpInput (HttpToken, FALSE);
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
ReceivedLength += Response->BodyLength;
|
|
}
|
|
|
|
if (ReceivedLength > ContentLength) {
|
|
goto EXIT;
|
|
}
|
|
|
|
if (Response->Data.Response == NULL) {
|
|
Response->Data.Response = AllocateZeroPool (sizeof (EFI_HTTP_RESPONSE_DATA));
|
|
if (Response->Data.Response == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
goto EXIT;
|
|
}
|
|
}
|
|
Response->Data.Response->StatusCode = HttpToken->ReturnHttpStatusCode;
|
|
Response->Body = ContentBuffer;
|
|
Response->BodyLength = ReceivedLength;
|
|
|
|
} else {
|
|
//
|
|
// Chunk transfer
|
|
// This function must be review and debug.
|
|
//
|
|
Block = AllocateZeroPool (REST_BLOCK_SIZE);
|
|
BlockSize = REST_BLOCK_SIZE;
|
|
if (Block == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
goto EXIT;
|
|
}
|
|
|
|
HttpToken->ChunkDataBuffer = NULL;
|
|
HttpToken->ChunkDataLength = 0;
|
|
while (!HttpIsMessageComplete (Parser)) {
|
|
Response->Body = Block;
|
|
Response->BodyLength = BlockSize;
|
|
Status = RestHttpInput (HttpToken, FALSE);
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
|
|
//
|
|
// Parse the new received block of the message-body, the block will be saved in cache.
|
|
//
|
|
Status = HttpParseMessageBody (
|
|
Parser,
|
|
Response->BodyLength,
|
|
Response->Body
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get correct length from parser of Chunk transfer.
|
|
//
|
|
Status = HttpGetEntityLength (Parser, &ContentLength);
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
|
|
Response->Data.Response->StatusCode = HttpToken->ReturnHttpStatusCode;
|
|
Response->Body = HttpToken->ChunkDataBuffer;
|
|
Response->BodyLength = HttpToken->ChunkDataLength;
|
|
}
|
|
|
|
EXIT:
|
|
if (EFI_ERROR (Status)) {
|
|
if (ContentBuffer != NULL) {
|
|
FreePool (ContentBuffer);
|
|
}
|
|
}
|
|
|
|
if (Parser != NULL) {
|
|
HttpFreeMsgParser (Parser);
|
|
}
|
|
if (!HeaderOnly && (HttpToken->IdentityMode)) {
|
|
if (ContentBuffer != NULL) {
|
|
Response->Body = ContentBuffer;
|
|
}
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
EFI_STATUS
|
|
RestHttpOutput (
|
|
IN REST_HTTP_TOKEN *HttpToken
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_HTTP_MESSAGE RequestMessage;
|
|
EFI_HTTP_REQUEST_DATA RequestData;
|
|
EFI_HTTP_PROTOCOL *Http;
|
|
EFI_HTTP_TOKEN RequestToken;
|
|
EFI_EVENT TimeoutEvent;
|
|
|
|
ZeroMem (&RequestData, sizeof (EFI_HTTP_REQUEST_DATA));
|
|
ZeroMem (&RequestMessage, sizeof (EFI_HTTP_MESSAGE));
|
|
ZeroMem (&RequestToken, sizeof (EFI_HTTP_TOKEN));
|
|
Http = HttpToken->Http;
|
|
|
|
//
|
|
// Prepare HTTP Token
|
|
//
|
|
RequestData.Method = HttpToken->HttpRequest->Method;
|
|
RequestData.Url = HttpToken->HttpRequest->Url;
|
|
RequestMessage.Data.Request = &RequestData;
|
|
|
|
//
|
|
// Prepare HTTP header, if header coming from external
|
|
// Compare and merge it
|
|
//
|
|
if (HttpToken->Request.HeaderCount == 0) {
|
|
RequestMessage.HeaderCount = sizeof (mCommonHeader) / sizeof (EFI_HTTP_HEADER);
|
|
RequestMessage.Headers = mCommonHeader;
|
|
} else {
|
|
Status = RestMegerHttpHeaders (&HttpToken->Request, &RequestMessage);
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update HOST name in HTTP header.
|
|
//
|
|
Status = RestUpdateHttpHost (&RequestMessage, HttpToken->HostName);
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
|
|
//
|
|
// Prepare HTTP body, if command is POST.
|
|
//
|
|
if ( HttpToken->HttpRequest->Method == HttpMethodPost ||
|
|
HttpToken->HttpRequest->Method == HttpMethodPatch ||
|
|
HttpToken->HttpRequest->Method == HttpMethodPut) {
|
|
if ((HttpToken->Request.BodyLength != 0) && (HttpToken->Request.Body != NULL)) {
|
|
RequestMessage.BodyLength = HttpToken->Request.BodyLength;
|
|
RequestMessage.Body = HttpToken->Request.Body;
|
|
}
|
|
}
|
|
RequestToken.Event = HttpToken->RequestEvent;
|
|
RequestToken.Message = &RequestMessage;
|
|
RequestToken.Status = EFI_NOT_READY;
|
|
HttpToken->IsTxDone = FALSE;
|
|
RestHttpHeaderDump (&HttpToken->Request);
|
|
|
|
//
|
|
// Start the timer, and wait Timeout seconds to receive the header packet.
|
|
//
|
|
TimeoutEvent = HttpToken->TimeoutEvent;
|
|
Status = gBS->SetTimer (TimeoutEvent, TimerRelative, HTTP_DELAY_TIME);
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
|
|
//
|
|
// Transfer HTTP request to server.
|
|
//
|
|
Status = Http->Request (
|
|
Http,
|
|
&RequestToken
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
|
|
//
|
|
// Wait for finish.
|
|
//
|
|
while (!HttpToken->IsTxDone && EFI_ERROR (gBS->CheckEvent (TimeoutEvent))) {
|
|
Http->Poll (Http);
|
|
}
|
|
|
|
gBS->SetTimer (TimeoutEvent, TimerCancel, 0);
|
|
if (!HttpToken->IsTxDone) {
|
|
RequestToken.Status = EFI_TIMEOUT;
|
|
}
|
|
|
|
EXIT:
|
|
if (DEBUG_MODE) {
|
|
AsciiPrint ("%08x\n", RequestToken.Status);
|
|
}
|
|
if ((RequestMessage.Headers != NULL) && (RequestMessage.Headers != mCommonHeader)) {
|
|
FreePool (RequestMessage.Headers);
|
|
}
|
|
return RequestToken.Status;
|
|
}
|
|
|
|
EFI_STATUS
|
|
RestHttpInput (
|
|
IN REST_HTTP_TOKEN *HttpToken,
|
|
IN BOOLEAN HeaderOnly
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_HTTP_MESSAGE ResponseMessage;
|
|
EFI_HTTP_RESPONSE_DATA ResponseData;
|
|
EFI_HTTP_PROTOCOL *Http;
|
|
EFI_EVENT TimeoutEvent;
|
|
UINT8 *Block;
|
|
UINTN BlockSize;
|
|
EFI_HTTP_TOKEN ResponseToken;
|
|
|
|
Block = NULL;
|
|
BlockSize = 0;
|
|
if (!HeaderOnly) {
|
|
if ((HttpToken->Response.BodyLength != 0) && (HttpToken->Response.Body != NULL)) {
|
|
Block = HttpToken->Response.Body;
|
|
BlockSize = HttpToken->Response.BodyLength;
|
|
} else {
|
|
Block = AllocateZeroPool (REST_BLOCK_SIZE);
|
|
if (Block == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
BlockSize = REST_BLOCK_SIZE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Start the timer, and wait Timeout seconds to receive the header packet.
|
|
//
|
|
TimeoutEvent = HttpToken->TimeoutEvent;
|
|
Status = gBS->SetTimer (TimeoutEvent, TimerRelative, HTTP_DELAY_TIME);
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
|
|
//
|
|
// Queue the response token to HTTP instances.
|
|
//
|
|
ZeroMem (&ResponseMessage, sizeof (EFI_HTTP_MESSAGE));
|
|
ZeroMem (&ResponseData, sizeof (EFI_HTTP_RESPONSE_DATA));
|
|
ZeroMem (&ResponseToken, sizeof (EFI_HTTP_TOKEN));
|
|
ResponseMessage.Data.Response = (HeaderOnly) ? &ResponseData : NULL;
|
|
ResponseMessage.Body = Block;
|
|
ResponseMessage.BodyLength = BlockSize;
|
|
ResponseMessage.HeaderCount = 0;
|
|
ResponseMessage.Headers = NULL;
|
|
ResponseToken.Message = &ResponseMessage;
|
|
ResponseToken.Status = EFI_NOT_READY;
|
|
ResponseToken.Event = HttpToken->ResponseEvent;
|
|
|
|
Http = HttpToken->Http;
|
|
HttpToken->IsRxDone = FALSE;
|
|
Status = Http->Response (
|
|
Http,
|
|
&ResponseToken
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
|
|
//
|
|
// Poll the network until receive finish.
|
|
//
|
|
while (!HttpToken->IsRxDone && EFI_ERROR (gBS->CheckEvent (TimeoutEvent))) {
|
|
Http->Poll (Http);
|
|
}
|
|
|
|
gBS->SetTimer (TimeoutEvent, TimerCancel, 0);
|
|
if (!HttpToken->IsRxDone) {
|
|
//
|
|
// Timeout occurs, cancel the response token.
|
|
//
|
|
Http->Cancel (Http, &ResponseToken);
|
|
Status = EFI_TIMEOUT;
|
|
goto EXIT;
|
|
}
|
|
|
|
if (DEBUG_MODE && HeaderOnly) {
|
|
AsciiPrint ("%08x %d %d\n", ResponseToken.Status, ResponseMessage.HeaderCount, ResponseMessage.BodyLength);
|
|
RestHttpHeaderDump (&ResponseMessage);
|
|
}
|
|
|
|
HttpToken->Response.Body = ResponseMessage.Body;
|
|
HttpToken->Response.BodyLength = ResponseMessage.BodyLength;
|
|
if ((ResponseMessage.HeaderCount != 0) && (ResponseMessage.Headers != NULL)) {
|
|
HttpToken->Response.HeaderCount = ResponseMessage.HeaderCount;
|
|
HttpToken->Response.Headers = ResponseMessage.Headers;
|
|
}
|
|
|
|
if (HeaderOnly) {
|
|
HttpToken->ReturnHttpStatusCode = ResponseMessage.Data.Response->StatusCode;
|
|
}
|
|
Status = ResponseToken.Status;
|
|
|
|
EXIT:
|
|
gBS->SetTimer (TimeoutEvent, TimerCancel, 0);
|
|
|
|
return Status;
|
|
}
|