/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2013 Red Hat, Inc.
 */

/**
 * SECTION:nmt-ip-entry
 * @short_description: #NmtNewtEntry for IP address entry
 *
 * #NmtIPEntry is an #NmtNewtEntry for entering IP addresses, or IP
 * address/prefix combination. It will only allow typing characters
 * that are valid in an IP address, and will set its
 * #NmtNewtWidget:valid property depending on whether it currently
 * contains a valid IP address.
 */

#include "libnm-client-aux-extern/nm-default-client.h"

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>

#include "nmt-ip-entry.h"

G_DEFINE_TYPE(NmtIPEntry, nmt_ip_entry, NMT_TYPE_NEWT_ENTRY)

#define NMT_IP_ENTRY_GET_PRIVATE(o) \
    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_IP_ENTRY, NmtIPEntryPrivate))

typedef struct {
    int      family;
    gboolean prefix;
    gboolean optional;

} NmtIPEntryPrivate;

enum {
    PROP_0,
    PROP_FAMILY,
    PROP_PREFIX,
    PROP_OPTIONAL,

    LAST_PROP
};

/**
 * nmt_ip_entry_new:
 * @width: the width of the entry
 * @family: the IP address family. Eg, %AF_INET
 * @prefix: whether to require a trailing "/prefix"
 * @optional: whether the address is optional
 *
 * Creates a new #NmtIPEntry, to accept IP addresses in the indicated
 * @family, or (if @prefix is %TRUE), to accept IP address/prefix combos.
 *
 * If @optional is %TRUE then the address is considered optional, and
 * so will still be #NmtNewtWidget:valid even when it is empty. If
 * @optional is %FALSE, the entry will be invalid when it is empty.
 */
NmtNewtWidget *
nmt_ip_entry_new(int width, int family, gboolean prefix, gboolean optional)
{
    return g_object_new(NMT_TYPE_IP_ENTRY,
                        "width",
                        width,
                        "family",
                        family,
                        "prefix",
                        prefix,
                        "optional",
                        optional,
                        NULL);
}

static gboolean
ip_entry_filter(NmtNewtEntry *entry, const char *text, int ch, int position, gpointer user_data)
{
    NmtIPEntryPrivate *priv = NMT_IP_ENTRY_GET_PRIVATE(entry);
    const char        *slash;
    gboolean           inaddr;

    if (g_ascii_isdigit(ch))
        return TRUE;

    slash = strchr(text, '/');
    if (ch == '/')
        return priv->prefix && slash == NULL;

    inaddr = !slash || (position <= (slash - text));

    if (priv->family == AF_INET) {
        if (ch == '.')
            return inaddr;
        else
            return FALSE;
    } else if (priv->family == AF_INET6) {
        if (g_ascii_isxdigit(ch) || ch == ':')
            return inaddr;
        else
            return FALSE;
    } else
        g_return_val_if_reached(FALSE);
}

static gboolean
ip_entry_validate(NmtNewtEntry *entry, const char *text, gpointer user_data)
{
    NmtIPEntryPrivate *priv = NMT_IP_ENTRY_GET_PRIVATE(entry);

    if (!*text)
        return priv->optional;
    if (priv->prefix)
        return nm_utils_parse_inaddr_prefix(priv->family, text, NULL, NULL);
    return nm_utils_parse_inaddr(priv->family, text, NULL);
}

static void
nmt_ip_entry_init(NmtIPEntry *entry)
{
    nmt_newt_entry_set_filter(NMT_NEWT_ENTRY(entry), ip_entry_filter, NULL);
    nmt_newt_entry_set_validator(NMT_NEWT_ENTRY(entry), ip_entry_validate, NULL);
}

static void
nmt_ip_entry_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
    NmtIPEntryPrivate *priv = NMT_IP_ENTRY_GET_PRIVATE(object);

    switch (prop_id) {
    case PROP_FAMILY:
        priv->family = g_value_get_int(value);
        break;
    case PROP_PREFIX:
        priv->prefix = g_value_get_boolean(value);
        break;
    case PROP_OPTIONAL:
        priv->optional = g_value_get_boolean(value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}

static void
nmt_ip_entry_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
    NmtIPEntryPrivate *priv = NMT_IP_ENTRY_GET_PRIVATE(object);

    switch (prop_id) {
    case PROP_FAMILY:
        g_value_set_int(value, priv->family);
        break;
    case PROP_PREFIX:
        g_value_set_boolean(value, priv->prefix);
        break;
    case PROP_OPTIONAL:
        g_value_set_boolean(value, priv->optional);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}

static void
nmt_ip_entry_class_init(NmtIPEntryClass *entry_class)
{
    GObjectClass *object_class = G_OBJECT_CLASS(entry_class);

    g_type_class_add_private(entry_class, sizeof(NmtIPEntryPrivate));

    /* virtual methods */
    object_class->set_property = nmt_ip_entry_set_property;
    object_class->get_property = nmt_ip_entry_get_property;

    /**
     * NmtIPEntry:family:
     *
     * The address family. Eg, %AF_INET
     */
    g_object_class_install_property(
        object_class,
        PROP_FAMILY,
        g_param_spec_int("family",
                         "",
                         "",
                         0,
                         G_MAXINT,
                         0,
                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
    /**
     * NmtIPEntry:prefix:
     *
     * If %TRUE, the entry accepts address/prefix combinations. If
     * %FALSE it accepts just addresses.
     */
    g_object_class_install_property(
        object_class,
        PROP_PREFIX,
        g_param_spec_boolean("prefix",
                             "",
                             "",
                             FALSE,
                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
    /**
     * NmtIPEntry:optional:
     *
     * If %TRUE, the entry will be #NmtNewtWidget:valid when it is
     * empty. If %FALSE, it will only be valid when it contains a
     * valid address or address/prefix.
     */
    g_object_class_install_property(
        object_class,
        PROP_OPTIONAL,
        g_param_spec_boolean("optional",
                             "",
                             "",
                             FALSE,
                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
