/*
 * $Id$
 * Copyright (C) 2000  ZetaBITS, Inc.
 * Copyright (C) 2000  Information-technology Promotion Agency, Japan
 * Copyright (C) 2001  Shugo Maeda <shugo@modruby.net>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * 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.
 */

#define CORE_PRIVATE
#include "mod_ruby.h"
#include "ruby_config.h"

static int default_safe_level = MR_DEFAULT_SAFE_LEVEL;

#define push_handler(p, handler, arg) { \
    if ((handler) == NULL) \
	(handler) = apr_array_make(p, 1, sizeof(const char*)); \
    *(const char **) apr_array_push(handler) = arg; \
}

void *ruby_create_server_config(pool *p, server_rec *s)
{
    ruby_server_config *conf =
	(ruby_server_config *) apr_pcalloc(p, sizeof(ruby_server_config));

    conf->load_path = apr_array_make(p, 1, sizeof(char*));
    conf->env = apr_table_make(p, 1);
    conf->timeout = MR_DEFAULT_TIMEOUT;
    conf->restrict_directives = MR_DEFAULT_RESTRICT_DIRECTIVES;
    conf->ruby_child_init_handler = NULL;
    return conf;
}

static array_header *merge_handlers(pool *p,
				    array_header *base,
				    array_header *add)
{
    if (base == NULL)
	return add;
    if (add == NULL)
	return base;
    return apr_array_append(p, add, base);
}

void *ruby_merge_server_config(pool *p, void *basev, void *addv)
{
    ruby_server_config *new =
	(ruby_server_config *) apr_pcalloc(p, sizeof(ruby_server_config));
    ruby_server_config *base = (ruby_server_config *) basev;
    ruby_server_config *add = (ruby_server_config *) addv;

    if (add->load_path == NULL) {
	new->load_path = base->load_path;
    }
    else if (base->load_path == NULL) {
	new->load_path = add->load_path;
    }
    else {
	new->load_path = apr_array_append(p, base->load_path, add->load_path);
    }
    new->env = apr_table_overlay(p, add->env, base->env);
    new->timeout = add->timeout ? add->timeout : base->timeout;
    new->restrict_directives = add->restrict_directives ?
	add->restrict_directives : base->restrict_directives;
    new->ruby_child_init_handler =
	merge_handlers(p, base->ruby_child_init_handler,
		       add->ruby_child_init_handler);
    return (void *) new;
}

void *ruby_create_dir_config(pool *p, char *dirname)
{
    ruby_dir_config *conf =
	(ruby_dir_config *) apr_palloc(p, sizeof (ruby_dir_config));

    conf->kcode = NULL;
    conf->env = apr_table_make(p, 5); 
    conf->safe_level = default_safe_level;
    conf->output_mode = MR_OUTPUT_DEFAULT;
    conf->load_path = NULL;
    conf->options = apr_table_make(p, 5); 
    conf->gc_per_request = MR_DEFAULT_GC_PER_REQUEST;
    conf->ruby_handler = NULL;
    conf->ruby_trans_handler = NULL;
    conf->ruby_authen_handler = NULL;
    conf->ruby_authz_handler = NULL;
    conf->ruby_access_handler = NULL;
    conf->ruby_type_handler = NULL;
    conf->ruby_fixup_handler = NULL;
    conf->ruby_log_handler = NULL;
#ifdef APACHE2
    conf->ruby_error_log_handler = NULL;
#endif
    conf->ruby_header_parser_handler = NULL;
    conf->ruby_post_read_request_handler = NULL;
    conf->ruby_init_handler = NULL;
    conf->ruby_cleanup_handler = NULL;
    return conf;
}

void *ruby_merge_dir_config(pool *p, void *basev, void *addv)
{
    ruby_dir_config *new =
	(ruby_dir_config *) apr_pcalloc(p, sizeof(ruby_dir_config));
    ruby_dir_config *base = (ruby_dir_config *) basev;
    ruby_dir_config *add = (ruby_dir_config *) addv;

    new->kcode = add->kcode ? add->kcode : base->kcode;
    new->env = apr_table_overlay(p, add->env, base->env);
    if (add->safe_level >= base->safe_level) {
	new->safe_level = add->safe_level;
    }
    else {
	fprintf(stderr, "mod_ruby: can't decrease RubySafeLevel\n");
	new->safe_level = base->safe_level;
    }
    new->output_mode = add->output_mode ? add->output_mode : base->output_mode;

    if (add->load_path == NULL) {
	new->load_path = base->load_path;
    }
    else if (base->load_path == NULL) {
	new->load_path = add->load_path;
    }
    else {
	new->load_path = apr_array_append(p, base->load_path, add->load_path);
    }

    new->options = apr_table_overlay(p, add->options, base->options);
    new->gc_per_request =
	add->gc_per_request ? add->gc_per_request : base->gc_per_request;

    new->ruby_handler =
	merge_handlers(p, base->ruby_handler, add->ruby_handler);
    new->ruby_trans_handler =
	merge_handlers(p, base->ruby_trans_handler, add->ruby_trans_handler);
    new->ruby_authen_handler =
	merge_handlers(p, base->ruby_authen_handler, add->ruby_authen_handler);
    new->ruby_authz_handler =
	merge_handlers(p, base->ruby_authz_handler, add->ruby_authz_handler);
    new->ruby_access_handler =
	merge_handlers(p, base->ruby_access_handler, add->ruby_access_handler);
    new->ruby_type_handler =
	merge_handlers(p, base->ruby_type_handler, add->ruby_type_handler);
    new->ruby_fixup_handler =
	merge_handlers(p, base->ruby_fixup_handler, add->ruby_fixup_handler);
    new->ruby_log_handler =
	merge_handlers(p, base->ruby_log_handler, add->ruby_log_handler);
#ifdef APACHE2
    new->ruby_error_log_handler =
	merge_handlers(p, base->ruby_error_log_handler, add->ruby_error_log_handler);
#endif
     new->ruby_header_parser_handler =
	merge_handlers(p, base->ruby_header_parser_handler,
		       add->ruby_header_parser_handler);
    new->ruby_post_read_request_handler =
	merge_handlers(p, base->ruby_post_read_request_handler,
		       add->ruby_post_read_request_handler);
    new->ruby_init_handler =
	merge_handlers(p, base->ruby_init_handler, add->ruby_init_handler);
    new->ruby_cleanup_handler =
	merge_handlers(p, base->ruby_cleanup_handler, add->ruby_cleanup_handler);
    return (void *) new;
}

static int is_restrict_directives(server_rec *server)
{
    ruby_server_config *sconf = get_server_config(server);

    return sconf->restrict_directives;
}

static int is_from_htaccess(cmd_parms *cmd, ruby_dir_config *conf)
{
    core_server_config *sconf;
    int access_name_len;
    int config_file_path_len;

    if (cmd->path == NULL || conf == NULL) return 0;
    sconf = ap_get_module_config(cmd->server->module_config, &core_module);

    access_name_len = strlen(sconf->access_name);
    if (cmd->config_file == NULL) return 0;
    config_file_path_len = strlen(cmd->config_file->name);
    if (config_file_path_len < access_name_len) return 0;
    if (strcmp(cmd->config_file->name + config_file_path_len - access_name_len,
	       sconf->access_name) != 0)
	return 0;

    return 1;
}

#define check_restrict_directives(cmd, dconf) { \
    if (is_restrict_directives((cmd)->server) && \
	is_from_htaccess((cmd), (dconf))) { \
	return apr_psprintf(cmd->pool, \
			   "RubyRestrictDirectives is enabled, " \
			   "%s is not available in .htaccess", \
			   cmd->cmd->name); \
    } \
}

const char *ruby_cmd_kanji_code(cmd_parms *cmd, void *conf,
				const char *arg)
{
    check_restrict_directives(cmd, conf)
    ((ruby_dir_config *) conf)->kcode = apr_pstrdup(cmd->pool, arg);
    return NULL;
}

const char *ruby_cmd_add_path(cmd_parms *cmd, void *dconf, const char *arg)
{
    ruby_server_config *sconf;

    check_restrict_directives(cmd, dconf)
    if (cmd->path == NULL) {
	sconf = get_server_config(cmd->server);
	*(const char **) apr_array_push(sconf->load_path) = arg;
    }
    else {
	if (((ruby_dir_config *) dconf)->load_path == NULL)
	    ((ruby_dir_config *) dconf)->load_path = apr_array_make(cmd->pool, 1, sizeof(const char*));
	*(const char **) apr_array_push(((ruby_dir_config *) dconf)->load_path) = arg;
    }
    return NULL;
}

typedef struct require_internal_arg {
    const char *filename;
    server_rec *server;
    ruby_server_config *sconf;
    ruby_dir_config *dconf;
    
} require_internal_arg_t;

static void *ruby_require_internal(require_internal_arg_t *arg)
{
    const char *filename = arg->filename;
    server_rec *server = arg->server;
    ruby_server_config *sconf = arg->sconf;
    ruby_dir_config *dconf = arg->dconf;
    VALUE fname, exit_status;
    int state;

    mod_ruby_setup_loadpath(sconf, dconf);
    fname = rb_str_new2(filename);
    rb_protect_funcall(Qnil, rb_intern("require"), &state, 1, fname);
    if (state == TAG_RAISE &&
	rb_obj_is_kind_of(rb_errinfo(), rb_eSystemExit)) {
	exit_status = rb_iv_get(rb_errinfo(), "status");
	exit(NUM2INT(exit_status));
    }
    if (state) {
	ruby_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, server,
		       "failed to require %s", filename);
	ruby_log_error_string(server, ruby_get_error_info(state));
    }
    return NULL;
}

static void ruby_require(pool *p, const char *filename,
			 server_rec *server,
			 ruby_server_config *sconf, ruby_dir_config *dconf)
{
    require_internal_arg_t *arg;

    arg = apr_palloc(p, sizeof(require_internal_arg_t));
    arg->filename = filename;
    arg->server = server;
    arg->sconf = sconf;
    arg->dconf = dconf;
#if APR_HAS_THREADS
    if (ruby_is_threaded_mpm) {
	apr_status_t status;
	char buf[256];

	status = ruby_call_interpreter(p,
				       (ruby_interp_func_t) ruby_require_internal,
				       arg, NULL, 0);
	if (status != APR_SUCCESS) {
	    apr_strerror(status, buf, sizeof(buf));
	    ruby_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, server,
			   "ruby_call_interpreter() failed: %s", buf);
	}
    }
    else {
#endif
	ruby_require_internal(arg);
#if APR_HAS_THREADS
    }
#endif
}

const char *ruby_cmd_require(cmd_parms *cmd, void *dconf, const char *arg)
{
    ruby_server_config *sconf = get_server_config(cmd->server);
    ruby_library_context *lib;

    check_restrict_directives(cmd, dconf)
    if (ruby_running()) {
	ruby_require(cmd->pool, arg, cmd->server, sconf, dconf);
    }
    else {
	if (ruby_required_libraries == NULL)
	    ruby_required_libraries =
		apr_array_make(cmd->pool, 1, sizeof(ruby_library_context));
	lib = (ruby_library_context *) apr_array_push(ruby_required_libraries);
	lib->filename = arg;
	lib->server_config = sconf;
	lib->dir_config = dconf;
    }
    return NULL;
}

const char *ruby_cmd_pass_env(cmd_parms *cmd, void *dummy, const char *arg)
{
    ruby_server_config *conf = get_server_config(cmd->server);
    const char *key;
    const char *val = strchr(arg, ':');

    if (val) {
	key = apr_pstrndup(cmd->pool, arg, val - arg);
	val++;
    }
    else {
	key = arg;
	val = getenv(key);
    }
    apr_table_set(conf->env, key, val);
    return NULL;
}

const char *ruby_cmd_set_env(cmd_parms *cmd, void *conf,
			     const char *key, const char *val)
{
    check_restrict_directives(cmd, conf)
    apr_table_set(((ruby_dir_config *) conf)->env, key, val);
    if (cmd->path == NULL) {
	ruby_server_config *sconf = get_server_config(cmd->server);
	apr_table_set(sconf->env, key, val);
    }
    return NULL;
}

const char *ruby_cmd_restrict_directives(cmd_parms *cmd, void *dummy, int flag)
{
    ruby_server_config *sconf = get_server_config(cmd->server);
    sconf->restrict_directives = flag;
    return NULL;
}

const char *ruby_cmd_timeout(cmd_parms *cmd, void *dummy, const char *arg)
{
    ruby_server_config *conf = get_server_config(cmd->server);

    conf->timeout = atoi(arg);
    return NULL;
}

const char *ruby_cmd_safe_level(cmd_parms *cmd, void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    if (cmd->path == NULL && !cmd->server->is_virtual) {
	((ruby_dir_config *) conf)->safe_level = default_safe_level = atoi(arg);
    }
    else {
	((ruby_dir_config *) conf)->safe_level = atoi(arg);
    }
    return NULL;
}

const char *ruby_cmd_output_mode(cmd_parms *cmd, void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    if (strcasecmp(arg, "nosync") == 0) {
	((ruby_dir_config *) conf)->output_mode = MR_OUTPUT_NOSYNC;
    }
    else if (strcasecmp(arg, "sync") == 0) {
	((ruby_dir_config *) conf)->output_mode = MR_OUTPUT_SYNC;
    }
    else if (strcasecmp(arg, "syncheader") == 0) {
	((ruby_dir_config *) conf)->output_mode = MR_OUTPUT_SYNC_HEADER;
    }
    else {
	return "unknown mode";
    }
    return NULL;
}

const char *ruby_cmd_option(cmd_parms *cmd, void *conf,
			    const char *key, const char *val)
{
    check_restrict_directives(cmd, conf)
    apr_table_set(((ruby_dir_config *) conf)->options, key, val);
    return NULL;
}

const char *ruby_cmd_gc_per_request(cmd_parms *cmd, void *conf, int flag)
{
    check_restrict_directives(cmd, conf)
    ((ruby_dir_config *) conf)->gc_per_request = flag;
    return NULL;
}

const char *ruby_cmd_handler(cmd_parms *cmd, void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    push_handler(cmd->pool, ((ruby_dir_config *) conf)->ruby_handler, arg);
    return NULL;
}

const char *ruby_cmd_trans_handler(cmd_parms *cmd, void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    push_handler(cmd->pool, ((ruby_dir_config *) conf)->ruby_trans_handler, arg);
    return NULL;
}

const char *ruby_cmd_authen_handler(cmd_parms *cmd, void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    push_handler(cmd->pool, ((ruby_dir_config *) conf)->ruby_authen_handler, arg);
    return NULL;
}

const char *ruby_cmd_authz_handler(cmd_parms *cmd, void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    push_handler(cmd->pool, ((ruby_dir_config *) conf)->ruby_authz_handler, arg);
    return NULL;
}

const char *ruby_cmd_access_handler(cmd_parms *cmd, void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    push_handler(cmd->pool, ((ruby_dir_config *) conf)->ruby_access_handler, arg);
    return NULL;
}

const char *ruby_cmd_type_handler(cmd_parms *cmd, void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    push_handler(cmd->pool, ((ruby_dir_config *) conf)->ruby_type_handler, arg);
    return NULL;
}

const char *ruby_cmd_fixup_handler(cmd_parms *cmd, void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    push_handler(cmd->pool, ((ruby_dir_config *) conf)->ruby_fixup_handler, arg);
    return NULL;
}

const char *ruby_cmd_log_handler(cmd_parms *cmd, void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    push_handler(cmd->pool, ((ruby_dir_config *) conf)->ruby_log_handler, arg);
    return NULL;
}

#ifdef APACHE2
const char *ruby_cmd_error_log_handler(cmd_parms *cmd, void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    push_handler(cmd->pool, ((ruby_dir_config *) conf)->ruby_error_log_handler, arg);
    return NULL;
}
#endif

const char *ruby_cmd_header_parser_handler(cmd_parms *cmd,
					   void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    push_handler(cmd->pool, ((ruby_dir_config *) conf)->ruby_header_parser_handler, arg);
    return NULL;
}

const char *ruby_cmd_post_read_request_handler(cmd_parms *cmd,
					       void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    push_handler(cmd->pool, ((ruby_dir_config *) conf)->ruby_post_read_request_handler, arg);
    return NULL;
}

const char *ruby_cmd_init_handler(cmd_parms *cmd,
				  void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    push_handler(cmd->pool, ((ruby_dir_config *) conf)->ruby_init_handler, arg);
    return NULL;
}

const char *ruby_cmd_cleanup_handler(cmd_parms *cmd,
				     void *conf, const char *arg)
{
    check_restrict_directives(cmd, conf)
    push_handler(cmd->pool, ((ruby_dir_config *) conf)->ruby_cleanup_handler, arg);
    return NULL;
}

const char *ruby_cmd_child_init_handler(cmd_parms *cmd,
					void *dummy, const char *arg)
{
    ruby_server_config *conf = get_server_config(cmd->server);

    push_handler(cmd->pool, conf->ruby_child_init_handler, arg);
    return NULL;
}

/*
 * Local variables:
 * mode: C
 * tab-width: 8
 * End:
 */

/* vim: set filetype=c ts=8 sw=4 : */
