/* -- TclWeb.c: Common API layer. */

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

/* Rivet config */

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

#include <tcl.h>
#include <sys/types.h>
#ifndef WIN32
#include <pwd.h>
#endif /* WIN32 */

#include <httpd.h>
#include <http_request.h>
#include <ap_compat.h>
#include <apr_strings.h>
#include "apache_request.h"
#include "mod_rivet.h"
#include "TclWeb.h"

extern module rivet_module;
extern mod_rivet_globals* module_globals;

/* It's kind of an overkill: we define macros for handling the
 * flags that control the handling of the three environment variables
 * classes (common, CGI and include variables). */

#define ENV_COMMON_VARS_M    1
#define ENV_CGI_VARS_M       2
#define ENV_VARS_M           4
#define ENV_VARS_RESET(env)  env = 0;
#define ENV_COMMON_VARS(env) env |= ENV_COMMON_VARS_M;
#define ENV_CGI_VARS(env)    env |= ENV_CGI_VARS_M;
#define ENV_VARS(env)        env |= ENV_VARS_M;
#define ENV_LOADED(env)      env |= ENV_COMMON_VARS_M | ENV_CGI_VARS_M | ENV_VARS_M;
#define ENV_IS_LOADED(env)          (env == (ENV_COMMON_VARS_M | ENV_CGI_VARS_M | ENV_VARS_M))
#define ENV_COMMON_VARS_LOADED(env) (env & ENV_COMMON_VARS_M) != 0
#define ENV_CGI_VARS_LOADED(env)    (env & ENV_CGI_VARS_M) != 0
#define ENV_VARS_LOADED(env)        (env & ENV_VARS_M) != 0

/* This is used below to determine what part of the parmsarray to parse. */

#define PARMSARRAY_COORDINATES(i,j,parray,nargs) i = 0; j = parray->nelts; \
if (source == VAR_SRC_QUERYSTRING) { j = nargs; } \
else if (source == VAR_SRC_POST) { i = nargs; }

/*
 * -- TclWeb_NewRequestObject
 *
 *
 */

TclWebRequest*
TclWeb_NewRequestObject (apr_pool_t *p)
{
    TclWebRequest* req = (TclWebRequest *)apr_pcalloc(p, sizeof(TclWebRequest));

    req->interp             = NULL;
    req->req                = NULL;
    req->apachereq          = ApacheRequest_new(p);
    req->headers_printed    = 0;
    req->headers_set        = 0;
    ENV_VARS_RESET(req->environment_set)
    req->charset            = NULL;  /* we will test against NULL to check if a charset *
                                      * was specified in the conf                       */
    return req;
}

/*
 * -- TclWeb_InitRequest
 *
 * called once on every HTTP request initializes fields and
 * objects referenced in a TclWebRequest object
 *
 * Arguments:
 *
 *  TclWebRequest* req:     a pointer to a TclWebRequest object to be intialized
 *  Tcl_Interp*    interp:  current Tcl_Interp object serving the request
 *  void*          arg:     generic pointer. Current implementation passes the
 *                          request_rec object pointer
 *
 */

int
TclWeb_InitRequest(rivet_thread_private* private, Tcl_Interp *interp)
{
    request_rec*        r   = private->r;
    TclWebRequest*      req = private->req;
    size_t content_type_len = strlen(r->content_type);

    req->interp             = interp;
    req->req                = r;
    req->apachereq          = ApacheRequest_init(req->apachereq,r);
    req->headers_printed    = 0;
    req->headers_set        = 0;
    ENV_VARS_RESET(req->environment_set)
    req->charset            = NULL;

    /*
     * if strlen(req->content_type) > strlen([RIVET|TCL]_FILE_CTYPE)
     * a charset parameters might be in the configuration like
     *
     * AddType 'application/x-httpd-rivet;charset=utf-8' rvt
     */

    if (((private->ctype==RIVET_TEMPLATE) && (content_type_len > strlen(RIVET_TEMPLATE_CTYPE))) || \
        ((private->ctype==RIVET_TCLFILE) && (content_type_len > strlen(RIVET_TCLFILE_CTYPE)))) {

        char* charset;

        /* we parse the content type: we are after a 'charset' parameter definition */

        charset = strstr(r->content_type,"charset");
        if (charset != NULL) {
            charset = apr_pstrdup(r->pool,charset);

            /* ther's some freedom about spaces in the AddType lines: let's strip them off */

            apr_collapse_spaces(charset,charset);
            req->charset = charset;
        }
    }

    return TCL_OK;
}

INLINE int
TclWeb_SendHeaders(TclWebRequest *req)
{
    //TODO: fix ap_send_http_header

    ap_send_http_header(req->req);

    return TCL_OK;
}

INLINE int
TclWeb_StopSending(TclWebRequest *req)
{
    req->req->connection->aborted = 1;
    return TCL_OK;
}

/* Set up the content type header */

int
TclWeb_SetHeaderType(char *header, TclWebRequest *req)
{

    if (req->headers_set)
        return TCL_ERROR;

    ap_set_content_type(req->req,apr_pstrdup(req->req->pool, header));
    req->headers_set = 1;
    return TCL_OK;
}

/* Printer headers if they haven't been printed yet */
int
TclWeb_PrintHeaders(TclWebRequest *req)
{
    if (req->headers_printed)
        return TCL_ERROR;

    /* Let's set the charset in the headers if one was set in the configuration  */

    if (!req->headers_set && (req->charset != NULL)) {
        TclWeb_SetHeaderType(apr_pstrcat(req->req->pool,"text/html;",req->charset,NULL),req);
    }

    if (req->headers_set == 0)
    {
        TclWeb_SetHeaderType(DEFAULT_HEADER_TYPE, req);
    }

    /*
     * seems that ap_send_http_header is redefined to ; in Apache2.2
     * ap_send_http_header(req->req);
     */

    TclWeb_SendHeaders(req);

    req->headers_printed = 1;
    return TCL_OK;
}

/* Print nice HTML formatted errors */
int
TclWeb_PrintError(CONST86 char *errstr, int htmlflag, TclWebRequest *req)
{
    TclWeb_SetHeaderType(DEFAULT_HEADER_TYPE, req);
    TclWeb_PrintHeaders(req);

    if (htmlflag != 1)
	ap_rputs(ER1, req->req);

    if (errstr != NULL)
    {
        if (htmlflag != 1)
        {
            ap_rputs(ap_escape_html(req->req->pool,errstr),req->req);
        } else {
            ap_rputs(errstr, req->req);
        }
    }
    if (htmlflag != 1)
	ap_rputs(ER2, req->req);

    return TCL_OK;
}

INLINE int
TclWeb_HeaderSet(char *header, char *val, TclWebRequest *req)
{
    apr_table_set(req->req->headers_out, header, val);
    return TCL_OK;
}

/*  * accessing output headers *
 *
 *  -- TclWeb_OutputHeaderSet: replicates the role of TclWeb_HeaderSet
 *
 *  - the name stresses the fact it's an accessor to the output
 *    headers
 *  - it returns nothing since it's a wrapper around an APR call
 *    that doesn't return anything
 *
 *  -- TclWeb_OutputHeaderGet: reads from the output headers and
 *  returns the value associated to a key. If the key is not
 *  existing it returns NULL
 *
 */

INLINE void
TclWeb_OutputHeaderSet(char *header, char *val, TclWebRequest *req)
{
    apr_table_set(req->req->headers_out, header, val);
}

INLINE const char*
TclWeb_OutputHeaderGet(char *header, TclWebRequest *req)
{
    return apr_table_get(req->req->headers_out, header);
}

INLINE int
TclWeb_HeaderAdd(char *header, char *val, TclWebRequest *req)
{
    apr_table_add(req->req->headers_out, header, val);
    return TCL_OK;
}

INLINE int
TclWeb_SetStatus(int status, TclWebRequest *req)
{
    req->req->status = status;
    return TCL_OK;
}

INLINE int
TclWeb_MakeURL(Tcl_Obj *result, char *filename, TclWebRequest *req)
{
    Tcl_SetStringObj(result,ap_construct_url(req->req->pool,filename,req->req),-1);
    return TCL_OK;
}

int
TclWeb_GetVar(Tcl_Obj *result, char *varname, int source, TclWebRequest *req)
{
    int i,j;
    apr_array_header_t *parmsarray = (apr_array_header_t *)apr_table_elts(req->apachereq->parms);
    apr_table_entry_t  *parms = (apr_table_entry_t *)parmsarray->elts;
    int flag = 0;

    PARMSARRAY_COORDINATES(i,j,parmsarray,req->apachereq->nargs)

    /* This isn't real efficient - move to hash table later on... */
    while (i < j)
    {
        char *parmkey = TclWeb_StringToUtf(parms[i].key, req);
        if (!strncmp(varname,parmkey,
                    strlen(varname) < strlen(parmkey) ?
                    strlen(parmkey) : strlen(varname)))
        {

            /* The following makes sure that we get one string,
               with no sub lists. */

            if (flag == 0)
            {

                flag = 1;
                Tcl_SetStringObj (result,TclWeb_StringToUtf(parms[i].val,req),-1);

            } else {

                Tcl_Obj *tmpobj;
                Tcl_Obj *tmpobjv[2];
                tmpobjv[0] = result;
                tmpobjv[1] = TclWeb_StringToUtfToObj (parms[i].val,req);
                tmpobj = Tcl_ConcatObj (2,tmpobjv);
                Tcl_SetStringObj (result,Tcl_GetString(tmpobj),-1);

            }

        }
        i++;
    }

    /*
     * We are assuming that checking result->length is a sane way to
     * establish the Tcl object representation character lenght but it
     * would obviously be more appropriate to call Tcl_GetCharLength(result)
     */

    if (result->length == 0) {
        Tcl_AddErrorInfo(req->interp,apr_psprintf(req->req->pool,"Variable '%s' not found",varname));
        return TCL_ERROR;
    }

    return TCL_OK;
}

int
TclWeb_GetVarAsList(Tcl_Obj *result, char *varname, int source, TclWebRequest *req) {
    int i, j;
    apr_array_header_t *parmsarray = (apr_array_header_t *)
        apr_table_elts(req->apachereq->parms);
    apr_table_entry_t *parms = (apr_table_entry_t *)parmsarray->elts;

    PARMSARRAY_COORDINATES(i,j,parmsarray,req->apachereq->nargs)

    /* This isn't real efficient - move to hash table later on. */
    while (i < j)
    {
        int tclcode;

        if (!strncmp(varname, TclWeb_StringToUtf(parms[i].key, req),
                 strlen(varname) < strlen(parms[i].key) ?
                 strlen(parms[i].key) : strlen(varname)))
        {
            tclcode = Tcl_ListObjAppendElement(req->interp,result,
                                               TclWeb_StringToUtfToObj(parms[i].val, req));
            if (tclcode != TCL_OK) { return tclcode; }
        }
        i++;
    }

    return TCL_OK;
}

int
TclWeb_GetAllVars(Tcl_Obj *result, int source, TclWebRequest *req)
{
    int i,j;
    apr_array_header_t *parmsarray = (apr_array_header_t *) apr_table_elts(req->apachereq->parms);
    apr_table_entry_t *parms = (apr_table_entry_t *)parmsarray->elts;

    PARMSARRAY_COORDINATES(i,j,parmsarray,req->apachereq->nargs)

    while (i < j)
    {
        int tclcode;
        tclcode = Tcl_ListObjAppendElement(req->interp,result,
                       TclWeb_StringToUtfToObj(parms[i].key,req));
        if (tclcode != TCL_OK) { return tclcode; }
        tclcode = Tcl_ListObjAppendElement(req->interp,result,
                       TclWeb_StringToUtfToObj(parms[i].val,req));
        if (tclcode != TCL_OK) { return tclcode; }

        i++;
    }

    return TCL_OK;
}

int
TclWeb_GetVarNames(Tcl_Obj *result, int source, TclWebRequest *req)
{
    int i,j;
    apr_array_header_t *parmsarray = (apr_array_header_t *) apr_table_elts(req->apachereq->parms);
    apr_table_entry_t *parms = (apr_table_entry_t *)parmsarray->elts;

    PARMSARRAY_COORDINATES(i,j,parmsarray,req->apachereq->nargs)

    while (i < j)
    {
        int tclcode;
        tclcode= Tcl_ListObjAppendElement(req->interp, result,
                                          TclWeb_StringToUtfToObj(parms[i].key, req));
        if (tclcode != TCL_OK) { return tclcode; }
        i++;
    }

    return TCL_OK;
}

int
TclWeb_VarExists(Tcl_Obj *result, char *varname, int source, TclWebRequest *req)
{
    int i, j;
    apr_array_header_t *parmsarray = (apr_array_header_t *)
        apr_table_elts(req->apachereq->parms);
    apr_table_entry_t *parms = (apr_table_entry_t *)parmsarray->elts;

    PARMSARRAY_COORDINATES(i,j,parmsarray,req->apachereq->nargs)

    /* This isn't real efficient - move to hash table later on. */
    while (i < j)
    {
        if (!strncmp(varname, TclWeb_StringToUtf(parms[i].key, req),
                    strlen(varname) < strlen(parms[i].key) ?
                    strlen(parms[i].key) : strlen(varname)))
        {
            Tcl_SetIntObj(result, 1);
            return TCL_OK;
        }
        i++;
    }
    Tcl_SetIntObj(result, 0);
    return TCL_OK;
}

int
TclWeb_VarNumber(Tcl_Obj *result, int source, TclWebRequest *req)
{
    apr_array_header_t *parmsarray = (apr_array_header_t *)
        apr_table_elts(req->apachereq->parms);

    if (source == VAR_SRC_QUERYSTRING) {
	    Tcl_SetIntObj(result, req->apachereq->nargs);
    } else if (source == VAR_SRC_POST) {
	    Tcl_SetIntObj(result, parmsarray->nelts - req->apachereq->nargs);
    } else {
	    Tcl_SetIntObj(result, parmsarray->nelts);
    }

    return TCL_OK;
}

/* Environment variables. Include variables handling */

/* These 2 array must be aligned and a one-to-one correspondence preserved
 * The enum include_vars_idx *must* be terminated by 'invalid_env_var'
 * Adding a new env variable requires
 *    + the name of the variable be listed in include_env_vars
 *    + a new value in the enumerator include_vars_idx must be added in the
 *      corresponding position
 *    + the switch construct in function TclWeb_SelectEnvIncludeVar must
 *      be expanded to handle the new case identified by the new enumerator value
 */

static const char* include_env_vars[] =
{
    "DATE_LOCAL","DATE_GMT","LAST_MODIFIED","DOCUMENT_URI","DOCUMENT_PATH_INFO","DOCUMENT_NAME",
    "QUERY_STRING_UNESCAPED","USER_NAME","RIVET_CACHE_FREE","RIVET_CACHE_SIZE",
    NULL
};
enum include_vars_idx {
    date_local=0,date_gmt,last_modified,document_uri,document_path_info,document_name,
    query_string_unescaped,user_name,rivet_cache_free,rivet_cache_size,
    invalid_env_var
};

/*  -- TclWeb_SelectEnvIncludeVar
 *
 *  Depending on the value idx of the enumerator a method is selected
 *  to return a string of a specific environment variable methods
 *  Adding new environment variables need new cases of the switch
 *  construct to be added, provided the data can be obtained from
 *  the rivet_thread_private structure
 *
 *   Arguments:
 *
 *      + rivet_thread_private* private: pointer to a thread private data structure
 *      + int idx: an integer value listed in the enumerator include_vars_idx
 *
 *   Results:
 *
 *      A character string pointer to the value of the environment variable or
 *      NULL if the enumerator value idx was invalid or resolving the environment
 *      variable was impossible
 *
 */

static char*
TclWeb_SelectEnvIncludeVar (rivet_thread_private* private,int idx)
{
    switch (idx)
    {
        case date_local:
        {
            apr_pool_t* pool = private->req->req->pool;
            apr_time_t date = private->req->req->request_time;

            return ap_ht_time(pool,date,DEFAULT_TIME_FORMAT,0);
        }
        case date_gmt:
        {
            apr_pool_t* pool = private->req->req->pool;
            apr_time_t date = private->req->req->request_time;

            return ap_ht_time(pool,date,DEFAULT_TIME_FORMAT,1);
        }
        case last_modified:
        {
            apr_pool_t* pool = private->req->req->pool;

            return ap_ht_time(pool,private->req->req->finfo.mtime,DEFAULT_TIME_FORMAT,1);
        }
        case document_uri:
        {
            return private->req->req->uri;
        }
        case document_path_info:
        {
            return private->req->req->path_info;
        }
        case document_name:
        {
            char *t;

            if ((t = strrchr(private->req->req->filename,'/'))) {
                return ++t;
            } else {
                return private->req->req->uri;
            }
        }
        case query_string_unescaped:
        {
            if (private->req->req->args) {
                apr_pool_t* pool = private->req->req->pool;
                char *arg_copy = (char*) apr_pstrdup(pool,private->req->req->args);

                ap_unescape_url(arg_copy);
                return ap_escape_shell_cmd(pool,arg_copy);
            } else {
                return NULL;
            }

        }
        case user_name:
        {
#ifndef WIN32
            struct passwd *pw = (struct passwd *) getpwuid(private->req->req->finfo.user);
            if (pw) {
                //apr_table_set( table, "USER_NAME",
                //        apr_pstrdup( pool, pw->pw_name ) );
                return pw->pw_name;
            } else {
                apr_pool_t* pool = private->req->req->pool;
                return (char*) apr_psprintf(pool,"user#%lu",(unsigned long)private->req->req->finfo.user);
            }
#else
            return NULL;
#endif
        }
        case rivet_cache_free:
        {
            apr_pool_t* pool = private->req->req->pool;
            return (char*) apr_psprintf (pool, "%d",(RIVET_PEEK_INTERP(private,private->running_conf))->cache_free);
        }
        case rivet_cache_size:
        {
            apr_pool_t* pool = private->req->req->pool;
            return (char*) apr_psprintf (pool, "%d",(RIVET_PEEK_INTERP(private,private->running_conf))->cache_size);
        }
    }
    return NULL;
}

/*
 * -- TclWeb_InitEnvVars
 *
 * Load the CGI and environment variables into the request_rec environment structure
 * Variables belong to 3 cathegories
 *
 *   + common variables (ap_add_common_vars)
 *   + CGI variables (ad_cgi_vars)
 *   + a miscellaneous set of variables
 *     listed in the array include_env_vars
 *
 * Each cathegory is controlled by flags in order to reduce the overhead of getting them
 * into request_rec in case previous call to ::rivet::env could have already forced them
 * into request_rec
 */

static void
TclWeb_InitEnvVars (rivet_thread_private* private)
{
    TclWebRequest *req = private->req;

    if (ENV_IS_LOADED(req->environment_set)) return;

    /* Retrieve cgi variables. */
    if (!ENV_CGI_VARS_LOADED(req->environment_set))
    {
        ap_add_cgi_vars(req->req);
    }
    if (!ENV_COMMON_VARS_LOADED(req->environment_set))
    {
        ap_add_common_vars(req->req);
    }

    /* Loading into 'req->req->subprocess_env' the include vars */

    /* actually this check is not necessary. ENV_VARS_M is set only here therefore
     * if it's set this function has been called already and it should have returned
     * at the beginning of ies execution. I keep it for clarity and uniformity with the
     * CGI variables and in case the incremental environment handling is extended
     */

    if (!ENV_VARS_LOADED(req->environment_set))
    {
        apr_table_t   *table;
        int            idx;

        table = req->req->subprocess_env;
        for (idx = 0;idx < invalid_env_var;idx++)
        {
            apr_table_set(table,include_env_vars[idx],TclWeb_SelectEnvIncludeVar(private,idx));
        }
    }

    ENV_LOADED(req->environment_set)
}

/* -- TclWeb_GetEnvIncludeVar
 *
 *  the environment variable named in key is searched among the include
 *  variables and then resolved by calling TclWeb_SelectEnvIncludeVar
 *
 *  Result:
 *
 *    a character string pointer to the environment variable value or
 *    NULL if the environment variable name in invalid or the variable
 *    could not be resolved
 *
 */

static char*
TclWeb_GetEnvIncludeVar (rivet_thread_private* private,char* key)
{
    int idx;

    for (idx = 0;idx < invalid_env_var; idx++)
    {
        const char* include_var_p = include_env_vars[idx];
        if (strncmp(key,include_var_p,strlen(key) < strlen(include_var_p) ? strlen(key) : strlen(include_var_p)) == 0)
        {
            return TclWeb_SelectEnvIncludeVar(private,idx);
        }
    }
    return NULL;
}

int
TclWeb_GetEnvVars(Tcl_Obj *envvar,rivet_thread_private* private)
{
    int i;

    apr_array_header_t *env_arr;
    apr_table_entry_t  *env;
    Tcl_Obj *key;
    Tcl_Obj *val;
    TclWebRequest *req;

    TclWeb_InitEnvVars(private);

    req = private->req;
    Tcl_IncrRefCount(envvar);
    /* Transfer Apache internal CGI variables to TCL request namespace. */
    env_arr = (apr_array_header_t *) apr_table_elts(req->req->subprocess_env);
    env     = (apr_table_entry_t *) env_arr->elts;
    for (i = 0; i < env_arr->nelts; ++i)
    {
        if ((!env[i].key) || (!env[i].val)) {
            continue;
        }

        key = TclWeb_StringToUtfToObj(env[i].key, req);
        val = TclWeb_StringToUtfToObj(env[i].val, req);
        Tcl_IncrRefCount(key);
        Tcl_IncrRefCount(val);

        /* Variable scope resolution changed to default (flags: 0)
         * to enable creation of the array in the caller's local scope.
         * Default behavior (creation in the ::request namespace)
         * is now more consistently constrained by fully qualifying
         * the default array names (see rivetCore.c). This should fix
         * Bug #48963
         */

        Tcl_ObjSetVar2(req->interp, envvar, key, val, 0);

        Tcl_DecrRefCount(key);
        Tcl_DecrRefCount(val);
    }
    Tcl_DecrRefCount(envvar);

    return TCL_OK;
}

int
TclWeb_GetHeaderVars(Tcl_Obj *headersvar,rivet_thread_private* private)
{
    int i;
    TclWebRequest *req;
    apr_array_header_t *hdrs_arr;
    apr_table_entry_t  *hdrs;
    Tcl_Obj *key;
    Tcl_Obj *val;

    req = private->req;

    // I actually don't see why we need to load the whole environment here
    //TclWeb_InitEnvVars(private);

    Tcl_IncrRefCount(headersvar);
    /* Transfer client request headers to TCL request namespace. */
    hdrs_arr = (apr_array_header_t*) apr_table_elts(req->req->headers_in);
    hdrs = (apr_table_entry_t *) hdrs_arr->elts;
    for (i = 0; i < hdrs_arr->nelts; ++i)
    {
        if (!hdrs[i].key)
            continue;

        key = TclWeb_StringToUtfToObj(hdrs[i].key, req);
        val = TclWeb_StringToUtfToObj(hdrs[i].val, req);
        Tcl_IncrRefCount(key);
        Tcl_IncrRefCount(val);

            /* See comment in TclWeb_GetEnvVars concerning Bug #48963*/

        Tcl_ObjSetVar2(req->interp, headersvar, key, val, 0);
        Tcl_DecrRefCount(key);
        Tcl_DecrRefCount(val);
    }

    /* Transfer Apache internal CGI variables to TCL request namespace. */
    Tcl_DecrRefCount(headersvar);
    return TCL_OK;
}

INLINE int
TclWeb_Base64Encode(char *out, char *in, TclWebRequest *req)
{
    out = ap_pbase64encode(req->req->pool, in);
    return TCL_OK;
}

INLINE int
TclWeb_Base64Decode(char *out, char *in, TclWebRequest *req)
{
    out = ap_pbase64decode(req->req->pool, in);
    return TCL_OK;
}

INLINE int
TclWeb_EscapeShellCommand(char *out, char *in, TclWebRequest *req)
{
    out = ap_escape_shell_cmd(req->req->pool, in);
    return TCL_OK;
}

/* Functions to convert strings to UTF encoding */

/* These API's are a bit different, because it's so much more
 * practical. */

char *TclWeb_StringToUtf(char *in, TclWebRequest *req)
{
    char *tmp;
    Tcl_DString dstr;
    Tcl_DStringInit(&dstr);
    Tcl_ExternalToUtfDString(NULL, in, (signed)strlen(in), &dstr);
    tmp = (char*) apr_pstrdup(req->req->pool, Tcl_DStringValue(&dstr));
    Tcl_DStringFree(&dstr);
    return tmp;
}

INLINE Tcl_Obj *
TclWeb_StringToUtfToObj(char *in, TclWebRequest *req)
{
    return Tcl_NewStringObj(TclWeb_StringToUtf(in, req), -1);
}

int TclWeb_PrepareUpload(char *varname, TclWebRequest *req)
{
    req->upload = ApacheUpload_find(req->apachereq->upload, varname);
    if (req->upload == NULL) {
	    return TCL_ERROR;
    } else {
	    return TCL_OK;
    }
}

int TclWeb_UploadChannel(char *varname, TclWebRequest *req)
{
    Tcl_Channel chan;

    chan = Tcl_OpenFileChannel(req->interp, req->upload->tempname, "r", 0);
    if (chan == NULL) {
	    return TCL_ERROR;
    } else {
        Tcl_Obj* result;

        if (Tcl_SetChannelOption(req->interp,chan,"-translation","binary") == TCL_ERROR) {
            return TCL_ERROR;
        }
        if (Tcl_SetChannelOption(req->interp,chan,"-encoding","binary") == TCL_ERROR) {
            return TCL_ERROR;
        }
        Tcl_RegisterChannel(req->interp,chan);

        result = Tcl_NewObj();
        Tcl_SetStringObj(result, Tcl_GetChannelName(chan), -1);
        Tcl_SetObjResult(req->interp, result);

        return TCL_OK;
    }
}

int TclWeb_UploadTempname(TclWebRequest *req)
{
    Tcl_Obj *tempname = Tcl_NewObj();

    Tcl_SetStringObj(tempname,TclWeb_StringToUtf(req->upload->tempname,req), -1);
    Tcl_SetObjResult(req->interp, tempname);

    return TCL_OK;
}


int TclWeb_UploadSave(char *varname, Tcl_Obj *filename, TclWebRequest *req)
{
	apr_status_t status;

	status = apr_file_copy(req->upload->tempname,Tcl_GetString(filename),APR_FILE_SOURCE_PERMS,req->req->pool);
	if (status == APR_SUCCESS) {
	    return TCL_OK;
	} else {

        /* apr_strerror docs don't tell anything about a demanded buffer size, we're just adopting a reasonable guess */

        char  error_msg[1024];
        char* tcl_error_msg;
        apr_strerror(status,error_msg,1024);

        tcl_error_msg = apr_psprintf(req->req->pool,"Error copying upload '%s' to '%s' (%s)", req->upload->tempname,
                                                                                              Tcl_GetString(filename),
                                                                                              error_msg);

        Tcl_AddErrorInfo(req->interp,tcl_error_msg);
		return TCL_ERROR;
	}
}

int TclWeb_UploadData(char *varname, TclWebRequest *req)
{
    Tcl_Obj* result;
    rivet_server_conf *rsc = NULL;

    rsc = RIVET_SERVER_CONF( req->req->server->module_config );
    /* This sucks - we should use the hook, but I want to
       get everything fixed and working first */
    if (rsc->upload_files_to_var)
    {
        Tcl_Channel chan;

        chan = Tcl_OpenFileChannel (req->interp, req->upload->tempname, "r", 0);
        if (chan == NULL) {
            char* tcl_error_msg;
            int error_number = Tcl_GetErrno();

            Tcl_AddErrorInfo(req->interp,"Error opening channel to uploaded data");
            tcl_error_msg = apr_psprintf(req->req->pool,"Error setting channel option '%s': %s",
                                                        Tcl_ErrnoId(), Tcl_ErrnoMsg(error_number));
            Tcl_AddErrorInfo(req->interp,tcl_error_msg);
            return TCL_ERROR;
        }
        if (Tcl_SetChannelOption(req->interp, chan, "-translation", "binary") == TCL_ERROR) {
            char* tcl_error_msg;
            int error_number = Tcl_GetErrno();

            tcl_error_msg = apr_psprintf(req->req->pool,"Error setting channel option '%s': %s",
                                                        Tcl_ErrnoId(), Tcl_ErrnoMsg(error_number));
            Tcl_AddErrorInfo(req->interp,tcl_error_msg);
            return TCL_ERROR;
        }
        if (Tcl_SetChannelOption(req->interp, chan, "-encoding", "binary") == TCL_ERROR) {
            char* tcl_error_msg;
            int error_number = Tcl_GetErrno();

            tcl_error_msg = apr_psprintf(req->req->pool,"Error setting channel option '%s': %s",
                                                        Tcl_ErrnoId(), Tcl_ErrnoMsg(error_number));
            Tcl_AddErrorInfo(req->interp,tcl_error_msg);
            return TCL_ERROR;
        }

        /* Put data in a variable  */
        result = Tcl_NewObj();
        Tcl_ReadChars(chan, result, (int)ApacheUpload_size(req->upload), 0);
        if (Tcl_Close(req->interp, chan) == TCL_ERROR) {
            return TCL_ERROR;
        }

        Tcl_SetObjResult(req->interp, result);
    } else {
        Tcl_AppendResult(req->interp,
                 "RivetServerConf UploadFilesToVar is not set", NULL);
        return TCL_ERROR;
    }

    return TCL_OK;
}

int TclWeb_UploadSize(TclWebRequest *req)
{
    Tcl_Obj* result = Tcl_NewObj();
    Tcl_SetIntObj(result, (int)ApacheUpload_size(req->upload));
    Tcl_SetObjResult(req->interp, result);
    return TCL_OK;
}

int TclWeb_UploadType(TclWebRequest *req)
{
    Tcl_Obj *type = Tcl_NewObj();

    /* If there is a type, return it, if not, return blank. */
    Tcl_SetStringObj(type, ApacheUpload_type(req->upload)
		     ? (char *)ApacheUpload_type(req->upload) : (char *)"", -1);

    Tcl_SetObjResult(req->interp, type);
    return TCL_OK;
}

int TclWeb_UploadFilename(TclWebRequest *req)
{
    Tcl_Obj *filename = Tcl_NewObj();
    Tcl_SetStringObj(filename,TclWeb_StringToUtf(req->upload->filename,req), -1);

    Tcl_SetObjResult(req->interp, filename);
    return TCL_OK;
}

int TclWeb_UploadNames(TclWebRequest *req)
{
    ApacheUpload *upload;
    Tcl_Obj      *names = Tcl_NewObj();

    upload = ApacheRequest_upload(req->apachereq);
    while (upload)
    {
        Tcl_ListObjAppendElement(req->interp,names,TclWeb_StringToUtfToObj(upload->name,req));
        upload = upload->next;
    }

    Tcl_SetObjResult(req->interp,names);
    return TCL_OK;
}

/*
 * -- TclWeb_GetEnvVar
 *
 * basically is the core of the ::rivet::env rivet command. The argument to
 * the command is stored in 'key' and the function starts a search in various
 * tables following the following order
 *
 *  + though undocumented in the manual the first table checked is HTTP
 *    headers table. ::rivet::env is actually like ::rivet::headers but for
 *    the *request_rec->headers_in table
 *  + the common CGI variables table is checked
 *  + the CGI 1.1 headers table is checked
 *  + the include variables list is checked calling TclWeb_GetEnvIncludeVar
 *
 *  Arguments:
 *
 *   - key: a string with the environment variable name
 *
 *  Results:
 *
 *    - a string pointer to the string with the variable translation or
 *    NULL if the environment variable is not found
 *
 */

char *
TclWeb_GetEnvVar(rivet_thread_private* private,char *key)
{
    char *val;
    TclWebRequest *req = private->req;

    /* Check to see if it's a header variable first. */
    val = (char *)apr_table_get (req->req->headers_in,key);
    if (val) { return val; }

    /* We incrementally prepare subprocess_env */
    /* CGI common vars first */

    if (!ENV_COMMON_VARS_LOADED(req->environment_set))
    {
        ap_add_common_vars(req->req);
        ENV_COMMON_VARS(req->environment_set)
    }
    val = (char *)apr_table_get(req->req->subprocess_env,key);
    if (val) { return val; }

    /* CGI HTTP 1.1 vars */

    if (!ENV_CGI_VARS_LOADED(req->environment_set))
    {
        ap_add_cgi_vars(req->req);
        ENV_CGI_VARS(req->environment_set)
    }
    val = (char *)apr_table_get(req->req->subprocess_env,key);
    if (val) { return val; }

    /* If everything failed we assumed the variable is one of
     * the 'include variables' and we try to resolve it calling
     * TclWeb_GetEnvIncludeVar, which returns NULL if the variable
     * is undefined */

    return TclWeb_GetEnvIncludeVar(private,key);
}

char *
TclWeb_GetVirtualFile(TclWebRequest *req, char *virtualname)
{
    request_rec *apreq;
    char *filename = NULL;

    apreq = ap_sub_req_lookup_uri( virtualname, req->req, NULL );

    //if( apreq->status == 200 && apreq->finfo.st_mode != 0 ) {
    //TODO: is this the right behaviour?
    if( apreq->status == 200 && apreq->finfo.filetype != APR_NOFILE ) {
        filename = apreq->filename;
    }
    if( apreq != NULL ) ap_destroy_sub_req( apreq );
    return( filename );
}

/*
 *-----------------------------------------------------------------------------
 *
 * TclWeb_GetRawPost --
 *
 * 	Fetch the raw POST data from the request.
 *
 * Results:
 *	The data, or NULL if it's not a POST or there is no data.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

char *
TclWeb_GetRawPost ( TclWebRequest *req, int *len )
{
    return ApacheRequest_get_raw_post(req->apachereq, len);
}
