blob: 914cf6c7bc264832fdef5014ec93472c1a26b240 [file] [log] [blame]
/* Copyright David Welton 1998, 1999 */
/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000 The Apache Software Foundation. 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.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
* ITS 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* Portions of this software are based upon public domain software
* originally written at the National Center for Supercomputing Applications,
* University of Illinois, Urbana-Champaign.
*/
/* $Id$ */
/* mod_dtcl.c by David Welton <davidw@apache.org> - originally mod_include. */
/* See http://tcl.apache.org/mod_dtcl/credits.ttml for additional credits. */
/*
* http_include.c: Handles the server-parsed HTML documents
*
* Original by Rob McCool; substantial fixups by David Robinson;
* incorporated into the Apache module framework by rst.
*
*/
#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_log.h"
#include "http_main.h"
#include "util_script.h"
#include "http_conf_globals.h"
#include <tcl.h>
#include <string.h>
#include "tcl_commands.h"
#include "apache_request.h"
#include "mod_dtcl.h"
/* *** Global variables *** */
request_rec *global_rr; /* request rec */
Tcl_Encoding system_encoding; /* Default encoding */
/* output buffer for initial buffer_add. We use traditional memory
management stuff on obuff - malloc, free, etc., because I couldn't
get it to work well with the apache functions - davidw */
obuff obuffer = {
NULL,
0
};
Tcl_Obj *namespacePrologue; /* initial bit of Tcl for namespace creation */
module MODULE_VAR_EXPORT dtcl_module;
char **objCacheList; /* Array of cached objects (for priority handling) */
Tcl_HashTable objCache; /* Objects cache - the key is the script name */
int buffer_output = 0; /* Start with output buffering off */
int headers_printed = 0; /* has the header been printed yet? */
int headers_set = 0; /* has the header been set yet? */
int content_sent = 0; /* make sure something gets sent */
int cacheSize = 0; /* size of cache, determined
either in conf files, or
set to
"ap_max_requests_per_child
/ 2"; in the
dtcl_init_handler function */
int cacheFreeSize = 0; /* free space in cache */
int upload_files_to_var = 0;
char *upload_dir = "/tmp/"; /* Upload directory */
unsigned int upload_max = 0; /* Maximum amount of data that may be uploaded */
typedef struct {
Tcl_Interp *server_interp; /* per server Tcl interpreter */
Tcl_Obj *dtcl_global_init_script; /* run once when apache is first started */
Tcl_Obj *dtcl_child_init_script;
Tcl_Obj *dtcl_child_exit_script;
Tcl_Obj *dtcl_before_script; /* script run before each page */
Tcl_Obj *dtcl_after_script; /* after */
Tcl_Obj *dtcl_error_script; /* after */
int dtcl_cache_size;
char *server_name;
} dtcl_server_conf;
#define GETREQINTERP(req) ((dtcl_server_conf *)ap_get_module_config(req->server->module_config, &dtcl_module))->server_interp
/* Functions for Tcl Channel */
static int closeproc(ClientData, Tcl_Interp *);
static int inputproc(ClientData, char *, int, int *);
static int outputproc(ClientData, char *, int, int *);
static int setoptionproc(ClientData, Tcl_Interp *, char *, char *);
/*
static int getoptionproc(ClientData, Tcl_Interp *, char *, Tcl_DString *); */
static void watchproc(ClientData, int);
static int gethandleproc(ClientData, int, ClientData *);
/* Apache BUFF Channel Type */
static Tcl_ChannelType Achan = {
"apache_channel",
NULL,
closeproc,
inputproc,
outputproc,
NULL,
setoptionproc,
NULL,
watchproc,
gethandleproc,
NULL
};
/* just need some arbitrary non-NULL pointer which can't also be a request_rec */
#define NESTED_INCLUDE_MAGIC (&dtcl_module)
int inputproc(ClientData instancedata, char *buf, int toRead, int *errorCodePtr)
{
return EINVAL;
}
/* This is the output 'method' for the Memory Buffer Tcl 'File'
Channel that we create to divert stdout to */
static int outputproc(ClientData instancedata, char *buf, int toWrite, int *errorCodePtr)
{
memwrite(&obuffer, buf, toWrite);
return toWrite;
}
static int closeproc(ClientData instancedata, Tcl_Interp *interp2)
{
print_headers(global_rr);
flush_output_buffer(global_rr);
return 0;
}
static int setoptionproc(ClientData instancedata, Tcl_Interp *interp, char *optionname, char *value)
{
return TCL_OK;
}
/*
int getoptionproc(ClientData instancedata, Tcl_Interp *intepr,
char *optionname, Tcl_DString *dsPtr)
{
return TCL_OK;
}
*/
static void watchproc(ClientData instancedata, int mask)
{
/* not much to do here */
return;
}
static int gethandleproc(ClientData instancedata, int direction, ClientData *handlePtr)
{
return TCL_ERROR;
}
/* Write something to the output buffer structure */
int memwrite(obuff *buffer, char *input, int len)
{
if (buffer->len == 0)
{
buffer->buf = Tcl_Alloc(len + 1);
memcpy(buffer->buf, input, len);
buffer->buf[len] = '\0';
buffer->len = len;
}
else
{
char *bufend;
buffer->buf = Tcl_Realloc(buffer->buf, len + buffer->len + 1);
bufend = buffer->buf + buffer->len;
memmove(bufend, input, len);
buffer->buf[len + buffer->len] = '\0';
buffer->len += len;
}
return len;
}
/* Set up the content type header */
int set_header_type(request_rec *r, char *header)
{
if (headers_set == 0)
{
r->content_type = header;
headers_set = 1;
return 1;
} else {
return 0;
}
}
/* Printer headers if they haven't been printed yet */
int print_headers(request_rec *r)
{
if (headers_printed == 0)
{
if (headers_set == 0)
set_header_type(r, DEFAULT_HEADER_TYPE);
ap_send_http_header(global_rr);
headers_printed = 1;
return 1;
} else {
return 0;
}
}
/* Print nice HTML formatted errors */
int print_error(request_rec *r, int htmlflag, char *errstr)
{
set_header_type(r, DEFAULT_HEADER_TYPE);
print_headers(r);
if (htmlflag != 1)
ap_rputs(ER1, r);
if (errstr != NULL)
{
if (htmlflag != 1)
{
ap_rputs(ap_escape_html(r->pool, errstr), r);
} else {
ap_rputs(errstr, r);
}
}
if (htmlflag != 1)
ap_rputs(ER2, r);
return 0;
}
/* Make sure that everything in the output buffer has been flushed. */
int flush_output_buffer(request_rec *r)
{
if (obuffer.len != 0)
{
ap_rwrite(obuffer.buf, obuffer.len, r);
Tcl_Free(obuffer.buf);
obuffer.len = 0;
obuffer.buf = NULL;
}
content_sent = 1;
return 0;
}
/* Function to convert strings to UTF encoding */
char *StringToUtf(char *input)
{
#if DTCL_I18N == 1
char *temp;
Tcl_DString dstr;
Tcl_DStringInit(&dstr);
Tcl_ExternalToUtfDString(system_encoding, input, strlen(input), &dstr);
temp = ap_pstrdup(global_rr->pool, Tcl_DStringValue(&dstr));
Tcl_DStringFree(&dstr);
return temp;
#else
/* If we aren't using the i18n stuff, no need to do anything */
return input;
#endif
}
/* Function to be used should we desire to upload files to a variable */
int dtcl_upload_hook(void *ptr, char *buf, int len, const ApacheUpload *upload)
{
Tcl_Interp *interp = ptr;
static void *oldptr;
int flag = 0;
/* if there is not a new file being added, keep adding it to the
same list element. Otherwise, start a new list element */
if (oldptr != upload)
flag = TCL_LIST_ELEMENT|TCL_APPEND_VALUE;
else
flag = TCL_APPEND_VALUE;
Tcl_ObjSetVar2(interp,
Tcl_NewStringObj("::request::UPLOAD", -1),
Tcl_NewStringObj("data", -1),
Tcl_NewByteArrayObj(buf, len),
flag);
return len;
}
/* Load, cache and eval a Tcl file */
int send_tcl_file(request_rec *r, char *filename, struct stat *finfo)
{
#if 1
/* Taken, in part, from tclIOUtil.c out of the Tcl
distribution, and modified */
/* Basically, what we are doing here is a Tcl_EvalFile, but
with the addition of caching code. */
int result;
int isNew;
char *hashKey;
Tcl_HashEntry *entry;
Tcl_Obj *cmdObjPtr;
Tcl_Interp *interp = GETREQINTERP(r);
/* Look for the script's compiled version. If it's not found, create it. */
hashKey = ap_psprintf(r->pool, "%s%ld%ld", r->filename, r->finfo.st_mtime, r->finfo.st_ctime);
entry = Tcl_CreateHashEntry(&objCache, hashKey, &isNew);
if (isNew || !cacheSize) {
char *cmdBuffer = (char *) NULL;
Tcl_Channel chan = Tcl_OpenFileChannel(interp, r->filename, "r", 0644);
if (chan == (Tcl_Channel) NULL)
{
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "couldn't read file \"", r->filename,
"\": ", Tcl_PosixError(interp), (char *) NULL);
goto error;
}
cmdBuffer = (char *) malloc(r->finfo.st_size + 1);
result = Tcl_Read(chan, cmdBuffer, r->finfo.st_size);
if (result < 0)
{
Tcl_Close(interp, chan);
Tcl_AppendResult(interp, "couldn't read file \"", r->filename,
"\": ", Tcl_PosixError(interp), (char *) NULL);
goto error;
}
cmdBuffer[result] = 0;
if (Tcl_Close(interp, chan) != TCL_OK)
goto error;
cmdObjPtr = Tcl_NewStringObj(cmdBuffer, result);
Tcl_IncrRefCount(cmdObjPtr);
Tcl_SetHashValue(entry, (ClientData)cmdObjPtr);
free(cmdBuffer);
if (cacheFreeSize) {
/* This MUST be malloc-ed, because it's permanent */
objCacheList[--cacheFreeSize ] = strdup(hashKey);
} else if (cacheSize) { /* if it's zero, we just skip this... */
Tcl_HashEntry *delEntry;
delEntry = Tcl_FindHashEntry(&objCache, objCacheList[cacheSize - 1]);
Tcl_DecrRefCount((Tcl_Obj *)Tcl_GetHashValue(delEntry));
Tcl_DeleteHashEntry(delEntry);
free(objCacheList[cacheSize - 1]);
memmove(objCacheList + 1, objCacheList, sizeof(char *)*(cacheSize -1));
objCacheList[0] = strdup(hashKey);
}
/* yuck */
goto end;
error:
if (cmdBuffer != (char *) NULL) {
free(cmdBuffer);
}
return TCL_ERROR;
end:
Tcl_EvalObj(interp, (cmdObjPtr));
} else {
Tcl_EvalObj(interp, (Tcl_Obj *)Tcl_GetHashValue(entry));
}
#else
Tcl_EvalFile(interp, r->filename);
#endif /* 1 */
print_headers(global_rr);
flush_output_buffer(global_rr);
return OK;
}
/* Parse and execute a ttml file */
int send_parsed_file(request_rec *r, char *filename, struct stat *finfo, int toplevel)
{
char *errorinfo;
char *hashKey;
int isNew;
dtcl_server_conf *dsc = NULL;
Tcl_Obj *outbuf;
Tcl_HashEntry *entry;
Tcl_Interp *interp = GETREQINTERP(r);
/* Look for the script's compiled version. If it's not found, create it. */
hashKey = ap_psprintf(r->pool, "%s%ld%ld%d", filename, finfo->st_mtime, finfo->st_ctime, toplevel);
entry = Tcl_CreateHashEntry(&objCache, hashKey, &isNew);
if (isNew || !cacheSize) {
/* BEGIN PARSER */
char inside = 0; /* are we inside the starting/ending delimiters */
const char *strstart = STARTING_SEQUENCE;
const char *strend = ENDING_SEQUENCE;
char c;
int ch;
int endseqlen = strlen(ENDING_SEQUENCE), startseqlen = strlen(STARTING_SEQUENCE), p = 0;
FILE *f = NULL;
dsc = (dtcl_server_conf *) ap_get_module_config(r->server->module_config, &dtcl_module);
if (!(f = ap_pfopen(r->pool, filename, "r")))
{
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"file permissions deny server access: %s", filename);
return HTTP_FORBIDDEN;
}
/* Beginning of the file parser */
if (toplevel)
{
outbuf = Tcl_NewStringObj("namespace eval request {\n", -1);
if (dsc->dtcl_before_script)
Tcl_AppendObjToObj(outbuf, dsc->dtcl_before_script);
Tcl_AppendToObj(outbuf, "buffer_add \"", -1);
}
else
outbuf = Tcl_NewStringObj("hputs \"\n", -1);
while ((ch = getc(f)) != EOF)
{
if (ch == -1)
if (ferror(f))
{
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"Encountered error in mod_dtcl getchar routine while reading %s",
r->uri);
ap_pfclose( r->pool, f);
}
c = ch;
if (!inside)
{
/* OUTSIDE */
#if USE_OLD_TAGS == 1
if (c == '<')
{
int nextchar = getc(f);
if (nextchar == '+')
{
Tcl_AppendToObj(outbuf, "\"\n", 2);
inside = 1;
p = 0;
continue;
} else {
ungetc(nextchar, f);
}
}
#endif
if (c == strstart[p])
{
if ((++p) == endseqlen)
{
/* ok, we have matched the whole ending sequence - do something */
Tcl_AppendToObj(outbuf, "\"\n", 2);
inside = 1;
p = 0;
continue;
}
} else {
if (p > 0)
Tcl_AppendToObj(outbuf, (char *)strstart, p);
/* or else just put the char in outbuf */
if (c == '$')
Tcl_AppendToObj(outbuf, "\\$", -1);
else if ( c == '[')
Tcl_AppendToObj(outbuf, "\\[", -1);
else if ( c == ']')
Tcl_AppendToObj(outbuf, "\\]", -1);
else if ( c == '"')
Tcl_AppendToObj(outbuf, "\\\"", -1);
else if ( c == '\\')
Tcl_AppendToObj(outbuf, "\\\\", -1);
else
Tcl_AppendToObj(outbuf, &c, 1);
p = 0;
continue;
}
} else {
/* INSIDE */
#if USE_OLD_TAGS == 1
if (c == '+')
{
int nextchar = getc(f);
if (nextchar == '>')
{
Tcl_AppendToObj(outbuf, "\n hputs \"", -1);
inside = 0;
p = 0;
continue;
} else {
ungetc(nextchar, f);
}
}
#endif
if (c == strend[p])
{
if ((++p) == startseqlen)
{
Tcl_AppendToObj(outbuf, "\n hputs \"", -1);
inside = 0;
p = 0;
continue;
}
}
else
{
/* plop stuff into outbuf, which we will then eval */
if (p > 0)
Tcl_AppendToObj(outbuf, (char *)strend, p);
Tcl_AppendToObj(outbuf, &c, 1);
p = 0;
}
}
}
ap_pfclose(r->pool, f);
if (!inside)
{
Tcl_AppendToObj(outbuf, "\"\n", 2);
}
if (toplevel)
{
if (dsc->dtcl_after_script)
Tcl_AppendObjToObj(outbuf, dsc->dtcl_after_script);
Tcl_AppendToObj(outbuf, "\n}\nnamespace delete request\n", -1);
}
else
Tcl_AppendToObj(outbuf, "\n", -1);
Tcl_IncrRefCount(outbuf);
#if DTCL_I18N == 1
/* Convert to encoding */
Tcl_SetStringObj(outbuf, StringToUtf(Tcl_GetString(outbuf)), -1);
#endif
Tcl_SetHashValue(entry, (ClientData)outbuf);
if (cacheFreeSize) {
/* This MUST be malloc-ed, because it's permanent */
objCacheList[--cacheFreeSize ] = strdup(hashKey);
} else if (cacheSize) { /* if it's zero, we just skip this... */
Tcl_HashEntry *delEntry;
/* a better algorithm wouldn't hurt */
delEntry = Tcl_FindHashEntry(&objCache, objCacheList[cacheSize - 1]);
Tcl_DecrRefCount((Tcl_Obj *)Tcl_GetHashValue(delEntry));
Tcl_DeleteHashEntry(delEntry);
free(objCacheList[cacheSize - 1]);
memmove(objCacheList + 1, objCacheList, sizeof(char *)*(cacheSize -1));
objCacheList[0] = strdup(hashKey);
}
/* END PARSER */
} else {
/* used the cached version */
outbuf = (Tcl_Obj *)Tcl_GetHashValue(entry);
}
#if DBG
print_error(r, 0,
Tcl_GetStringFromObj(outbuf, (int *)NULL));
return OK;
#endif
if (Tcl_EvalObj(interp, outbuf) == TCL_ERROR)
{
print_headers(global_rr);
flush_output_buffer(global_rr);
if (dsc->dtcl_error_script)
{
if (Tcl_EvalObj(interp, dsc->dtcl_error_script) == TCL_ERROR)
print_error(r, 1, "<b>Tcl_ErrorScript failed!</b>");
} else {
/* default action */
errorinfo = Tcl_GetVar(interp, "errorInfo", 0);
print_error(r, 0, errorinfo);
print_error(r, 1, "<p><b>OUTPUT BUFFER:</b></p>");
print_error(r, 0, Tcl_GetStringFromObj(outbuf, (int *)NULL));
}
/* "</pre><b>OUTPUT BUFFER</b><pre>\n",
Tcl_GetStringFromObj(outbuf, (int *)NULL)); */
} else {
/* We make sure to flush the output if buffer_add was the only output */
print_headers(global_rr);
flush_output_buffer(global_rr);
}
return OK;
}
/* Set things up to execute a file, then execute */
int send_content(request_rec *r)
{
char error[MAX_STRING_LEN];
char timefmt[MAX_STRING_LEN];
int errstatus;
Tcl_Interp *interp;
ApacheRequest *req;
ApacheUpload *upload;
global_rr = r; /* Assign request to global request var */
interp = GETREQINTERP(r);
r->allowed |= (1 << M_GET);
r->allowed |= (1 << M_POST);
if (r->method_number != M_GET && r->method_number != M_POST)
return DECLINED;
if (r->finfo.st_mode == 0)
{
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
"File does not exist: %s",
(r->path_info
? ap_pstrcat(r->pool, r->filename, r->path_info, NULL)
: r->filename));
return HTTP_NOT_FOUND;
}
if ((errstatus = ap_meets_conditions(r)) != OK)
return errstatus;
/* We need to send it as html */
/* r->content_type = DEFAULT_HEADER_TYPE; */
if (r->header_only)
{
set_header_type(r, DEFAULT_HEADER_TYPE);
print_headers(r);
return OK;
}
ap_cpystrn(error, DEFAULT_ERROR_MSG, sizeof(error));
ap_cpystrn(timefmt, DEFAULT_TIME_FORMAT, sizeof(timefmt));
ap_chdir_file(r->filename);
if (Tcl_EvalObj(interp, namespacePrologue) == TCL_ERROR)
{
ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Could not create request namespace\n");
exit(1);
}
/* Apache Request stuff */
req = ApacheRequest_new(r);
if (upload_files_to_var)
{
req->hook_data = interp;
req->upload_hook = dtcl_upload_hook;
}
ApacheRequest___parse(req);
/* take results and create tcl variables from them */
if (req->parms)
{
int i;
array_header *parmsarray = ap_table_elts(req->parms);
table_entry *parms = (table_entry *)parmsarray->elts;
Tcl_Obj *varsobj = Tcl_NewStringObj("::request::VARS", -1);
for (i = 0; i < parmsarray->nelts; ++i)
{
if (!parms[i].key)
continue;
Tcl_ObjSetVar2(interp, varsobj,
STRING_TO_UTF_TO_OBJ(parms[i].key),
STRING_TO_UTF_TO_OBJ(parms[i].val),
0);
}
}
upload = req->upload;
/* Loop through uploaded files */
while (upload)
{
char *type = NULL;
char *channelname = NULL;
Tcl_Channel chan;
/* The name of the file uploaded */
Tcl_ObjSetVar2(interp,
Tcl_NewStringObj("::request::UPLOAD", -1),
Tcl_NewStringObj("filename", -1),
Tcl_NewStringObj(upload->filename, -1),
TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
/* The variable name of the file upload */
Tcl_ObjSetVar2(interp,
Tcl_NewStringObj("::request::UPLOAD", -1),
Tcl_NewStringObj("name", -1),
Tcl_NewStringObj(upload->name, -1),
TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
Tcl_ObjSetVar2(interp,
Tcl_NewStringObj("::request::UPLOAD", -1),
Tcl_NewStringObj("size", -1),
Tcl_NewIntObj(upload->size),
TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
type = (char *)ap_table_get(upload->info, "Content-type");
if (type)
{
Tcl_ObjSetVar2(interp,
Tcl_NewStringObj("::request::UPLOAD", -1),
Tcl_NewStringObj("type", -1),
Tcl_NewStringObj(type, -1), /* kill end of line */
TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
}
if (!upload_files_to_var)
{
if (upload->fp != NULL)
{
chan = Tcl_MakeFileChannel((ClientData *)fileno(upload->fp), TCL_READABLE);
Tcl_RegisterChannel(interp, chan);
channelname = Tcl_GetChannelName(chan);
Tcl_ObjSetVar2(interp,
Tcl_NewStringObj("::request::UPLOAD", -1),
Tcl_NewStringObj("channelname", -1),
Tcl_NewStringObj(channelname, -1), /* kill end of line */
TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
}
}
upload = upload->next;
}
if(!strcmp(r->content_type, "application/x-httpd-tcl"))
{
/* It's a TTML file */
send_parsed_file(r, r->filename, &(r->finfo), 1);
} else {
/* It's a plain Tcl file */
send_tcl_file(r, r->filename, &(r->finfo));
}
/* reset globals */
buffer_output = 0;
headers_printed = 0;
headers_set = 0;
content_sent = 0;
return OK;
}
/* This is done in two places, so I decided to group the creates in
one function */
void tcl_create_commands(Tcl_Interp *interp)
{
Tcl_CreateObjCommand(interp, "hputs", Hputs, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
Tcl_CreateObjCommand(interp, "buffer_add", Buffer_Add, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
Tcl_CreateObjCommand(interp, "buffered", Buffered, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
Tcl_CreateObjCommand(interp, "headers", Headers, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
Tcl_CreateObjCommand(interp, "hgetvars", HGetVars, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
Tcl_CreateObjCommand(interp, "include", Include, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
Tcl_CreateObjCommand(interp, "parse", Parse, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
Tcl_CreateObjCommand(interp, "hflush", HFlush, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
Tcl_CreateObjCommand(interp, "dtcl_info", Dtcl_Info, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
Tcl_CreateObjCommand(interp, "no_body", No_Body, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
}
void tcl_init_stuff(server_rec *s, pool *p)
{
int rslt;
Tcl_Channel achan;
Tcl_Interp *interp;
dtcl_server_conf *dsc = (dtcl_server_conf *) ap_get_module_config(s->module_config, &dtcl_module);
server_rec *sr;
/* Initialize TCL stuff */
interp = Tcl_CreateInterp();
dsc->server_interp = interp; /* root interpreter */
/* Create TCL commands to deal with Apache's BUFFs. */
achan = Tcl_CreateChannel(&Achan, "apacheout", NULL, TCL_WRITABLE);
system_encoding = Tcl_GetEncoding(NULL, "iso8859-1"); /* FIXME */
Tcl_SetStdChannel(achan, TCL_STDOUT);
Tcl_SetChannelOption(interp, achan, "-buffering", "none");
Tcl_RegisterChannel(interp, achan);
if (interp == NULL)
{
ap_log_error(APLOG_MARK, APLOG_ERR, s, "Error in Tcl_CreateInterp, aborting\n");
exit(1);
}
#if (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION > 0)
Tcl_FindExecutable(""); /* Needed for locating init.tcl */
#endif
if (Tcl_Init(interp) == TCL_ERROR)
{
ap_log_error(APLOG_MARK, APLOG_ERR, s, Tcl_GetStringResult(interp));
exit(1);
}
tcl_create_commands(interp);
namespacePrologue = Tcl_NewStringObj(
"catch { namespace delete request }\n"
"namespace eval request { }\n"
"proc ::request::global { args } { foreach arg $args { uplevel \"::global ::request::$arg\" } }\n", -1);
Tcl_IncrRefCount(namespacePrologue);
#if DBG
ap_log_error(APLOG_MARK, APLOG_ERR, s, "Config string = \"%s\"", Tcl_GetStringFromObj(dsc->dtcl_global_init_script, NULL)); /* XXX */
ap_log_error(APLOG_MARK, APLOG_ERR, s, "Cache size = \"%d\"", dsc->dtcl_cache_size); /* XXX */
#endif
if (dsc->dtcl_global_init_script != NULL)
{
rslt = Tcl_EvalObjEx(interp, dsc->dtcl_global_init_script, 0);
if (rslt != TCL_OK)
{
ap_log_error(APLOG_MARK, APLOG_ERR, s, "%s",
Tcl_GetVar(interp, "errorInfo", 0));
}
}
/* This is what happens if it is not set by the user */
if(dsc->dtcl_cache_size < 0)
{
if (ap_max_requests_per_child != 0)
cacheSize = ap_max_requests_per_child / 2;
else
cacheSize = 10; /* Arbitrary number FIXME */
cacheFreeSize = cacheSize;
} else if (dsc->dtcl_cache_size > 0) {
cacheSize = dsc->dtcl_cache_size;
cacheFreeSize = dsc->dtcl_cache_size;
} else {
cacheSize = 0;
}
/* Initializing cache structures */
objCacheList = malloc(cacheSize * sizeof(char *));
Tcl_InitHashTable(&objCache, TCL_STRING_KEYS);
sr = s;
while (sr)
{
/* Ok, this stuff should set up slave interpreters for other
virtual hosts */
dtcl_server_conf *mydsc = (dtcl_server_conf *) ap_get_module_config(sr->module_config, &dtcl_module);
if (!mydsc->server_interp)
{
mydsc->server_interp = Tcl_CreateSlave(interp, sr->server_hostname, 0);
tcl_create_commands(mydsc->server_interp);
Tcl_SetChannelOption(mydsc->server_interp, achan, "-buffering", "none");
Tcl_RegisterChannel(mydsc->server_interp, achan);
}
mydsc->server_name = ap_pstrdup(p, sr->server_hostname);
sr = sr->next;
}
}
MODULE_VAR_EXPORT void dtcl_init_handler(server_rec *s, pool *p)
{
#if THREADED_TCL == 0
tcl_init_stuff(s, p);
#endif
#ifndef HIDE_DTCL_VERSION
ap_add_version_component("mod_dtcl/"DTCL_VERSION);
#else
ap_add_version_component("mod_dtcl");
#endif /* !HIDE_DTCL_VERSION */
}
const char *set_script(cmd_parms *cmd, void *dummy, char *arg, char *arg2)
{
Tcl_Obj *objarg;
server_rec *s = cmd->server;
dtcl_server_conf *dsc = (dtcl_server_conf *)ap_get_module_config(s->module_config, &dtcl_module);
if (arg == NULL || arg2 == NULL)
return "Mod_Dtcl Error: Dtcl_Script requires two arguments";
objarg = Tcl_NewStringObj(arg2, -1);
Tcl_IncrRefCount(objarg);
Tcl_AppendToObj(objarg, "\n", 1);
if (strcmp(arg, "GlobalInitScript") == 0) {
dsc->dtcl_global_init_script = objarg;
} else if (strcmp(arg, "ChildInitScript") == 0) {
dsc->dtcl_child_init_script = objarg;
} else if (strcmp(arg, "ChildExitScript") == 0) {
dsc->dtcl_child_exit_script = objarg;
} else if (strcmp(arg, "BeforeScript") == 0) {
dsc->dtcl_before_script = objarg;
} else if (strcmp(arg, "AfterScript") == 0) {
dsc->dtcl_after_script = objarg;
} else if (strcmp(arg, "ErrorScript") == 0) {
dsc->dtcl_error_script = objarg;
} else {
return "Mod_Dtcl Error: Dtcl_Script must have a second argument, which is one of: GlobalInitScript, ChildInitScript, ChildExitScript, BeforeScript, AfterScript";
}
return NULL;
}
const char *set_cachesize(cmd_parms *cmd, void *dummy, char *arg)
{
server_rec *s = cmd->server;
dtcl_server_conf *dsc = (dtcl_server_conf *)ap_get_module_config(s->module_config, &dtcl_module);
dsc->dtcl_cache_size = strtol(arg, NULL, 10);
return NULL;
}
const char *set_uploaddir(cmd_parms *cmd, void *dummy, char *arg)
{
upload_dir = arg;
return NULL;
}
const char *set_uploadmax(cmd_parms *cmd, void *dummy, char *arg)
{
upload_max = strtol(arg, NULL, 10);
return NULL;
}
const char *set_filestovar(cmd_parms *cmd, void *dummy, char *arg)
{
upload_files_to_var = strtol(arg, NULL, 10);
return NULL;
}
void *create_dtcl_config(pool *p, server_rec *s)
{
dtcl_server_conf *dsc = (dtcl_server_conf *) ap_pcalloc(p, sizeof(dtcl_server_conf));
dsc->dtcl_global_init_script = NULL;
dsc->dtcl_child_init_script = NULL;
dsc->dtcl_child_exit_script = NULL;
dsc->dtcl_before_script = NULL;
dsc->dtcl_after_script = NULL;
dsc->dtcl_error_script = NULL;
dsc->dtcl_cache_size = -1;
dsc->server_name = ap_pstrdup(p, s->server_hostname);
return dsc;
}
void *merge_dtcl_config(pool *p, void *basev, void *overridesv)
{
dtcl_server_conf *dsc = (dtcl_server_conf *) ap_pcalloc(p, sizeof(dtcl_server_conf));
dtcl_server_conf *base = (dtcl_server_conf *) basev;
dtcl_server_conf *overrides = (dtcl_server_conf *) overridesv;
dsc->server_interp = overrides->server_interp ? overrides->server_interp : base->server_interp;
dsc->dtcl_global_init_script = overrides->dtcl_global_init_script ? overrides->dtcl_global_init_script : base->dtcl_global_init_script;
dsc->dtcl_child_init_script = overrides->dtcl_child_init_script ? overrides->dtcl_child_init_script : base->dtcl_child_init_script;
dsc->dtcl_child_exit_script = overrides->dtcl_child_exit_script ? overrides->dtcl_child_exit_script : base->dtcl_child_exit_script;
dsc->dtcl_before_script = overrides->dtcl_before_script ? overrides->dtcl_before_script : base->dtcl_before_script;
dsc->dtcl_after_script = overrides->dtcl_after_script ? overrides->dtcl_after_script : base->dtcl_after_script;
dsc->dtcl_error_script = overrides->dtcl_error_script ? overrides->dtcl_error_script : base->dtcl_error_script;
dsc->dtcl_cache_size = overrides->dtcl_cache_size ? overrides->dtcl_cache_size : base->dtcl_cache_size;
dsc->server_name = overrides->server_name ? overrides->server_name : base->server_name;
return dsc;
}
void dtcl_child_init(server_rec *s, pool *p)
{
server_rec *sr;
dtcl_server_conf *dsc;
#if THREADED_TCL == 1
tcl_init_stuff(s, p);
#endif
sr = s;
while(sr)
{
dsc = (dtcl_server_conf *) ap_get_module_config(sr->module_config, &dtcl_module);
if (dsc->dtcl_child_init_script != NULL)
if (Tcl_EvalObjEx(dsc->server_interp, dsc->dtcl_child_init_script, 0) != TCL_OK)
ap_log_error(APLOG_MARK, APLOG_ERR, s,
"Problem running child init script: %s", Tcl_GetString(dsc->dtcl_child_init_script));
sr = sr->next;
}
}
void dtcl_child_exit(server_rec *s, pool *p)
{
dtcl_server_conf *dsc = (dtcl_server_conf *) ap_get_module_config(s->module_config, &dtcl_module);
if (dsc->dtcl_child_exit_script != NULL)
if (Tcl_EvalObjEx(dsc->server_interp, dsc->dtcl_child_exit_script, 0) != TCL_OK)
ap_log_error(APLOG_MARK, APLOG_ERR, s,
"Problem running child exit script: %s", Tcl_GetStringFromObj(dsc->dtcl_child_exit_script, NULL));
}
const handler_rec dtcl_handlers[] =
{
{"application/x-httpd-tcl", send_content},
{"application/x-dtcl-tcl", send_content},
{NULL}
};
const command_rec dtcl_cmds[] =
{
{"Dtcl_Script", set_script, NULL, RSRC_CONF, TAKE2, "Dtcl_Script GlobalInitScript|ChildInitScript|ChildExitScript|BeforeScript|AfterScript|ErrorScript scriptname.tcl"},
{"Dtcl_CacheSize", set_cachesize, NULL, RSRC_CONF, TAKE1, "Dtcl_Cachesize cachesize"},
{"Dtcl_UploadDirectory", set_uploaddir, NULL, RSRC_CONF, TAKE1, "Dtcl_UploadDirectory dirname"},
{"Dtcl_UploadMaxSize", set_uploadmax, NULL, RSRC_CONF, TAKE1, "Dtcl_UploadMaxSize size"},
{"Dtcl_UploadFilesToVar", set_filestovar, NULL, RSRC_CONF, TAKE1, "Dtcl_UploadFilesToVar 1/0"},
{NULL}
};
module MODULE_VAR_EXPORT dtcl_module =
{
STANDARD_MODULE_STUFF,
dtcl_init_handler, /* initializer */
NULL, /* dir config creater */
NULL, /* dir merger --- default is to override */
create_dtcl_config, /* server config */
merge_dtcl_config, /* merge server config */
dtcl_cmds, /* command table */
dtcl_handlers, /* handlers */
NULL, /* filename translation */
NULL, /* check_user_id */
NULL, /* check auth */
NULL, /* check access */
NULL, /* type_checker */
NULL, /* fixups */
NULL, /* logger */
NULL, /* header parser */
dtcl_child_init, /* child_init */
dtcl_child_exit, /* child_exit */
NULL /* post read-request */
};
/*
Local Variables: ***
compile-command: "./builddtcl.sh shared" ***
End: ***
*/