/** @file
  Main file for Type shell level 3 function.

  (C) Copyright 2013-2015 Hewlett-Packard Development Company, L.P.<BR>
  Copyright (c) 2009 - 2019, Intel Corporation. All rights reserved. <BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "UefiShellLevel3CommandsLib.h"

#include <Library/ShellLib.h>

/**
  Display a single file to StdOut.

  If both Ascii and UCS2 are FALSE attempt to discover the file type.

  @param[in] Handle   The handle to the file to display.
  @param[in] Ascii    TRUE to force ASCII, FALSE othewise.
  @param[in] UCS2     TRUE to force UCS2, FALSE othewise.

  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
  @retval EFI_SUCCESS           The operation was successful.
**/
EFI_STATUS
TypeFileByHandle (
  IN SHELL_FILE_HANDLE  Handle,
  IN BOOLEAN            Ascii,
  IN BOOLEAN            UCS2
  )
{
  UINTN       ReadSize;
  VOID        *Buffer;
  VOID        *AllocatedBuffer;
  EFI_STATUS  Status;
  UINTN       LoopVar;
  UINTN       LoopSize;
  CHAR16      AsciiChar;
  CHAR16      Ucs2Char;

  ReadSize        = PcdGet32 (PcdShellFileOperationSize);
  AllocatedBuffer = AllocateZeroPool (ReadSize);
  if (AllocatedBuffer == NULL) {
    return (EFI_OUT_OF_RESOURCES);
  }

  Status = ShellSetFilePosition (Handle, 0);
  ASSERT_EFI_ERROR (Status);

  while (ReadSize == ((UINTN)PcdGet32 (PcdShellFileOperationSize))) {
    Buffer = AllocatedBuffer;
    ZeroMem (Buffer, ReadSize);
    Status = ShellReadFile (Handle, &ReadSize, Buffer);
    if (EFI_ERROR (Status)) {
      break;
    }

    if (!(Ascii|UCS2)) {
      if (*(UINT16 *)Buffer == gUnicodeFileTag) {
        UCS2 = TRUE;
      } else {
        Ascii = TRUE;
      }
    }

    if (Ascii) {
      LoopSize = ReadSize;
      for (LoopVar = 0; LoopVar < LoopSize; LoopVar++) {
        //
        // The valid range of ASCII characters is 0x20-0x7E.
        // Display "." when there is an invalid character.
        //
        AsciiChar = CHAR_NULL;
        AsciiChar = ((CHAR8 *)Buffer)[LoopVar];
        if ((AsciiChar == '\r') || (AsciiChar == '\n')) {
          //
          // Allow Line Feed (LF) (0xA) & Carriage Return (CR) (0xD)
          // characters to be displayed as is.
          //
          if (((AsciiChar == '\n') && (LoopVar == 0)) ||
              ((AsciiChar == '\n') && (((CHAR8 *)Buffer)[LoopVar-1] != '\r')))
          {
            //
            // In case file begin with single line Feed or Line Feed (0xA) is
            // encountered & Carriage Return (0xD) was not previous character,
            // print CR and LF. This is because Shell 2.0 requires carriage
            // return with line feed for displaying each new line from left.
            //
            ShellPrintEx (-1, -1, L"\r\n");
            continue;
          }
        } else {
          //
          // For all other characters which are not printable, display '.'
          //
          if ((AsciiChar < 0x20) || (AsciiChar >= 0x7F)) {
            AsciiChar = '.';
          }
        }

        ShellPrintEx (-1, -1, L"%c", AsciiChar);
      }
    } else {
      if (*(UINT16 *)Buffer == gUnicodeFileTag) {
        //
        // For unicode files, skip displaying the byte order marker.
        //
        Buffer   = ((UINT16 *)Buffer) + 1;
        LoopSize = (ReadSize / (sizeof (CHAR16))) - 1;
      } else {
        LoopSize = ReadSize / (sizeof (CHAR16));
      }

      for (LoopVar = 0; LoopVar < LoopSize; LoopVar++) {
        //
        // An invalid range of characters is 0x0-0x1F.
        // Display "." when there is an invalid character.
        //
        Ucs2Char = CHAR_NULL;
        Ucs2Char = ((CHAR16 *)Buffer)[LoopVar];
        if ((Ucs2Char == '\r') || (Ucs2Char == '\n')) {
          //
          // Allow Line Feed (LF) (0xA) & Carriage Return (CR) (0xD)
          // characters to be displayed as is.
          //
          if (((Ucs2Char == '\n') && (LoopVar == 0)) ||
              ((Ucs2Char == '\n') && (((CHAR16 *)Buffer)[LoopVar-1] != '\r')))
          {
            //
            // In case file begin with single line Feed or Line Feed (0xA) is
            // encountered & Carriage Return (0xD) was not previous character,
            // print CR and LF. This is because Shell 2.0 requires carriage
            // return with line feed for displaying each new line from left.
            //
            ShellPrintEx (-1, -1, L"\r\n");
            continue;
          }
        } else if (Ucs2Char < 0x20) {
          //
          // For all other characters which are not printable, display '.'
          //
          Ucs2Char = L'.';
        }

        ShellPrintEx (-1, -1, L"%c", Ucs2Char);
      }
    }

    if (ShellGetExecutionBreakFlag ()) {
      break;
    }
  }

  FreePool (AllocatedBuffer);
  ShellPrintEx (-1, -1, L"\r\n");
  return (Status);
}

STATIC CONST SHELL_PARAM_ITEM  ParamList[] = {
  { L"-a", TypeFlag },
  { L"-u", TypeFlag },
  { NULL,  TypeMax  }
};

/**
  Function for 'type' command.

  @param[in] ImageHandle  Handle to the Image (NULL if Internal).
  @param[in] SystemTable  Pointer to the System Table (NULL if Internal).
**/
SHELL_STATUS
EFIAPI
ShellCommandRunType (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS           Status;
  LIST_ENTRY           *Package;
  CHAR16               *ProblemParam;
  CONST CHAR16         *Param;
  SHELL_STATUS         ShellStatus;
  UINTN                ParamCount;
  EFI_SHELL_FILE_INFO  *FileList;
  EFI_SHELL_FILE_INFO  *Node;
  BOOLEAN              AsciiMode;
  BOOLEAN              UnicodeMode;

  ProblemParam = NULL;
  ShellStatus  = SHELL_SUCCESS;
  ParamCount   = 0;
  FileList     = NULL;

  //
  // initialize the shell lib (we must be in non-auto-init...)
  //
  Status = ShellInitialize ();
  ASSERT_EFI_ERROR (Status);

  Status = CommandInit ();
  ASSERT_EFI_ERROR (Status);

  //
  // parse the command line
  //
  Status = ShellCommandLineParse (ParamList, &Package, &ProblemParam, TRUE);
  if (EFI_ERROR (Status)) {
    if ((Status == EFI_VOLUME_CORRUPTED) && (ProblemParam != NULL)) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PROBLEM), gShellLevel3HiiHandle, L"type", ProblemParam);
      FreePool (ProblemParam);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      ASSERT (FALSE);
    }
  } else {
    //
    // check for "-?"
    //
    if (ShellCommandLineGetFlag (Package, L"-?")) {
      ASSERT (FALSE);
    }

    AsciiMode   = ShellCommandLineGetFlag (Package, L"-a");
    UnicodeMode = ShellCommandLineGetFlag (Package, L"-u");

    if (AsciiMode && UnicodeMode) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_PARAM_INV), gShellLevel3HiiHandle, L"type", L"-a & -u");
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else if (ShellCommandLineGetRawValue (Package, 1) == NULL) {
      //
      // we insufficient parameters
      //
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_TOO_FEW), gShellLevel3HiiHandle, L"type");
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      //
      // get a list with each file specified by parameters
      // if parameter is a directory then add all the files below it to the list
      //
      for ( ParamCount = 1, Param = ShellCommandLineGetRawValue (Package, ParamCount)
            ; Param != NULL
            ; ParamCount++, Param = ShellCommandLineGetRawValue (Package, ParamCount)
            )
      {
        Status = ShellOpenFileMetaArg ((CHAR16 *)Param, EFI_FILE_MODE_READ, &FileList);
        if (EFI_ERROR (Status)) {
          ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_FILE_OPEN_FAIL), gShellLevel3HiiHandle, L"type", (CHAR16 *)Param);
          ShellStatus = SHELL_NOT_FOUND;
          break;
        }

        //
        // make sure we completed the param parsing successfully...
        // Also make sure that any previous action was successful
        //
        if (ShellStatus == SHELL_SUCCESS) {
          //
          // check that we have at least 1 file
          //
          if ((FileList == NULL) || IsListEmpty (&FileList->Link)) {
            ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_FILE_NF), gShellLevel3HiiHandle, L"type", Param);
            continue;
          } else {
            //
            // loop through the list and make sure we are not aborting...
            //
            for ( Node = (EFI_SHELL_FILE_INFO *)GetFirstNode (&FileList->Link)
                  ; !IsNull (&FileList->Link, &Node->Link) && !ShellGetExecutionBreakFlag ()
                  ; Node = (EFI_SHELL_FILE_INFO *)GetNextNode (&FileList->Link, &Node->Link)
                  )
            {
              if (ShellGetExecutionBreakFlag ()) {
                break;
              }

              //
              // make sure the file opened ok
              //
              if (EFI_ERROR (Node->Status)) {
                ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_FILE_OPEN_FAIL), gShellLevel3HiiHandle, L"type", Node->FileName);
                ShellStatus = SHELL_NOT_FOUND;
                continue;
              }

              //
              // make sure its not a directory
              //
              if (FileHandleIsDirectory (Node->Handle) == EFI_SUCCESS) {
                ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_IS_DIR), gShellLevel3HiiHandle, L"type", Node->FileName);
                ShellStatus = SHELL_NOT_FOUND;
                continue;
              }

              //
              // do it
              //
              Status = TypeFileByHandle (Node->Handle, AsciiMode, UnicodeMode);
              if (EFI_ERROR (Status)) {
                ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_TYP_ERROR), gShellLevel3HiiHandle, L"type", Node->FileName);
                ShellStatus = SHELL_INVALID_PARAMETER;
              }

              ASSERT (ShellStatus == SHELL_SUCCESS);
            }
          }
        }

        //
        // Free the fileList
        //
        if ((FileList != NULL) && !IsListEmpty (&FileList->Link)) {
          Status = ShellCloseFileMetaArg (&FileList);
        }

        ASSERT_EFI_ERROR (Status);
        FileList = NULL;
      }
    }

    //
    // free the command line package
    //
    ShellCommandLineFreeVarList (Package);
  }

  if (ShellGetExecutionBreakFlag ()) {
    return (SHELL_ABORTED);
  }

  return (ShellStatus);
}
