/** @file
|
This file is cloned from DMTF libredfish library tag v1.0.0 and maintained
|
by EDKII.
|
|
//----------------------------------------------------------------------------
|
// Copyright Notice:
|
// Copyright 2017 Distributed Management Task Force, Inc. All rights reserved.
|
// License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libredfish/LICENSE.md
|
//----------------------------------------------------------------------------
|
|
Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>
|
(C) Copyright 2021 Hewlett Packard Enterprise Development LP<BR>
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
**/
|
|
#include <redfishService.h>
|
#include <redfishPayload.h>
|
#include <redpath.h>
|
|
static int initRest(redfishService* service, void * restProtocol);
|
static redfishService* createServiceEnumeratorNoAuth(const char* host, const char* rootUri, bool enumerate, unsigned int flags, void * restProtocol);
|
static redfishService* createServiceEnumeratorBasicAuth(const char* host, const char* rootUri, const char* username, const char* password, unsigned int flags, void * restProtocol);
|
static redfishService* createServiceEnumeratorSessionAuth(const char* host, const char* rootUri, const char* username, const char* password, unsigned int flags, void * restProtocol);
|
static char* makeUrlForService(redfishService* service, const char* uri);
|
static json_t* getVersions(redfishService* service, const char* rootUri);
|
static void addStringToJsonObject(json_t* object, const char* key, const char* value);
|
|
CHAR16*
|
C8ToC16 (CHAR8 *AsciiStr)
|
{
|
CHAR16 *Str;
|
UINTN BufLen;
|
|
BufLen = (AsciiStrLen (AsciiStr) + 1) * 2;
|
Str = AllocatePool (BufLen);
|
ASSERT (Str != NULL);
|
|
AsciiStrToUnicodeStrS (AsciiStr, Str, AsciiStrLen (AsciiStr) + 1);
|
|
return Str;
|
}
|
|
VOID
|
RestConfigFreeHttpRequestData (
|
IN EFI_HTTP_REQUEST_DATA *RequestData
|
)
|
{
|
if (RequestData == NULL) {
|
return ;
|
}
|
|
if (RequestData->Url != NULL) {
|
FreePool (RequestData->Url);
|
}
|
|
FreePool (RequestData);
|
}
|
|
VOID
|
RestConfigFreeHttpMessage (
|
IN EFI_HTTP_MESSAGE *Message,
|
IN BOOLEAN IsRequest
|
)
|
{
|
if (Message == NULL) {
|
return ;
|
}
|
|
if (IsRequest) {
|
RestConfigFreeHttpRequestData (Message->Data.Request);
|
Message->Data.Request = NULL;
|
} else {
|
if (Message->Data.Response != NULL) {
|
FreePool (Message->Data.Response);
|
Message->Data.Response = NULL;
|
}
|
}
|
|
if (Message->Headers != NULL) {
|
FreePool (Message->Headers);
|
Message->Headers = NULL;
|
}
|
if (Message->Body != NULL) {
|
FreePool (Message->Body);
|
Message->Body = NULL;
|
}
|
}
|
|
/**
|
Converts the Unicode string to ASCII string to a new allocated buffer.
|
|
@param[in] String Unicode string to be converted.
|
|
@return Buffer points to ASCII string, or NULL if error happens.
|
|
**/
|
|
CHAR8 *
|
UnicodeStrDupToAsciiStr (
|
CONST CHAR16 *String
|
)
|
{
|
CHAR8 *AsciiStr;
|
UINTN BufLen;
|
EFI_STATUS Status;
|
|
BufLen = StrLen (String) + 1;
|
AsciiStr = AllocatePool (BufLen);
|
if (AsciiStr == NULL) {
|
return NULL;
|
}
|
|
Status = UnicodeStrToAsciiStrS (String, AsciiStr, BufLen);
|
if (EFI_ERROR (Status)) {
|
return NULL;
|
}
|
|
return AsciiStr;
|
}
|
/**
|
This function encodes the content.
|
|
@param[in] ContentEncodedValue HTTP conent encoded value.
|
@param[in] OriginalContent Original content.
|
@param[out] EncodedContent Pointer to receive encoded content.
|
@param[out] EncodedContentLength Length of encoded content.
|
|
@retval EFI_SUCCESS Content encoded successfully.
|
@retval EFI_UNSUPPORTED No source encoding funciton,
|
@retval EFI_INVALID_PARAMETER One of the given parameter is invalid.
|
|
**/
|
EFI_STATUS
|
EncodeRequestContent (
|
IN CHAR8 *ContentEncodedValue,
|
IN CHAR8 *OriginalContent,
|
OUT VOID **EncodedContent,
|
OUT UINTN *EncodedContentLength
|
)
|
{
|
EFI_STATUS Status;
|
VOID *EncodedPointer;
|
UINTN EncodedLength;
|
|
if (OriginalContent == NULL || EncodedContent == NULL || EncodedContentLength == NULL) {
|
return EFI_INVALID_PARAMETER;
|
}
|
Status = RedfishContentEncode (
|
ContentEncodedValue,
|
OriginalContent,
|
AsciiStrLen (OriginalContent),
|
&EncodedPointer,
|
&EncodedLength
|
);
|
if (Status == EFI_SUCCESS) {
|
*EncodedContent = EncodedPointer;
|
*EncodedContentLength = EncodedLength;
|
return EFI_SUCCESS;
|
}
|
return Status;
|
}
|
|
/**
|
This function decodes the content. The Memory block pointed by
|
ContentPointer would be freed and replaced with the cooked Redfish
|
payload.
|
|
@param[in] ContentEncodedValue HTTP conent encoded value.
|
@param[in, out] ContentPointer Pointer to encoded content.
|
Pointer of decoded content when out.
|
@param[in, out] ContentLength Pointer to the length of encoded content.
|
Length of decoded content when out.
|
|
@retval EFI_SUCCESS Functinos found.
|
@retval EFI_UNSUPPORTED No functions found.
|
@retval EFI_INVALID_PARAMETER One of the given parameter is invalid.
|
|
**/
|
EFI_STATUS
|
DecodeResponseContent (
|
IN CHAR8 *ContentEncodedValue,
|
IN OUT VOID **ContentPointer,
|
IN OUT UINTN *ContentLength
|
)
|
{
|
EFI_STATUS Status;
|
VOID *DecodedPointer;
|
UINTN DecodedLength;
|
|
if (ContentEncodedValue == NULL) {
|
return EFI_INVALID_PARAMETER;
|
}
|
Status = RedfishContentDecode (
|
ContentEncodedValue,
|
*ContentPointer,
|
*ContentLength,
|
&DecodedPointer,
|
&DecodedLength
|
);
|
if (Status == EFI_SUCCESS) {
|
FreePool (*ContentPointer);
|
*ContentPointer = DecodedPointer;
|
*ContentLength = DecodedLength;
|
}
|
return Status;
|
}
|
|
/**
|
Create a HTTP URL string for specific Redfish resource.
|
|
This function build a URL string from the Redfish Host interface record and caller specified
|
relative path of the resource.
|
|
Callers are responsible for freeing the returned string storage pointed by HttpUrl.
|
|
@param[in] RedfishData Redfish network host interface record.
|
@param[in] RelativePath Relative path of a resource.
|
@param[out] HttpUrl The pointer to store the returned URL string.
|
|
@retval EFI_SUCCESS Build the URL string successfully.
|
@retval EFI_INVALID_PARAMETER RedfishData or HttpUrl is NULL.
|
@retval EFI_OUT_OF_RESOURCES There are not enough memory resources.
|
|
**/
|
EFI_STATUS
|
RedfishBuildUrl (
|
IN REDFISH_CONFIG_SERVICE_INFORMATION *RedfishConfigServiceInfo,
|
IN CHAR16 *RelativePath, OPTIONAL
|
OUT CHAR16 **HttpUrl
|
)
|
{
|
CHAR16 *Url;
|
CHAR16 *UrlHead;
|
UINTN UrlLength;
|
UINTN PathLen;
|
|
if ((RedfishConfigServiceInfo == NULL) || (HttpUrl == NULL)) {
|
return EFI_INVALID_PARAMETER;
|
}
|
|
//
|
// RFC2616: http_URL = "http(s):" "//" host [ ":" port ] [ abs_path [ "?" query ]]
|
//
|
if (RelativePath == NULL) {
|
PathLen = 0;
|
} else {
|
PathLen = StrLen (RelativePath);
|
}
|
UrlLength = StrLen (HTTPS_FLAG) + StrLen (REDFISH_FIRST_URL) + 1 + StrLen(RedfishConfigServiceInfo->RedfishServiceLocation) + PathLen;
|
Url = AllocateZeroPool (UrlLength * sizeof (CHAR16));
|
if (Url == NULL) {
|
return EFI_OUT_OF_RESOURCES;
|
}
|
|
UrlHead = Url;
|
//
|
// Copy "http://" or "https://" according RedfishServiceIpPort.
|
//
|
if (!RedfishConfigServiceInfo->RedfishServiceUseHttps) {
|
StrCpyS (Url, StrLen (HTTPS_FLAG) + 1, HTTP_FLAG);
|
Url = Url + StrLen (HTTP_FLAG);
|
} else {
|
StrCpyS (Url, StrLen (HTTPS_FLAG) + 1, HTTPS_FLAG);
|
Url = Url + StrLen (HTTPS_FLAG);
|
}
|
|
StrCpyS (Url, StrLen (RedfishConfigServiceInfo->RedfishServiceLocation) + 1, RedfishConfigServiceInfo->RedfishServiceLocation);
|
Url = Url + StrLen (RedfishConfigServiceInfo->RedfishServiceLocation);
|
|
//
|
// Copy abs_path
|
//
|
if (RelativePath != NULL && PathLen != 0 ) {
|
StrnCpyS (Url, UrlLength, RelativePath, PathLen);
|
}
|
*HttpUrl = UrlHead;
|
return EFI_SUCCESS;
|
}
|
|
redfishService* createServiceEnumerator(REDFISH_CONFIG_SERVICE_INFORMATION *RedfishConfigServiceInfo, const char* rootUri, enumeratorAuthentication* auth, unsigned int flags)
|
{
|
EFI_STATUS Status;
|
CHAR16 *HttpUrl;
|
CHAR8 *AsciiHost;
|
EFI_REST_EX_PROTOCOL *RestEx;
|
redfishService *ret;
|
|
HttpUrl = NULL;
|
AsciiHost = NULL;
|
RestEx = NULL;
|
ret = NULL;
|
|
if (RedfishConfigServiceInfo->RedfishServiceRestExHandle == NULL) {
|
goto ON_EXIT;
|
}
|
Status = RedfishBuildUrl(RedfishConfigServiceInfo, NULL, &HttpUrl);
|
if (EFI_ERROR (Status)) {
|
goto ON_EXIT;
|
}
|
|
ASSERT (HttpUrl != NULL);
|
|
AsciiHost = UnicodeStrDupToAsciiStr (HttpUrl);
|
if (AsciiHost == NULL) {
|
goto ON_EXIT;
|
}
|
|
Status = gBS->HandleProtocol (
|
RedfishConfigServiceInfo->RedfishServiceRestExHandle,
|
&gEfiRestExProtocolGuid,
|
(VOID **)&RestEx
|
);
|
if (EFI_ERROR (Status)) {
|
goto ON_EXIT;
|
}
|
if(auth == NULL) {
|
ret = createServiceEnumeratorNoAuth(AsciiHost, rootUri, true, flags, RestEx);
|
} else if(auth->authType == REDFISH_AUTH_BASIC) {
|
ret = createServiceEnumeratorBasicAuth(AsciiHost, rootUri, auth->authCodes.userPass.username, auth->authCodes.userPass.password, flags, RestEx);
|
} else if(auth->authType == REDFISH_AUTH_SESSION) {
|
ret = createServiceEnumeratorSessionAuth(AsciiHost, rootUri, auth->authCodes.userPass.username, auth->authCodes.userPass.password, flags, RestEx);
|
} else {
|
goto ON_EXIT;
|
}
|
|
ret->RestEx = RestEx;
|
ON_EXIT:
|
if (HttpUrl != NULL) {
|
FreePool (HttpUrl);
|
}
|
|
if (AsciiHost != NULL) {
|
FreePool (AsciiHost);
|
}
|
|
return ret;
|
}
|
|
json_t* getUriFromService(redfishService* service, const char* uri, EFI_HTTP_STATUS_CODE** StatusCode)
|
{
|
char* url;
|
json_t* ret;
|
HTTP_IO_HEADER *HttpIoHeader = NULL;
|
EFI_STATUS Status;
|
EFI_HTTP_REQUEST_DATA *RequestData = NULL;
|
EFI_HTTP_MESSAGE *RequestMsg = NULL;
|
EFI_HTTP_MESSAGE ResponseMsg;
|
EFI_HTTP_HEADER *ContentEncodedHeader;
|
|
if(service == NULL || uri == NULL || StatusCode == NULL)
|
{
|
return NULL;
|
}
|
|
*StatusCode = NULL;
|
|
url = makeUrlForService(service, uri);
|
if(!url)
|
{
|
return NULL;
|
}
|
|
DEBUG((DEBUG_INFO, "libredfish: getUriFromService(): %a\n", url));
|
|
//
|
// Step 1: Create HTTP request message with 4 headers:
|
//
|
HttpIoHeader = HttpIoCreateHeader ((service->sessionToken || service->basicAuthStr) ? 6 : 5);
|
if (HttpIoHeader == NULL) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
if(service->sessionToken)
|
{
|
Status = HttpIoSetHeader (HttpIoHeader, "X-Auth-Token", service->sessionToken);
|
ASSERT_EFI_ERROR (Status);
|
} else if (service->basicAuthStr) {
|
Status = HttpIoSetHeader (HttpIoHeader, "Authorization", service->basicAuthStr);
|
ASSERT_EFI_ERROR (Status);
|
}
|
|
Status = HttpIoSetHeader (HttpIoHeader, "Host", service->HostHeaderValue);
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "OData-Version", "4.0");
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "Accept", "application/json");
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "User-Agent", "libredfish");
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "Connection", "Keep-Alive");
|
ASSERT_EFI_ERROR (Status);
|
|
//
|
// Step 2: build the rest of HTTP request info.
|
//
|
RequestData = AllocateZeroPool (sizeof (EFI_HTTP_REQUEST_DATA));
|
if (RequestData == NULL) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
RequestData->Method = HttpMethodGet;
|
RequestData->Url = C8ToC16 (url);
|
|
//
|
// Step 3: fill in EFI_HTTP_MESSAGE
|
//
|
RequestMsg = AllocateZeroPool (sizeof (EFI_HTTP_MESSAGE));
|
if (RequestMsg == NULL) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
RequestMsg->Data.Request = RequestData;
|
RequestMsg->HeaderCount = HttpIoHeader->HeaderCount;
|
RequestMsg->Headers = HttpIoHeader->Headers;
|
|
ZeroMem (&ResponseMsg, sizeof (ResponseMsg));
|
|
//
|
// Step 4: call RESTEx to get response from REST service.
|
//
|
Status = service->RestEx->SendReceive (service->RestEx, RequestMsg, &ResponseMsg);
|
if (EFI_ERROR (Status)) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
//
|
// Step 5: Return the HTTP StatusCode and Body message.
|
//
|
if (ResponseMsg.Data.Response != NULL) {
|
*StatusCode = AllocateZeroPool (sizeof (EFI_HTTP_STATUS_CODE));
|
if (*StatusCode == NULL) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
//
|
// The caller shall take the responsibility to free the buffer.
|
//
|
**StatusCode = ResponseMsg.Data.Response->StatusCode;
|
}
|
|
if (ResponseMsg.BodyLength != 0 && ResponseMsg.Body != NULL) {
|
//
|
// Check if data is encoded.
|
//
|
ContentEncodedHeader = HttpFindHeader (ResponseMsg.HeaderCount, ResponseMsg.Headers, HTTP_HEADER_CONTENT_ENCODING);
|
if (ContentEncodedHeader != NULL) {
|
//
|
// The content is encoded.
|
//
|
Status = DecodeResponseContent (ContentEncodedHeader->FieldValue, &ResponseMsg.Body, &ResponseMsg.BodyLength);
|
if (EFI_ERROR (Status)) {
|
DEBUG ((DEBUG_ERROR, "%a: Failed to decompress the response content %r\n.", __FUNCTION__, Status));
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
}
|
ret = json_loadb (ResponseMsg.Body, ResponseMsg.BodyLength, 0, NULL);
|
} else {
|
//
|
// There is no message body returned from server.
|
//
|
ret = NULL;
|
}
|
|
ON_EXIT:
|
if (url != NULL) {
|
free (url);
|
}
|
|
if (HttpIoHeader != NULL) {
|
HttpIoFreeHeader (HttpIoHeader);
|
}
|
|
if (RequestData != NULL) {
|
RestConfigFreeHttpRequestData (RequestData);
|
}
|
|
if (RequestMsg != NULL) {
|
FreePool (RequestMsg);
|
}
|
|
RestConfigFreeHttpMessage (&ResponseMsg, FALSE);
|
|
return ret;
|
}
|
|
json_t* patchUriFromService(redfishService* service, const char* uri, const char* content, EFI_HTTP_STATUS_CODE** StatusCode)
|
{
|
char* url;
|
json_t* ret;
|
HTTP_IO_HEADER *HttpIoHeader = NULL;
|
EFI_STATUS Status;
|
EFI_HTTP_REQUEST_DATA *RequestData = NULL;
|
EFI_HTTP_MESSAGE *RequestMsg = NULL;
|
EFI_HTTP_MESSAGE ResponseMsg;
|
CHAR8 ContentLengthStr[80];
|
CHAR8 *EncodedContent;
|
UINTN EncodedContentLen;
|
|
if(service == NULL || uri == NULL || content == NULL || StatusCode == NULL)
|
{
|
return NULL;
|
}
|
|
*StatusCode = NULL;
|
|
url = makeUrlForService(service, uri);
|
if(!url)
|
{
|
return NULL;
|
}
|
|
DEBUG((DEBUG_INFO, "libredfish: patchUriFromService(): %a\n", url));
|
|
//
|
// Step 1: Create HTTP request message with 4 headers:
|
//
|
HttpIoHeader = HttpIoCreateHeader ((service->sessionToken || service->basicAuthStr) ? 9 : 8);
|
if (HttpIoHeader == NULL) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
if(service->sessionToken)
|
{
|
Status = HttpIoSetHeader (HttpIoHeader, "X-Auth-Token", service->sessionToken);
|
ASSERT_EFI_ERROR (Status);
|
} else if (service->basicAuthStr) {
|
Status = HttpIoSetHeader (HttpIoHeader, "Authorization", service->basicAuthStr);
|
ASSERT_EFI_ERROR (Status);
|
}
|
|
Status = HttpIoSetHeader (HttpIoHeader, "Host", service->HostHeaderValue);
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "Content-Type", "application/json");
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "Accept", "application/json");
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "User-Agent", "libredfish");
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "Connection", "Keep-Alive");
|
ASSERT_EFI_ERROR (Status);
|
|
AsciiSPrint(
|
ContentLengthStr,
|
sizeof (ContentLengthStr),
|
"%lu",
|
(UINT64) strlen(content)
|
);
|
Status = HttpIoSetHeader (HttpIoHeader, "Content-Length", ContentLengthStr);
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "OData-Version", "4.0");
|
ASSERT_EFI_ERROR (Status);
|
|
//
|
// Step 2: build the rest of HTTP request info.
|
//
|
RequestData = AllocateZeroPool (sizeof (EFI_HTTP_REQUEST_DATA));
|
if (RequestData == NULL) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
RequestData->Method = HttpMethodPatch;
|
RequestData->Url = C8ToC16 (url);
|
|
//
|
// Step 3: fill in EFI_HTTP_MESSAGE
|
//
|
RequestMsg = AllocateZeroPool (sizeof (EFI_HTTP_MESSAGE));
|
if (RequestMsg == NULL) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
EncodedContent = (CHAR8 *)content;
|
EncodedContentLen = strlen(content);
|
//
|
// We currently only support gzip Content-Encoding.
|
//
|
Status = EncodeRequestContent ((CHAR8 *)HTTP_CONTENT_ENCODING_GZIP, (CHAR8 *)content, (VOID **)&EncodedContent, &EncodedContentLen);
|
if (Status == EFI_INVALID_PARAMETER) {
|
DEBUG((DEBUG_ERROR, "%a: Error to encode content.\n", __FUNCTION__));
|
ret = NULL;
|
goto ON_EXIT;
|
} else if (Status == EFI_UNSUPPORTED) {
|
DEBUG((DEBUG_INFO, "No content coding for %a! Use raw data instead.\n", HTTP_CONTENT_ENCODING_GZIP));
|
Status = HttpIoSetHeader (HttpIoHeader, "Content-Encoding", HTTP_CONTENT_ENCODING_IDENTITY);
|
ASSERT_EFI_ERROR (Status);
|
} else {
|
Status = HttpIoSetHeader (HttpIoHeader, "Content-Encoding", HTTP_CONTENT_ENCODING_GZIP);
|
ASSERT_EFI_ERROR (Status);
|
}
|
|
RequestMsg->Data.Request = RequestData;
|
RequestMsg->HeaderCount = HttpIoHeader->HeaderCount;
|
RequestMsg->Headers = HttpIoHeader->Headers;
|
RequestMsg->BodyLength = EncodedContentLen;
|
RequestMsg->Body = (VOID*) EncodedContent;
|
|
ZeroMem (&ResponseMsg, sizeof (ResponseMsg));
|
|
//
|
// Step 4: call RESTEx to get response from REST service.
|
//
|
Status = service->RestEx->SendReceive (service->RestEx, RequestMsg, &ResponseMsg);
|
if (EFI_ERROR (Status)) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
//
|
// Step 5: Return the HTTP StatusCode and Body message.
|
//
|
if (ResponseMsg.Data.Response != NULL) {
|
*StatusCode = AllocateZeroPool (sizeof (EFI_HTTP_STATUS_CODE));
|
if (*StatusCode == NULL) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
//
|
// The caller shall take the responsibility to free the buffer.
|
//
|
**StatusCode = ResponseMsg.Data.Response->StatusCode;
|
}
|
|
if (EncodedContent != content) {
|
FreePool (EncodedContent);
|
}
|
|
|
if (ResponseMsg.BodyLength != 0 && ResponseMsg.Body != NULL) {
|
ret = json_loadb (ResponseMsg.Body, ResponseMsg.BodyLength, 0, NULL);
|
} else {
|
//
|
// There is no message body returned from server.
|
//
|
ret = NULL;
|
}
|
|
ON_EXIT:
|
if (url != NULL) {
|
free (url);
|
}
|
|
if (HttpIoHeader != NULL) {
|
HttpIoFreeHeader (HttpIoHeader);
|
}
|
|
if (RequestData != NULL) {
|
RestConfigFreeHttpRequestData (RequestData);
|
}
|
|
if (RequestMsg != NULL) {
|
FreePool (RequestMsg);
|
}
|
|
RestConfigFreeHttpMessage (&ResponseMsg, FALSE);
|
|
return ret;
|
}
|
|
json_t* postUriFromService(redfishService* service, const char* uri, const char* content, size_t contentLength, const char* contentType, EFI_HTTP_STATUS_CODE** StatusCode)
|
{
|
char* url = NULL;
|
json_t* ret;
|
HTTP_IO_HEADER *HttpIoHeader = NULL;
|
EFI_STATUS Status;
|
EFI_HTTP_REQUEST_DATA *RequestData = NULL;
|
EFI_HTTP_MESSAGE *RequestMsg = NULL;
|
EFI_HTTP_MESSAGE ResponseMsg;
|
CHAR8 ContentLengthStr[80];
|
EFI_HTTP_HEADER *HttpHeader = NULL;
|
|
ret = NULL;
|
|
if(service == NULL || uri == NULL || content == NULL || StatusCode == NULL)
|
{
|
return NULL;
|
}
|
|
*StatusCode = NULL;
|
|
url = makeUrlForService(service, uri);
|
if(!url)
|
{
|
return NULL;
|
}
|
|
DEBUG((DEBUG_INFO, "libredfish: postUriFromService(): %a\n", url));
|
|
if(contentLength == 0)
|
{
|
contentLength = strlen(content);
|
}
|
|
//
|
// Step 1: Create HTTP request message with 4 headers:
|
//
|
HttpIoHeader = HttpIoCreateHeader ((service->sessionToken || service->basicAuthStr) ? 8 : 7);
|
if (HttpIoHeader == NULL) {
|
goto ON_EXIT;
|
}
|
|
if(service->sessionToken)
|
{
|
Status = HttpIoSetHeader (HttpIoHeader, "X-Auth-Token", service->sessionToken);
|
ASSERT_EFI_ERROR (Status);
|
} else if (service->basicAuthStr) {
|
Status = HttpIoSetHeader (HttpIoHeader, "Authorization", service->basicAuthStr);
|
ASSERT_EFI_ERROR (Status);
|
}
|
|
if(contentType == NULL) {
|
Status = HttpIoSetHeader (HttpIoHeader, "Content-Type", "application/json");
|
ASSERT_EFI_ERROR (Status);
|
} else {
|
Status = HttpIoSetHeader (HttpIoHeader, "Content-Type", (CHAR8 *) contentType);
|
ASSERT_EFI_ERROR (Status);
|
}
|
Status = HttpIoSetHeader (HttpIoHeader, "Host", service->HostHeaderValue);
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "Accept", "application/json");
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "User-Agent", "libredfish");
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "Connection", "Keep-Alive");
|
ASSERT_EFI_ERROR (Status);
|
AsciiSPrint(
|
ContentLengthStr,
|
sizeof (ContentLengthStr),
|
"%lu",
|
(UINT64) contentLength
|
);
|
Status = HttpIoSetHeader (HttpIoHeader, "Content-Length", ContentLengthStr);
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "OData-Version", "4.0");
|
ASSERT_EFI_ERROR (Status);
|
|
//
|
// Step 2: build the rest of HTTP request info.
|
//
|
RequestData = AllocateZeroPool (sizeof (EFI_HTTP_REQUEST_DATA));
|
if (RequestData == NULL) {
|
goto ON_EXIT;
|
}
|
|
RequestData->Method = HttpMethodPost;
|
RequestData->Url = C8ToC16 (url);
|
|
//
|
// Step 3: fill in EFI_HTTP_MESSAGE
|
//
|
RequestMsg = AllocateZeroPool (sizeof (EFI_HTTP_MESSAGE));
|
if (RequestMsg == NULL) {
|
goto ON_EXIT;
|
}
|
|
RequestMsg->Data.Request = RequestData;
|
RequestMsg->HeaderCount = HttpIoHeader->HeaderCount;
|
RequestMsg->Headers = HttpIoHeader->Headers;
|
RequestMsg->BodyLength = contentLength;
|
RequestMsg->Body = (VOID*) content;
|
|
ZeroMem (&ResponseMsg, sizeof (ResponseMsg));
|
|
//
|
// Step 4: call RESTEx to get response from REST service.
|
//
|
Status = service->RestEx->SendReceive (service->RestEx, RequestMsg, &ResponseMsg);
|
if (EFI_ERROR (Status)) {
|
goto ON_EXIT;
|
}
|
|
//
|
// Step 5: Return the HTTP StatusCode and Body message.
|
//
|
if (ResponseMsg.Data.Response != NULL) {
|
*StatusCode = AllocateZeroPool (sizeof (EFI_HTTP_STATUS_CODE));
|
if (*StatusCode == NULL) {
|
goto ON_EXIT;
|
}
|
|
//
|
// The caller shall take the responsibility to free the buffer.
|
//
|
**StatusCode = ResponseMsg.Data.Response->StatusCode;
|
}
|
|
if (ResponseMsg.BodyLength != 0 && ResponseMsg.Body != NULL) {
|
ret = json_loadb (ResponseMsg.Body, ResponseMsg.BodyLength, 0, NULL);
|
}
|
|
//
|
// Step 6: Parsing the HttpHeader to retrive the X-Auth-Token if the HTTP StatusCode is correct.
|
//
|
if (ResponseMsg.Data.Response->StatusCode == HTTP_STATUS_200_OK ||
|
ResponseMsg.Data.Response->StatusCode == HTTP_STATUS_204_NO_CONTENT) {
|
HttpHeader = HttpFindHeader (ResponseMsg.HeaderCount, ResponseMsg.Headers, "X-Auth-Token");
|
if (HttpHeader != NULL) {
|
if(service->sessionToken)
|
{
|
free(service->sessionToken);
|
}
|
service->sessionToken = AllocateCopyPool (AsciiStrSize (HttpHeader->FieldValue), HttpHeader->FieldValue);
|
}
|
|
/*
|
//
|
// Below opeation seems to be unnecessary.
|
// Besides, the FieldValue for the Location is the full HTTP URI (Http://0.0.0.0:5000/XXX), so we can't use it as the
|
// parameter of getUriFromService () directly.
|
//
|
HttpHeader = HttpFindHeader (ResponseMsg.HeaderCount, ResponseMsg.Headers, "Location");
|
if (HttpHeader != NULL) {
|
ret = getUriFromService(service, HttpHeader->FieldValue);
|
goto ON_EXIT;
|
}
|
*/
|
}
|
|
ON_EXIT:
|
if (url != NULL) {
|
free (url);
|
}
|
|
if (HttpIoHeader != NULL) {
|
HttpIoFreeHeader (HttpIoHeader);
|
}
|
|
if (RequestData != NULL) {
|
RestConfigFreeHttpRequestData (RequestData);
|
}
|
|
if (RequestMsg != NULL) {
|
FreePool (RequestMsg);
|
}
|
|
RestConfigFreeHttpMessage (&ResponseMsg, FALSE);
|
|
return ret;
|
}
|
|
json_t* deleteUriFromService(redfishService* service, const char* uri, EFI_HTTP_STATUS_CODE** StatusCode)
|
{
|
char* url;
|
json_t* ret;
|
HTTP_IO_HEADER *HttpIoHeader = NULL;
|
EFI_STATUS Status;
|
EFI_HTTP_REQUEST_DATA *RequestData = NULL;
|
EFI_HTTP_MESSAGE *RequestMsg = NULL;
|
EFI_HTTP_MESSAGE ResponseMsg;
|
|
ret = NULL;
|
|
if(service == NULL || uri == NULL || StatusCode == NULL)
|
{
|
return NULL;
|
}
|
|
*StatusCode = NULL;
|
|
url = makeUrlForService(service, uri);
|
if(!url)
|
{
|
return NULL;
|
}
|
|
DEBUG((DEBUG_INFO, "libredfish: deleteUriFromService(): %a\n", url));
|
|
//
|
// Step 1: Create HTTP request message with 4 headers:
|
//
|
HttpIoHeader = HttpIoCreateHeader ((service->sessionToken || service->basicAuthStr) ? 5 : 4);
|
if (HttpIoHeader == NULL) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
if(service->sessionToken)
|
{
|
Status = HttpIoSetHeader (HttpIoHeader, "X-Auth-Token", service->sessionToken);
|
ASSERT_EFI_ERROR (Status);
|
} else if (service->basicAuthStr) {
|
Status = HttpIoSetHeader (HttpIoHeader, "Authorization", service->basicAuthStr);
|
ASSERT_EFI_ERROR (Status);
|
}
|
Status = HttpIoSetHeader (HttpIoHeader, "Host", service->HostHeaderValue);
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "User-Agent", "libredfish");
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "OData-Version", "4.0");
|
ASSERT_EFI_ERROR (Status);
|
Status = HttpIoSetHeader (HttpIoHeader, "Connection", "Keep-Alive");
|
ASSERT_EFI_ERROR (Status);
|
|
//
|
// Step 2: build the rest of HTTP request info.
|
//
|
RequestData = AllocateZeroPool (sizeof (EFI_HTTP_REQUEST_DATA));
|
if (RequestData == NULL) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
RequestData->Method = HttpMethodDelete;
|
RequestData->Url = C8ToC16 (url);
|
|
//
|
// Step 3: fill in EFI_HTTP_MESSAGE
|
//
|
RequestMsg = AllocateZeroPool (sizeof (EFI_HTTP_MESSAGE));
|
if (RequestMsg == NULL) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
RequestMsg->Data.Request = RequestData;
|
RequestMsg->HeaderCount = HttpIoHeader->HeaderCount;
|
RequestMsg->Headers = HttpIoHeader->Headers;
|
|
ZeroMem (&ResponseMsg, sizeof (ResponseMsg));
|
|
//
|
// Step 4: call RESTEx to get response from REST service.
|
//
|
Status = service->RestEx->SendReceive (service->RestEx, RequestMsg, &ResponseMsg);
|
if (EFI_ERROR (Status)) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
//
|
// Step 5: Return the HTTP StatusCode and Body message.
|
//
|
if (ResponseMsg.Data.Response != NULL) {
|
*StatusCode = AllocateZeroPool (sizeof (EFI_HTTP_STATUS_CODE));
|
if (*StatusCode == NULL) {
|
ret = NULL;
|
goto ON_EXIT;
|
}
|
|
//
|
// The caller shall take the responsibility to free the buffer.
|
//
|
**StatusCode = ResponseMsg.Data.Response->StatusCode;
|
}
|
|
if (ResponseMsg.BodyLength != 0 && ResponseMsg.Body != NULL) {
|
ret = json_loadb (ResponseMsg.Body, ResponseMsg.BodyLength, 0, NULL);
|
}
|
|
ON_EXIT:
|
if (url != NULL) {
|
free (url);
|
}
|
|
if (HttpIoHeader != NULL) {
|
HttpIoFreeHeader (HttpIoHeader);
|
}
|
|
if (RequestData != NULL) {
|
RestConfigFreeHttpRequestData (RequestData);
|
}
|
|
if (RequestMsg != NULL) {
|
FreePool (RequestMsg);
|
}
|
|
RestConfigFreeHttpMessage (&ResponseMsg, FALSE);
|
|
return ret;
|
}
|
|
redfishPayload* getRedfishServiceRoot(redfishService* service, const char* version, EFI_HTTP_STATUS_CODE** StatusCode)
|
{
|
json_t* value;
|
json_t* versionNode;
|
const char* verUrl;
|
|
if(version == NULL)
|
{
|
versionNode = json_object_get(service->versions, "v1");
|
}
|
else
|
{
|
versionNode = json_object_get(service->versions, version);
|
}
|
if(versionNode == NULL)
|
{
|
return NULL;
|
}
|
verUrl = json_string_value(versionNode);
|
if(verUrl == NULL)
|
{
|
return NULL;
|
}
|
value = getUriFromService(service, verUrl, StatusCode);
|
if(value == NULL)
|
{
|
if((service->flags & REDFISH_FLAG_SERVICE_NO_VERSION_DOC) == 0)
|
{
|
json_decref(versionNode);
|
}
|
return NULL;
|
}
|
return createRedfishPayload(value, service);
|
}
|
|
redfishPayload* getPayloadByPath(redfishService* service, const char* path, EFI_HTTP_STATUS_CODE** StatusCode)
|
{
|
redPathNode* redpath;
|
redfishPayload* root;
|
redfishPayload* ret;
|
|
if(!service || !path || StatusCode == NULL)
|
{
|
return NULL;
|
}
|
|
*StatusCode = NULL;
|
|
redpath = parseRedPath(path);
|
if(!redpath)
|
{
|
return NULL;
|
}
|
if(!redpath->isRoot)
|
{
|
cleanupRedPath(redpath);
|
return NULL;
|
}
|
root = getRedfishServiceRoot(service, redpath->version, StatusCode);
|
if (*StatusCode == NULL || **StatusCode < HTTP_STATUS_200_OK || **StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT) {
|
cleanupRedPath(redpath);
|
return root;
|
}
|
|
if(redpath->next == NULL)
|
{
|
cleanupRedPath(redpath);
|
return root;
|
}
|
|
FreePool (*StatusCode);
|
*StatusCode = NULL;
|
|
ret = getPayloadForPath(root, redpath->next, StatusCode);
|
if (*StatusCode == NULL && ret != NULL) {
|
//
|
// In such a case, the Redfish resource is parsed from the input payload (root) directly.
|
// So, we still return HTTP_STATUS_200_OK.
|
//
|
*StatusCode = AllocateZeroPool (sizeof (EFI_HTTP_STATUS_CODE));
|
if (*StatusCode == NULL) {
|
ret = NULL;
|
} else {
|
**StatusCode = HTTP_STATUS_200_OK;
|
}
|
}
|
cleanupPayload(root);
|
cleanupRedPath(redpath);
|
return ret;
|
}
|
|
void cleanupServiceEnumerator(redfishService* service)
|
{
|
if(!service)
|
{
|
return;
|
}
|
free(service->host);
|
json_decref(service->versions);
|
if(service->sessionToken != NULL)
|
{
|
ZeroMem (service->sessionToken, (UINTN)strlen(service->sessionToken));
|
FreePool(service->sessionToken);
|
}
|
if (service->basicAuthStr != NULL) {
|
ZeroMem (service->basicAuthStr, (UINTN)strlen(service->basicAuthStr));
|
FreePool (service->basicAuthStr);
|
}
|
free(service);
|
}
|
|
static int initRest(redfishService* service, void * restProtocol)
|
{
|
service->RestEx = restProtocol;
|
return 0;
|
}
|
|
static redfishService* createServiceEnumeratorNoAuth(const char* host, const char* rootUri, bool enumerate, unsigned int flags, void * restProtocol)
|
{
|
redfishService* ret;
|
char *HostStart;
|
|
ret = (redfishService*)calloc(1, sizeof(redfishService));
|
ZeroMem (ret, sizeof(redfishService));
|
if(initRest(ret, restProtocol) != 0)
|
{
|
free(ret);
|
return NULL;
|
}
|
ret->host = AllocateCopyPool(AsciiStrSize(host), host);
|
ret->flags = flags;
|
if(enumerate)
|
{
|
ret->versions = getVersions(ret, rootUri);
|
}
|
HostStart = strstr (ret->host, "//");
|
if (HostStart != NULL && (*(HostStart + 2) != '\0')) {
|
ret->HostHeaderValue = HostStart + 2;
|
}
|
|
return ret;
|
}
|
|
EFI_STATUS
|
createBasicAuthStr (
|
IN redfishService* service,
|
IN CONST CHAR8 *UserId,
|
IN CONST CHAR8 *Password
|
)
|
{
|
EFI_STATUS Status;
|
CHAR8 *RawAuthValue;
|
UINTN RawAuthBufSize;
|
CHAR8 *EnAuthValue;
|
UINTN EnAuthValueSize;
|
CHAR8 *BasicWithEnAuthValue;
|
UINTN BasicBufSize;
|
|
EnAuthValue = NULL;
|
EnAuthValueSize = 0;
|
|
RawAuthBufSize = AsciiStrLen (UserId) + AsciiStrLen (Password) + 2;
|
RawAuthValue = AllocatePool (RawAuthBufSize);
|
if (RawAuthValue == NULL) {
|
return EFI_OUT_OF_RESOURCES;
|
}
|
|
//
|
// Build raw AuthValue (UserId:Password).
|
//
|
AsciiSPrint (
|
RawAuthValue,
|
RawAuthBufSize,
|
"%a:%a",
|
UserId,
|
Password
|
);
|
|
//
|
// Encoding RawAuthValue into Base64 format.
|
//
|
Status = Base64Encode (
|
(CONST UINT8 *) RawAuthValue,
|
AsciiStrLen (RawAuthValue),
|
EnAuthValue,
|
&EnAuthValueSize
|
);
|
if (Status == EFI_BUFFER_TOO_SMALL) {
|
EnAuthValue = (CHAR8 *) AllocateZeroPool (EnAuthValueSize);
|
if (EnAuthValue == NULL) {
|
Status = EFI_OUT_OF_RESOURCES;
|
return Status;
|
}
|
|
Status = Base64Encode (
|
(CONST UINT8 *) RawAuthValue,
|
AsciiStrLen (RawAuthValue),
|
EnAuthValue,
|
&EnAuthValueSize
|
);
|
}
|
|
if (EFI_ERROR (Status)) {
|
goto Exit;
|
}
|
|
BasicBufSize = AsciiStrLen ("Basic ") + AsciiStrLen(EnAuthValue) + 2;
|
BasicWithEnAuthValue = AllocatePool (BasicBufSize);
|
if (BasicWithEnAuthValue == NULL) {
|
Status = EFI_OUT_OF_RESOURCES;
|
goto Exit;
|
}
|
|
//
|
// Build encoded EnAuthValue with Basic (Basic EnAuthValue).
|
//
|
AsciiSPrint (
|
BasicWithEnAuthValue,
|
BasicBufSize,
|
"%a %a",
|
"Basic",
|
EnAuthValue
|
);
|
|
service->basicAuthStr = BasicWithEnAuthValue;
|
|
Exit:
|
if (RawAuthValue != NULL) {
|
ZeroMem (RawAuthValue, RawAuthBufSize);
|
FreePool (RawAuthValue);
|
}
|
|
if (EnAuthValue != NULL) {
|
ZeroMem (EnAuthValue, EnAuthValueSize);
|
FreePool (EnAuthValue);
|
}
|
|
return Status;
|
}
|
|
static redfishService* createServiceEnumeratorBasicAuth(const char* host, const char* rootUri, const char* username, const char* password, unsigned int flags, void * restProtocol)
|
{
|
redfishService* ret;
|
EFI_STATUS Status;
|
|
ret = createServiceEnumeratorNoAuth(host, rootUri, false, flags, restProtocol);
|
|
// add basic auth str
|
Status = createBasicAuthStr (ret, username, password);
|
if (EFI_ERROR(Status)) {
|
cleanupServiceEnumerator (ret);
|
return NULL;
|
}
|
|
ret->versions = getVersions(ret, rootUri);
|
return ret;
|
}
|
|
static redfishService* createServiceEnumeratorSessionAuth(const char* host, const char* rootUri, const char* username, const char* password, unsigned int flags, void * restProtocol)
|
{
|
redfishService* ret;
|
redfishPayload* payload;
|
redfishPayload* links;
|
json_t* sessionPayload;
|
json_t* session;
|
json_t* odataId;
|
const char* uri;
|
json_t* post;
|
char* content;
|
EFI_HTTP_STATUS_CODE *StatusCode;
|
|
content = NULL;
|
StatusCode = NULL;
|
|
ret = createServiceEnumeratorNoAuth(host, rootUri, true, flags, restProtocol);
|
if(ret == NULL)
|
{
|
return NULL;
|
}
|
payload = getRedfishServiceRoot(ret, NULL, &StatusCode);
|
if(StatusCode == NULL || *StatusCode < HTTP_STATUS_200_OK || *StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT)
|
{
|
if (StatusCode != NULL) {
|
FreePool (StatusCode);
|
}
|
|
if (payload != NULL) {
|
cleanupPayload(payload);
|
}
|
cleanupServiceEnumerator(ret);
|
return NULL;
|
}
|
|
if (StatusCode != NULL) {
|
FreePool (StatusCode);
|
StatusCode = NULL;
|
}
|
|
links = getPayloadByNodeName(payload, "Links", &StatusCode);
|
cleanupPayload(payload);
|
if(links == NULL)
|
{
|
cleanupServiceEnumerator(ret);
|
return NULL;
|
}
|
session = json_object_get(links->json, "Sessions");
|
if(session == NULL)
|
{
|
cleanupPayload(links);
|
cleanupServiceEnumerator(ret);
|
return NULL;
|
}
|
odataId = json_object_get(session, "@odata.id");
|
if(odataId == NULL)
|
{
|
cleanupPayload(links);
|
cleanupServiceEnumerator(ret);
|
return NULL;
|
}
|
uri = json_string_value(odataId);
|
post = json_object();
|
addStringToJsonObject(post, "UserName", username);
|
addStringToJsonObject(post, "Password", password);
|
content = json_dumps(post, 0);
|
json_decref(post);
|
sessionPayload = postUriFromService(ret, uri, content, 0, NULL, &StatusCode);
|
|
if (content != NULL) {
|
ZeroMem (content, (UINTN)strlen(content));
|
free(content);
|
}
|
|
if(sessionPayload == NULL || StatusCode == NULL || *StatusCode < HTTP_STATUS_200_OK || *StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT)
|
{
|
//Failed to create session!
|
|
cleanupPayload(links);
|
cleanupServiceEnumerator(ret);
|
|
if (StatusCode != NULL) {
|
FreePool (StatusCode);
|
}
|
|
if (sessionPayload != NULL) {
|
json_decref(sessionPayload);
|
}
|
|
return NULL;
|
}
|
json_decref(sessionPayload);
|
cleanupPayload(links);
|
FreePool (StatusCode);
|
return ret;
|
}
|
|
static char* makeUrlForService(redfishService* service, const char* uri)
|
{
|
char* url;
|
if(service->host == NULL)
|
{
|
return NULL;
|
}
|
url = (char*)malloc(strlen(service->host)+strlen(uri)+1);
|
strcpy(url, service->host);
|
strcat(url, uri);
|
return url;
|
}
|
|
static json_t* getVersions(redfishService* service, const char* rootUri)
|
{
|
json_t* ret = NULL;
|
EFI_HTTP_STATUS_CODE* StatusCode = NULL;
|
|
if(service->flags & REDFISH_FLAG_SERVICE_NO_VERSION_DOC)
|
{
|
service->versions = json_object();
|
if(service->versions == NULL)
|
{
|
return NULL;
|
}
|
addStringToJsonObject(service->versions, "v1", "/redfish/v1");
|
return service->versions;
|
}
|
if(rootUri != NULL)
|
{
|
ret = getUriFromService(service, rootUri, &StatusCode);
|
}
|
else
|
{
|
ret = getUriFromService(service, "/redfish", &StatusCode);
|
}
|
|
if (ret == NULL || StatusCode == NULL || *StatusCode < HTTP_STATUS_200_OK || *StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT) {
|
if (ret != NULL) {
|
json_decref(ret);
|
}
|
ret = NULL;
|
}
|
|
if (StatusCode != NULL) {
|
FreePool (StatusCode);
|
}
|
|
return ret;
|
}
|
|
static void addStringToJsonObject(json_t* object, const char* key, const char* value)
|
{
|
json_t* jValue = json_string(value);
|
|
json_object_set(object, key, jValue);
|
|
json_decref(jValue);
|
}
|