/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
 *
 * Copyright 2025 GNOME Foundation, Inc.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Authors:
 *  - Philip Withnall <pwithnall@gnome.org>
 */

#include "config.h"

#include <glib.h>

#include "filter-list.h"


/**
 * mct_filter_list_parse_from_data:
 * @filter_contents: (array length=filter_contents_len): contents of the filter
 *   list file, this may contain arbitrary byte values
 * @filter_contents_len: length of @filter_contents, in bytes
 * @callback: (scope call) (not nullable): callback to call for each entry
 *   parsed from @filter_contents
 * @user_data: data to pass to @callback
 * @error: return location for a [type@GLib.Error]
 *
 * Parse a filter list from the given data.
 *
 * @filter_contents is typically expected to be a memory mapped file.
 *
 * A filter list is a set of hostnames to filter. The format is common to many
 * filter lists available on the internet:
 *  - At most one hostname per line
 *  - Lines separated by `\n` characters
 *  - Comment lines start with `#` and continue to the end of the line
 *  - Whitespace is stripped from each line and blank lines are ignored
 *
 * Returns: true on success, false otherwise
 * Since: 0.14.0
 */
gboolean
mct_filter_list_parse_from_data (const char                  *filter_contents,
                                 size_t                       filter_contents_len,
                                 MctFilterListParseCallback   callback,
                                 void                        *user_data,
                                 GError                     **error)
{
  size_t filter_contents_pos = 0;

  while (filter_contents_pos < filter_contents_len)
    {
      /* Consume leading whitespace. */
      while (filter_contents_pos < filter_contents_len &&
             g_ascii_isspace (filter_contents[filter_contents_pos]))
        filter_contents_pos++;
      if (filter_contents_pos >= filter_contents_len)
        break;

      /* If the line is a comment, skip the rest of the line. */
      if (filter_contents[filter_contents_pos] == '#')
        {
          const char *next_newline = memchr (filter_contents + filter_contents_pos,
                                             '\n',
                                             filter_contents_len - filter_contents_pos);

          if (next_newline == NULL)
            break;

          filter_contents_pos = next_newline - filter_contents + 1;
        }
      else
        {
          /* Otherwise, it’s a hostname, so grab that until the next
           * newline, then chop off any trailing whitespace. */
          const char *hostname = filter_contents + filter_contents_pos;
          size_t hostname_len;
          const char *next_newline = memchr (filter_contents + filter_contents_pos,
                                             '\n',
                                             filter_contents_len - filter_contents_pos);

          if (next_newline == NULL)
            hostname_len = filter_contents_len - filter_contents_pos;
          else
            hostname_len = next_newline - hostname;

          while (hostname_len > 0 && g_ascii_isspace (hostname[hostname_len - 1]))
            hostname_len--;

          g_assert (hostname_len > 0);

          /* Expose the parsed hostname to the caller to validate and save as
           * they wish */
          if (!callback (hostname, hostname_len, user_data, error))
            return FALSE;

          if (next_newline == NULL)
            break;

          filter_contents_pos = next_newline - filter_contents + 1;
        }
    }

  return TRUE;
}
