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

/***************************************************************************
 * Copyright (C) 2017-2026 ZmartZone Holding BV
 * All rights reserved.
 *
 * DISCLAIMER OF WARRANTIES:
 *
 * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
 * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
 * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  NOR ARE THERE ANY
 * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
 * USAGE.  FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
 * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
 * WILL BE UNINTERRUPTED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @Author: Hans Zandbelt - hans.zandbelt@openidc.com
 */

#include "cfg/dir.h"
#include "cfg/cfg_int.h"
#include "cfg/parse.h"
#include "util/util.h"
#include "util/util_cfg.h"

/*
 * "unset" sentinel for oauth_token_introspect_interval: the standard OIDC_CONFIG_POS_INT_UNSET (-1)
 * cannot be used since -1 is a valid configured value for this directive
 */
#define OIDC_INTROSPECT_INTERVAL_UNSET -2

/*
 * directory related configuration
 */
/*
 * single source of truth for the simple pointer-merged and int-merged members of oidc_dir_cfg_t:
 * the struct declaration, oidc_cfg_dir_config_create and oidc_cfg_dir_config_merge are generated
 * from this list (see the same pattern in cfg/oauth.c); members with special semantics are listed
 * separately below the macro expansion and handled by hand in all three places
 */
#define OIDC_DIR_CFG_SIMPLE_MEMBERS(PTR, INT)                                                                          \
	PTR(char *, discover_url)                                                                                      \
	PTR(char *, cookie_path)                                                                                       \
	PTR(char *, cookie)                                                                                            \
	PTR(char *, authn_header)                                                                                      \
	PTR(char *, unauthz_arg)                                                                                       \
	PTR(apr_array_header_t *, pass_cookies)                                                                        \
	PTR(apr_array_header_t *, strip_cookies)                                                                       \
	PTR(oidc_apr_expr_t *, path_auth_request_expr)                                                                 \
	PTR(oidc_apr_expr_t *, path_scope_expr)                                                                        \
	PTR(oidc_apr_expr_t *, unauth_expression)                                                                      \
	PTR(oidc_apr_expr_t *, userinfo_claims_expr)                                                                   \
	PTR(char *, state_cookie_prefix)                                                                               \
	PTR(apr_array_header_t *, pass_userinfo_as)                                                                    \
	INT(unauth_action)                                                                                             \
	INT(unautz_action)                                                                                             \
	INT(pass_info_in)                                                                                              \
	INT(pass_info_encoding)                                                                                        \
	INT(oauth_accept_token_in)                                                                                     \
	INT(preserve_post)                                                                                             \
	INT(pass_access_token)                                                                                         \
	INT(pass_refresh_token)                                                                                        \
	INT(refresh_access_token_before_expiry)                                                                        \
	INT(action_on_error_refresh)                                                                                   \
	INT(pass_idtoken_as)

#define OIDC_DIR_M_DECL_PTR(type, name) type name;
#define OIDC_DIR_M_DECL_INT(name) int name;
#define OIDC_DIR_M_CREATE_PTR(type, name) c->name = NULL;
#define OIDC_DIR_M_CREATE_INT(name) c->name = OIDC_CONFIG_POS_INT_UNSET;
#define OIDC_DIR_M_MERGE_PTR(type, name) c->name = _oidc_cfg_merge_ptr(add->name, base->name);
#define OIDC_DIR_M_MERGE_INT(name) c->name = _oidc_cfg_merge_pos_int(add->name, base->name);

struct oidc_dir_cfg_t {
	OIDC_DIR_CFG_SIMPLE_MEMBERS(OIDC_DIR_M_DECL_PTR, OIDC_DIR_M_DECL_INT)
	/* special: created as an (empty) hash, merged on entry count */
	apr_hash_t *oauth_accept_token_options;
	/* special: -1 is a valid value so this uses its own OIDC_INTROSPECT_INTERVAL_UNSET sentinel */
	int oauth_token_introspect_interval;
};

#define OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR "claims"
#define OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR "payload"
#define OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR "serialized"
#define OIDC_PASS_IDTOKEN_OFF_STR "off"

/*
 * define how to pass the id_token/claims in HTTP headers
 */
const char *oidc_cmd_dir_pass_idtoken_as_set(cmd_parms *cmd, void *m, const char *arg) {
	oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;

	/* only read when the parse below succeeds and has written it; seeded so the optimizer does not
	 * have to prove that across the call (-Wmaybe-uninitialized) */
	oidc_pass_idtoken_as_t type = OIDC_PASS_IDTOKEN_AS_CLAIMS;
	const char *rv = NULL;

	static const oidc_cfg_option_t options[] = {
	    {OIDC_PASS_IDTOKEN_AS_CLAIMS, OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR},
	    {OIDC_PASS_IDTOKEN_AS_PAYLOAD, OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR},
	    {OIDC_PASS_IDTOKEN_AS_SERIALIZED, OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR},
	    {OIDC_PASS_IDTOKEN_OFF, OIDC_PASS_IDTOKEN_OFF_STR}};

	if (arg) {
		rv = oidc_cfg_parse_option(cmd->pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, (int *)&type);
		if (rv != NULL)
			return OIDC_CONFIG_DIR_RV(cmd, rv);
		// NB: assign the first to override the "unset" default
		if (dir_cfg->pass_idtoken_as == OIDC_CONFIG_POS_INT_UNSET)
			dir_cfg->pass_idtoken_as = type;
		else
			dir_cfg->pass_idtoken_as |= type;
	}

	return NULL;
}

#define OIDC_PASS_USERINFO_AS_CLAIMS_STR "claims"
#define OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR "json"
#define OIDC_PASS_USERINFO_AS_JWT_STR "jwt"
#define OIDC_PASS_USERINFO_AS_SIGNED_JWT_STR "signed_jwt"

/*
 * parse a "pass id token as" value from the provided strings
 */
static const char *oidc_cfg_dir_parse_pass_userinfo_as(apr_pool_t *pool, const char *v,
						       oidc_pass_user_info_as_t **result) {
	const char *rv = NULL;
	/* only read when the parse below succeeds and has written it; seeded so the optimizer does not
	 * have to prove that across the call (-Wmaybe-uninitialized) */
	oidc_pass_userinfo_enum_t type = OIDC_PASS_USERINFO_AS_CLAIMS;
	static const oidc_cfg_option_t options[] = {
	    {OIDC_PASS_USERINFO_AS_CLAIMS, OIDC_PASS_USERINFO_AS_CLAIMS_STR},
	    {OIDC_PASS_USERINFO_AS_JSON_OBJECT, OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR},
	    {OIDC_PASS_USERINFO_AS_JWT, OIDC_PASS_USERINFO_AS_JWT_STR},
	    {OIDC_PASS_USERINFO_AS_SIGNED_JWT, OIDC_PASS_USERINFO_AS_SIGNED_JWT_STR}};

	/* split "<type>:<name>" without writing into `v`: oidc_cfg_dir_post_config passes the default
	 * as a string literal, so truncating it in place would write to read-only memory. Copying out
	 * just the type also avoids duplicating the whole string when there is no separator at all. */
	const char *sep = _oidc_strstr(v, ":");
	const char *value = (sep != NULL) ? apr_pstrndup(pool, v, (apr_size_t)(sep - v)) : v;
	const char *name = (sep != NULL) ? sep + 1 : NULL;

	rv = oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), value, (int *)&type);
	if (rv != NULL)
		return rv;

	*result = apr_pcalloc(pool, sizeof(oidc_pass_user_info_as_t));
	(*result)->type = type;
	if (name)
		(*result)->name = apr_pstrdup(pool, name);

	return NULL;
}

/*
 * define how to pass the userinfo/claims in HTTP headers
 */
const char *oidc_cmd_dir_pass_userinfo_as_set(cmd_parms *cmd, void *m, const char *arg) {
	oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;
	const char *rv = NULL;
	oidc_pass_user_info_as_t *p = NULL;
	rv = oidc_cfg_dir_parse_pass_userinfo_as(cmd->pool, arg, &p);
	if (rv != NULL)
		return OIDC_CONFIG_DIR_RV(cmd, rv);
	if (dir_cfg->pass_userinfo_as == NULL)
		dir_cfg->pass_userinfo_as = apr_array_make(cmd->pool, 3, sizeof(const oidc_pass_user_info_as_t *));
	APR_ARRAY_PUSH(dir_cfg->pass_userinfo_as, const oidc_pass_user_info_as_t *) = p;
	return NULL;
}

#define OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR "header"
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR "post"
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR "query"
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR "cookie"
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC_STR "basic"

static const oidc_cfg_option_t oidc_oauth_accept_token_in_options[] = {
    {OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER, OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR},
    {OIDC_OAUTH_ACCEPT_TOKEN_IN_POST, OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR},
    {OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY, OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR},
    {OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE, OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR},
    {OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC, OIDC_OAUTH_ACCEPT_TOKEN_IN_BASIC_STR}};

/*
 * convert an "accept OAuth 2.0 token in" byte value to a string representation
 */
const char *oidc_cfg_dir_accept_oauth_token_in2str(apr_pool_t *pool, oidc_oauth_accept_token_in_t v) {
	static oidc_cfg_option_t enabled[OIDC_CFG_OPTIONS_SIZE(oidc_oauth_accept_token_in_options)];
	int i = 0;
	for (int j = 0; j < OIDC_CFG_OPTIONS_SIZE(oidc_oauth_accept_token_in_options); j++) {
		if (v & oidc_oauth_accept_token_in_options[j].val) {
			enabled[i] = oidc_oauth_accept_token_in_options[j];
			i++;
		}
	}
	return oidc_cfg_parse_options_flatten(pool, enabled, i);
}

#define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_NAME_DEFAULT "PA.global"
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_SEPARATOR ":"

/*
 * define which method of pass an OAuth Bearer token is accepted
 */
const char *oidc_cmd_dir_accept_oauth_token_in_set(cmd_parms *cmd, void *m, const char *arg) {
	oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;
	int v = 0;
	const char *rv = NULL;
	const char *s = NULL;
	char *p = NULL;

	s = apr_pstrdup(cmd->pool, arg);
	p = _oidc_strstr(s, OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_SEPARATOR);

	if (p != NULL) {
		*p = '\0';
		p++;
	} else {
		p = OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_NAME_DEFAULT;
	}

	rv = oidc_cfg_parse_option(cmd->pool, oidc_oauth_accept_token_in_options,
				   OIDC_CFG_OPTIONS_SIZE(oidc_oauth_accept_token_in_options), s, &v);
	if (rv != NULL)
		return OIDC_CONFIG_DIR_RV(cmd, rv);

	if (dir_cfg->oauth_accept_token_in == OIDC_CONFIG_POS_INT_UNSET)
		dir_cfg->oauth_accept_token_in = v;
	else
		dir_cfg->oauth_accept_token_in |= v;

	if (v == OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE)
		apr_hash_set(dir_cfg->oauth_accept_token_options, OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME,
			     APR_HASH_KEY_STRING, p);

	return NULL;
}

/*
 * specify cookies names to pass/strip
 */
const char *oidc_cmd_dir_strip_cookies_set(cmd_parms *cmd, void *m, const char *arg) {
	oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;
	return oidc_cfg_string_list_add(cmd->pool, &dir_cfg->strip_cookies, arg);
}

const char *oidc_cmd_dir_pass_cookies_set(cmd_parms *cmd, void *m, const char *arg) {
	oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;
	return oidc_cfg_string_list_add(cmd->pool, &dir_cfg->pass_cookies, arg);
}

/*
 * Body generators for the per-directory (oidc_dir_cfg_t) accessors declared in
 * cfg/dir.h: for member `foo` they emit oidc_cmd_dir_foo_set() and
 * oidc_cfg_dir_foo_get(); those names are token-pasted here and so appear in
 * no source line.
 */
#define OIDC_CFG_DIR_MEMBER_FUNC_GET(member, type, def_val, unset_val)                                                 \
	type oidc_cfg_dir_##member##_get(request_rec *r) {                                                             \
		oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module);               \
		if (dir_cfg->member == unset_val)                                                                      \
			return (type)def_val;                                                                          \
		return (type)dir_cfg->member;                                                                          \
	}

#define OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(member, type, def_val)                                                        \
	OIDC_CFG_DIR_MEMBER_FUNC_GET(member, type, def_val, OIDC_CONFIG_POS_INT_UNSET)

#define OIDC_CFG_DIR_MEMBER_FUNCS_INT(member, type, parse, def_val)                                                    \
	const char *oidc_cmd_dir_##member##_set(cmd_parms *cmd, void *m, const char *arg) {                            \
		oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;                                                         \
		int v = -1;                                                                                            \
		const char *rv = parse;                                                                                \
		if (rv == NULL)                                                                                        \
			dir_cfg->member = v;                                                                           \
		return OIDC_CONFIG_DIR_RV(cmd, rv);                                                                    \
	}                                                                                                              \
	OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(member, type, def_val)

/* default for preserving POST parameters across authentication requests */
#define OIDC_DEFAULT_PRESERVE_POST 0
OIDC_CFG_DIR_MEMBER_FUNCS_INT(preserve_post, int, oidc_cfg_parse_boolean(cmd->pool, arg, &v),
			      OIDC_DEFAULT_PRESERVE_POST)

#define OIDC_APPINFO_ENCODING_NONE_STR "none"
#define OIDC_APPINFO_ENCODING_LATIN1_STR "latin1"
#define OIDC_APPINFO_ENCODING_BASE64URL_STR "base64url"

#define OIDC_APPINFO_PASS_NONE_STR "none"
#define OIDC_APPINFO_PASS_HEADERS_STR "headers"
#define OIDC_APPINFO_PASS_ENVVARS_STR "environment"
#define OIDC_APPINFO_PASS_BOTH_STR "both"

#define OIDC_APPINFO_PASS_BOTH (OIDC_APPINFO_PASS_HEADERS | OIDC_APPINFO_PASS_ENVVARS)

/*
 * define how to pass claims information to the application: in headers and/or environment variables
 * and optionally specify the encoding applied to the values
 */
const char *oidc_cmd_dir_pass_claims_as_set(cmd_parms *cmd, void *m, const char *arg1, const char *arg2) {
	oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;
	const char *rv = NULL;

	static const oidc_cfg_option_t pass_options[] = {{OIDC_APPINFO_PASS_NONE, OIDC_APPINFO_PASS_NONE_STR},
							 {OIDC_APPINFO_PASS_HEADERS, OIDC_APPINFO_PASS_HEADERS_STR},
							 {OIDC_APPINFO_PASS_ENVVARS, OIDC_APPINFO_PASS_ENVVARS_STR},
							 {OIDC_APPINFO_PASS_BOTH, OIDC_APPINFO_PASS_BOTH_STR}};

	rv = oidc_cfg_parse_option(cmd->pool, pass_options, OIDC_CFG_OPTIONS_SIZE(pass_options), arg1,
				   &dir_cfg->pass_info_in);

	static const oidc_cfg_option_t encoding_options[] = {
	    {OIDC_APPINFO_ENCODING_NONE, OIDC_APPINFO_ENCODING_NONE_STR},
	    {OIDC_APPINFO_ENCODING_BASE64URL, OIDC_APPINFO_ENCODING_BASE64URL_STR},
	    {OIDC_APPINFO_ENCODING_LATIN1, OIDC_APPINFO_ENCODING_LATIN1_STR}};

	if ((rv == NULL) && (arg2 != NULL))
		rv = oidc_cfg_parse_option(cmd->pool, encoding_options, OIDC_CFG_OPTIONS_SIZE(encoding_options), arg2,
					   &dir_cfg->pass_info_encoding);

	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

#define OIDC_UNAUTH_AUTHENTICATE_STR "auth"
#define OIDC_UNAUTH_PASS_STR "pass"
#define OIDC_UNAUTH_RETURN401_STR "401"
#define OIDC_UNAUTH_RETURN410_STR "410"
#define OIDC_UNAUTH_RETURN407_STR "407"

static const oidc_cfg_option_t unauth_action_options[] = {{OIDC_UNAUTH_AUTHENTICATE, OIDC_UNAUTH_AUTHENTICATE_STR},
							  {OIDC_UNAUTH_PASS, OIDC_UNAUTH_PASS_STR},
							  {OIDC_UNAUTH_RETURN401, OIDC_UNAUTH_RETURN401_STR},
							  {OIDC_UNAUTH_RETURN410, OIDC_UNAUTH_RETURN410_STR},
							  {OIDC_UNAUTH_RETURN407, OIDC_UNAUTH_RETURN407_STR}};

static const char *oidc_cfg_dir_unauth_action2str(oidc_unauth_action_t action) {
	for (int i = 0; i < OIDC_CFG_OPTIONS_SIZE(unauth_action_options); i++) {
		/* oidc_cfg_option_t holds the value as an int; the cast keeps the comparison signed */
		if ((int)action == unauth_action_options[i].val)
			return unauth_action_options[i].str;
	}
	return NULL;
}

/*
 * define how to act on unauthenticated requests
 */
const char *oidc_cmd_dir_unauth_action_set(cmd_parms *cmd, void *m, const char *arg1, const char *arg2) {
	oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;
	const char *rv =
	    oidc_cfg_parse_option(cmd->pool, unauth_action_options, OIDC_CFG_OPTIONS_SIZE(unauth_action_options), arg1,
				  &dir_cfg->unauth_action);
	if (rv == NULL)
		rv = oidc_util_apr_expr_parse(cmd, arg2, &dir_cfg->unauth_expression, OIDC_APR_EXPR_RESULT_BOOLEAN);
	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

#define OIDC_UNAUTZ_RETURN403_STR "403"
#define OIDC_UNAUTZ_RETURN401_STR "401"
#define OIDC_UNAUTZ_AUTHENTICATE_STR "auth"
#define OIDC_UNAUTZ_RETURN302_STR "302"

/*
 * define how to act on unauthorized requests
 */
const char *oidc_cmd_dir_unautz_action_set(cmd_parms *cmd, void *m, const char *arg1, const char *arg2) {
	oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;
	static const oidc_cfg_option_t options[] = {{OIDC_UNAUTZ_RETURN403, OIDC_UNAUTZ_RETURN403_STR},
						    {OIDC_UNAUTZ_RETURN401, OIDC_UNAUTZ_RETURN401_STR},
						    {OIDC_UNAUTZ_AUTHENTICATE, OIDC_UNAUTZ_AUTHENTICATE_STR},
						    {OIDC_UNAUTZ_RETURN302, OIDC_UNAUTZ_RETURN302_STR}};
	const char *rv =
	    oidc_cfg_parse_option(cmd->pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg1, &dir_cfg->unautz_action);
	if ((rv == NULL) && (arg2 != NULL)) {
		dir_cfg->unauthz_arg = apr_pstrdup(cmd->pool, arg2);
	} else if (dir_cfg->unautz_action == OIDC_UNAUTZ_RETURN302) {
		rv =
		    apr_psprintf(cmd->temp_pool, "the (2nd) URL argument to %s must be set", cmd->directive->directive);
		return rv;
	}
	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

#ifdef USE_LIBJQ

const char *oidc_cmd_dir_userinfo_claims_expr_set(cmd_parms *cmd, void *m, const char *arg) {
	oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;
	const char *rv =
	    oidc_util_apr_expr_parse(cmd, arg, &dir_cfg->userinfo_claims_expr, OIDC_APR_EXPR_RESULT_STRING);
	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

#endif

const char *oidc_cmd_dir_path_auth_request_params_set(cmd_parms *cmd, void *m, const char *arg) {
	oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;
	const char *rv = NULL;
	rv = oidc_util_apr_expr_parse(cmd, arg, &dir_cfg->path_auth_request_expr, OIDC_APR_EXPR_RESULT_STRING);
	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

const char *oidc_cmd_dir_path_scope_set(cmd_parms *cmd, void *m, const char *arg) {
	oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;
	const char *rv = NULL;
	rv = oidc_util_apr_expr_parse(cmd, arg, &dir_cfg->path_scope_expr, OIDC_APR_EXPR_RESULT_STRING);
	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

#define OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MIN 0
#define OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MAX (3600 * 24 * 365)

/*
 * set the time in seconds that the access token needs to be valid for
 */
const char *oidc_cmd_dir_refresh_access_token_before_expiry_set(cmd_parms *cmd, void *m, const char *arg1,
								const char *arg2) {
	oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;
	const char *rv = NULL;

	rv = oidc_cfg_parse_int_min_max(cmd->pool, arg1, &dir_cfg->refresh_access_token_before_expiry,
					OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MIN,
					OIDC_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY_MAX);
	if (rv != NULL)
		goto end;

	if (arg2)
		rv = oidc_cfg_parse_action_on_error_refresh_as(
		    cmd->pool, arg2, (oidc_on_error_action_t *)&dir_cfg->action_on_error_refresh);

end:

	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

#define OIDC_CFG_DIR_MEMBER_FUNC_PTR(member, type, parse, def_val)                                                     \
	const char *oidc_cmd_dir_##member##_set(cmd_parms *cmd, void *m, const char *arg) {                            \
		oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;                                                         \
		const char *rv = parse;                                                                                \
		return OIDC_CONFIG_DIR_RV(cmd, rv);                                                                    \
	}                                                                                                              \
                                                                                                                       \
	OIDC_CFG_DIR_MEMBER_FUNC_GET(member, type, def_val, NULL)

#define OIDC_CFG_DIR_MEMBER_FUNC_STR(member, type, def_val)                                                            \
	const char *oidc_cmd_dir_##member##_set(cmd_parms *cmd, void *m, const char *arg) {                            \
		oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;                                                         \
		dir_cfg->member = apr_pstrdup(cmd->pool, arg);                                                         \
		return NULL;                                                                                           \
	}                                                                                                              \
                                                                                                                       \
	OIDC_CFG_DIR_MEMBER_FUNC_GET(member, type, def_val, NULL)

/* define the default number of seconds that the access token needs to be valid for; -1 = no refresh */
#define OIDC_DEFAULT_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY -1
OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(refresh_access_token_before_expiry, int,
				 OIDC_DEFAULT_REFRESH_ACCESS_TOKEN_BEFORE_EXPIRY)

/* default action to be taken on access token refresh error */
#define OIDC_DEFAULT_ON_ERROR_REFRESH OIDC_ON_ERROR_502;
OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(action_on_error_refresh, oidc_on_error_action_t, OIDC_DEFAULT_ON_ERROR_REFRESH)

/* default prefix of the state cookie that binds the state in the authorization request/response to the browser */
#define OIDC_DEFAULT_STATE_COOKIE_PREFIX "mod_auth_openidc_state_"
OIDC_CFG_DIR_MEMBER_FUNC_STR(state_cookie_prefix, const char *, OIDC_DEFAULT_STATE_COOKIE_PREFIX)

OIDC_CFG_DIR_MEMBER_FUNC_PTR(discover_url, const char *,
			     oidc_cfg_parse_relative_or_absolute_url(cmd->pool, arg, &dir_cfg->discover_url), NULL)

/* default name of the session cookie */
#define OIDC_DEFAULT_COOKIE "mod_auth_openidc_session"
OIDC_CFG_DIR_MEMBER_FUNC_STR(cookie, const char *, OIDC_DEFAULT_COOKIE)

/* default cookie path */
#define OIDC_DEFAULT_COOKIE_PATH "/"
OIDC_CFG_DIR_MEMBER_FUNC_STR(cookie_path, const char *, OIDC_DEFAULT_COOKIE_PATH)

/* default for the HTTP header name in which the remote user name is passed */
#define OIDC_DEFAULT_AUTHN_HEADER NULL
OIDC_CFG_DIR_MEMBER_FUNC_STR(authn_header, const char *, OIDC_DEFAULT_AUTHN_HEADER)

/* default for passing app info in headers */
#define OIDC_DEFAULT_PASS_APPINFO_IN OIDC_APPINFO_PASS_BOTH
OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(pass_info_in, oidc_appinfo_pass_in_t, OIDC_DEFAULT_PASS_APPINFO_IN)

/* default for passing app info in a specific encoding */
#define OIDC_DEFAULT_APPINFO_ENCODING OIDC_APPINFO_ENCODING_LATIN1
OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(pass_info_encoding, oidc_appinfo_encoding_t, OIDC_DEFAULT_APPINFO_ENCODING)

/* default for passing the access token in a header/environment variable */
#define OIDC_DEFAULT_PASS_ACCESS_TOKEN 1
OIDC_CFG_DIR_MEMBER_FUNCS_INT(pass_access_token, apr_byte_t, oidc_cfg_parse_boolean(cmd->pool, arg, &v),
			      OIDC_DEFAULT_PASS_ACCESS_TOKEN)

/* default for passing the refresh token in a header/environment variable */
#define OIDC_DEFAULT_PASS_REFRESH_TOKEN 0
OIDC_CFG_DIR_MEMBER_FUNCS_INT(pass_refresh_token, apr_byte_t, oidc_cfg_parse_boolean(cmd->pool, arg, &v),
			      OIDC_DEFAULT_PASS_REFRESH_TOKEN)

#define OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER
OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(oauth_accept_token_in, oidc_oauth_accept_token_in_t,
				 OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT)

const char *oidc_cfg_dir_accept_token_in_option_get(request_rec *r, const char *key) {
	oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module);
	return apr_hash_get(dir_cfg->oauth_accept_token_options, key, APR_HASH_KEY_STRING);
}

#define OIDC_OAUTH_ACCESS_TOKEN_INTROSPECTION_INTERVAL_MIN -1
#define OIDC_OAUTH_ACCESS_TOKEN_INTROSPECTION_INTERVAL_MAX (3600 * 24 * 365)

/* default value for the token introspection interval (0 = disabled, no expiry of claims) */
#define OIDC_DEFAULT_TOKEN_INTROSPECTION_INTERVAL 0

const char *oidc_cmd_dir_token_introspection_interval_set(cmd_parms *cmd, void *m, const char *arg) {
	oidc_dir_cfg_t *dir_cfg = (oidc_dir_cfg_t *)m;
	const char *rv = oidc_cfg_parse_int_min_max(cmd->pool, arg, &dir_cfg->oauth_token_introspect_interval,
						    OIDC_OAUTH_ACCESS_TOKEN_INTROSPECTION_INTERVAL_MIN,
						    OIDC_OAUTH_ACCESS_TOKEN_INTROSPECTION_INTERVAL_MAX);
	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

int oidc_cfg_dir_token_introspection_interval_get(request_rec *r) {
	const oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module);
	if (dir_cfg->oauth_token_introspect_interval <= OIDC_INTROSPECT_INTERVAL_UNSET)
		return OIDC_DEFAULT_TOKEN_INTROSPECTION_INTERVAL;
	return dir_cfg->oauth_token_introspect_interval;
}

OIDC_CFG_DIR_MEMBER_FUNC_GET(pass_cookies, const apr_array_header_t *, NULL, NULL)
OIDC_CFG_DIR_MEMBER_FUNC_GET(strip_cookies, const apr_array_header_t *, NULL, NULL)

/* default action to take on an incoming unauthenticated request */
#define OIDC_DEFAULT_UNAUTH_ACTION OIDC_UNAUTH_AUTHENTICATE

oidc_unauth_action_t oidc_cfg_dir_unauth_action_get(request_rec *r) {
	oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module);
	const char *s = NULL;
	oidc_unauth_action_t action = OIDC_CONFIG_POS_INT_UNSET;

	if (dir_cfg->unauth_action == OIDC_CONFIG_POS_INT_UNSET) {
		action = OIDC_DEFAULT_UNAUTH_ACTION;
		goto end;
	}

	if (dir_cfg->unauth_expression == NULL) {
		action = dir_cfg->unauth_action;
		goto end;
	}

	s = oidc_util_apr_expr_exec(r, dir_cfg->unauth_expression, OIDC_APR_EXPR_RESULT_BOOLEAN);

	action = (s != NULL) ? dir_cfg->unauth_action : OIDC_DEFAULT_UNAUTH_ACTION;

	oidc_debug(r, "expression evaluation resulted in: %s (%s) for: %s", oidc_cfg_dir_unauth_action2str(action),
		   s != NULL ? "true" : "false (default)", dir_cfg->unauth_expression->str);

end:

	return action;
}

apr_byte_t oidc_cfg_dir_unauth_expr_is_set(request_rec *r) {
	const oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module);
	return (dir_cfg->unauth_expression != NULL) ? TRUE : FALSE;
}

/* default action to take on an incoming authorized request */
#define OIDC_DEFAULT_UNAUTZ_ACTION OIDC_UNAUTZ_RETURN403
OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(unautz_action, oidc_unautz_action_t, OIDC_DEFAULT_UNAUTZ_ACTION)

const char *oidc_cfg_dir_unauthz_arg_get(request_rec *r) {
	const oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module);
	return dir_cfg->unauthz_arg;
}

const char *oidc_cfg_dir_path_auth_request_params_get(request_rec *r) {
	const oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module);
	return oidc_util_apr_expr_exec(r, dir_cfg->path_auth_request_expr, OIDC_APR_EXPR_RESULT_STRING);
}

/* default pass user info as */
#define OIDC_DEFAULT_PASS_USERINFO_AS OIDC_PASS_USERINFO_AS_CLAIMS_STR

static apr_array_header_t *pass_userinfo_as_default = NULL;

/*
 * initialize the default pass-userinfo-as array once, from the (single-threaded) post-config phase;
 * lazy initialization from the request path is not safe under threaded MPMs because the static and
 * the process pool it allocates from are shared across worker threads without locking.
 */
int oidc_cfg_dir_post_config(server_rec *s) {
	oidc_pass_user_info_as_t *p = NULL;
	if (pass_userinfo_as_default != NULL)
		return OK;
	pass_userinfo_as_default = apr_array_make(s->process->pool, 3, sizeof(const oidc_pass_user_info_as_t *));
	if (oidc_cfg_dir_parse_pass_userinfo_as(s->process->pool, OIDC_DEFAULT_PASS_USERINFO_AS, &p) != NULL)
		return HTTP_INTERNAL_SERVER_ERROR;
	APR_ARRAY_PUSH(pass_userinfo_as_default, const oidc_pass_user_info_as_t *) = p;
	return OK;
}

const apr_array_header_t *oidc_cfg_dir_pass_userinfo_as_get(request_rec *r) {
	const oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module);
	return dir_cfg->pass_userinfo_as ? dir_cfg->pass_userinfo_as : pass_userinfo_as_default;
}

/* default pass id_token as */
#define OIDC_DEFAULT_PASS_IDTOKEN_AS OIDC_PASS_IDTOKEN_AS_CLAIMS
OIDC_CFG_DIR_MEMBER_FUNC_INT_GET(pass_idtoken_as, oidc_pass_idtoken_as_t, OIDC_DEFAULT_PASS_IDTOKEN_AS)

#ifdef USE_LIBJQ
const char *oidc_cfg_dir_userinfo_claims_expr_get(request_rec *r) {
	const oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module);
	return oidc_util_apr_expr_exec(r, dir_cfg->userinfo_claims_expr, OIDC_APR_EXPR_RESULT_STRING);
}
#endif

const char *oidc_cfg_dir_path_scope_get(request_rec *r) {
	const oidc_dir_cfg_t *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module);
	return oidc_util_apr_expr_exec(r, dir_cfg->path_scope_expr, OIDC_APR_EXPR_RESULT_STRING);
}

/*
 * create a new directory config record with defaults
 */
void *oidc_cfg_dir_config_create(apr_pool_t *pool, char *path) {
	oidc_dir_cfg_t *c = apr_pcalloc(pool, sizeof(oidc_dir_cfg_t));
	OIDC_DIR_CFG_SIMPLE_MEMBERS(OIDC_DIR_M_CREATE_PTR, OIDC_DIR_M_CREATE_INT)
	c->oauth_accept_token_options = apr_hash_make(pool);
	c->oauth_token_introspect_interval = OIDC_INTROSPECT_INTERVAL_UNSET;
	return c;
}

/*
 * pick the "add" hash if it has any entries, otherwise fall back to "base"
 */
static apr_hash_t *_oidc_cfg_dir_merge_hash(apr_hash_t *add, apr_hash_t *base) {
	return apr_hash_count(add) > 0 ? add : base;
}

/*
 * pick the "add" introspect interval if it was explicitly configured
 * (i.e. not the OIDC_INTROSPECT_INTERVAL_UNSET default), otherwise fall back to "base"
 */
static int _oidc_cfg_dir_merge_introspect_interval(int add, int base) {
	return add > OIDC_INTROSPECT_INTERVAL_UNSET ? add : base;
}

/*
 * merge a new directory config with a base one
 */
void *oidc_cfg_dir_config_merge(apr_pool_t *pool, void *BASE, void *ADD) {
	oidc_dir_cfg_t *c = apr_pcalloc(pool, sizeof(oidc_dir_cfg_t));
	const oidc_dir_cfg_t *base = BASE;
	const oidc_dir_cfg_t *add = ADD;
	OIDC_DIR_CFG_SIMPLE_MEMBERS(OIDC_DIR_M_MERGE_PTR, OIDC_DIR_M_MERGE_INT)
	c->oauth_accept_token_options =
	    _oidc_cfg_dir_merge_hash(add->oauth_accept_token_options, base->oauth_accept_token_options);
	c->oauth_token_introspect_interval = _oidc_cfg_dir_merge_introspect_interval(
	    add->oauth_token_introspect_interval, base->oauth_token_introspect_interval);
	return c;
}
