/** @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);
}
