/* ====================================================================
 *    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.
 * ====================================================================
 */

#include <apr.h>
#include <apr_pools.h>
#include <apr_hash.h>

#define APR_WANT_STRFUNC
#include <apr_want.h>

#include "serf_private.h"


/*******************************************************************/
/* Character class lookup */

static APR_INLINE int ct_isalnum(char c)
{
    return ((c >= '0' && c <= '9')
            || (c >= 'A' && c <= 'Z')
            || (c >= 'a' && c <= 'z'));
}

/* A `token` is a sequence of ASCII digits, lowercase and uppercase letters,
   and the following punctiation marks:

       ! # $ % & ' * + - . ^ _ ` | ~

   See: https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.2
*/
static APR_INLINE int ct_istoken(char c)
{
    static const char punct[] = "!#$%&'*+-.^_`|~";
    return c && (ct_isalnum(c) || strchr(punct, c));
}

/* A `token68` is a sequence of ASCII digits, lowercase and uppercase letters,
   and the following punctiation marks:

       - . _ ~ + /

   followed by zero or more `=` signs.
   See: https://www.rfc-editor.org/rfc/rfc9110.html#section-11.2
*/
static APR_INLINE int ct_istoken68(char c)
{
    static const char punct[] = "-._~+/";
    return c && (ct_isalnum(c) || strchr(punct, c));
}

/*******************************************************************/
/* Syntactic elements. */

/* Fold ASCII to lowercase. */
static APR_INLINE char ct_tolower(char c)
{
    /* FIXME: Shouid this just be replaced by apr_tolower?
              On the other hand, we should only case-fold ASCII letters,
              while apr_tolower() may be sensitive to the locale. */
    if (c >= 'A' && c <= 'Z')
        return (char)(c + 'a' - 'A');
    return c;
}

/* Skip spaces. */
static const char *skip_space(const char *src)
{
    /* In HTTP land, only tab and space count as whitespace. */
    return src + strspn(src, " \t");
}

/* Skip non-space characters. */
static const char *skip_not_space(const char *src)
{
    return src + strcspn(src, " \t");
}

/* Skip token68 */
static const char *skip_token68(const char *src)
{
    while (ct_istoken68(*src))
        ++src;
    while (*src == '=')
        ++src;
    return src;
}

#if 0
static const char *skip_comment(const char *src)
{
    if (*src != '(')
        return NULL;

    ++src;
    while (*src && *src != ')')
    {
        if (*src == '(') {
            src = skip_comment(src);
            if (!src)
                return NULL;
        }
        else {
            if (*src == '\\')
                ++src;          /* Skip escape from quoted-pair */
            if (*src)
                ++src;
        }
    }

    if (!*src)
        return NULL;
    return src + 1;
}
#endif

static const char *copy_quoted_string(char **dst, const char *src)
{
    if (*src != '"')
        return NULL;

    ++src;
    while (*src && *src != '"')
    {
        if (*src == '\\')
            ++src;              /* Skip escape from quoted-pair */
        if (*src)
            *((*dst)++) = *src++;
    }

    if (!*src)
        return NULL;
    return src + 1;
}

static const char *copy_token_key(char **dst, const char *src)
{
    if (!ct_istoken(*src))
        return NULL;

    do { *((*dst)++) = ct_tolower(*src++); } while (ct_istoken(*src));
    return src;
}

static const char *copy_token(char **dst, const char *src)
{
    if (!ct_istoken(*src))
        return NULL;

    do { *((*dst)++) = *src++; } while (ct_istoken(*src));
    return src;
}

/*******************************************************************/
/* Internal API */

apr_hash_t *serf__parse_authn_parameters(const char *attrs, apr_pool_t *pool)
{
    apr_hash_t *dict = apr_hash_make(pool);
    char *dst = apr_palloc(pool, strlen(attrs) + 1);
    const char *src = skip_space(attrs);
    const char *const end = skip_token68(src);

    /* Check if the parameters are a single token68. */
    if (end != src && !*skip_space(end)) {
        const apr_size_t len = end - src;
        memcpy(dst, src, len);
        dst[len] = '\0';
        apr_hash_set(dict, "", 0, dst);
        return dict;
    }

    while (src && *src)
    {
        const char *key;
        apr_ssize_t keylen;
        const char *value;

        /* Parse the key, a lower-cased token. */
        key = dst;
        src = copy_token_key(&dst, src);
        if (!src || key == dst || *src != '=')
            break;
        keylen = dst - key;
        *dst++ = '\0';

        /* Parse the value, either a token or a quoted string. */
        ++src;
        value = dst;
        if (*src == '"') {
            src = copy_quoted_string(&dst, src);
            if (!src)
                break;
        } else {
            src = copy_token(&dst, src);
            if (!src || value == dst)
                break;
        }
        *dst++ = '\0';

        /* Must be at the end of the string or at a valid separator. */
        src = skip_space(src);
        if (*src) {
            if (*src != ',')
                break;
            /* Skip the separator and any spaces after the separator. */
            src = skip_space(src + 1);
        }

        /* Put the new key/value pair into the dict. */
        apr_hash_set(dict, key, keylen, value);
    }

    return dict;
}


void serf__tolower_inplace(char *dst, apr_size_t length)
{
    apr_size_t i;
    for (i = 0; i < length; ++i)
        dst[i] = ct_tolower(dst[i]);
}


const char *serf__tolower(const char *src, apr_pool_t *pool)
{
    const apr_size_t len = strlen(src) + 1;
    apr_size_t i;

    char *dst = apr_palloc(pool, len);
    for (i = 0; i < len; ++i)
        dst[i] = ct_tolower(src[i]); /* The NUL byte is copied, too. */
    return dst;
}


const char *serf__find_token(const char *token, apr_size_t len, const char *src)
{
    if (len == 0)
        len = strlen(token);

    src = skip_space(src);
    while (*src) {
        const char *end = skip_not_space(src);
        if (end - src == len && 0 == strncmp(token, src, len))
            return src;
        src = skip_space(end);
    }
    return NULL;
}
