/** @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.", __func__, 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", __func__));
    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 *
deleteUriFromServiceEx (
  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];
  size_t                 contentLength;

  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) ? 8 : 7);
  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);

  Status = HttpIoSetHeader (HttpIoHeader, "Content-Type", "application/json");
  ASSERT_EFI_ERROR (Status);

  if (content != NULL) {
    contentLength = strlen (content);
    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) {
    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;

  if (content != NULL) {
    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)) {
    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;
}

json_t *
deleteUriFromService (
  redfishService        *service,
  const char            *uri,
  EFI_HTTP_STATUS_CODE  **StatusCode
  )
{
  return deleteUriFromServiceEx (service, uri, NULL, StatusCode);
}

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);
}
