/*
 * rivetConf.c - Functions for accessing Rivet configuration variables
 *
 * Functions in this file implement core function to be called mainly
 * by the Rivet_InspectCmd function, which implments command 'inspect'
 *
 */

/* Copyright 2002-2004 The Apache Software Foundation

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

/* $Id: */

#include <tcl.h>
#include <string.h>
#include <apr_errno.h>
#include <apr_strings.h>
#include <apr_tables.h>

#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_core.h"
#include "http_main.h"
#include "util_script.h"
#include "http_config.h"

#include "mod_rivet.h"

/* These arrays must be kept aligned. confDirectives must be NULL terminated */

static const char* confDirectives[] = 
{ 
                    "ServerInitScript", 
                    "GlobalInitScript",
                    "ChildInitScript",
                    "ChildExitScript",
                    "BeforeScript",
                    "AfterScript",
                    "AfterEveryScript",
                    "AbortScript",
                    "ErrorScript",
                    "UploadMaxSize",
                    "UploadDirectory",
                    "UploadFilesToVar",
                    "SeparateVirtualInterps",
                    "SeparateChannels",
                    "HonorHeaderOnlyRequests",
                    NULL 
};

enum confIndices {
                    server_init_script,
                    global_init_script,
                    child_init_script,
                    child_exit_script,
                    before_script,
                    after_script,
                    after_every_script,
                    abort_script,
                    error_script,
                    upload_max,
                    upload_directory,
                    upload_files_to_var,
                    separate_virtual_interps,
                    separate_channels,
                    honor_header_only_requests,
                    conf_index_terminator 
};

/* 
 * -- Rivet_ReadConfParameter
 *
 * This procedure reads a single field named par_name from 
 * rivet_server_conf structure and returns a Tcl_Obj pointer
 * containing the field value. See confDirectives for a list
 * of possible names. If the procedure is queried for a non  
 * existing field a NULL is returned.
 *
 *  Arguments:
 *
 *  - interp: pointer to the current Tcl interpreter structure
 *  - rsc: a pointer to a rivet_server_conf structure
 *  - par_name: parameter name (as listed in confDirectives)
 *
 * Returned value:
 *
 *  - A Tcl_Obj pointer to the parameter value. A NULL
 * pointer works as a signal for an error (invalid parameter)
 * - If the parameter value in the configuration is undefined
 * then the Tcl_Obj string contains the string 'undefined'
 *
 */

Tcl_Obj*
Rivet_ReadConfParameter ( Tcl_Interp*        interp,
                          rivet_server_conf* rsc,
                          Tcl_Obj*           par_name)
{
    int parameter_i;
    Tcl_Obj* par_value = NULL;

    if (Tcl_GetIndexFromObj(interp, par_name, confDirectives,
            "<one of mod_rivet configuration directives>", 0, &parameter_i) == TCL_ERROR) {
        return NULL;
    }

    switch (parameter_i)
    {
        case server_init_script:
        {
            par_value = rsc->rivet_server_init_script;
            break;
        }
        case global_init_script:
        {
            par_value = rsc->rivet_global_init_script;
            break;
        }
        case child_init_script:
        {
            par_value = rsc->rivet_child_init_script;
            break;
        }
        case child_exit_script:
        {
            par_value = rsc->rivet_child_exit_script;
            break;
        }
        case before_script:
        {
            par_value = rsc->rivet_before_script;
            break;
        }
        case after_script:
        {
            par_value = rsc->rivet_after_script;
            break;
        }
        case after_every_script:
        {
            par_value = rsc->after_every_script;
            break;
        }
        case abort_script:
        {
            par_value = rsc->rivet_abort_script;
            break;
        }
        case error_script:
        {
            par_value = rsc->rivet_error_script;
            break;
        }
        case upload_max:
        {
            par_value = Tcl_NewIntObj(rsc->upload_max);
            break;
        }
        case upload_directory:
        {
            par_value = Tcl_NewStringObj(rsc->upload_dir,-1);
            break;
        }
        case upload_files_to_var:
        {
            par_value = Tcl_NewIntObj(rsc->upload_files_to_var);
            break;
        }
        case separate_virtual_interps:
        {
            par_value = Tcl_NewIntObj(rsc->separate_virtual_interps);
            break;
        }
        case separate_channels:
        {
            par_value = Tcl_NewIntObj(rsc->separate_channels);
            break;
        }
        case honor_header_only_requests:
        {
            par_value = Tcl_NewIntObj(rsc->honor_header_only_reqs);
            break;
        }
        default:
        {
            return NULL;
        }
    }

    if (par_value == NULL) 
    {
        par_value = Tcl_NewStringObj("undefined",-1);
    }

    return par_value;
}

/* 
 * Rivet_ReadConfTable: 
 * 
 * This procedure builds a key-value list from an apr table
 * It's called by Rivet_BuildConfDictionary to read theRivet 
 * configuration tables but it can work for every apr table
 *
 * Arguments:
 *
 *  - interp: Tcl_Interp pointer
 *  - table: an apr_table_t pointer
 *
 */

static Tcl_Obj* 
Rivet_ReadConfTable (Tcl_Interp*   interp,
                     apr_table_t*  table)
{
    Tcl_Obj*            key;
    Tcl_Obj*            val;
    apr_array_header_t *arr;
    apr_table_entry_t  *elts;
    int                 nelts,i;
    int                 tcl_status  = TCL_OK;
    Tcl_Obj*            keyval_list = Tcl_NewObj();

    //Tcl_IncrRefCount(keyval_list);

    arr   = (apr_array_header_t*) apr_table_elts( table );
    elts  = (apr_table_entry_t *) arr->elts;
    nelts = arr->nelts;

    for (i = 0; i < nelts; i++)
    {
        key = Tcl_NewStringObj( elts[i].key, -1);
        val = Tcl_NewStringObj( elts[i].val, -1);
        Tcl_IncrRefCount(key);
        Tcl_IncrRefCount(val);

        tcl_status = Tcl_ListObjAppendElement (interp,keyval_list,key);
        if (tcl_status == TCL_ERROR)
        {   
            Tcl_DecrRefCount(keyval_list);
            Tcl_DecrRefCount(key);
            Tcl_DecrRefCount(val);
            return NULL;
        }

        tcl_status = Tcl_ListObjAppendElement (interp,keyval_list,val);
        if (tcl_status == TCL_ERROR)
        {
            Tcl_DecrRefCount(keyval_list);
            Tcl_DecrRefCount(key);
            Tcl_DecrRefCount(val);
            return NULL;
        }

        Tcl_DecrRefCount(key);
        Tcl_DecrRefCount(val);
    }

    return keyval_list;
}


/*
 * -- Rivet_BuildConfDictionary
 * 
 * Parameters set in the configuration files are collected in three
 * APR tables by Rivet_ServerConf,Rivet_DirConf and Rivet_UserConf. 
 *
 * Arguments:
 *
 * - interp: Tcl_Interp pointer
 * - rivet_conf: pointer to a rivet_server_conf structure as
 *   returned by Rivet_GetConf
 *
 * Returned value:
 *
 *  - Tcl dictionary storing the dir/user/server configuration. The
 *    dictionary refCount is incremented 
 *
 */

Tcl_Obj* Rivet_BuildConfDictionary ( Tcl_Interp*           interp,
                                     rivet_server_conf*    rivet_conf)
{
    apr_table_t* conf_tables[3];
    Tcl_Obj*     keyval_list = NULL;
    Tcl_Obj*     key_list[2];
    int          it;
    Tcl_Obj*     conf_dict = Tcl_NewObj();

    static const char* section_names[] = 
    {
        "dir",
        "user",
        "server"
    };

    enum
    {
        dir_conf_section,
        user_conf_section,
        server_conf_section
    };

    conf_tables[0] = rivet_conf->rivet_dir_vars;
    conf_tables[1] = rivet_conf->rivet_user_vars;
    conf_tables[2] = rivet_conf->rivet_server_vars;

    // Tcl_IncrRefCount(conf_dict);

    for (it=0; it < 3; it++)
    {
        keyval_list = Rivet_ReadConfTable(interp,conf_tables[it]);

        if (keyval_list != NULL)
        {
            int       i;
            Tcl_Obj** objArrayPnt;
            int       objArrayCnt;
            Tcl_Obj*  val;
            
            Tcl_IncrRefCount(keyval_list);

            key_list[0] = Tcl_NewStringObj(section_names[it],-1);
            Tcl_IncrRefCount(key_list[0]);

            Tcl_ListObjGetElements(interp,keyval_list,&objArrayCnt,&objArrayPnt);
            for (i=0; i < objArrayCnt; i+=2)
            {
                key_list[1] = objArrayPnt[i];
                val         = objArrayPnt[i+1];

                Tcl_IncrRefCount(key_list[1]);
                Tcl_IncrRefCount(val);

                Tcl_DictObjPutKeyList(interp,conf_dict,2,key_list,val);

                Tcl_DecrRefCount(key_list[1]);
                Tcl_DecrRefCount(val);
            }
            Tcl_DecrRefCount(key_list[0]);
            Tcl_DecrRefCount(keyval_list);
        }
        else
        {
            return NULL;
        }
    }

    return conf_dict;
}


/*
 * Rivet_CurrentConfDict 
 *
 * This function is called by Rivet_InspectCmd which implements command
 * '::rivet::inspect -all'. The function returns a dictionary where every
 * parameter name (confDirectives) is associated to its value stored in
 * the rivet_server_conf as returned by Rivet_GetConf
 *
 * Arguments:
 *
 * - interp: Tcl interpreter pointer
 * - rivet_conf: a pointer to a rivet_server_conf structure
 *
 * Returned value_
 *
 * - a Tcl_Obj* pointer to a dictionary. The function is guaranteed to
 *  return a Tcl_Obj pointer
 *   
 */

Tcl_Obj* Rivet_CurrentConfDict ( Tcl_Interp*           interp,
                                 rivet_server_conf*    rivet_conf)
{
    Tcl_Obj* dictObj = Tcl_NewObj();
    Tcl_Obj* par_name; 
    static const char** p;

    for (p = confDirectives; (*p) != NULL; p++)
    {
        Tcl_Obj* par_value;

        par_name = Tcl_NewStringObj(*p,-1);
        Tcl_IncrRefCount(par_name);

        par_value = Rivet_ReadConfParameter(interp,rivet_conf,par_name);
        ap_assert(par_value != NULL);

        Tcl_IncrRefCount(par_value);
        Tcl_DictObjPut(interp,dictObj,par_name,par_value);
        Tcl_DecrRefCount(par_value);

        Tcl_DecrRefCount(par_name);
    }

    return dictObj;
}

/*
 * -- Rivet_CurrentServerRec
 *
 * ::rivet::inspect provides also some basic access to 
 * fields of the server_rec object.
 *
 * 
 */

Tcl_Obj* 
Rivet_CurrentServerRec (Tcl_Interp* interp, server_rec* s )
{
    Tcl_Obj* dictObj; 
    Tcl_Obj* field_name;
    Tcl_Obj* field_value;
    
    dictObj = Tcl_NewObj();

    field_value = Tcl_NewStringObj(s->server_hostname,-1);
    field_name  = Tcl_NewStringObj("hostname",-1);
    Tcl_IncrRefCount(field_value);
    Tcl_IncrRefCount(field_name);

    Tcl_DictObjPut(interp,dictObj,field_name,field_value);

    Tcl_DecrRefCount(field_value);
    Tcl_DecrRefCount(field_name);

    field_value = Tcl_NewStringObj(s->error_fname,-1);
    field_name  = Tcl_NewStringObj("errorlog",-1);
    Tcl_IncrRefCount(field_value);
    Tcl_IncrRefCount(field_name);

    Tcl_DictObjPut(interp,dictObj,field_name,field_value);

    Tcl_DecrRefCount(field_value);
    Tcl_DecrRefCount(field_name);

    field_value = Tcl_NewStringObj(s->server_admin,-1);
    field_name  = Tcl_NewStringObj("admin",-1);
    Tcl_IncrRefCount(field_value);
    Tcl_IncrRefCount(field_name);

    Tcl_DictObjPut(interp,dictObj,field_name,field_value);

    Tcl_DecrRefCount(field_value);
    Tcl_DecrRefCount(field_name);

    field_value = Tcl_NewStringObj(s->path,-1);
    field_name  = Tcl_NewStringObj("server_path",-1);
    Tcl_IncrRefCount(field_value);
    Tcl_IncrRefCount(field_name);

    Tcl_DictObjPut(interp,dictObj,field_name,field_value);

    Tcl_DecrRefCount(field_value);
    Tcl_DecrRefCount(field_name);

    field_value = Tcl_NewIntObj(s->is_virtual);
    field_name  = Tcl_NewStringObj("virtual",-1);
    Tcl_IncrRefCount(field_value);
    Tcl_IncrRefCount(field_name);

    Tcl_DictObjPut(interp,dictObj,field_name,field_value);

    Tcl_DecrRefCount(field_value);
    Tcl_DecrRefCount(field_name);
    return dictObj;
}
