/** @file
  Functions for manipulating file names.

Copyright (c) 2005 - 2015, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "Fat.h"

/**

  This function checks whether the input FileName is a valid 8.3 short name.
  If the input FileName is a valid 8.3, the output is the 8.3 short name;
  otherwise, the output is the base tag of 8.3 short name.

  @param  FileName              - The input unicode filename.
  @param  File8Dot3Name         - The output ascii 8.3 short name or base tag of 8.3 short name.

  @retval TRUE                  - The input unicode filename is a valid 8.3 short name.
  @retval FALSE                 - The input unicode filename is not a valid 8.3 short name.

**/
BOOLEAN
FatCheckIs8Dot3Name (
  IN  CHAR16    *FileName,
  OUT CHAR8     *File8Dot3Name
  )
{
  BOOLEAN PossibleShortName;
  CHAR16  *TempName;
  CHAR16  *ExtendName;
  CHAR16  *SeparateDot;
  UINTN   MainNameLen;
  UINTN   ExtendNameLen;

  PossibleShortName = TRUE;
  SeparateDot       = NULL;
  SetMem (File8Dot3Name, FAT_NAME_LEN, ' ');
  for (TempName = FileName; *TempName != '\0'; TempName++) {
    if (*TempName == L'.') {
      SeparateDot = TempName;
    }
  }

  if (SeparateDot == NULL) {
    //
    // Extended filename is not detected
    //
    MainNameLen   = TempName - FileName;
    ExtendName    = TempName;
    ExtendNameLen = 0;
  } else {
    //
    // Extended filename is detected
    //
    MainNameLen   = SeparateDot - FileName;
    ExtendName    = SeparateDot + 1;
    ExtendNameLen = TempName - ExtendName;
  }
  //
  // We scan the filename for the second time
  // to check if there exists any extra blanks and dots
  //
  while (--TempName >= FileName) {
    if ((*TempName == L'.' || *TempName == L' ') && (TempName != SeparateDot)) {
      //
      // There exist extra blanks and dots
      //
      PossibleShortName = FALSE;
    }
  }

  if (MainNameLen == 0) {
    PossibleShortName = FALSE;
  }

  if (MainNameLen > FAT_MAIN_NAME_LEN) {
    PossibleShortName = FALSE;
    MainNameLen       = FAT_MAIN_NAME_LEN;
  }

  if (ExtendNameLen > FAT_EXTEND_NAME_LEN) {
    PossibleShortName = FALSE;
    ExtendNameLen     = FAT_EXTEND_NAME_LEN;
  }

  if (FatStrToFat (FileName, MainNameLen, File8Dot3Name)) {
    PossibleShortName = FALSE;
  }

  if (FatStrToFat (ExtendName, ExtendNameLen, File8Dot3Name + FAT_MAIN_NAME_LEN)) {
    PossibleShortName = FALSE;
  }

  return PossibleShortName;
}

/**

  Trim the trailing blanks of fat name.

  @param  Name                  - The Char8 string needs to be trimed.
  @param  Len                   - The length of the fat name.

  The real length of the fat name after the trailing blanks are trimmed.

**/
STATIC
UINTN
FatTrimAsciiTrailingBlanks (
  IN CHAR8        *Name,
  IN UINTN        Len
  )
{
  while (Len > 0 && Name[Len - 1] == ' ') {
    Len--;
  }

  return Len;
}

/**

  Convert the ascii fat name to the unicode string and strip trailing spaces,
  and if necessary, convert the unicode string to lower case.

  @param  FatName               - The Char8 string needs to be converted.
  @param  Len                   - The length of the fat name.
  @param  LowerCase             - Indicate whether to convert the string to lower case.
  @param  Str                   - The result of the convertion.

**/
VOID
FatNameToStr (
  IN  CHAR8            *FatName,
  IN  UINTN            Len,
  IN  UINTN            LowerCase,
  OUT CHAR16           *Str
  )
{
  //
  // First, trim the trailing blanks
  //
  Len = FatTrimAsciiTrailingBlanks (FatName, Len);
  //
  // Convert fat string to unicode string
  //
  FatFatToStr (Len, FatName, Str);

  //
  // If the name is to be lower cased, do it now
  //
  if (LowerCase != 0) {
    FatStrLwr (Str);
  }
}

/**

  This function generates 8Dot3 name from user specified name for a newly created file.

  @param  Parent                - The parent directory.
  @param  DirEnt                - The directory entry whose 8Dot3Name needs to be generated.

**/
VOID
FatCreate8Dot3Name (
  IN FAT_OFILE    *Parent,
  IN FAT_DIRENT   *DirEnt
  )
{
  CHAR8 *ShortName;
  CHAR8 *ShortNameChar;
  UINTN BaseTagLen;
  UINTN Index;
  UINTN Retry;
  UINT8 Segment;
  union {
    UINT32  Crc;
    struct HEX_DATA {
      UINT8 Segment : HASH_VALUE_TAG_LEN;
    } Hex[HASH_VALUE_TAG_LEN];
  } HashValue;
  //
  // Make sure the whole directory has been loaded
  //
  ASSERT (Parent->ODir->EndOfDir);
  ShortName = DirEnt->Entry.FileName;

  //
  // Trim trailing blanks of 8.3 name
  //
  BaseTagLen = FatTrimAsciiTrailingBlanks (ShortName, FAT_MAIN_NAME_LEN);
  if (BaseTagLen > SPEC_BASE_TAG_LEN) {
    BaseTagLen = SPEC_BASE_TAG_LEN;
  }
  //
  // We first use the algorithm described by spec.
  //
  ShortNameChar     = ShortName + BaseTagLen;
  *ShortNameChar++  = '~';
  *ShortNameChar    = '1';
  Retry = 0;
  while (*FatShortNameHashSearch (Parent->ODir, ShortName) != NULL) {
    *ShortNameChar = (CHAR8)(*ShortNameChar + 1);
    if (++Retry == MAX_SPEC_RETRY) {
      //
      // We use new algorithm to generate 8.3 name
      //
      ASSERT (DirEnt->FileString != NULL);
      gBS->CalculateCrc32 (DirEnt->FileString, StrSize (DirEnt->FileString), &HashValue.Crc);

      if (BaseTagLen > HASH_BASE_TAG_LEN) {
        BaseTagLen = HASH_BASE_TAG_LEN;
      }

      ShortNameChar = ShortName + BaseTagLen;
      for (Index = 0; Index < HASH_VALUE_TAG_LEN; Index++) {
        Segment = HashValue.Hex[Index].Segment;
        if (Segment > 9) {
          *ShortNameChar++ = (CHAR8)(Segment - 10 + 'A');
        } else {
          *ShortNameChar++ = (CHAR8)(Segment + '0');
        }
      }

      *ShortNameChar++  = '~';
      *ShortNameChar    = '1';
    }
  }
}

/**

  Check the string is lower case or upper case
  and it is used by fatname to dir entry count

  @param Str                   - The string which needs to be checked.
  @param InCaseFlag            - The input case flag which is returned when the string is lower case.

  @retval OutCaseFlag           - The output case flag.

**/
STATIC
UINT8
FatCheckNameCase (
  IN CHAR16           *Str,
  IN UINT8            InCaseFlag
  )
{
  CHAR16  Buffer[FAT_MAIN_NAME_LEN + 1 + FAT_EXTEND_NAME_LEN + 1];
  UINT8   OutCaseFlag;

  //
  // Assume the case of input string is mixed
  //
  OutCaseFlag = FAT_CASE_MIXED;
  //
  // Lower case a copy of the string, if it matches the
  // original then the string is lower case
  //
  StrCpyS (Buffer, ARRAY_SIZE (Buffer), Str);
  FatStrLwr (Buffer);
  if (StrCmp (Str, Buffer) == 0) {
    OutCaseFlag = InCaseFlag;
  }
  //
  // Upper case a copy of the string, if it matches the
  // original then the string is upper case
  //
  StrCpyS (Buffer, ARRAY_SIZE (Buffer), Str);
  FatStrUpr (Buffer);
  if (StrCmp (Str, Buffer) == 0) {
    OutCaseFlag = 0;
  }

  return OutCaseFlag;
}

/**

  Set the caseflag value for the directory entry.

  @param DirEnt                - The logical directory entry whose caseflag value is to be set.

**/
VOID
FatSetCaseFlag (
  IN FAT_DIRENT   *DirEnt
  )
{
  CHAR16  LfnBuffer[FAT_MAIN_NAME_LEN + 1 + FAT_EXTEND_NAME_LEN + 1];
  CHAR16  *TempCharPtr;
  CHAR16  *ExtendName;
  CHAR16  *FileNameCharPtr;
  UINT8   CaseFlag;

  ExtendName      = NULL;
  TempCharPtr     = LfnBuffer;
  FileNameCharPtr = DirEnt->FileString;
  ASSERT (StrSize (DirEnt->FileString) <= sizeof (LfnBuffer));
  while ((*TempCharPtr = *FileNameCharPtr) != 0) {
    if (*TempCharPtr == L'.') {
      ExtendName = TempCharPtr;
    }

    TempCharPtr++;
    FileNameCharPtr++;
  }

  CaseFlag = 0;
  if (ExtendName != NULL) {
    *ExtendName = 0;
    ExtendName++;
    CaseFlag = (UINT8)(CaseFlag | FatCheckNameCase (ExtendName, FAT_CASE_EXT_LOWER));
  }

  CaseFlag = (UINT8)(CaseFlag | FatCheckNameCase (LfnBuffer, FAT_CASE_NAME_LOWER));
  if ((CaseFlag & FAT_CASE_MIXED) == 0) {
    //
    // We just need one directory entry to store this file name entry
    //
    DirEnt->Entry.CaseFlag = CaseFlag;
  } else {
    //
    // We need one extra directory entry to store the mixed case entry
    //
    DirEnt->Entry.CaseFlag = 0;
    DirEnt->EntryCount++;
  }
}

/**

  Convert the 8.3 ASCII fat name to cased Unicode string according to case flag.

  @param  DirEnt                - The corresponding directory entry.
  @param  FileString            - The output Unicode file name.
  @param  FileStringMax           The max length of FileString.

**/
VOID
FatGetFileNameViaCaseFlag (
  IN     FAT_DIRENT   *DirEnt,
  IN OUT CHAR16       *FileString,
  IN     UINTN        FileStringMax
  )
{
  UINT8   CaseFlag;
  CHAR8   *File8Dot3Name;
  CHAR16  TempExt[1 + FAT_EXTEND_NAME_LEN + 1];
  //
  // Store file extension like ".txt"
  //
  CaseFlag      = DirEnt->Entry.CaseFlag;
  File8Dot3Name = DirEnt->Entry.FileName;

  FatNameToStr (File8Dot3Name, FAT_MAIN_NAME_LEN, CaseFlag & FAT_CASE_NAME_LOWER, FileString);
  FatNameToStr (File8Dot3Name + FAT_MAIN_NAME_LEN, FAT_EXTEND_NAME_LEN, CaseFlag & FAT_CASE_EXT_LOWER, &TempExt[1]);
  if (TempExt[1] != 0) {
    TempExt[0] = L'.';
    StrCatS (FileString, FileStringMax, TempExt);
  }
}

/**

  Get the Check sum for a short name.

  @param  ShortNameString       - The short name for a file.

  @retval Sum                   - UINT8 checksum.

**/
UINT8
FatCheckSum (
  IN CHAR8  *ShortNameString
  )
{
  UINTN ShortNameLen;
  UINT8 Sum;
  Sum = 0;
  for (ShortNameLen = FAT_NAME_LEN; ShortNameLen != 0; ShortNameLen--) {
    Sum = (UINT8)((((Sum & 1) != 0) ? 0x80 : 0) + (Sum >> 1) + *ShortNameString++);
  }

  return Sum;
}

/**

  Takes Path as input, returns the next name component
  in Name, and returns the position after Name (e.g., the
  start of the next name component)

  @param  Path                  - The path of one file.
  @param  Name                  - The next name component in Path.

  The position after Name in the Path

**/
CHAR16 *
FatGetNextNameComponent (
  IN  CHAR16      *Path,
  OUT CHAR16      *Name
  )
{
  while (*Path != 0 && *Path != PATH_NAME_SEPARATOR) {
    *Name++ = *Path++;
  }
  *Name = 0;
  //
  // Get off of trailing path name separator
  //
  while (*Path == PATH_NAME_SEPARATOR) {
    Path++;
  }

  return Path;
}

/**

  Check whether the IFileName is valid long file name. If the IFileName is a valid
  long file name, then we trim the possible leading blanks and leading/trailing dots.
  the trimmed filename is stored in OutputFileName

  @param  InputFileName         - The input file name.
  @param  OutputFileName        - The output file name.

  @retval TRUE                  - The InputFileName is a valid long file name.
  @retval FALSE                 - The InputFileName is not a valid long file name.

**/
BOOLEAN
FatFileNameIsValid (
  IN  CHAR16  *InputFileName,
  OUT CHAR16  *OutputFileName
  )
{
  CHAR16  *TempNamePointer;
  CHAR16  TempChar;
  //
  // Trim Leading blanks
  //
  while (*InputFileName == L' ') {
    InputFileName++;
  }

  TempNamePointer = OutputFileName;
  while (*InputFileName != 0) {
    *TempNamePointer++ = *InputFileName++;
  }
  //
  // Trim Trailing blanks and dots
  //
  while (TempNamePointer > OutputFileName) {
    TempChar = *(TempNamePointer - 1);
    if (TempChar != L' ' && TempChar != L'.') {
      break;
    }

    TempNamePointer--;
  }

  *TempNamePointer = 0;

  //
  // Per FAT Spec the file name should meet the following criteria:
  //   C1. Length (FileLongName) <= 255
  //   C2. Length (X:FileFullPath<NUL>) <= 260
  // Here we check C1.
  //
  if (TempNamePointer - OutputFileName > EFI_FILE_STRING_LENGTH) {
    return FALSE;
  }
  //
  // See if there is any illegal characters within the name
  //
  do {
    if (*OutputFileName < 0x20 ||
        *OutputFileName == '\"' ||
        *OutputFileName == '*' ||
        *OutputFileName == '/' ||
        *OutputFileName == ':' ||
        *OutputFileName == '<' ||
        *OutputFileName == '>' ||
        *OutputFileName == '?' ||
        *OutputFileName == '\\' ||
        *OutputFileName == '|'
        ) {
      return FALSE;
    }

    OutputFileName++;
  } while (*OutputFileName != 0);
  return TRUE;
}
