//  [c]
error_t ftpClientParseDirEntry(char_t *line, FtpDirEntry *dirEntry)
{
   uint_t i;
   size_t n;
   char_t *p;
   char_t *token;

   //Abbreviated months
   static const char_t months[13][4] =
   {
      "   ",
      "Jan",
      "Feb",
      "Mar",
      "Apr",
      "May",
      "Jun",
      "Jul",
      "Aug",
      "Sep",
      "Oct",
      "Nov",
      "Dec"
   };

   //Read first field
   token = osStrtok_r(line, " \t", &p);
   //Invalid directory entry?
   if(token == NULL)
      return ERROR_INVALID_SYNTAX;

   //MS-DOS listing format?
   if(osIsdigit(token[0]))
   {
      //Check modification date format
      if(osStrlen(token) == 8 && token[2] == '-' && token[5] == '-')
      {
         //The format of the date is mm-dd-yy
         dirEntry->modified.month = (uint8_t) osStrtoul(token, NULL, 10);
         dirEntry->modified.day = (uint8_t) osStrtoul(token + 3, NULL, 10);
         dirEntry->modified.year = (uint16_t) osStrtoul(token + 6, NULL, 10) + 2000;
      }
      else if(osStrlen(token) == 10 && token[2] == '/' && token[5] == '/')
      {
         //The format of the date is mm/dd/yyyy
         dirEntry->modified.month = (uint8_t) osStrtoul(token, NULL, 10);
         dirEntry->modified.day = (uint8_t) osStrtoul(token + 3, NULL, 10);
         dirEntry->modified.year = (uint16_t) osStrtoul(token + 6, NULL, 10);
      }
      else
      {
         //Invalid time format
         return ERROR_INVALID_SYNTAX;
      }

      //Read modification time
      token = osStrtok_r(NULL, " ", &p);
      //Invalid directory entry?
      if(token == NULL)
         return ERROR_INVALID_SYNTAX;

      //Check modification time format
      if(osStrlen(token) >= 5 && token[2] == ':')
      {
         //The format of the time hh:mm
         dirEntry->modified.hours = (uint8_t) osStrtoul(token, NULL, 10);
         dirEntry->modified.minutes = (uint8_t) osStrtoul(token + 3, NULL, 10);

         //The PM period covers the 12 hours from noon to midnight
         if(strstr(token, "PM") != NULL)
            dirEntry->modified.hours += 12;
      }
      else
      {
         //Invalid time format
         return ERROR_INVALID_SYNTAX;
      }

      //Read next field
      token = osStrtok_r(NULL, " ", &p);
      //Invalid directory entry?
      if(token == NULL)
         return ERROR_INVALID_SYNTAX;

      //Check whether the current entry is a directory
      if(!osStrcmp(token, "<DIR>"))
      {
         //Update attributes
         dirEntry->attributes |= FTP_FILE_ATTR_DIRECTORY;
      }
      else
      {
         //Save the size of the file
         dirEntry->size = osStrtoul(token, NULL, 10);
      }

      //Read filename field
      token = osStrtok_r(NULL, " \r\n", &p);
      //Invalid directory entry?
      if(token == NULL)
         return ERROR_INVALID_SYNTAX;

      //Retrieve the length of the filename
      n = osStrlen(token);
      //Limit the number of characters to copy
      n = MIN(n, FTP_CLIENT_MAX_FILENAME_LEN);

      //Copy the filename
      osStrncpy(dirEntry->name, token, n);
      //Properly terminate the string with a NULL character
      dirEntry->name[n] = '\0';
   }
   //Unix listing format?
   else
   {
      //Check file permissions
      if(strchr(token, 'd') != NULL)
         dirEntry->attributes |= FTP_FILE_ATTR_DIRECTORY;
      if(strchr(token, 'w') == NULL)
         dirEntry->attributes |= FTP_FILE_ATTR_READ_ONLY;

      //Read next field
      token = osStrtok_r(NULL, " ", &p);
      //Invalid directory entry?
      if(token == NULL)
         return ERROR_INVALID_SYNTAX;

      //Discard owner field
      token = osStrtok_r(NULL, " ", &p);
      //Invalid directory entry?
      if(token == NULL)
         return ERROR_INVALID_SYNTAX;

      //Discard group field
      token = osStrtok_r(NULL, " ", &p);
      //Invalid directory entry?
      if(token == NULL)
         return ERROR_INVALID_SYNTAX;

      //Read size field
      token = osStrtok_r(NULL, " ", &p);
      //Invalid directory entry?
      if(token == NULL)
         return ERROR_INVALID_SYNTAX;

      //Save the size of the file
      dirEntry->size = osStrtoul(token, NULL, 10);

      //Read modification time (month)
      token = osStrtok_r(NULL, " ", &p);
      //Invalid directory entry?
      if(token == NULL)
         return ERROR_INVALID_SYNTAX;

      //Decode the 3-letter month name
      for(i = 1; i <= 12; i++)
      {
         //Compare month name
         if(!osStrcmp(token, months[i]))
         {
            //Save month number
            dirEntry->modified.month = i;
            break;
         }
      }

      //Read modification time (day)
      token = osStrtok_r(NULL, " ", &p);
      //Invalid directory entry?
      if(token == NULL)
         return ERROR_INVALID_SYNTAX;

      //Save day number
      dirEntry->modified.day = (uint8_t) osStrtoul(token, NULL, 10);

      //Read next field
      token = osStrtok_r(NULL, " ", &p);
      //Invalid directory entry?
      if(token == NULL)
         return ERROR_INVALID_SYNTAX;

      //Check modification time format
      if(osStrlen(token) == 4)
      {
         //The format of the year is yyyy
         dirEntry->modified.year = (uint16_t) osStrtoul(token, NULL, 10);

      }
      else if(osStrlen(token) == 5)
      {
         //The format of the time hh:mm
         token[2] = '\0';
         dirEntry->modified.hours = (uint8_t) osStrtoul(token, NULL, 10);
         dirEntry->modified.minutes = (uint8_t) osStrtoul(token + 3, NULL, 10);
      }
      else
      {
         //Invalid time format
         return ERROR_INVALID_SYNTAX;
      }

      //Read filename field
      token = osStrtok_r(NULL, " \r\n", &p);
      //Invalid directory entry?
      if(token == NULL)
         return ERROR_INVALID_SYNTAX;

      //Retrieve the length of the filename
      n = osStrlen(token);
      //Limit the number of characters to copy
      n = MIN(n, FTP_CLIENT_MAX_FILENAME_LEN);

      //Copy the filename
      osStrncpy(dirEntry->name, token, n);
      //Properly terminate the string with a NULL character
      dirEntry->name[n] = '\0';
   }

   //The directory entry is valid
   return NO_ERROR;
}