blob: ab87909f04145db8b5b9e41d76d9e9d60976b546 [file] [log] [blame]
/* -- 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;
#define TCLWEBPOOL req->req->pool
#define BUFSZ 4096
/* This is used below to determine what part of the parmsarray to parse. */
#define PARMSARRAY_COORDINATES i = 0; j = parmsarray->nelts; \
if (source == VAR_SRC_QUERYSTRING) { j = req->apachereq->nargs; } \
else if (source == VAR_SRC_POST) { i = req->apachereq->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;
req->environment_set = 0;
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;
req->environment_set = 0;
req->charset = NULL;
/*
* if strlen(req->content_type) > strlen([RIVET|TCL]_FILE_CTYPE)
* a charset parameters might be there
*/
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;
// req->req->content_type = (char *) apr_pstrdup(req->req->pool, header);
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);
/* ap_send_http_header(req->req); */
req->headers_printed = 1;
return TCL_OK;
}
/* Print nice HTML formatted errors */
int
TclWeb_PrintError(CONST84 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(TCLWEBPOOL, 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(TCLWEBPOOL,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;
/* 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++;
}
if (result->length == 0)
{
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;
/* 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_ListObjAppendElement(req->interp, result,
TclWeb_StringToUtfToObj(parms[i].val, req));
}
i++;
}
if (result == NULL)
{
return TCL_ERROR;
}
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;
while (i < j)
{
Tcl_ListObjAppendElement(req->interp, result,
TclWeb_StringToUtfToObj(parms[i].key, req));
Tcl_ListObjAppendElement(req->interp, result,
TclWeb_StringToUtfToObj(parms[i].val, req));
i++;
}
if (result == NULL)
{
return TCL_ERROR;
}
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;
while (i < j)
{
Tcl_ListObjAppendElement(req->interp, result,
TclWeb_StringToUtfToObj(parms[i].key, req));
i++;
}
if (result == NULL)
{
return TCL_ERROR;
}
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;
/* 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;
}
/*
* Load the Apache environment and CGI variables into the request. If we
* have already done so, we don't need to do it again.
*/
static void
TclWeb_InitEnvVars (rivet_thread_private* private)
{
//rivet_server_conf *rsc;
char *timefmt = DEFAULT_TIME_FORMAT;
char *t;
apr_time_t date;
#ifndef WIN32
struct passwd *pw;
#endif /* ndef WIN32 */
TclWebRequest *req;
apr_table_t *table;
req = private->req;
table = req->req->subprocess_env;
date = req->req->request_time;
if( req->environment_set ) return;
//rsc = RIVET_SERVER_CONF( req->req->server->module_config );
/* Retrieve cgi variables. */
ap_add_cgi_vars( req->req );
ap_add_common_vars( req->req );
/* These were the "include vars" */
apr_table_set( table, "DATE_LOCAL",
ap_ht_time( TCLWEBPOOL, date, timefmt, 0 ) );
apr_table_set( table, "DATE_GMT",
ap_ht_time( TCLWEBPOOL, date, timefmt, 1 ) );
apr_table_set( table, "LAST_MODIFIED",
ap_ht_time( TCLWEBPOOL, req->req->finfo.mtime, timefmt, 1 ) );
apr_table_set( table, "DOCUMENT_URI", req->req->uri );
apr_table_set( table, "DOCUMENT_PATH_INFO", req->req->path_info );
if ((t = strrchr(req->req->filename, '/'))) {
apr_table_set( table, "DOCUMENT_NAME", ++t );
} else {
apr_table_set( table, "DOCUMENT_NAME", req->req->uri );
}
if( req->req->args ) {
char *arg_copy = (char*) apr_pstrdup(TCLWEBPOOL, req->req->args);
ap_unescape_url(arg_copy);
apr_table_set( table, "QUERY_STRING_UNESCAPED",
ap_escape_shell_cmd( TCLWEBPOOL, arg_copy ) );
}
#ifndef WIN32
pw = (struct passwd *) getpwuid(req->req->finfo.user);
if( pw ) {
//apr_table_set( table, "USER_NAME",
// apr_pstrdup( TCLWEBPOOL, pw->pw_name ) );
} else {
apr_table_set( table, "USER_NAME",
(char*) apr_psprintf( TCLWEBPOOL, "user#%lu",
(unsigned long)req->req->finfo.user ) );
}
#endif
/* Here we create some variables with Rivet internal information. */
apr_table_set (table, "RIVET_CACHE_FREE",
(char*) apr_psprintf (TCLWEBPOOL, "%d",(RIVET_PEEK_INTERP(private,private->running_conf))->cache_free));
apr_table_set (table, "RIVET_CACHE_SIZE",
(char*) apr_psprintf (TCLWEBPOOL, "%d",(RIVET_PEEK_INTERP(private,private->running_conf))->cache_size));
req->environment_set = 1;
}
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;
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(TCLWEBPOOL, in);
return TCL_OK;
}
INLINE int
TclWeb_Base64Decode(char *out, char *in, TclWebRequest *req)
{
out = ap_pbase64decode(TCLWEBPOOL, in);
return TCL_OK;
}
INLINE int
TclWeb_EscapeShellCommand(char *out, char *in, TclWebRequest *req)
{
out = ap_escape_shell_cmd(TCLWEBPOOL, 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(TCLWEBPOOL, 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 {
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) {
return TCL_ERROR;
}
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;
}
/* 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;
}
char *
TclWeb_GetEnvVar(rivet_thread_private* private,char *key)
{
char *val;
TclWebRequest *req;
req = private->req;
TclWeb_InitEnvVars(private);
/* Check to see if it's a header variable first. */
val = (char *)apr_table_get( req->req->headers_in, key );
if( !val ) {
val = (char *)apr_table_get( req->req->subprocess_env, key );
}
return val;
}
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 )
{
return ApacheRequest_get_raw_post(req->apachereq);
}