/****************************************************************************
 * libc/netdb/lib_parsehostile.c
 *
 *   Copyright (C) 2015 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include <arpa/inet.h>

#include "libc.h"

#ifdef CONFIG_NETDB_HOSTFILE

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Check if character is any kind of white space (except for newline) */

#define lib_isspace(c) \
  ((c) == ' '  || (c) == '\t' || (c) == '\r' || (c) == '\f' || c == '\v')

/****************************************************************************
 * Private Type Definitions
 ****************************************************************************/

/* This is the layout of the caller provided memory area */

struct hostent_info_s
{
  FAR char *hi_aliases[CONFIG_NETDB_MAX_ALTNAMES + 1];
  FAR char *hi_addrlist[2];
  char hi_data[1];
};


/****************************************************************************
 * Private functions
 ****************************************************************************/

/****************************************************************************
 * Name: lib_skipspaces
 *
 * Description:
 *   Read from the 'stream' until a non-whitespace character is read or the
 *   end-of-line or end-of-file is encountered.
 *
 * Input Parameters:
 *   stream - The stream to read from
 *   nread  - A count to the pointer of characters read.  Will be
 *     incremented after each successful character read.
 *
 * Returned Value:
 *   The first non-whitespace character read.  This will be the newline
 *   character of EROF if the end-of-line or end-of-file is encountered.
 *
 ****************************************************************************/

static int lib_skipspaces(FAR FILE *stream, FAR size_t *nread)
{
  int ch;

  /* Skip over most white space (but not newline) */

  do
    {
      ch = fgetc(stream);
      if (ch != EOF)
        {
          (*nread)++;
        }
    }
  while (lib_isspace(ch));

  return ch;
}

/****************************************************************************
 * Name: lib_skipline
 *
 * Description:
 *   Read from the 'stream' until the end-of-line or end-of-file is
 *   encountered.
 *
 * Input Parameters:
 *   stream - The stream to read from
 *   nread  - A count to the pointer of characters read.  Will be
 *     incremented after each successful character read.
 *
 * Returned Value:
 *   The character that terminated the line.  This may be either the newline
 *   character or EOF.
 *
 ****************************************************************************/

static int lib_skipline(FAR FILE *stream, FAR size_t *nread)
{
  int ch;

  /* Skip over all characters until we encounter a newline or end-of-file */

  do
    {
      ch = fgetc(stream);
      if (ch != EOF)
        {
          (*nread)++;
        }
    }
  while (ch != EOF && ch != '\n');

  return ch;
}

/****************************************************************************
 * Name: lib_copystring
 *
 * Description:
 *   Read from the 'stream' And copy each byte to the buffer at 'ptr' until
 *   either a whitespace delimiter, the end-of-line, or the end-of-file is
 *   encountered.
 *
 * Input Parameters:
 *   stream - The stream to read from
 *   ptr - The pointer to the buffer to receive the string
 *   nread  - A count to the pointer of characters read.  Will be
 *     incremented after each successful character read.
 *   buflen - The size of the buffer in bytes
 *   terminator - The actual character the terminated the copy is returned
 *     to this location.
 *
 * Returned Value:
 *  Number of bytes written to the buffer on success.  0 if the end of
 *  file is encountered (or a read error occurs).  A negated errno value on
 *  any failure:
 *
 *    -ERANGE - Insufficient buffer space to hold the string.
 *
 ****************************************************************************/

static ssize_t lib_copystring(FAR FILE *stream, FAR char *ptr, FAR size_t *nread,
                              size_t buflen, FAR int *terminator)
{
  size_t nwritten = 0;
  int ch;

  /* Copy the string from the file until any whitepace delimiter is
   * encountered
   */

  for(;;)
    {
      /* Read the next character from the file */

      ch = fgetc(stream);
      if (ch != EOF)
        {
          (*nread)++;
        }

      /* Check for whitepace (including \n') or EOF terminating the string */

      if (isspace(ch) || ch == EOF)
        {
          /* Remeber what terminated the string */

          *terminator = ch;

          /* Add NUL termination */

          *ptr++ = '\0';

          /* Return EOF if nothing has written */
          return nwritten == 0 ? 0 : nwritten + 1;
        }

      /* Write the next string to the buffer */

      *ptr++ = ch;
      nwritten++;

      /* There there space to buffer one more character? */

      if (nwritten >= buflen)
        {
          return -ERANGE;
        }
    }
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: lib_parse_hostfile
 *
 * Description:
 *   Parse the next line from the hosts file.
 *
 * Input Parameters:
 *   stream - File stream of the opened hosts file with the file pointer
 *     positioned at the beginning of the next host entry.
 *   host - Caller provided location to return the host data.
 *   buf - Caller provided buffer to hold string data associated with the
 *     host data.
 *   buflen - The size of the caller-provided buffer
 *
 * Returned Value:
 *   The non-zero number of bytes read from the hosts file is returned if
 *   the host entry was successfully read.  Zero is returned if the end
 *   of the host file has been reached.  A negated errno value is return
 *   in the event a failure:
 *
 *     ERANGE - Buffer not big enough
 *     ESPIPE - End of file (or possibly a read error).
 *     EAGAIN - Error parsing the line (E.g., missing hostname)
 *
 ****************************************************************************/

ssize_t lib_parse_hostfile(FAR FILE *stream, FAR struct hostent *host,
                           FAR char *buf, size_t buflen)
{
  FAR struct hostent_info_s *info;
  FAR char addrstring[48];
  FAR char *ptr;
  FAR char *start;
  socklen_t addrlen;
  size_t nread = 0;
  ssize_t nwritten;
  int ret;
  int ch;
  int i;

  /* Verify that we have a buffer big enough to get started (it still may not
   * be big enough).
   */

  if (buflen <= sizeof(struct hostent_info_s))
   {
     return -ERANGE;
   }

  info    = (FAR struct hostent_info_s *)buf;
  ptr     = info->hi_data;
  buflen -= (sizeof(struct hostent_info_s) - 1);

  memset(host, 0, sizeof(struct hostent));
  memset(info, 0, sizeof(struct hostent_info_s));

  /* Skip over any leading spaces */

  do
    {
      ch = lib_skipspaces(stream, &nread);
      if (ch == EOF)
        {
          return -EPIPE;
        }

      /* Skip comment lines beginning with '#' */

      if (ch == '#')
        {
           /* Skip to the end of line. */

          ch = lib_skipline(stream, &nread);
          if (ch == EOF)
            {
              return -EPIPE;
            }
        }
    }
  while (ch == '\n');

  /* Parse the IP address */

  addrstring[0] = ch;

  nwritten = lib_copystring(stream, &addrstring[1], &nread, 47, &ch);
  if (nwritten <= 0)
    {
      return nwritten;
    }

  if (!lib_isspace(ch))
    {
      /* The string was terminated with a newline of EOF */

      return -EAGAIN;
    }

  /* If the address contains a colon, say it is IPv6 */

  if (strchr(addrstring, ':') != NULL)
    {
      /* Make sure that space remains to hold the IPv6 address */

      addrlen = sizeof(struct in6_addr);
      if (buflen < addrlen)
        {
          return -ERANGE;
        }

      ret = inet_pton(AF_INET6, addrstring, ptr);
      if (ret < 0)
        {
          /* Conversion failed.  Entry is corrupted */

          (void)lib_skipline(stream, &nread);
          return -EAGAIN;
        }

      host->h_addrtype  = AF_INET6;
    }
  else
    {
      /* Make sure that space remains to hold the IPv4 address */

      addrlen = sizeof(struct in_addr);
      if (buflen < addrlen)
        {
          return -ERANGE;
        }

      ret = inet_pton(AF_INET, addrstring, ptr);
      if (ret < 0)
        {
          /* Conversion failed.  Entry is corrupted */

          (void)lib_skipline(stream, &nread);
          return -EAGAIN;
        }

      host->h_addrtype  = AF_INET;
    }

  info->hi_addrlist[0] = ptr;
  host->h_addr_list    = info->hi_addrlist;
  host->h_length       = addrlen;

  ptr                 += addrlen;
  buflen              -= addrlen;

  /* Skip over any additional whitespace */

  ch = lib_skipspaces(stream, &nread);
  if (ch == EOF)
    {
      return -EPIPE;
    }
  else if (ch == '\n')
    {
      return -EAGAIN;
    }

  /* Parse the host name */

  start = ptr;
  *ptr++ = ch;
  buflen--;

  nwritten = lib_copystring(stream, ptr, &nread, buflen, &ch);
  if (nwritten <= 0)
    {
      return nwritten;
    }

  host->h_name = start;

  if (!lib_isspace(ch))
    {
      /* The string was terminated with a newline or EOF */

      return nread;
    }

  ptr += nwritten;
  buflen -= nwritten;

  /* Parse any host name aliases */

  for (i = 0; i < CONFIG_NETDB_MAX_ALTNAMES; i++)
    {
      /* Skip over any leading whitespace */

      ch = lib_skipspaces(stream, &nread);
      if (ch == EOF || ch == '\n')
        {
          /* No further aliases on the line */

          return nread;
        }

      /* Parse the next alias */

      start = ptr;
      *ptr++ = ch;
      buflen--;

      nwritten = lib_copystring(stream, ptr, &nread, buflen, &ch);
      if (nwritten < 0)
        {
          return nwritten;
        }
      else if (nwritten == 0)
        {
          return nread;
        }

       /* Save the pointer to the beginning of the next alias */

       info->hi_aliases[i] = start;
       if (host->h_aliases == NULL)
         {
           host->h_aliases = info->hi_aliases;
         }

      if (!lib_isspace(ch))
        {
          /* The string was terminated with a newline of EOF */

          return nread;
        }

      ptr += nwritten;
      buflen -= nwritten;
    }

  /* We get here only if there are more than CONFIG_NETDB_MAX_ALTNAMES
   * aliases on the line.  Skip to the endof the line, ignoring any
   * additional aliases.
   */

  (void)lib_skipline(stream, &nread);
  return nread;
}

#endif /* CONFIG_NETDB_HOSTFILE */