/***
  This file is part of avahi.

  avahi 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.

  avahi 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 avahi; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  USA.
***/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <assert.h>

#include "domain.h"
#include "malloc.h"
#include "error.h"
#include "address.h"
#include "utf8.h"

/* Read the first label from string *name, unescape "\" and write it to dest */
char *avahi_unescape_label(const char **name, char *dest, size_t size) {
    unsigned i = 0;
    char *d;

    assert(dest);
    assert(size > 0);
    assert(name);

    d = dest;

    for (;;) {
        if (i >= size)
            return NULL;

        if (**name == '.') {
            (*name)++;
            break;
        }

        if (**name == 0)
            break;

        if (**name == '\\') {
            /* Escaped character */

            (*name) ++;

            if (**name == 0)
                /* Ending NUL */
                return NULL;

            else if (**name == '\\' || **name == '.') {
                /* Escaped backslash or dot */
                *(d++) = *((*name) ++);
                i++;
            } else if (isdigit(**name)) {
                int n;

                /* Escaped literal ASCII character */

                if (!isdigit(*(*name+1)) || !isdigit(*(*name+2)))
                    return NULL;

                n = ((uint8_t) (**name - '0') * 100) + ((uint8_t) (*(*name+1) - '0') * 10) + ((uint8_t) (*(*name +2) - '0'));

                if (n > 255 || n == 0)
                    return NULL;

                *(d++) = (char) n;
                i++;

                (*name) += 3;
            } else
                return NULL;

        } else {

            /* Normal character */

            *(d++) = *((*name) ++);
            i++;
        }
    }

    assert(i < size);

    *d = 0;

    if (!avahi_utf8_valid(dest))
        return NULL;

    return dest;
}

/* Escape "\" and ".", append \0 */
char *avahi_escape_label(const char* src, size_t src_length, char **ret_name, size_t *ret_size) {
    char *r;

    assert(src);
    assert(ret_name);
    assert(*ret_name);
    assert(ret_size);
    assert(*ret_size > 0);

    r = *ret_name;

    while (src_length > 0) {
        if (*src == '.' || *src == '\\') {

            /* Dot or backslash */

            if (*ret_size < 3)
                return NULL;

            *((*ret_name) ++) = '\\';
            *((*ret_name) ++) = *src;
            (*ret_size) -= 2;

        } else if (
            *src == '_' ||
            *src == '-' ||
            (*src >= '0' && *src <= '9') ||
            (*src >= 'a' && *src <= 'z') ||
            (*src >= 'A' && *src <= 'Z')) {

            /* Proper character */

            if (*ret_size < 2)
                return NULL;

            *((*ret_name)++) = *src;
            (*ret_size) --;

        } else {

            /* Everything else */

            if (*ret_size < 5)
                return NULL;

            *((*ret_name) ++) = '\\';
            *((*ret_name) ++) = '0' + (char)  ((uint8_t) *src / 100);
            *((*ret_name) ++) = '0' + (char) (((uint8_t) *src / 10) % 10);
            *((*ret_name) ++) = '0' + (char)  ((uint8_t) *src % 10);

            (*ret_size) -= 4;
        }

        src_length --;
        src++;
    }

    **ret_name = 0;

    return r;
}

char *avahi_normalize_name(const char *s, char *ret_s, size_t size) {
    int empty = 1;
    char *r;

    assert(s);
    assert(ret_s);
    assert(size > 0);

    r = ret_s;
    *ret_s = 0;

    while (*s) {
        char label[AVAHI_LABEL_MAX];

        if (!(avahi_unescape_label(&s, label, sizeof(label))))
            return NULL;

        if (label[0] == 0) {

            if (*s == 0 && empty)
                return ret_s;

            return NULL;
        }

        if (!empty) {
            if (size < 2)
                return NULL;

            *(r++) = '.';
            size--;

        } else
            empty = 0;

        if (!(avahi_escape_label(label, strlen(label), &r, &size)))
            return NULL;
    }

    return ret_s;
}

char *avahi_normalize_name_strdup(const char *s) {
    char t[AVAHI_DOMAIN_NAME_MAX];
    assert(s);

    if (!(avahi_normalize_name(s, t, sizeof(t))))
        return NULL;

    return avahi_strdup(t);
}

int avahi_domain_equal(const char *a, const char *b) {
    assert(a);
    assert(b);

    if (a == b)
        return 1;

    for (;;) {
        char ca[AVAHI_LABEL_MAX], cb[AVAHI_LABEL_MAX], *r;

        r = avahi_unescape_label(&a, ca, sizeof(ca));
        assert(r);
        r = avahi_unescape_label(&b, cb, sizeof(cb));
        assert(r);

        if (strcasecmp(ca, cb))
            return 0;

        if (!*a && !*b)
            return 1;
    }

    return 1;
}

int avahi_is_valid_service_type_generic(const char *t) {
    assert(t);

    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t)
        return 0;

    do {
        char label[AVAHI_LABEL_MAX];

        if (!(avahi_unescape_label(&t, label, sizeof(label))))
            return 0;

        if (strlen(label) <= 2 || label[0] != '_')
            return 0;

    } while (*t);

    return 1;
}

int avahi_is_valid_service_type_strict(const char *t) {
    char label[AVAHI_LABEL_MAX];
    assert(t);

    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t)
        return 0;

    /* Application name */

    if (!(avahi_unescape_label(&t, label, sizeof(label))))
        return 0;

    if (strlen(label) <= 2 || label[0] != '_')
        return 0;

    if (!*t)
        return 0;

    /* _tcp or _udp boilerplate */

    if (!(avahi_unescape_label(&t, label, sizeof(label))))
        return 0;

    if (strcasecmp(label, "_tcp") && strcasecmp(label, "_udp"))
        return 0;

    if (*t)
        return 0;

    return 1;
}

const char *avahi_get_type_from_subtype(const char *t) {
    char label[AVAHI_LABEL_MAX];
    const char *ret;
    assert(t);

    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t)
        return NULL;

    /* Subtype name */

    if (!(avahi_unescape_label(&t, label, sizeof(label))))
        return NULL;

    if (strlen(label) <= 2 || label[0] != '_')
        return NULL;

    if (!*t)
        return NULL;

    /* String "_sub" */

    if (!(avahi_unescape_label(&t, label, sizeof(label))))
        return NULL;

    if (strcasecmp(label, "_sub"))
        return NULL;

    if (!*t)
        return NULL;

    ret = t;

    /* Application name */

    if (!(avahi_unescape_label(&t, label, sizeof(label))))
        return NULL;

    if (strlen(label) <= 2 || label[0] != '_')
        return NULL;

    if (!*t)
        return NULL;

    /* _tcp or _udp boilerplate */

    if (!(avahi_unescape_label(&t, label, sizeof(label))))
        return NULL;

    if (strcasecmp(label, "_tcp") && strcasecmp(label, "_udp"))
        return NULL;

    if (*t)
        return NULL;

    return ret;
}

int avahi_is_valid_service_subtype(const char *t) {
    assert(t);

    return !!avahi_get_type_from_subtype(t);
}

int avahi_is_valid_domain_name(const char *t) {
    int is_first = 1;
    assert(t);

    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX)
        return 0;

    do {
        char label[AVAHI_LABEL_MAX];

        if (!(avahi_unescape_label(&t, label, sizeof(label))))
            return 0;

        /* Explicitly allow the root domain name */
        if (is_first && label[0] == 0 && *t == 0)
            return 1;

        is_first = 0;

        if (label[0] == 0)
            return 0;

    } while (*t);

    return 1;
}

int avahi_is_valid_service_name(const char *t) {
    assert(t);

    if (strlen(t) >= AVAHI_LABEL_MAX || !*t)
        return 0;

    return 1;
}

int avahi_is_valid_host_name(const char *t) {
    char label[AVAHI_LABEL_MAX];
    assert(t);

    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX || !*t)
        return 0;

    if (!(avahi_unescape_label(&t, label, sizeof(label))))
        return 0;

    if (strlen(label) < 1)
        return 0;

    if (*t)
        return 0;

    return 1;
}

unsigned avahi_domain_hash(const char *s) {
    unsigned hash = 0;

    while (*s) {
        char c[AVAHI_LABEL_MAX], *p, *r;

        r = avahi_unescape_label(&s, c, sizeof(c));
        assert(r);

        for (p = c; *p; p++)
            hash = 31 * hash + tolower(*p);
    }

    return hash;
}

int avahi_service_name_join(char *p, size_t size, const char *name, const char *type, const char *domain) {
    char escaped_name[AVAHI_LABEL_MAX*4];
    char normalized_type[AVAHI_DOMAIN_NAME_MAX];
    char normalized_domain[AVAHI_DOMAIN_NAME_MAX];

    assert(p);

    /* Validity checks */

    if ((name && !avahi_is_valid_service_name(name)))
        return AVAHI_ERR_INVALID_SERVICE_NAME;

    if (!avahi_is_valid_service_type_generic(type))
        return AVAHI_ERR_INVALID_SERVICE_TYPE;

    if (!avahi_is_valid_domain_name(domain))
        return AVAHI_ERR_INVALID_DOMAIN_NAME;

    /* Preparation */

    if (name) {
        size_t l = sizeof(escaped_name);
        char *e = escaped_name, *r;
        r = avahi_escape_label(name, strlen(name), &e, &l);
        assert(r);
    }

    if (!(avahi_normalize_name(type, normalized_type, sizeof(normalized_type))))
        return AVAHI_ERR_INVALID_SERVICE_TYPE;

    if (!(avahi_normalize_name(domain, normalized_domain, sizeof(normalized_domain))))
        return AVAHI_ERR_INVALID_DOMAIN_NAME;

    /* Concatenation */

    snprintf(p, size, "%s%s%s.%s", name ? escaped_name : "", name ? "." : "", normalized_type, normalized_domain);

    return AVAHI_OK;
}

#ifndef HAVE_STRLCPY

static size_t strlcpy(char *dest, const char *src, size_t n) {
    assert(dest);
    assert(src);

    if (n > 0) {
        strncpy(dest, src, n-1);
        dest[n-1] = 0;
    }

    return strlen(src);
}

#endif

int avahi_service_name_split(const char *p, char *name, size_t name_size, char *type, size_t type_size, char *domain, size_t domain_size) {
    enum {
        NAME,
        TYPE,
        DOMAIN
    } state;
    int type_empty = 1, domain_empty = 1;

    assert(p);
    assert(type);
    assert(type_size > 0);
    assert(domain);
    assert(domain_size > 0);

    if (name) {
        assert(name_size > 0);
        *name = 0;
        state = NAME;
    } else
        state = TYPE;

    *type = *domain = 0;

    while (*p) {
        char buf[64];

        if (!(avahi_unescape_label(&p, buf, sizeof(buf))))
            return -1;

        switch (state) {
            case NAME:
                strlcpy(name, buf, name_size);
                state = TYPE;
                break;

            case TYPE:

                if (buf[0] == '_') {

                    if (!type_empty) {
                        if (!type_size)
                            return AVAHI_ERR_NO_MEMORY;

                        *(type++) = '.';
                        type_size --;

                    } else
                        type_empty = 0;

                    if (!(avahi_escape_label(buf, strlen(buf), &type, &type_size)))
                        return AVAHI_ERR_NO_MEMORY;

                    break;
                }

                state = DOMAIN;
                /* fall through */

            case DOMAIN:

                if (!domain_empty) {
                    if (!domain_size)
                        return AVAHI_ERR_NO_MEMORY;

                    *(domain++) = '.';
                    domain_size --;
                } else
                    domain_empty = 0;

                if (!(avahi_escape_label(buf, strlen(buf), &domain, &domain_size)))
                    return AVAHI_ERR_NO_MEMORY;

                break;
        }
    }

    return 0;
}

int avahi_is_valid_fqdn(const char *t) {
    char label[AVAHI_LABEL_MAX];
    char normalized[AVAHI_DOMAIN_NAME_MAX];
    const char *k = t;
    AvahiAddress a;
    assert(t);

    if (strlen(t) >= AVAHI_DOMAIN_NAME_MAX)
        return 0;

    if (!avahi_is_valid_domain_name(t))
        return 0;

    /* Check if there are at least two labels*/
    if (!(avahi_unescape_label(&k, label, sizeof(label))))
        return 0;

    if (label[0] == 0 || !k)
        return 0;

    if (!(avahi_unescape_label(&k, label, sizeof(label))))
        return 0;

    if (label[0] == 0 || !k)
        return 0;

    /* Make sure that the name is not an IP address */
    if (!(avahi_normalize_name(t, normalized, sizeof(normalized))))
        return 0;

    if (avahi_address_parse(normalized, AVAHI_PROTO_UNSPEC, &a))
        return 0;

    return 1;
}
