Home | History | Annotate | Download | only in IniParsingLib
      1 /** @file
      2   This library parses the INI configuration file.
      3 
      4   The INI file format is:
      5     ================
      6     [SectionName]
      7     EntryName=EntryValue
      8     ================
      9 
     10     Where:
     11       1) SectionName is an ASCII string. The valid format is [A-Za-z0-9_]+
     12       2) EntryName is an ASCII string. The valid format is [A-Za-z0-9_]+
     13       3) EntryValue can be:
     14          3.1) an ASCII String. The valid format is [A-Za-z0-9_]+
     15          3.2) a GUID. The valid format is xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, where x is [A-Fa-f0-9]
     16          3.3) a decimal value. The valid format is [0-9]+
     17          3.4) a heximal value. The valid format is 0x[A-Fa-f0-9]+
     18       4) '#' or ';' can be used as comment at anywhere.
     19       5) TAB(0x20) or SPACE(0x9) can be used as separator.
     20       6) LF(\n, 0xA) or CR(\r, 0xD) can be used as line break.
     21 
     22   Caution: This module requires additional review when modified.
     23   This driver will have external input - INI data file.
     24 
     25   OpenIniFile(), PreProcessDataFile(), ProfileGetSection(), ProfileGetEntry()
     26   will receive untrusted input and do basic validation.
     27 
     28   Copyright (c) 2016, Intel Corporation. All rights reserved.<BR>
     29 
     30   This program and the accompanying materials
     31   are licensed and made available under the terms and conditions
     32   of the BSD License which accompanies this distribution.  The
     33   full text of the license may be found at
     34   http://opensource.org/licenses/bsd-license.php
     35 
     36   THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
     37   WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
     38 
     39 **/
     40 
     41 #include <Uefi.h>
     42 #include <Library/BaseLib.h>
     43 #include <Library/BaseMemoryLib.h>
     44 #include <Library/DebugLib.h>
     45 #include <Library/MemoryAllocationLib.h>
     46 
     47 #define IS_HYPHEN(a)               ((a) == '-')
     48 #define IS_NULL(a)                 ((a) == '\0')
     49 
     50 // This is default allocation. Reallocation will happen if it is not enough.
     51 #define MAX_LINE_LENGTH           512
     52 
     53 typedef struct _INI_SECTION_ITEM SECTION_ITEM;
     54 struct _INI_SECTION_ITEM {
     55   CHAR8                           *PtrSection;
     56   UINTN                           SecNameLen;
     57   CHAR8                           *PtrEntry;
     58   CHAR8                           *PtrValue;
     59   SECTION_ITEM                    *PtrNext;
     60 };
     61 
     62 typedef struct _INI_COMMENT_LINE COMMENT_LINE;
     63 struct _INI_COMMENT_LINE {
     64   CHAR8                           *PtrComment;
     65   COMMENT_LINE                    *PtrNext;
     66 };
     67 
     68 typedef struct {
     69   SECTION_ITEM                  *SectionHead;
     70   COMMENT_LINE                  *CommentHead;
     71 } INI_PARSING_LIB_CONTEXT;
     72 
     73 /**
     74   Return if the digital char is valid.
     75 
     76   @param[in] DigitalChar    The digital char to be checked.
     77   @param[in] IncludeHex     If it include HEX char.
     78 
     79   @retval TRUE   The digital char is valid.
     80   @retval FALSE  The digital char is invalid.
     81 **/
     82 BOOLEAN
     83 IsValidDigitalChar (
     84   IN CHAR8    DigitalChar,
     85   IN BOOLEAN  IncludeHex
     86   )
     87 {
     88   if (DigitalChar >= '0' && DigitalChar <= '9') {
     89     return TRUE;
     90   }
     91   if (IncludeHex) {
     92     if (DigitalChar >= 'a' && DigitalChar <= 'f') {
     93       return TRUE;
     94     }
     95     if (DigitalChar >= 'A' && DigitalChar <= 'F') {
     96       return TRUE;
     97     }
     98   }
     99   return FALSE;
    100 }
    101 
    102 /**
    103   Return if the name char is valid.
    104 
    105   @param[in] NameChar    The name char to be checked.
    106 
    107   @retval TRUE   The name char is valid.
    108   @retval FALSE  The name char is invalid.
    109 **/
    110 BOOLEAN
    111 IsValidNameChar (
    112   IN CHAR8  NameChar
    113   )
    114 {
    115   if (NameChar >= 'a' && NameChar <= 'z') {
    116     return TRUE;
    117   }
    118   if (NameChar >= 'A' && NameChar <= 'Z') {
    119     return TRUE;
    120   }
    121   if (NameChar >= '0' && NameChar <= '9') {
    122     return TRUE;
    123   }
    124   if (NameChar == '_') {
    125     return TRUE;
    126   }
    127   return FALSE;
    128 }
    129 
    130 /**
    131   Return if the digital string is valid.
    132 
    133   @param[in] Digital        The digital to be checked.
    134   @param[in] Length         The length of digital string in bytes.
    135   @param[in] IncludeHex     If it include HEX char.
    136 
    137   @retval TRUE   The digital string is valid.
    138   @retval FALSE  The digital string is invalid.
    139 **/
    140 BOOLEAN
    141 IsValidDigital (
    142   IN CHAR8    *Digital,
    143   IN UINTN    Length,
    144   IN BOOLEAN  IncludeHex
    145   )
    146 {
    147   UINTN  Index;
    148   for (Index = 0; Index < Length; Index++) {
    149     if (!IsValidDigitalChar(Digital[Index], IncludeHex)) {
    150       return FALSE;
    151     }
    152   }
    153   return TRUE;
    154 }
    155 
    156 /**
    157   Return if the decimal string is valid.
    158 
    159   @param[in] Decimal The decimal string to be checked.
    160   @param[in] Length  The length of decimal string in bytes.
    161 
    162   @retval TRUE   The decimal string is valid.
    163   @retval FALSE  The decimal string is invalid.
    164 **/
    165 BOOLEAN
    166 IsValidDecimalString (
    167   IN CHAR8  *Decimal,
    168   IN UINTN  Length
    169   )
    170 {
    171   return IsValidDigital(Decimal, Length, FALSE);
    172 }
    173 
    174 /**
    175   Return if the heximal string is valid.
    176 
    177   @param[in] Hex     The heximal string to be checked.
    178   @param[in] Length  The length of heximal string in bytes.
    179 
    180   @retval TRUE   The heximal string is valid.
    181   @retval FALSE  The heximal string is invalid.
    182 **/
    183 BOOLEAN
    184 IsValidHexString (
    185   IN CHAR8  *Hex,
    186   IN UINTN  Length
    187   )
    188 {
    189   if (Length <= 2) {
    190     return FALSE;
    191   }
    192   if (Hex[0] != '0') {
    193     return FALSE;
    194   }
    195   if (Hex[1] != 'x' && Hex[1] != 'X') {
    196     return FALSE;
    197   }
    198   return IsValidDigital(&Hex[2], Length - 2, TRUE);
    199 }
    200 
    201 /**
    202   Return if the name string is valid.
    203 
    204   @param[in] Name    The name to be checked.
    205   @param[in] Length  The length of name string in bytes.
    206 
    207   @retval TRUE   The name string is valid.
    208   @retval FALSE  The name string is invalid.
    209 **/
    210 BOOLEAN
    211 IsValidName (
    212   IN CHAR8  *Name,
    213   IN UINTN  Length
    214   )
    215 {
    216   UINTN  Index;
    217   for (Index = 0; Index < Length; Index++) {
    218     if (!IsValidNameChar(Name[Index])) {
    219       return FALSE;
    220     }
    221   }
    222   return TRUE;
    223 }
    224 
    225 /**
    226   Return if the value string is valid GUID.
    227 
    228   @param[in] Value   The value to be checked.
    229   @param[in] Length  The length of value string in bytes.
    230 
    231   @retval TRUE   The value string is valid GUID.
    232   @retval FALSE  The value string is invalid GUID.
    233 **/
    234 BOOLEAN
    235 IsValidGuid (
    236   IN CHAR8  *Value,
    237   IN UINTN  Length
    238   )
    239 {
    240   if (Length != sizeof("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") - 1) {
    241     return FALSE;
    242   }
    243   if (!IS_HYPHEN(Value[8])) {
    244     return FALSE;
    245   }
    246   if (!IS_HYPHEN(Value[13])) {
    247     return FALSE;
    248   }
    249   if (!IS_HYPHEN(Value[18])) {
    250     return FALSE;
    251   }
    252   if (!IS_HYPHEN(Value[23])) {
    253     return FALSE;
    254   }
    255   if (!IsValidDigital(&Value[0], 8, TRUE)) {
    256     return FALSE;
    257   }
    258   if (!IsValidDigital(&Value[9], 4, TRUE)) {
    259     return FALSE;
    260   }
    261   if (!IsValidDigital(&Value[14], 4, TRUE)) {
    262     return FALSE;
    263   }
    264   if (!IsValidDigital(&Value[19], 4, TRUE)) {
    265     return FALSE;
    266   }
    267   if (!IsValidDigital(&Value[24], 12, TRUE)) {
    268     return FALSE;
    269   }
    270   return TRUE;
    271 }
    272 
    273 /**
    274   Return if the value string is valid.
    275 
    276   @param[in] Value    The value to be checked.
    277   @param[in] Length  The length of value string in bytes.
    278 
    279   @retval TRUE   The name string is valid.
    280   @retval FALSE  The name string is invalid.
    281 **/
    282 BOOLEAN
    283 IsValidValue (
    284   IN CHAR8  *Value,
    285   IN UINTN  Length
    286   )
    287 {
    288   if (IsValidName(Value, Length) || IsValidGuid(Value, Length)) {
    289     return TRUE;
    290   }
    291   return FALSE;
    292 }
    293 
    294 /**
    295   Dump an INI config file context.
    296 
    297   @param[in] Context         INI Config file context.
    298 **/
    299 VOID
    300 DumpIniSection (
    301   IN VOID  *Context
    302   )
    303 {
    304   INI_PARSING_LIB_CONTEXT               *IniContext;
    305   SECTION_ITEM                          *PtrSection;
    306   SECTION_ITEM                          *Section;
    307 
    308   if (Context == NULL) {
    309     return;
    310   }
    311 
    312   IniContext = Context;
    313   Section = IniContext->SectionHead;
    314 
    315   while (Section != NULL) {
    316     PtrSection = Section;
    317     Section = Section->PtrNext;
    318     if (PtrSection->PtrSection != NULL) {
    319       DEBUG((DEBUG_VERBOSE, "Section - %a\n", PtrSection->PtrSection));
    320     }
    321     if (PtrSection->PtrEntry != NULL) {
    322       DEBUG ((DEBUG_VERBOSE, "  Entry - %a\n", PtrSection->PtrEntry));
    323     }
    324     if (PtrSection->PtrValue != NULL) {
    325       DEBUG((DEBUG_VERBOSE, "  Value - %a\n", PtrSection->PtrValue));
    326     }
    327   }
    328 }
    329 
    330 /**
    331   Copy one line data from buffer data to the line buffer.
    332 
    333   @param[in]      Buffer          Buffer data.
    334   @param[in]      BufferSize      Buffer Size.
    335   @param[in, out] LineBuffer      Line buffer to store the found line data.
    336   @param[in, out] LineSize        On input, size of the input line buffer.
    337                                   On output, size of the actual line buffer.
    338 
    339   @retval EFI_BUFFER_TOO_SMALL  The size of input line buffer is not enough.
    340   @retval EFI_SUCCESS           Copy line data into the line buffer.
    341 
    342 **/
    343 EFI_STATUS
    344 ProfileGetLine (
    345   IN      UINT8                         *Buffer,
    346   IN      UINTN                         BufferSize,
    347   IN OUT  UINT8                         *LineBuffer,
    348   IN OUT  UINTN                         *LineSize
    349   )
    350 {
    351   UINTN                                 Length;
    352   UINT8                                 *PtrBuf;
    353   UINTN                                 PtrEnd;
    354 
    355   PtrBuf      = Buffer;
    356   PtrEnd      = (UINTN)Buffer + BufferSize;
    357 
    358   //
    359   // 0x0D indicates a line break. Otherwise there is no line break
    360   //
    361   while ((UINTN)PtrBuf < PtrEnd) {
    362     if (*PtrBuf == 0x0D || *PtrBuf == 0x0A) {
    363       break;
    364     }
    365     PtrBuf++;
    366   }
    367 
    368   if ((UINTN)PtrBuf >= (PtrEnd - 1)) {
    369     //
    370     // The buffer ends without any line break
    371     // or it is the last character of the buffer
    372     //
    373     Length    = BufferSize;
    374   } else if (*(PtrBuf + 1) == 0x0A) {
    375     //
    376     // Further check if a 0x0A follows. If yes, count 0xA
    377     //
    378     Length    = (UINTN) PtrBuf - (UINTN) Buffer + 2;
    379   } else {
    380     Length    = (UINTN) PtrBuf - (UINTN) Buffer + 1;
    381   }
    382 
    383   if (Length > (*LineSize)) {
    384     *LineSize = Length;
    385     return EFI_BUFFER_TOO_SMALL;
    386   }
    387 
    388   SetMem (LineBuffer, *LineSize, 0x0);
    389   *LineSize   = Length;
    390   CopyMem (LineBuffer, Buffer, Length);
    391 
    392   return EFI_SUCCESS;
    393 }
    394 
    395 /**
    396   Trim Buffer by removing all CR, LF, TAB, and SPACE chars in its head and tail.
    397 
    398   @param[in, out] Buffer          On input,  buffer data to be trimed.
    399                                   On output, the trimmed buffer.
    400   @param[in, out] BufferSize      On input,  size of original buffer data.
    401                                   On output, size of the trimmed buffer.
    402 
    403 **/
    404 VOID
    405 ProfileTrim (
    406   IN OUT  UINT8                         *Buffer,
    407   IN OUT  UINTN                         *BufferSize
    408   )
    409 {
    410   UINTN                                 Length;
    411   UINT8                                 *PtrBuf;
    412   UINT8                                 *PtrEnd;
    413 
    414   if (*BufferSize == 0) {
    415     return;
    416   }
    417 
    418   //
    419   // Trim the tail first, include CR, LF, TAB, and SPACE.
    420   //
    421   Length          = *BufferSize;
    422   PtrBuf          = (UINT8 *) ((UINTN) Buffer + Length - 1);
    423   while (PtrBuf >= Buffer) {
    424     if ((*PtrBuf != 0x0D) && (*PtrBuf != 0x0A )
    425       && (*PtrBuf != 0x20) && (*PtrBuf != 0x09)) {
    426       break;
    427     }
    428     PtrBuf --;
    429   }
    430 
    431   //
    432   // all spaces, a blank line, return directly;
    433   //
    434   if (PtrBuf < Buffer) {
    435     *BufferSize   = 0;
    436     return;
    437   }
    438 
    439   Length          = (UINTN)PtrBuf - (UINTN)Buffer + 1;
    440   PtrEnd          = PtrBuf;
    441   PtrBuf          = Buffer;
    442 
    443   //
    444   // Now skip the heading CR, LF, TAB and SPACE
    445   //
    446   while (PtrBuf <= PtrEnd) {
    447     if ((*PtrBuf != 0x0D) && (*PtrBuf != 0x0A )
    448       && (*PtrBuf != 0x20) && (*PtrBuf != 0x09)) {
    449       break;
    450     }
    451     PtrBuf++;
    452   }
    453 
    454   //
    455   // If no heading CR, LF, TAB or SPACE, directly return
    456   //
    457   if (PtrBuf == Buffer) {
    458     *BufferSize   = Length;
    459     return;
    460   }
    461 
    462   *BufferSize     = (UINTN)PtrEnd - (UINTN)PtrBuf + 1;
    463 
    464   //
    465   // The first Buffer..PtrBuf characters are CR, LF, TAB or SPACE.
    466   // Now move out all these characters.
    467   //
    468   while (PtrBuf <= PtrEnd) {
    469     *Buffer       = *PtrBuf;
    470     Buffer++;
    471     PtrBuf++;
    472   }
    473 
    474   return;
    475 }
    476 
    477 /**
    478   Insert new comment item into comment head.
    479 
    480   @param[in]      Buffer          Comment buffer to be added.
    481   @param[in]      BufferSize      Size of comment buffer.
    482   @param[in, out] CommentHead     Comment Item head entry.
    483 
    484   @retval EFI_OUT_OF_RESOURCES   No enough memory is allocated.
    485   @retval EFI_SUCCESS            New comment item is inserted.
    486 
    487 **/
    488 EFI_STATUS
    489 ProfileGetComments (
    490   IN      UINT8                         *Buffer,
    491   IN      UINTN                         BufferSize,
    492   IN OUT  COMMENT_LINE                  **CommentHead
    493   )
    494 {
    495   COMMENT_LINE                          *CommentItem;
    496 
    497   CommentItem = NULL;
    498   CommentItem = AllocatePool (sizeof (COMMENT_LINE));
    499   if (CommentItem == NULL) {
    500     return EFI_OUT_OF_RESOURCES;
    501   }
    502 
    503   CommentItem->PtrNext  = *CommentHead;
    504   *CommentHead          = CommentItem;
    505 
    506   //
    507   // Add a trailing '\0'
    508   //
    509   CommentItem->PtrComment = AllocatePool (BufferSize + 1);
    510   if (CommentItem->PtrComment == NULL) {
    511     FreePool (CommentItem);
    512     return EFI_OUT_OF_RESOURCES;
    513   }
    514   CopyMem (CommentItem->PtrComment, Buffer, BufferSize);
    515   *(CommentItem->PtrComment + BufferSize) = '\0';
    516 
    517   return EFI_SUCCESS;
    518 }
    519 
    520 /**
    521   Add new section item into Section head.
    522 
    523   @param[in]      Buffer          Section item data buffer.
    524   @param[in]      BufferSize      Size of section item.
    525   @param[in, out] SectionHead     Section item head entry.
    526 
    527   @retval EFI_OUT_OF_RESOURCES   No enough memory is allocated.
    528   @retval EFI_SUCCESS            Section item is NULL or Section item is added.
    529 
    530 **/
    531 EFI_STATUS
    532 ProfileGetSection (
    533   IN      UINT8                         *Buffer,
    534   IN      UINTN                         BufferSize,
    535   IN OUT  SECTION_ITEM                  **SectionHead
    536   )
    537 {
    538   SECTION_ITEM                          *SectionItem;
    539   UINTN                                 Length;
    540   UINT8                                 *PtrBuf;
    541   UINT8                                 *PtrEnd;
    542 
    543   ASSERT(BufferSize >= 1);
    544   //
    545   // The first character of Buffer is '[', now we want for ']'
    546   //
    547   PtrEnd      = (UINT8 *)((UINTN)Buffer + BufferSize - 1);
    548   PtrBuf      = (UINT8 *)((UINTN)Buffer + 1);
    549   while (PtrBuf <= PtrEnd) {
    550     if (*PtrBuf == ']') {
    551       break;
    552     }
    553     PtrBuf ++;
    554   }
    555   if (PtrBuf > PtrEnd) {
    556     //
    557     // Not found. Invalid line
    558     //
    559     return EFI_NOT_FOUND;
    560   }
    561   if (PtrBuf <= Buffer + 1) {
    562     // Empty name
    563     return EFI_NOT_FOUND;
    564   }
    565 
    566   //
    567   // excluding the heading '[' and tailing ']'
    568   //
    569   Length      = PtrBuf - Buffer - 1;
    570   ProfileTrim (
    571     Buffer + 1,
    572     &Length
    573   );
    574 
    575   //
    576   // Invalid line if the section name is null
    577   //
    578   if (Length == 0) {
    579     return EFI_NOT_FOUND;
    580   }
    581 
    582   if (!IsValidName((CHAR8 *)Buffer + 1, Length)) {
    583     return EFI_INVALID_PARAMETER;
    584   }
    585 
    586   SectionItem = AllocatePool (sizeof (SECTION_ITEM));
    587   if (SectionItem == NULL) {
    588     return EFI_OUT_OF_RESOURCES;
    589   }
    590 
    591   SectionItem->PtrSection = NULL;
    592   SectionItem->SecNameLen = Length;
    593   SectionItem->PtrEntry   = NULL;
    594   SectionItem->PtrValue   = NULL;
    595   SectionItem->PtrNext    = *SectionHead;
    596   *SectionHead            = SectionItem;
    597 
    598   //
    599   // Add a trailing '\0'
    600   //
    601   SectionItem->PtrSection = AllocatePool (Length + 1);
    602   if (SectionItem->PtrSection == NULL) {
    603     return EFI_OUT_OF_RESOURCES;
    604   }
    605 
    606   //
    607   // excluding the heading '['
    608   //
    609   CopyMem (SectionItem->PtrSection, Buffer + 1, Length);
    610   *(SectionItem->PtrSection + Length) = '\0';
    611 
    612   return EFI_SUCCESS;
    613 }
    614 
    615 /**
    616   Add new section entry and entry value into Section head.
    617 
    618   @param[in]      Buffer          Section entry data buffer.
    619   @param[in]      BufferSize      Size of section entry.
    620   @param[in, out] SectionHead     Section item head entry.
    621 
    622   @retval EFI_OUT_OF_RESOURCES   No enough memory is allocated.
    623   @retval EFI_SUCCESS            Section entry is added.
    624   @retval EFI_NOT_FOUND          Section entry is not found.
    625   @retval EFI_INVALID_PARAMETER  Section entry is invalid.
    626 
    627 **/
    628 EFI_STATUS
    629 ProfileGetEntry (
    630   IN      UINT8                         *Buffer,
    631   IN      UINTN                         BufferSize,
    632   IN OUT  SECTION_ITEM                  **SectionHead
    633   )
    634 {
    635   EFI_STATUS                            Status;
    636   SECTION_ITEM                          *SectionItem;
    637   SECTION_ITEM                          *PtrSection;
    638   UINTN                                 Length;
    639   UINT8                                 *PtrBuf;
    640   UINT8                                 *PtrEnd;
    641 
    642   Status      = EFI_SUCCESS;
    643   PtrBuf      = Buffer;
    644   PtrEnd      = (UINT8 *) ((UINTN)Buffer + BufferSize - 1);
    645 
    646   //
    647   // First search for '='
    648   //
    649   while (PtrBuf <= PtrEnd) {
    650     if (*PtrBuf == '=') {
    651       break;
    652     }
    653     PtrBuf++;
    654   }
    655   if (PtrBuf > PtrEnd) {
    656     //
    657     // Not found. Invalid line
    658     //
    659     return EFI_NOT_FOUND;
    660   }
    661   if (PtrBuf <= Buffer) {
    662     // Empty name
    663     return EFI_NOT_FOUND;
    664   }
    665 
    666   //
    667   // excluding the tailing '='
    668   //
    669   Length      = PtrBuf - Buffer;
    670   ProfileTrim (
    671     Buffer,
    672     &Length
    673   );
    674 
    675   //
    676   // Invalid line if the entry name is null
    677   //
    678   if (Length == 0) {
    679     return EFI_NOT_FOUND;
    680   }
    681 
    682   if (!IsValidName((CHAR8 *)Buffer, Length)) {
    683     return EFI_INVALID_PARAMETER;
    684   }
    685 
    686   //
    687   // Omit this line if no section header has been found before
    688   //
    689   if (*SectionHead == NULL) {
    690     return Status;
    691   }
    692   PtrSection  = *SectionHead;
    693 
    694   SectionItem = AllocatePool (sizeof (SECTION_ITEM));
    695   if (SectionItem == NULL) {
    696     return EFI_OUT_OF_RESOURCES;
    697   }
    698 
    699   SectionItem->PtrSection = NULL;
    700   SectionItem->PtrEntry   = NULL;
    701   SectionItem->PtrValue   = NULL;
    702   SectionItem->SecNameLen = PtrSection->SecNameLen;
    703   SectionItem->PtrNext    = *SectionHead;
    704   *SectionHead            = SectionItem;
    705 
    706   //
    707   // SectionName, add a trailing '\0'
    708   //
    709   SectionItem->PtrSection = AllocatePool (PtrSection->SecNameLen + 1);
    710   if (SectionItem->PtrSection == NULL) {
    711     return EFI_OUT_OF_RESOURCES;
    712   }
    713   CopyMem (SectionItem->PtrSection, PtrSection->PtrSection, PtrSection->SecNameLen + 1);
    714 
    715   //
    716   // EntryName, add a trailing '\0'
    717   //
    718   SectionItem->PtrEntry = AllocatePool (Length + 1);
    719   if (SectionItem->PtrEntry == NULL) {
    720     FreePool(SectionItem->PtrSection);
    721     return EFI_OUT_OF_RESOURCES;
    722   }
    723   CopyMem (SectionItem->PtrEntry, Buffer, Length);
    724   *(SectionItem->PtrEntry + Length) = '\0';
    725 
    726   //
    727   // Next search for '#' or ';'
    728   //
    729   PtrBuf      = PtrBuf + 1;
    730   Buffer      = PtrBuf;
    731   while (PtrBuf <= PtrEnd) {
    732     if (*PtrBuf == '#' || *PtrBuf == ';') {
    733       break;
    734     }
    735     PtrBuf++;
    736   }
    737   if (PtrBuf <= Buffer) {
    738     // Empty name
    739     FreePool(SectionItem->PtrEntry);
    740     FreePool(SectionItem->PtrSection);
    741     return EFI_NOT_FOUND;
    742   }
    743   Length      = PtrBuf - Buffer;
    744   ProfileTrim (
    745     Buffer,
    746     &Length
    747   );
    748 
    749   //
    750   // Invalid line if the entry value is null
    751   //
    752   if (Length == 0) {
    753     FreePool(SectionItem->PtrEntry);
    754     FreePool(SectionItem->PtrSection);
    755     return EFI_NOT_FOUND;
    756   }
    757 
    758   if (!IsValidValue((CHAR8 *)Buffer, Length)) {
    759     FreePool(SectionItem->PtrEntry);
    760     FreePool(SectionItem->PtrSection);
    761     return EFI_INVALID_PARAMETER;
    762   }
    763 
    764   //
    765   // EntryValue, add a trailing '\0'
    766   //
    767   SectionItem->PtrValue = AllocatePool (Length + 1);
    768   if (SectionItem->PtrValue == NULL) {
    769     FreePool(SectionItem->PtrEntry);
    770     FreePool(SectionItem->PtrSection);
    771     return EFI_OUT_OF_RESOURCES;
    772   }
    773   CopyMem (SectionItem->PtrValue, Buffer, Length);
    774   *(SectionItem->PtrValue + Length) = '\0';
    775 
    776   return EFI_SUCCESS;
    777 }
    778 
    779 /**
    780   Free all comment entry and section entry.
    781 
    782   @param[in] Section         Section entry list.
    783   @param[in] Comment         Comment entry list.
    784 
    785 **/
    786 VOID
    787 FreeAllList (
    788   IN      SECTION_ITEM                  *Section,
    789   IN      COMMENT_LINE                  *Comment
    790   )
    791 {
    792   SECTION_ITEM                          *PtrSection;
    793   COMMENT_LINE                          *PtrComment;
    794 
    795   while (Section != NULL) {
    796     PtrSection    = Section;
    797     Section       = Section->PtrNext;
    798     if (PtrSection->PtrEntry != NULL) {
    799       FreePool (PtrSection->PtrEntry);
    800     }
    801     if (PtrSection->PtrSection != NULL) {
    802       FreePool (PtrSection->PtrSection);
    803     }
    804     if (PtrSection->PtrValue != NULL) {
    805       FreePool (PtrSection->PtrValue);
    806     }
    807     FreePool (PtrSection);
    808   }
    809 
    810   while (Comment != NULL) {
    811     PtrComment    = Comment;
    812     Comment       = Comment->PtrNext;
    813     if (PtrComment->PtrComment != NULL) {
    814       FreePool (PtrComment->PtrComment);
    815     }
    816     FreePool (PtrComment);
    817   }
    818 
    819   return;
    820 }
    821 
    822 /**
    823   Get section entry value.
    824 
    825   @param[in]  Section         Section entry list.
    826   @param[in]  SectionName     Section name.
    827   @param[in]  EntryName       Section entry name.
    828   @param[out] EntryValue      Point to the got entry value.
    829 
    830   @retval EFI_NOT_FOUND  Section is not found.
    831   @retval EFI_SUCCESS    Section entry value is got.
    832 
    833 **/
    834 EFI_STATUS
    835 UpdateGetProfileString (
    836   IN      SECTION_ITEM                  *Section,
    837   IN      CHAR8                         *SectionName,
    838   IN      CHAR8                         *EntryName,
    839   OUT     CHAR8                         **EntryValue
    840   )
    841 {
    842   *EntryValue   = NULL;
    843 
    844   while (Section != NULL) {
    845     if (AsciiStrCmp ((CONST CHAR8 *) Section->PtrSection, (CONST CHAR8 *) SectionName) == 0) {
    846       if (Section->PtrEntry != NULL) {
    847         if (AsciiStrCmp ((CONST CHAR8 *) Section->PtrEntry, (CONST CHAR8 *) EntryName) == 0) {
    848           break;
    849         }
    850       }
    851     }
    852     Section     = Section->PtrNext;
    853   }
    854 
    855   if (Section == NULL) {
    856     return EFI_NOT_FOUND;
    857   }
    858 
    859   *EntryValue   = Section->PtrValue;
    860 
    861   return EFI_SUCCESS;
    862 }
    863 
    864 /**
    865   Converts a list of string to a specified buffer.
    866 
    867   @param[out] Buf             The output buffer that contains the string.
    868   @param[in]  BufferLength    The length of the buffer
    869   @param[in]  Str             The input string that contains the hex number
    870 
    871   @retval EFI_SUCCESS    The string was successfully converted to the buffer.
    872 
    873 **/
    874 EFI_STATUS
    875 AsciiStrToBuf (
    876   OUT UINT8    *Buf,
    877   IN  UINTN    BufferLength,
    878   IN  CHAR8    *Str
    879   )
    880 {
    881   UINTN       Index;
    882   UINTN       StrLength;
    883   UINT8       Digit;
    884   UINT8       Byte;
    885 
    886   Digit = 0;
    887 
    888   //
    889   // Two hex char make up one byte
    890   //
    891   StrLength = BufferLength * 2;
    892 
    893   for(Index = 0; Index < StrLength; Index++, Str++) {
    894 
    895     if ((*Str >= 'a') && (*Str <= 'f')) {
    896       Digit = (UINT8) (*Str - 'a' + 0x0A);
    897     } else if ((*Str >= 'A') && (*Str <= 'F')) {
    898       Digit = (UINT8) (*Str - 'A' + 0x0A);
    899     } else if ((*Str >= '0') && (*Str <= '9')) {
    900       Digit = (UINT8) (*Str - '0');
    901     } else {
    902       return EFI_INVALID_PARAMETER;
    903     }
    904 
    905     //
    906     // For odd characters, write the upper nibble for each buffer byte,
    907     // and for even characters, the lower nibble.
    908     //
    909     if ((Index & 1) == 0) {
    910       Byte = (UINT8) (Digit << 4);
    911     } else {
    912       Byte = Buf[Index / 2];
    913       Byte &= 0xF0;
    914       Byte = (UINT8) (Byte | Digit);
    915     }
    916 
    917     Buf[Index / 2] = Byte;
    918   }
    919 
    920   return EFI_SUCCESS;
    921 }
    922 
    923 /**
    924   Converts a string to GUID value.
    925   Guid Format is xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    926 
    927   @param[in]  Str              The registry format GUID string that contains the GUID value.
    928   @param[out] Guid             A pointer to the converted GUID value.
    929 
    930   @retval EFI_SUCCESS     The GUID string was successfully converted to the GUID value.
    931   @retval EFI_UNSUPPORTED The input string is not in registry format.
    932   @return others          Some error occurred when converting part of GUID value.
    933 
    934 **/
    935 EFI_STATUS
    936 AsciiStrToGuid (
    937   IN  CHAR8    *Str,
    938   OUT EFI_GUID *Guid
    939   )
    940 {
    941   //
    942   // Get the first UINT32 data
    943   //
    944   Guid->Data1 = (UINT32) AsciiStrHexToUint64  (Str);
    945   while (!IS_HYPHEN (*Str) && !IS_NULL (*Str)) {
    946     Str ++;
    947   }
    948 
    949   if (IS_HYPHEN (*Str)) {
    950     Str++;
    951   } else {
    952     return EFI_UNSUPPORTED;
    953   }
    954 
    955   //
    956   // Get the second UINT16 data
    957   //
    958   Guid->Data2 = (UINT16) AsciiStrHexToUint64  (Str);
    959   while (!IS_HYPHEN (*Str) && !IS_NULL (*Str)) {
    960     Str ++;
    961   }
    962 
    963   if (IS_HYPHEN (*Str)) {
    964     Str++;
    965   } else {
    966     return EFI_UNSUPPORTED;
    967   }
    968 
    969   //
    970   // Get the third UINT16 data
    971   //
    972   Guid->Data3 = (UINT16) AsciiStrHexToUint64  (Str);
    973   while (!IS_HYPHEN (*Str) && !IS_NULL (*Str)) {
    974     Str ++;
    975   }
    976 
    977   if (IS_HYPHEN (*Str)) {
    978     Str++;
    979   } else {
    980     return EFI_UNSUPPORTED;
    981   }
    982 
    983   //
    984   // Get the following 8 bytes data
    985   //
    986   AsciiStrToBuf (&Guid->Data4[0], 2, Str);
    987   //
    988   // Skip 2 byte hex chars
    989   //
    990   Str += 2 * 2;
    991 
    992   if (IS_HYPHEN (*Str)) {
    993     Str++;
    994   } else {
    995     return EFI_UNSUPPORTED;
    996   }
    997   AsciiStrToBuf (&Guid->Data4[2], 6, Str);
    998 
    999   return EFI_SUCCESS;
   1000 }
   1001 
   1002 /**
   1003   Pre process config data buffer into Section entry list and Comment entry list.
   1004 
   1005   @param[in]      DataBuffer      Config raw file buffer.
   1006   @param[in]      BufferSize      Size of raw buffer.
   1007   @param[in, out] SectionHead     Pointer to the section entry list.
   1008   @param[in, out] CommentHead     Pointer to the comment entry list.
   1009 
   1010   @retval EFI_OUT_OF_RESOURCES  No enough memory is allocated.
   1011   @retval EFI_SUCCESS           Config data buffer is preprocessed.
   1012   @retval EFI_NOT_FOUND         Config data buffer is invalid, because Section or Entry is not found.
   1013   @retval EFI_INVALID_PARAMETER Config data buffer is invalid, because Section or Entry is invalid.
   1014 
   1015 **/
   1016 EFI_STATUS
   1017 PreProcessDataFile (
   1018   IN      UINT8                         *DataBuffer,
   1019   IN      UINTN                         BufferSize,
   1020   IN OUT  SECTION_ITEM                  **SectionHead,
   1021   IN OUT  COMMENT_LINE                  **CommentHead
   1022   )
   1023 {
   1024   EFI_STATUS                            Status;
   1025   CHAR8                                 *Source;
   1026   CHAR8                                 *CurrentPtr;
   1027   CHAR8                                 *BufferEnd;
   1028   CHAR8                                 *PtrLine;
   1029   UINTN                                 LineLength;
   1030   UINTN                                 SourceLength;
   1031   UINTN                                 MaxLineLength;
   1032 
   1033   *SectionHead          = NULL;
   1034   *CommentHead          = NULL;
   1035   BufferEnd             = (CHAR8 *) ( (UINTN) DataBuffer + BufferSize);
   1036   CurrentPtr            = (CHAR8 *) DataBuffer;
   1037   MaxLineLength         = MAX_LINE_LENGTH;
   1038   Status                = EFI_SUCCESS;
   1039 
   1040   PtrLine = AllocatePool (MaxLineLength);
   1041   if (PtrLine == NULL) {
   1042     return EFI_OUT_OF_RESOURCES;
   1043   }
   1044 
   1045   while (CurrentPtr < BufferEnd) {
   1046     Source              = CurrentPtr;
   1047     SourceLength        = (UINTN)BufferEnd - (UINTN)CurrentPtr;
   1048     LineLength          = MaxLineLength;
   1049     //
   1050     // With the assumption that line length is less than 512
   1051     // characters. Otherwise BUFFER_TOO_SMALL will be returned.
   1052     //
   1053     Status              = ProfileGetLine (
   1054                             (UINT8 *) Source,
   1055                             SourceLength,
   1056                             (UINT8 *) PtrLine,
   1057                             &LineLength
   1058                             );
   1059     if (EFI_ERROR (Status)) {
   1060       if (Status == EFI_BUFFER_TOO_SMALL) {
   1061         //
   1062         // If buffer too small, re-allocate the buffer according
   1063         // to the returned LineLength and try again.
   1064         //
   1065         FreePool (PtrLine);
   1066         PtrLine         = NULL;
   1067         PtrLine = AllocatePool (LineLength);
   1068         if (PtrLine == NULL) {
   1069           Status        = EFI_OUT_OF_RESOURCES;
   1070           break;
   1071         }
   1072         SourceLength    = LineLength;
   1073         Status          = ProfileGetLine (
   1074                             (UINT8 *) Source,
   1075                             SourceLength,
   1076                             (UINT8 *) PtrLine,
   1077                             &LineLength
   1078                             );
   1079         if (EFI_ERROR (Status)) {
   1080           break;
   1081         }
   1082         MaxLineLength   = LineLength;
   1083       } else {
   1084         break;
   1085       }
   1086     }
   1087     CurrentPtr          = (CHAR8 *) ( (UINTN) CurrentPtr + LineLength);
   1088 
   1089     //
   1090     // Line got. Trim the line before processing it.
   1091     //
   1092     ProfileTrim (
   1093       (UINT8 *) PtrLine,
   1094       &LineLength
   1095    );
   1096 
   1097     //
   1098     // Blank line
   1099     //
   1100     if (LineLength == 0) {
   1101       continue;
   1102     }
   1103 
   1104     if (PtrLine[0] == '#' || PtrLine[0] == ';') {
   1105       Status            = ProfileGetComments (
   1106                             (UINT8 *) PtrLine,
   1107                             LineLength,
   1108                             CommentHead
   1109                             );
   1110     } else if (PtrLine[0] == '[') {
   1111       Status            = ProfileGetSection (
   1112                             (UINT8 *) PtrLine,
   1113                             LineLength,
   1114                             SectionHead
   1115                             );
   1116     } else {
   1117       Status            = ProfileGetEntry (
   1118                             (UINT8 *) PtrLine,
   1119                             LineLength,
   1120                             SectionHead
   1121                             );
   1122     }
   1123 
   1124     if (EFI_ERROR (Status)) {
   1125       break;
   1126     }
   1127   }
   1128 
   1129   //
   1130   // Free buffer
   1131   //
   1132   FreePool (PtrLine);
   1133 
   1134   return Status;
   1135 }
   1136 
   1137 /**
   1138   Open an INI config file and return a context.
   1139 
   1140   @param[in] DataBuffer      Config raw file buffer.
   1141   @param[in] BufferSize      Size of raw buffer.
   1142 
   1143   @return       Config data buffer is opened and context is returned.
   1144   @retval NULL  No enough memory is allocated.
   1145   @retval NULL  Config data buffer is invalid.
   1146 **/
   1147 VOID *
   1148 EFIAPI
   1149 OpenIniFile (
   1150   IN      UINT8                         *DataBuffer,
   1151   IN      UINTN                         BufferSize
   1152   )
   1153 {
   1154   EFI_STATUS                            Status;
   1155   INI_PARSING_LIB_CONTEXT               *IniContext;
   1156 
   1157   if (DataBuffer == NULL || BufferSize == 0) {
   1158     return NULL;
   1159   }
   1160 
   1161   IniContext = AllocateZeroPool(sizeof(INI_PARSING_LIB_CONTEXT));
   1162   if (IniContext == NULL) {
   1163     return NULL;
   1164   }
   1165 
   1166   //
   1167   // First process the data buffer and get all sections and entries
   1168   //
   1169   Status = PreProcessDataFile (
   1170              DataBuffer,
   1171              BufferSize,
   1172              &IniContext->SectionHead,
   1173              &IniContext->CommentHead
   1174              );
   1175   if (EFI_ERROR(Status)) {
   1176     FreePool(IniContext);
   1177     return NULL;
   1178   }
   1179   DEBUG_CODE_BEGIN ();
   1180     DumpIniSection(IniContext);
   1181   DEBUG_CODE_END ();
   1182   return IniContext;
   1183 }
   1184 
   1185 /**
   1186   Get section entry string value.
   1187 
   1188   @param[in]  Context         INI Config file context.
   1189   @param[in]  SectionName     Section name.
   1190   @param[in]  EntryName       Section entry name.
   1191   @param[out] EntryValue      Point to the got entry string value.
   1192 
   1193   @retval EFI_SUCCESS    Section entry string value is got.
   1194   @retval EFI_NOT_FOUND  Section is not found.
   1195 **/
   1196 EFI_STATUS
   1197 EFIAPI
   1198 GetStringFromDataFile(
   1199   IN      VOID                          *Context,
   1200   IN      CHAR8                         *SectionName,
   1201   IN      CHAR8                         *EntryName,
   1202   OUT     CHAR8                         **EntryValue
   1203   )
   1204 {
   1205   INI_PARSING_LIB_CONTEXT               *IniContext;
   1206   EFI_STATUS                            Status;
   1207 
   1208   if (Context == NULL || SectionName == NULL || EntryName == NULL || EntryValue == NULL) {
   1209     return EFI_INVALID_PARAMETER;
   1210   }
   1211 
   1212   IniContext = Context;
   1213 
   1214   *EntryValue  = NULL;
   1215   Status = UpdateGetProfileString (
   1216              IniContext->SectionHead,
   1217              SectionName,
   1218              EntryName,
   1219              EntryValue
   1220              );
   1221   return Status;
   1222 }
   1223 
   1224 /**
   1225   Get section entry GUID value.
   1226 
   1227   @param[in]  Context         INI Config file context.
   1228   @param[in]  SectionName     Section name.
   1229   @param[in]  EntryName       Section entry name.
   1230   @param[out] Guid            Point to the got GUID value.
   1231 
   1232   @retval EFI_SUCCESS    Section entry GUID value is got.
   1233   @retval EFI_NOT_FOUND  Section is not found.
   1234 **/
   1235 EFI_STATUS
   1236 EFIAPI
   1237 GetGuidFromDataFile (
   1238   IN      VOID                          *Context,
   1239   IN      CHAR8                         *SectionName,
   1240   IN      CHAR8                         *EntryName,
   1241   OUT     EFI_GUID                      *Guid
   1242   )
   1243 {
   1244   CHAR8                                 *Value;
   1245   EFI_STATUS                            Status;
   1246 
   1247   if (Context == NULL || SectionName == NULL || EntryName == NULL || Guid == NULL) {
   1248     return EFI_INVALID_PARAMETER;
   1249   }
   1250 
   1251   Status = GetStringFromDataFile(
   1252              Context,
   1253              SectionName,
   1254              EntryName,
   1255              &Value
   1256              );
   1257   if (EFI_ERROR(Status)) {
   1258     return EFI_NOT_FOUND;
   1259   }
   1260   ASSERT (Value != NULL);
   1261   if (!IsValidGuid(Value, AsciiStrLen(Value))) {
   1262     return EFI_NOT_FOUND;
   1263   }
   1264   Status = AsciiStrToGuid(Value, Guid);
   1265   if (EFI_ERROR (Status)) {
   1266     return EFI_NOT_FOUND;
   1267   }
   1268   return EFI_SUCCESS;
   1269 }
   1270 
   1271 /**
   1272   Get section entry decimal UINTN value.
   1273 
   1274   @param[in]  Context         INI Config file context.
   1275   @param[in]  SectionName     Section name.
   1276   @param[in]  EntryName       Section entry name.
   1277   @param[out] Data            Point to the got decimal UINTN value.
   1278 
   1279   @retval EFI_SUCCESS    Section entry decimal UINTN value is got.
   1280   @retval EFI_NOT_FOUND  Section is not found.
   1281 **/
   1282 EFI_STATUS
   1283 EFIAPI
   1284 GetDecimalUintnFromDataFile (
   1285   IN      VOID                          *Context,
   1286   IN      CHAR8                         *SectionName,
   1287   IN      CHAR8                         *EntryName,
   1288   OUT     UINTN                         *Data
   1289   )
   1290 {
   1291   CHAR8                                 *Value;
   1292   EFI_STATUS                            Status;
   1293 
   1294   if (Context == NULL || SectionName == NULL || EntryName == NULL || Data == NULL) {
   1295     return EFI_INVALID_PARAMETER;
   1296   }
   1297 
   1298   Status = GetStringFromDataFile(
   1299              Context,
   1300              SectionName,
   1301              EntryName,
   1302              &Value
   1303              );
   1304   if (EFI_ERROR(Status)) {
   1305     return EFI_NOT_FOUND;
   1306   }
   1307   ASSERT (Value != NULL);
   1308   if (!IsValidDecimalString(Value, AsciiStrLen(Value))) {
   1309     return EFI_NOT_FOUND;
   1310   }
   1311   *Data = AsciiStrDecimalToUintn(Value);
   1312   return EFI_SUCCESS;
   1313 }
   1314 
   1315 /**
   1316   Get section entry heximal UINTN value.
   1317 
   1318   @param[in]  Context         INI Config file context.
   1319   @param[in]  SectionName     Section name.
   1320   @param[in]  EntryName       Section entry name.
   1321   @param[out] Data            Point to the got heximal UINTN value.
   1322 
   1323   @retval EFI_SUCCESS    Section entry heximal UINTN value is got.
   1324   @retval EFI_NOT_FOUND  Section is not found.
   1325 **/
   1326 EFI_STATUS
   1327 EFIAPI
   1328 GetHexUintnFromDataFile (
   1329   IN      VOID                          *Context,
   1330   IN      CHAR8                         *SectionName,
   1331   IN      CHAR8                         *EntryName,
   1332   OUT     UINTN                         *Data
   1333   )
   1334 {
   1335   CHAR8                                 *Value;
   1336   EFI_STATUS                            Status;
   1337 
   1338   if (Context == NULL || SectionName == NULL || EntryName == NULL || Data == NULL) {
   1339     return EFI_INVALID_PARAMETER;
   1340   }
   1341 
   1342   Status = GetStringFromDataFile(
   1343              Context,
   1344              SectionName,
   1345              EntryName,
   1346              &Value
   1347              );
   1348   if (EFI_ERROR(Status)) {
   1349     return EFI_NOT_FOUND;
   1350   }
   1351   ASSERT (Value != NULL);
   1352   if (!IsValidHexString(Value, AsciiStrLen(Value))) {
   1353     return EFI_NOT_FOUND;
   1354   }
   1355   *Data = AsciiStrHexToUintn(Value);
   1356   return EFI_SUCCESS;
   1357 }
   1358 
   1359 /**
   1360   Get section entry heximal UINT64 value.
   1361 
   1362   @param[in]  Context         INI Config file context.
   1363   @param[in]  SectionName     Section name.
   1364   @param[in]  EntryName       Section entry name.
   1365   @param[out] Data            Point to the got heximal UINT64 value.
   1366 
   1367   @retval EFI_SUCCESS    Section entry heximal UINT64 value is got.
   1368   @retval EFI_NOT_FOUND  Section is not found.
   1369 **/
   1370 EFI_STATUS
   1371 EFIAPI
   1372 GetHexUint64FromDataFile (
   1373   IN      VOID                          *Context,
   1374   IN      CHAR8                         *SectionName,
   1375   IN      CHAR8                         *EntryName,
   1376   OUT     UINT64                        *Data
   1377   )
   1378 {
   1379   CHAR8                                 *Value;
   1380   EFI_STATUS                            Status;
   1381 
   1382   if (Context == NULL || SectionName == NULL || EntryName == NULL || Data == NULL) {
   1383     return EFI_INVALID_PARAMETER;
   1384   }
   1385 
   1386   Status = GetStringFromDataFile(
   1387              Context,
   1388              SectionName,
   1389              EntryName,
   1390              &Value
   1391              );
   1392   if (EFI_ERROR(Status)) {
   1393     return EFI_NOT_FOUND;
   1394   }
   1395   ASSERT (Value != NULL);
   1396   if (!IsValidHexString(Value, AsciiStrLen(Value))) {
   1397     return EFI_NOT_FOUND;
   1398   }
   1399   *Data = AsciiStrHexToUint64(Value);
   1400   return EFI_SUCCESS;
   1401 }
   1402 
   1403 /**
   1404   Close an INI config file and free the context.
   1405 
   1406   @param[in] Context         INI Config file context.
   1407 **/
   1408 VOID
   1409 EFIAPI
   1410 CloseIniFile (
   1411   IN      VOID                          *Context
   1412   )
   1413 {
   1414   INI_PARSING_LIB_CONTEXT               *IniContext;
   1415 
   1416   if (Context == NULL) {
   1417     return ;
   1418   }
   1419 
   1420   IniContext = Context;
   1421   FreeAllList(IniContext->SectionHead, IniContext->CommentHead);
   1422 
   1423   return;
   1424 }
   1425