/****************************************************************************
 * libs/libc/netdb/lib_parsehostfile.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * 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 "lib_netdb.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];
  int       hi_addrtypes[1];
  int       hi_lengths[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 (; ; )
    {
      /* There there space to buffer one more character? */

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

      /* 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)
        {
          /* Remember what terminated the string */

          *terminator = ch;

          /* Add NUL termination */

          *ptr++ = '\0';

          /* Return EOF if nothing has written */

          return nwritten + 1;
        }

      /* Write the next string to the buffer */

      *ptr++ = ch;
      nwritten++;
    }
}

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

/****************************************************************************
 * Name: 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 parse_hostfile(FAR FILE *stream, FAR struct hostent_s *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_s));
  memset(info, 0, sizeof(struct hostent_info_s));

  host->h_addrtypes = info->hi_addrtypes;
  host->h_lengths   = info->hi_lengths;
  host->h_addr_list = info->hi_addrlist;

  /* 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 ch == EOF ? -EPIPE : -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 */

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

      host->h_addrtypes[0] = 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 */

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

      host->h_addrtypes[0] = AF_INET;
    }

  host->h_addr_list[0] = ptr;
  host->h_lengths[0]   = 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;
    }
  else if (buflen == 0)
    {
      return -ERANGE;
    }

  /* 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++)
    {
      /* Skip over any leading whitespace */

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

          return nread;
        }
      else if (buflen == 0 || i >= CONFIG_NETDB_MAX_ALTNAMES)
        {
          return -ERANGE;
        }

      /* Parse the next alias */

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

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

      /* 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;
    }
}

#endif /* CONFIG_NETDB_HOSTFILE */