| /* rivetCore.c -- Core commands which are compiled into mod_rivet itself */ |
| |
| /* |
| 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 <sys/stat.h> |
| |
| #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_config.h" |
| |
| #include <tcl.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <apr_errno.h> |
| #include <apr_strings.h> |
| #include <apr_portable.h> |
| |
| #include "apache_request.h" |
| #include "mod_rivet.h" |
| #include "rivet.h" |
| #include "TclWeb.h" |
| /* Function prototypes are defined with EXTERN. Since we are in the same DLL, |
| * no need to keep this extern... */ |
| #ifdef EXTERN |
| # undef EXTERN |
| # define EXTERN |
| #endif /* EXTERN */ |
| #include "rivetParser.h" |
| #include "mod_rivet_generator.h" |
| #include "mod_rivet_cache.h" |
| |
| #define ENV_ARRAY_NAME "::request::env" |
| #define HEADERS_ARRAY_NAME "::request::headers" |
| #define COOKIES_ARRAY_NAME "cookies" |
| |
| extern module rivet_module; |
| extern char* TclWeb_GetRawPost (TclWebRequest *req); |
| extern mod_rivet_globals* module_globals; |
| extern apr_threadkey_t* rivet_thread_key; |
| |
| #define POOL (private->r->pool) |
| |
| /* define a convenience macro to cast the ClientData |
| * into the thread private data pointer */ |
| |
| #define THREAD_PRIVATE_DATA(p) p = (rivet_thread_private *)clientData; |
| |
| /* |
| * -- Rivet_NoRequestRec |
| * |
| * Adds standard error information to the interpreter. This procedure makes |
| * sense only when called by C functions implementing Tcl commands that |
| * are meaningful only if a valid requiest_rec object is defined. These |
| * procedures must return TCL_ERROR right away after Rivet_NoRequestRecord |
| * returns |
| * |
| * Arguments: |
| * |
| * Tcl_Interp*: current Tcl interpreter |
| * Tcl_Obj*: Tcl string object with the command name |
| * |
| * Results: |
| * |
| * None |
| * |
| */ |
| |
| static void |
| Rivet_NoRequestRec (Tcl_Interp* interp, Tcl_Obj* command) |
| { |
| Tcl_AddErrorInfo(interp, "Cannot call "); |
| Tcl_AppendObjToErrorInfo(interp,command); |
| Tcl_AppendObjToErrorInfo(interp,Tcl_NewStringObj(" outside a request processing",-1)); |
| } |
| |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_MakeURL -- |
| * |
| * Make a self-referencing URL. |
| * |
| * Results: |
| * Standard Tcl result. |
| * |
| * Side Effects: |
| * None. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_MakeURL ) |
| { |
| rivet_thread_private* private; |
| Tcl_Obj* result = NULL; |
| char* url_target_name; |
| int target_length; |
| |
| if (objc > 2) |
| { |
| Tcl_WrongNumArgs(interp, 1, objv, "filename"); |
| return TCL_ERROR; |
| } |
| |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::makeurl") |
| if (objc == 1) |
| { |
| url_target_name = TclWeb_GetEnvVar (private,"SCRIPT_NAME"); |
| } |
| else |
| { |
| url_target_name = Tcl_GetStringFromObj(objv[1],&target_length); |
| |
| // we check the first character for a '/' (absolute path) |
| // If we are dealing with a relative path we prepend it with |
| // the SCRIPT_NAME environment variable |
| |
| if (url_target_name[0] != '/') |
| { |
| /* relative path */ |
| char* script_name = TclWeb_GetEnvVar (private,"SCRIPT_NAME"); |
| size_t script_name_l = strlen(script_name); |
| |
| // regardless the reason for a SCRIPT_NAME being undefined we |
| // prevent a segfault and we revert the behavior of makeurl |
| // to the case of an absolute path |
| |
| if (script_name_l > 0) |
| { |
| // script name may have the form a directory path (and mod_rewrite |
| // could have mapped it to a .tcl or .rvt script) |
| |
| if (script_name[script_name_l-1] == '/') |
| { |
| url_target_name = apr_pstrcat(private->req->req->pool,script_name,url_target_name,NULL); |
| } |
| else |
| { |
| url_target_name = apr_pstrcat(private->req->req->pool,script_name,"/",url_target_name,NULL); |
| } |
| } |
| else |
| { |
| url_target_name = apr_pstrcat(private->req->req->pool,"/",url_target_name,NULL); |
| } |
| } |
| } |
| |
| result = Tcl_NewObj(); |
| TclWeb_MakeURL(result, url_target_name, private->req); |
| Tcl_SetObjResult(interp, result); |
| |
| return TCL_OK; |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_Parse -- |
| * |
| * Include and parse a Rivet file. |
| * |
| * Results: |
| * Standard Tcl result. |
| * |
| * Side Effects: |
| * Whatever occurs in the Rivet page parsed. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_Parse ) |
| { |
| rivet_thread_private* private; |
| char* filename = 0; |
| apr_status_t stat_s; |
| apr_finfo_t finfo_b; |
| char* cache_key; |
| rivet_thread_interp* rivet_interp; |
| Tcl_HashEntry* entry = NULL; |
| Tcl_Obj* script = NULL; |
| int result; |
| |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::parse") |
| |
| if( objc < 2 || objc > 3 ) |
| { |
| Tcl_WrongNumArgs(interp, 1, objv, "?-virtual? filename"); |
| return TCL_ERROR; |
| } |
| |
| if( objc == 2 ) { |
| |
| filename = Tcl_GetStringFromObj( objv[1], (int *)NULL ); |
| |
| } else { |
| |
| if (STREQU( Tcl_GetStringFromObj(objv[1], (int *)NULL), "-virtual")) { |
| |
| /* */ |
| |
| filename = TclWeb_GetVirtualFile(private->req,Tcl_GetStringFromObj(objv[2],(int *)NULL)); |
| |
| } else if ( STREQU( Tcl_GetStringFromObj(objv[1], (int *)NULL), "-string")) { |
| |
| int res; |
| Tcl_Obj* script = objv[2]; |
| Tcl_Obj* outbuf = Tcl_NewObj(); |
| |
| /* we parse and compose the script ourselves before passing it to Tcl_EvalObjEx */ |
| |
| Tcl_IncrRefCount(outbuf); |
| Tcl_AppendToObj(outbuf, "puts -nonewline \"", -1); |
| |
| /* If we are not inside a <? ?> section, add the closing ". */ |
| if (Rivet_Parser(outbuf, script) == 0) |
| { |
| Tcl_AppendToObj(outbuf, "\"\n", 2); |
| } |
| |
| Tcl_AppendToObj(outbuf,"\n",-1); |
| |
| res = Tcl_EvalObjEx(interp,outbuf,0); |
| |
| Tcl_DecrRefCount(outbuf); |
| return res; |
| //return Rivet_ParseExecString(private, objv[2]); |
| |
| } else { |
| |
| Tcl_WrongNumArgs( interp, 1, objv, "?-virtual? filename | -string template_string" ); |
| return TCL_ERROR; |
| |
| } |
| |
| } |
| |
| if (!strcmp(filename, private->r->filename)) |
| { |
| Tcl_AddErrorInfo(interp, "Cannot recursively call the same file!"); |
| return TCL_ERROR; |
| } |
| |
| stat_s = apr_stat(&finfo_b,filename,APR_FINFO_NORM,private->r->pool); |
| if (stat_s != APR_SUCCESS) |
| { |
| char apr_error_message[RIVET_MSG_BUFFER_SIZE]; |
| |
| Tcl_AddErrorInfo(interp,apr_strerror(stat_s,apr_error_message,RIVET_MSG_BUFFER_SIZE)); |
| return TCL_ERROR; |
| } |
| |
| /* */ |
| |
| cache_key = |
| RivetCache_MakeKey( private->pool,filename, |
| finfo_b.ctime,finfo_b.mtime, |
| IS_USER_CONF(private->running_conf),0); |
| |
| rivet_interp = RIVET_PEEK_INTERP(private,private->running_conf); |
| entry = RivetCache_EntryLookup (rivet_interp,cache_key); |
| |
| if (entry == NULL) |
| { |
| script = Tcl_NewObj(); |
| Tcl_IncrRefCount(script); |
| |
| result = Rivet_GetRivetFile(filename,script,interp); |
| if (result != TCL_OK) |
| { |
| Tcl_AddErrorInfo(interp,apr_pstrcat(private->pool,"Could not read file ",filename,NULL)); |
| Tcl_DecrRefCount(script); |
| return result; |
| } |
| |
| if (rivet_interp->cache_free > 0) |
| { |
| int isNew; |
| Tcl_HashEntry* entry; |
| |
| entry = RivetCache_CreateEntry (rivet_interp,cache_key,&isNew); |
| ap_assert(isNew == 1); |
| RivetCache_StoreScript(rivet_interp,entry,script); |
| } |
| else if ((rivet_interp->flags & RIVET_CACHE_FULL) == 0) |
| { |
| rivet_interp->flags |= RIVET_CACHE_FULL; |
| ap_log_error (APLOG_MARK, APLOG_NOTICE, APR_EGENERAL, private->r->server,"%s %s (%s),", |
| "Rivet cache full when parsing ", |
| private->r->filename, |
| private->r->server->server_hostname); |
| } |
| |
| result = Tcl_EvalObjEx(interp,script,0); |
| Tcl_DecrRefCount(script); |
| return result; |
| |
| } else { |
| script = RivetCache_FetchScript(entry); |
| return Tcl_EvalObjEx(interp,script,0); |
| } |
| |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_Include -- |
| * |
| * Includes a file literally in the output stream. Useful for |
| * images, plain HTML and the like. |
| * |
| * Results: |
| * A standard Tcl result. |
| * |
| * Side Effects: |
| * Adds to the output stream. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_Include ) |
| { |
| rivet_thread_private* private; |
| int sz; |
| Tcl_Channel fd; |
| Tcl_Channel tclstdout; |
| Tcl_Obj* outobj; |
| char* filename; |
| Tcl_DString transoptions; |
| Tcl_DString encoptions; |
| |
| if( objc < 2 || objc > 3 ) |
| { |
| Tcl_WrongNumArgs(interp, 1, objv, "?-virtual? filename"); |
| return TCL_ERROR; |
| } |
| |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::include") |
| |
| if( objc == 2 ) { |
| filename = Tcl_GetStringFromObj( objv[1], (int *)NULL ); |
| } else { |
| if( !STREQU( Tcl_GetStringFromObj(objv[1], (int *)NULL), "-virtual") ) { |
| Tcl_WrongNumArgs( interp, 1, objv, "?-virtual? filename" ); |
| return TCL_ERROR; |
| } |
| |
| CHECK_REQUEST_REC(private,"::rivet::include -virtual") |
| filename = TclWeb_GetVirtualFile(private->req,Tcl_GetStringFromObj(objv[2], (int *)NULL) ); |
| } |
| |
| fd = Tcl_OpenFileChannel(interp, filename, "r", 0664); |
| |
| if (fd == NULL) |
| { |
| return TCL_ERROR; |
| } |
| Tcl_SetChannelOption(interp, fd, "-translation", "binary"); |
| |
| outobj = Tcl_NewObj(); |
| Tcl_IncrRefCount(outobj); |
| sz = Tcl_ReadChars(fd, outobj, -1, 0); |
| if (sz == -1) |
| { |
| Tcl_AddErrorInfo(interp, Tcl_PosixError(interp)); |
| Tcl_DecrRefCount(outobj); |
| return TCL_ERROR; |
| } |
| |
| /* What we are doing is saving the translation and encoding |
| * options, setting them both to binary, and the restoring the |
| * previous settings. */ |
| Tcl_DStringInit(&transoptions); |
| Tcl_DStringInit(&encoptions); |
| tclstdout = Tcl_GetChannel(interp, "stdout", NULL); |
| Tcl_GetChannelOption(interp, tclstdout, "-translation", &transoptions); |
| Tcl_GetChannelOption(interp, tclstdout, "-encoding", &encoptions); |
| Tcl_SetChannelOption(interp, tclstdout, "-translation", "binary"); |
| Tcl_WriteObj(tclstdout, outobj); |
| Tcl_SetChannelOption(interp, tclstdout, "-translation", Tcl_DStringValue(&transoptions)); |
| Tcl_SetChannelOption(interp, tclstdout, "-encoding", Tcl_DStringValue(&encoptions)); |
| Tcl_DStringFree(&transoptions); |
| Tcl_DStringFree(&encoptions); |
| Tcl_DecrRefCount(outobj); |
| return Tcl_Close(interp, fd); |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_Headers -- |
| * |
| * Command to manipulate HTTP headers from Tcl. |
| * |
| * Results: |
| * A standard Tcl result. |
| * |
| * Side Effects: |
| * None. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_Headers ) |
| { |
| char *opt; |
| rivet_thread_private* private; |
| |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::headers") |
| |
| if (objc < 2) |
| { |
| Tcl_WrongNumArgs(interp, 1, objv, "option arg ?arg ...?"); |
| return TCL_ERROR; |
| } |
| |
| opt = Tcl_GetStringFromObj(objv[1], NULL); |
| |
| /* Basic introspection returning the value of the headers_printed flag */ |
| |
| if (!strcmp("sent",opt)) |
| { |
| Tcl_SetObjResult(interp, Tcl_NewIntObj(private->req->headers_printed)); |
| return TCL_OK; |
| } |
| |
| if (private->req->headers_printed != 0) |
| { |
| Tcl_AddObjErrorInfo(interp,"Cannot manipulate headers - already sent", -1); |
| return TCL_ERROR; |
| } |
| |
| if (!strcmp("redirect", opt)) /* ### redirect ### */ |
| { |
| if (objc != 3) |
| { |
| Tcl_WrongNumArgs(interp, 2, objv, "new-url"); |
| return TCL_ERROR; |
| } |
| apr_table_set(private->r->headers_out, "Location", |
| Tcl_GetStringFromObj (objv[2], (int *)NULL)); |
| TclWeb_SetStatus(301, private->req); |
| return TCL_OK; |
| } |
| else if (!strcmp("get", opt)) /* ### get ### */ |
| { |
| const char* header_value; |
| |
| if (objc != 3) |
| { |
| Tcl_WrongNumArgs(interp, 2, objv, "headername"); |
| return TCL_ERROR; |
| } |
| |
| header_value = TclWeb_OutputHeaderGet(Tcl_GetString(objv[2]),private->req); |
| |
| Tcl_SetObjResult(interp,Tcl_NewStringObj(header_value ? header_value : "",-1)); |
| } |
| else if (!strcmp("set", opt)) /* ### set ### */ |
| { |
| if (objc != 4) |
| { |
| Tcl_WrongNumArgs(interp, 2, objv, "headername value"); |
| return TCL_ERROR; |
| } |
| TclWeb_HeaderSet(Tcl_GetString(objv[2]), Tcl_GetString(objv[3]), private->req); |
| } |
| else if (!strcmp("add", opt)) /* ### set ### */ |
| { |
| if (objc != 4) |
| { |
| Tcl_WrongNumArgs(interp, 2, objv, "headername value"); |
| return TCL_ERROR; |
| } |
| TclWeb_HeaderAdd(Tcl_GetString(objv[2]), Tcl_GetString(objv[3]), private->req); |
| } |
| else if (!strcmp("type", opt)) /* ### set ### */ |
| { |
| if (objc != 3) |
| { |
| Tcl_WrongNumArgs(interp, 2, objv, "mime/type"); |
| return TCL_ERROR; |
| } |
| TclWeb_SetHeaderType(Tcl_GetString(objv[2]), private->req); |
| } |
| else if (!strcmp("numeric", opt)) /* ### numeric ### */ |
| { |
| int st = 200; |
| |
| if (objc != 3) |
| { |
| Tcl_WrongNumArgs(interp, 2, objv, "response_code_number"); |
| return TCL_ERROR; |
| } |
| if (Tcl_GetIntFromObj(interp, objv[2], &st) != TCL_ERROR) { |
| TclWeb_SetStatus(st, private->req); |
| } else { |
| return TCL_ERROR; |
| } |
| |
| } else { |
| |
| Tcl_Obj* result = Tcl_NewStringObj("unrecognized subcommand: ",-1); |
| Tcl_IncrRefCount(result); |
| Tcl_AppendStringsToObj(result,opt,NULL); |
| |
| Tcl_SetObjResult(interp, result); |
| Tcl_DecrRefCount(result); |
| return TCL_ERROR; |
| |
| } |
| return TCL_OK; |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_LoadEnv -- |
| * |
| * Load the "environmental variables" - those variables that are |
| * set in the environment in a standard CGI program. If no array |
| * name is supplied, they are loaded into an array whose name is |
| * the value of the ENV_ARRAY_NAME #define. |
| * |
| * Results: |
| * A standard Tcl result. |
| * |
| * Side Effects: |
| * None. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_LoadEnv ) |
| { |
| rivet_thread_private* private; |
| Tcl_Obj* ArrayObj; |
| |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::load_env") |
| if( objc > 2 ) { |
| Tcl_WrongNumArgs( interp, 1, objv, "?arrayName?" ); |
| return TCL_ERROR; |
| } |
| |
| if( objc == 2 ) { |
| ArrayObj = objv[1]; |
| } else { |
| ArrayObj = Tcl_NewStringObj( ENV_ARRAY_NAME, -1 ); |
| } |
| |
| return TclWeb_GetEnvVars(ArrayObj,private); |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_LoadHeaders -- |
| * |
| * Load the HTTP headers supplied by the client into a Tcl array, |
| * whose name defaults to the value of the HEADERS_ARRAY_NAME |
| * #define. |
| * |
| * Results: |
| * A standard Tcl result. |
| * |
| * Side Effects: |
| * Creates an array variable if none exists. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER ( Rivet_LoadHeaders ) |
| { |
| rivet_thread_private* private; |
| Tcl_Obj* ArrayObj; |
| |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::load_headers") |
| if( objc > 2 ) { |
| Tcl_WrongNumArgs( interp, 1, objv, "?arrayName?" ); |
| return TCL_ERROR; |
| } |
| |
| if( objc == 2 ) { |
| ArrayObj = objv[1]; |
| } else { |
| ArrayObj = Tcl_NewStringObj( HEADERS_ARRAY_NAME, -1 ); |
| } |
| |
| return TclWeb_GetHeaderVars(ArrayObj,private); |
| } |
| |
| /* Tcl command to return a particular variable. */ |
| |
| /* Use: |
| */ |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_Var -- |
| * |
| * Returns information about GET or POST variables: |
| * |
| * var get foo ?default? |
| * var list foo |
| * var names |
| * var number |
| * var all |
| * |
| * Results: |
| * A standard Tcl result. |
| * |
| * Side Effects: |
| * None. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER ( Rivet_Var ) |
| { |
| rivet_thread_private* private; |
| const char* cmd; |
| char* command; |
| Tcl_Obj* result = NULL; |
| int source; |
| register const char *p; |
| |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::var,::rivet::var_post,::rivet::var_qs") |
| if (objc < 2 || objc > 4) |
| { |
| Tcl_WrongNumArgs(interp, 1, objv, |
| "(get varname ?default?|list varname|exists varname|names" |
| "|number|all)"); |
| return TCL_ERROR; |
| } |
| cmd = Tcl_GetString(objv[0]); |
| command = Tcl_GetString(objv[1]); |
| result = Tcl_NewObj(); |
| |
| /* determine if var_qs, var_post or var was called */ |
| |
| /* first of all we have to skip the namespace string at the beginning of the command: |
| * |
| * This fragment of code is taken from tcl 8.6.6 (tclNamesp.c) and it's part of the |
| * function implementing Tcl "namespace tail", as such it should be authoritative |
| * regarding the stripping of the namespace from a FQ command name |
| */ |
| |
| for (p = cmd; *p != '\0'; p++) { |
| /* empty body */ |
| } |
| |
| while (--p > cmd) { |
| if ((*p == ':') && (*(p-1) == ':')) { |
| p++; /* Just after the last "::" */ |
| break; |
| } |
| } |
| cmd = p; |
| |
| if (!strcmp(cmd, "var_qs")) source = VAR_SRC_QUERYSTRING; |
| else if (!strcmp(cmd, "var_post")) source = VAR_SRC_POST; |
| else source = VAR_SRC_ALL; |
| |
| if (!strcmp(command, "get")) |
| { |
| char *key = NULL; |
| char *deflt = NULL; |
| if (objc != 3 && objc != 4) |
| { |
| Tcl_WrongNumArgs(interp, 2, objv, "variablename ?defaultval?"); |
| return TCL_ERROR; |
| } |
| key = Tcl_GetStringFromObj(objv[2], NULL); |
| if (objc == 4) |
| { |
| deflt = Tcl_GetString(objv[3]); |
| } |
| |
| if (TclWeb_GetVar(result, key, source, private->req) != TCL_OK) |
| { |
| if (deflt == NULL) { |
| Tcl_SetStringObj(result, "", -1); |
| } else { |
| Tcl_SetStringObj(result, deflt, -1); |
| } |
| } |
| } else if(!strcmp(command, "exists")) { |
| char *key; |
| if (objc != 3) |
| { |
| Tcl_WrongNumArgs(interp, 2, objv, "variablename"); |
| return TCL_ERROR; |
| } |
| key = Tcl_GetString(objv[2]); |
| |
| TclWeb_VarExists(result, key, source, private->req); |
| } else if(!strcmp(command, "list")) { |
| char *key; |
| if (objc != 3) |
| { |
| Tcl_WrongNumArgs(interp, 2, objv, "variablename"); |
| return TCL_ERROR; |
| } |
| key = Tcl_GetStringFromObj(objv[2], NULL); |
| |
| if (TclWeb_GetVarAsList(result, key, source, private->req) != TCL_OK) |
| { |
| result = Tcl_NewStringObj("", -1); |
| } |
| } else if(!strcmp(command, "names")) { |
| if (objc != 2) |
| { |
| Tcl_WrongNumArgs(interp, 2, objv, NULL); |
| return TCL_ERROR; |
| } |
| |
| if (TclWeb_GetVarNames(result, source, private->req) != TCL_OK) |
| { |
| result = Tcl_NewStringObj("", -1); |
| } |
| } else if(!strcmp(command, "number")) { |
| if (objc != 2) |
| { |
| Tcl_WrongNumArgs(interp, 2, objv, NULL); |
| return TCL_ERROR; |
| } |
| |
| TclWeb_VarNumber(result, source, private->req); |
| } else if(!strcmp(command, "all")) { |
| if (objc != 2) |
| { |
| Tcl_WrongNumArgs(interp, 2, objv, NULL); |
| return TCL_ERROR; |
| } |
| if (TclWeb_GetAllVars(result, source, private->req) != TCL_OK) |
| { |
| result = Tcl_NewStringObj("", -1); |
| } |
| } else { |
| /* bad command */ |
| Tcl_AppendResult(interp, "bad option: must be one of ", |
| "'get, list, names, number, all'", NULL); |
| return TCL_ERROR; |
| } |
| Tcl_SetObjResult(interp, result); |
| |
| return TCL_OK; |
| } |
| |
| /* |
| */ |
| |
| static int append_key_callback (void *data, const char *key, const char *val) |
| { |
| Tcl_Obj *list = data; |
| |
| Tcl_ListObjAppendElement (NULL, list, Tcl_NewStringObj (key, -1)); |
| return 1; |
| } |
| |
| static int |
| append_key_value_callback (void *data, const char *key, const char *val) |
| { |
| Tcl_Obj *list = data; |
| |
| Tcl_ListObjAppendElement (NULL, list, Tcl_NewStringObj (key, -1)); |
| Tcl_ListObjAppendElement (NULL, list, Tcl_NewStringObj (val, -1)); |
| return 1; |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_ApacheTable -- |
| * |
| * Deals with Rivet key-value tables in the request structure |
| * |
| * apache_table get tablename key |
| * apache_table set tablename key value |
| * apache_table set tablename list |
| * apache_table exists tablename key |
| * apache_table unset tablename key |
| * apache_table names tablename |
| * apache_table array_get tablename |
| * apache_table clear tablename |
| * |
| * Table names can be "notes", "headers_in", "headers_out", |
| * "err_headers_out", and "subprocess_env". |
| * |
| * Results: |
| * A standard Tcl result. |
| * |
| * Side Effects: |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_ApacheTable ) |
| { |
| apr_table_t *table = NULL; |
| int subcommandindex; |
| |
| static CONST84 char *SubCommand[] = { |
| "get", |
| "set", |
| "exists", |
| "unset", |
| "names", |
| "array_get", |
| "clear", |
| NULL |
| }; |
| |
| enum subcommand { |
| SUB_GET, |
| SUB_SET, |
| SUB_EXISTS, |
| SUB_UNSET, |
| SUB_NAMES, |
| SUB_ARRAY_GET, |
| SUB_CLEAR |
| }; |
| |
| static CONST84 char *tableNames[] = { |
| "notes", |
| "headers_in", |
| "headers_out", |
| "err_headers_out", |
| "subprocess_env", |
| NULL |
| }; |
| |
| int tableindex; |
| |
| enum tablename { |
| TABLE_NOTES, |
| TABLE_HEADERS_IN, |
| TABLE_HEADERS_OUT, |
| TABLE_ERR_HEADERS_OUT, |
| TABLE_SUBPROCESS_ENV |
| }; |
| |
| rivet_thread_private* private; |
| |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::apache_table") |
| |
| if ((objc < 3) || (objc > 5)) { |
| Tcl_WrongNumArgs(interp, 1, objv, "option tablename ?args?"); |
| return TCL_ERROR; |
| } |
| |
| if (Tcl_GetIndexFromObj(interp, objv[1], SubCommand, |
| "get|set|unset|list", |
| 0, &subcommandindex) == TCL_ERROR) { |
| return TCL_ERROR; |
| } |
| |
| if (Tcl_GetIndexFromObj (interp, objv[2], tableNames, |
| "notes|headers_in|headers_out|err_header_out|subprocess_env", |
| 0, &tableindex) == TCL_ERROR) { |
| return TCL_ERROR; |
| } |
| |
| switch ((enum tablename)tableindex) |
| { |
| case TABLE_NOTES: { |
| table = private->r->notes; |
| break; |
| } |
| |
| case TABLE_HEADERS_IN: { |
| table = private->r->headers_in; |
| break; |
| } |
| |
| case TABLE_HEADERS_OUT: { |
| table = private->r->headers_out; |
| break; |
| } |
| |
| case TABLE_ERR_HEADERS_OUT: { |
| table = private->r->err_headers_out; |
| break; |
| } |
| |
| case TABLE_SUBPROCESS_ENV: { |
| table = private->r->subprocess_env; |
| break; |
| } |
| } |
| |
| switch ((enum subcommand)subcommandindex) |
| { |
| case SUB_GET: { |
| const char *key; |
| const char *value; |
| |
| if (objc != 4) { |
| Tcl_WrongNumArgs(interp, 2, objv, "tablename key"); |
| return TCL_ERROR; |
| } |
| key = Tcl_GetString (objv[3]); |
| value = apr_table_get (table, key); |
| |
| if (value != NULL) { |
| Tcl_SetObjResult (interp, Tcl_NewStringObj (value, -1)); |
| } |
| break; |
| } |
| case SUB_EXISTS: { |
| const char *key; |
| const char *value; |
| |
| if (objc != 4) { |
| Tcl_WrongNumArgs(interp, 2, objv, "tablename key"); |
| return TCL_ERROR; |
| } |
| |
| key = Tcl_GetString (objv[3]); |
| value = apr_table_get (table, key); |
| |
| Tcl_SetObjResult (interp, Tcl_NewBooleanObj (value != NULL)); |
| break; |
| } |
| case SUB_SET: { |
| int i; |
| char *key; |
| char *value; |
| |
| if (objc == 4) { |
| int listObjc; |
| Tcl_Obj **listObjv; |
| |
| if (Tcl_ListObjGetElements (interp, objv[3], &listObjc, &listObjv) == TCL_ERROR) { |
| return TCL_ERROR; |
| } |
| |
| if (listObjc % 2 == 1) { |
| Tcl_SetObjResult (interp, Tcl_NewStringObj ("list must have even number of elements", -1)); |
| return TCL_ERROR; |
| } |
| |
| for (i = 0; i < listObjc; i += 2) { |
| apr_table_set (table, Tcl_GetString (listObjv[i]), Tcl_GetString (listObjv[i+1])); |
| } |
| |
| break; |
| } |
| |
| if (objc != 5) { |
| Tcl_WrongNumArgs(interp, 2, objv, "tablename key value"); |
| return TCL_ERROR; |
| } |
| |
| key = Tcl_GetString (objv[3]); |
| value = Tcl_GetString (objv[4]); |
| |
| apr_table_set (table, key, value); |
| break; |
| } |
| |
| case SUB_UNSET: { |
| char *key; |
| |
| if (objc != 4) { |
| Tcl_WrongNumArgs(interp, 2, objv, "tablename key"); |
| return TCL_ERROR; |
| } |
| |
| key = Tcl_GetString (objv[3]); |
| apr_table_unset (table, key); |
| break; |
| } |
| |
| case SUB_NAMES: { |
| Tcl_Obj *list = Tcl_NewObj (); |
| |
| apr_table_do(append_key_callback, (void*)list, table, NULL); |
| |
| Tcl_SetObjResult (interp, list); |
| break; |
| } |
| |
| case SUB_ARRAY_GET: { |
| Tcl_Obj *list = Tcl_NewObj (); |
| |
| apr_table_do(append_key_value_callback, (void*)list, table, NULL); |
| |
| Tcl_SetObjResult (interp, list); |
| break; |
| } |
| |
| case SUB_CLEAR: { |
| apr_table_clear (table); |
| } |
| } |
| |
| return TCL_OK; |
| } |
| |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_Upload -- |
| * |
| * Deals with file uploads (multipart/form-data): |
| * |
| * Results: |
| * A standard Tcl result. |
| * |
| * Side Effects: |
| * Has the potential to create files on the file system, or work |
| * with large amounts of data. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_Upload ) |
| { |
| char* varname = NULL; |
| int subcommandindex; |
| |
| /* ::rivet::upload subcommands must register |
| * |
| * - subcommand definition |
| * - subcommand integer progressive index |
| * - subcommand required (minimum) number of arguments |
| * |
| * +----------------------------------------+-------+ |
| * | argv[1] argv[2] argv[3] | argc | |
| * +----------------------------------------+-------+ |
| * | upload channel uploadname | 3 | |
| * | upload save uploadname filename | 4 | |
| * | upload data uploadname | 3 | |
| * | upload exists uploadname | 3 | |
| * | upload size uploadname | 3 | |
| * | upload type uploadname | 3 | |
| * | upload filename uploadname | 3 | |
| * | upload tempname uploadname | 3 | |
| * | upload names | 2 | |
| * +----------------------------------------+-------+ |
| * |
| * a subcommand first optional argument must be the name |
| * of an upload |
| */ |
| |
| static CONST84 char *SubCommand[] = { |
| "channel", |
| "save", |
| "data", |
| "exists", |
| "size", |
| "type", |
| "filename", |
| "tempname", |
| "names", |
| NULL |
| }; |
| |
| enum subcommand { |
| CHANNEL, |
| SAVE, |
| DATA, |
| EXISTS, |
| SIZE, |
| TYPE, |
| FILENAME, |
| TEMPNAME, |
| NAMES |
| }; |
| |
| static CONST84 int cmds_objc[] = { 3,4,3,3,3,3,3,3,2 }; |
| int expected_objc; |
| |
| rivet_thread_private* private; |
| |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::upload") |
| if (Tcl_GetIndexFromObj(interp, objv[1], SubCommand, |
| "channel|save|data|exists|size|type|filename|tempname|names", |
| 0, &subcommandindex) == TCL_ERROR) { |
| return TCL_ERROR; |
| } |
| |
| expected_objc = cmds_objc[subcommandindex]; |
| |
| if (objc != expected_objc) { |
| Tcl_Obj* infoobj = Tcl_NewStringObj("Wrong argument numbers: ",-1); |
| |
| Tcl_IncrRefCount(infoobj); |
| Tcl_AppendObjToObj(infoobj,Tcl_NewIntObj(expected_objc)); |
| Tcl_AppendStringsToObj(infoobj," arguments expected"); |
| Tcl_AppendObjToErrorInfo(interp, infoobj); |
| Tcl_DecrRefCount(infoobj); |
| |
| if (subcommandindex == SAVE) { |
| Tcl_WrongNumArgs(interp, 2, objv, "uploadname filename"); |
| } else { |
| Tcl_WrongNumArgs(interp, objc, objv, "uploadname"); |
| } |
| return TCL_ERROR; |
| } |
| |
| /* We check whether an upload with a given name exists */ |
| |
| if (objc >= 3) { |
| int tcl_status; |
| varname = Tcl_GetString(objv[2]); |
| |
| /* TclWeb_PrepareUpload calls ApacheUpload_find and returns |
| * TCL_OK if the named upload exists in the current request */ |
| tcl_status = TclWeb_PrepareUpload(varname, private->req); |
| |
| if (subcommandindex == EXISTS) { |
| Tcl_Obj* result = NULL; |
| int upload_prepared = 0; |
| |
| if (tcl_status == TCL_OK) upload_prepared = 1; |
| |
| result = Tcl_NewObj(); |
| Tcl_SetIntObj(result,upload_prepared); |
| Tcl_SetObjResult(interp, result); |
| return TCL_OK; |
| |
| } |
| |
| if (tcl_status != TCL_OK) |
| { |
| Tcl_AddErrorInfo(interp, "Unable to find the upload named '"); |
| Tcl_AppendObjToErrorInfo(interp,Tcl_NewStringObj(varname,-1)); |
| Tcl_AppendObjToErrorInfo(interp,Tcl_NewStringObj("'",-1)); |
| return TCL_ERROR; |
| } |
| } |
| |
| /* CHANNEL : get the upload channel name |
| * SAVE : save data to a specified filename |
| * DATA : get the uploaded data into a Tcl variable |
| * SIZE : uploaded data size |
| * TYPE : upload mimetype |
| * FILENAME : upload original filename |
| * TEMPNAME : temporary file where the upload is taking place |
| * NAMES : list of uploads |
| * |
| * the procedure shouldn't reach for the default case |
| */ |
| |
| switch ((enum subcommand)subcommandindex) |
| { |
| case CHANNEL: |
| return TclWeb_UploadChannel(varname, private->req); |
| case SAVE: |
| return TclWeb_UploadSave(varname, objv[3], private->req); |
| case DATA: |
| return TclWeb_UploadData(varname, private->req); |
| case SIZE: |
| return TclWeb_UploadSize(private->req); |
| case TYPE: |
| return TclWeb_UploadType(private->req); |
| case FILENAME: |
| return TclWeb_UploadFilename(private->req); |
| case TEMPNAME: |
| return TclWeb_UploadTempname(private->req); |
| case NAMES: |
| return TclWeb_UploadNames(private->req); |
| default: |
| Tcl_WrongNumArgs(interp, 1, objv,"Rivet internal error: inconsistent argument"); |
| } |
| return TCL_ERROR; |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_RawPost -- |
| * |
| * Returns the raw POST data. |
| * |
| * Results: |
| * The raw post data, or an empty string if there is none. |
| * |
| * Side Effects: |
| * None. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER ( Rivet_RawPost ) |
| { |
| char* data; |
| Tcl_Obj* retval; |
| rivet_thread_private* private; |
| |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::raw_post") |
| |
| data = TclWeb_GetRawPost(private->req); |
| |
| if (!data) { |
| data = ""; |
| } |
| retval = Tcl_NewStringObj(data, -1); |
| Tcl_SetObjResult(interp, retval); |
| return TCL_OK; |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_NoBody -- |
| * |
| * Tcl command to erase body, so that only header is returned. |
| * Necessary for 304 responses. |
| * |
| * Results: |
| * A standard Tcl return value. |
| * |
| * Side Effects: |
| * Eliminates any body returned in the HTTP response. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_NoBody ) |
| { |
| rivet_thread_private* private; |
| |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::no_body") |
| |
| if (private->req->content_sent == 1) { |
| Tcl_AddErrorInfo(interp, "Content already sent"); |
| return TCL_ERROR; |
| } |
| |
| private->req->content_sent = 1; |
| return TCL_OK; |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_AbortPageCmd -- |
| * |
| * Similar in purpose to PHP's "die" command, which halts all |
| * further output to the user. Like an "exit" for web pages, but |
| * without actually exiting the apache child. |
| * |
| * Results: |
| * A standard Tcl result. |
| * |
| * Side Effects: |
| * Flushes the standard (apache) output channel, and tells apache |
| * to stop sending data. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_AbortPageCmd ) |
| { |
| rivet_thread_private* private; |
| static char *errorMessage = "Page generation terminated by abort_page directive"; |
| |
| if (objc > 2) |
| { |
| Tcl_WrongNumArgs(interp, 1, objv, ""); |
| return TCL_ERROR; |
| } |
| |
| THREAD_PRIVATE_DATA(private) |
| |
| if (objc == 2) |
| { |
| char* cmd_arg = Tcl_GetStringFromObj(objv[1],NULL); |
| |
| if (strcmp(cmd_arg,"-aborting") == 0) |
| { |
| Tcl_SetObjResult (interp,Tcl_NewBooleanObj(private->page_aborting)); |
| return TCL_OK; |
| } |
| |
| if (strcmp(cmd_arg,"-exiting") == 0) |
| { |
| Tcl_SetObjResult (interp,Tcl_NewBooleanObj(private->thread_exit)); |
| return TCL_OK; |
| } |
| |
| /* |
| * we assume abort_code to be null, as abort_page shouldn't run twice while |
| * processing the same request |
| */ |
| |
| if (private->abort_code == NULL) |
| { |
| private->abort_code = objv[1]; |
| Tcl_IncrRefCount(private->abort_code); |
| } |
| } |
| |
| /* |
| * If page_aborting is true then this is the second call to abort_page |
| * processing the same request: we ignore it and return a normal |
| * completion code |
| */ |
| |
| if (private->page_aborting) |
| { |
| return TCL_OK; |
| } |
| |
| /* this is the first (and supposedly unique) abort_page call during this request */ |
| |
| /* we eleveta the page_aborting flag to the actual flag controlling the page abort execution. |
| * We still return the RIVET and ABORTPAGE_CODE, but internally |
| * its page_aborting that will drive the code execution after abort_page |
| */ |
| |
| private->page_aborting = 1; |
| |
| Tcl_AddErrorInfo (interp, errorMessage); |
| Tcl_SetErrorCode (interp, "RIVET", ABORTPAGE_CODE, errorMessage, (char *)NULL); |
| return TCL_ERROR; |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * Rivet_AbortCodeCmd -- |
| * |
| * Returns the abort code stored internally by passing a user defined parameter |
| * to the command 'abort_page'. |
| * |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| TCL_CMD_HEADER( Rivet_AbortCodeCmd ) |
| { |
| rivet_thread_private* private; |
| |
| THREAD_PRIVATE_DATA(private) |
| |
| if (private->abort_code != NULL) |
| { |
| Tcl_SetObjResult(interp,private->abort_code); |
| } |
| |
| return TCL_OK; |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_EnvCmd -- |
| * |
| * Loads a single environmental variable, to avoid the overhead |
| * of storing all of them when only one is needed. |
| * |
| * Results: |
| * A standard Tcl result. |
| * |
| * Side Effects: |
| * None. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_EnvCmd ) |
| { |
| char* key; |
| char* val; |
| rivet_thread_private* private; |
| |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::env") |
| |
| if( objc != 2 ) { |
| Tcl_WrongNumArgs( interp, 1, objv, "variable" ); |
| return TCL_ERROR; |
| } |
| |
| key = Tcl_GetStringFromObj( objv[1], NULL ); |
| |
| val = TclWeb_GetEnvVar( private, key ); |
| |
| Tcl_SetObjResult(interp, Tcl_NewStringObj( val, -1 ) ); |
| return TCL_OK; |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_ExitCmd -- |
| * |
| * Calls the MPM specific exit procedure. For a threaded MPM (such |
| * as 'worker') the procedure should cause a thread to exit, not the |
| * whole process with all its threads. In this case the procedure |
| * returns an TCL_ERROR code that has to be handled in mod_rivet so that |
| * the error is ignored and the request procedure interrupted. |
| * For a non threaded MPM (such as 'prefork') the single child process |
| * exits thus reproducing an ordinary 'exit' command. |
| * |
| * Result: |
| * |
| * TCL_ERROR |
| * |
| * Side Effects: |
| * |
| * - non threaded MPMs: the child process exits for good |
| * - threaded MPMs: the child process exits after all Tcl threads |
| * are told to exit |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_ExitCmd ) |
| { |
| int value; |
| rivet_thread_private* private; |
| char* errorMessage = "page generation interrupted by exit command"; |
| |
| if ((objc != 1) && (objc != 2)) { |
| Tcl_WrongNumArgs(interp, 1, objv, "?returnCode?"); |
| return TCL_ERROR; |
| } |
| |
| if (objc == 1) { |
| value = 0; |
| } else if (Tcl_GetIntFromObj(interp, objv[1], &value) != TCL_OK) { |
| return TCL_ERROR; |
| } |
| |
| THREAD_PRIVATE_DATA(private) |
| |
| private->page_aborting = 1; |
| private->abort_code = Tcl_NewDictObj(); |
| |
| /* The private->abort_code ref count is decremented before |
| * request processing terminates */ |
| |
| Tcl_IncrRefCount(private->abort_code); |
| |
| /* |
| * mod_rivet traps call to exit and offers a chance to handle them |
| * in the way we handle ::rivet::abort_page calls |
| */ |
| |
| Tcl_DictObjPut(interp,private->abort_code, |
| Tcl_NewStringObj("error_code",-1), |
| Tcl_NewStringObj("exit",-1)); |
| |
| Tcl_DictObjPut(interp,private->abort_code, |
| Tcl_NewStringObj("return_code",-1), |
| Tcl_NewIntObj(value)); |
| |
| private->thread_exit = 1; |
| private->exit_status = value; |
| |
| /* this call actually could never return for a non-threaded MPM bridge |
| * as it eventually will call Tcl_Exit |
| */ |
| Tcl_AddErrorInfo (interp, errorMessage); |
| Tcl_SetErrorCode (interp, "RIVET", THREAD_EXIT_CODE, errorMessage, (char *)NULL); |
| |
| return TCL_ERROR; |
| } |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_VirtualFilenameCmd -- |
| * |
| * Gets file according to its relationship with the request's |
| * root. (FIXME - check this). |
| * |
| * Results: |
| * A standard Tcl result. |
| * |
| * Side Effects: |
| * None. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_VirtualFilenameCmd ) |
| { |
| rivet_thread_private* private; |
| char* filename; |
| char* virtual; |
| |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::virtual_filename") |
| if( objc != 2 ) { |
| Tcl_WrongNumArgs( interp, 1, objv, "filename" ); |
| return TCL_ERROR; |
| } |
| |
| virtual = Tcl_GetStringFromObj( objv[1], NULL ); |
| filename = TclWeb_GetVirtualFile( private->req, virtual ); |
| |
| Tcl_SetObjResult(interp, Tcl_NewStringObj( filename, -1 ) ); |
| return TCL_OK; |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_Inspect -- |
| * |
| * Rivet configuration introspection. Command '::rivet::inspect' |
| * returns a dictionary of configuration data: |
| * |
| * Results: |
| * A dictionary or parameter value |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_InspectCmd ) |
| { |
| rivet_thread_private* private; |
| rivet_server_conf* rsc; |
| int status = TCL_OK; |
| |
| THREAD_PRIVATE_DATA(private) |
| |
| if (objc == 1) |
| { |
| Tcl_Obj* dictObj; |
| |
| CHECK_REQUEST_REC(private,"::rivet::inspect") |
| rsc = Rivet_GetConf(private->r); |
| dictObj = Rivet_BuildConfDictionary(interp,rsc); |
| if (dictObj != NULL) { |
| Tcl_IncrRefCount(dictObj); |
| Tcl_SetObjResult(interp,dictObj); |
| Tcl_DecrRefCount(dictObj); |
| } else { |
| status = TCL_ERROR; |
| } |
| |
| } |
| else if (objc == 2) |
| { |
| Tcl_Obj* par_name = objv[1]; |
| char* cmd_arg; |
| |
| Tcl_IncrRefCount(par_name); |
| cmd_arg = Tcl_GetStringFromObj(par_name,NULL); |
| |
| if (STRNEQU(cmd_arg,"script")) |
| { |
| Tcl_Obj* cmd; |
| |
| if (private != NULL) |
| { |
| if (private->r != NULL) |
| { |
| Tcl_SetObjResult(interp,Tcl_NewStringObj(private->r->filename,-1)); |
| return TCL_OK; |
| } |
| } |
| |
| cmd = Tcl_NewStringObj("info script",-1); |
| |
| Tcl_IncrRefCount(cmd); |
| status = Tcl_EvalObjEx(interp,cmd,TCL_EVAL_DIRECT); |
| Tcl_DecrRefCount(cmd); |
| |
| } |
| else if (STRNEQU(cmd_arg,"-all")) |
| { |
| Tcl_Obj* dictObj; |
| |
| CHECK_REQUEST_REC(private,"::rivet::inspect -all") |
| rsc = Rivet_GetConf(private->r); |
| dictObj = Rivet_CurrentConfDict(interp,rsc); |
| Tcl_IncrRefCount(dictObj); |
| Tcl_SetObjResult(interp,dictObj); |
| Tcl_DecrRefCount(dictObj); |
| |
| } |
| else if (STRNEQU(cmd_arg,"server")) |
| { |
| Tcl_Obj* dictObj; |
| server_rec* srec; |
| |
| if (private == NULL) { |
| srec = module_globals->server; |
| } else { |
| |
| if (private->r == NULL) { |
| srec = module_globals->server; |
| } else { |
| srec = private->r->server; |
| } |
| |
| } |
| |
| /* we read data from the server_rec */ |
| |
| dictObj = Rivet_CurrentServerRec(interp,srec); |
| Tcl_IncrRefCount(dictObj); |
| Tcl_SetObjResult(interp,dictObj); |
| Tcl_DecrRefCount(dictObj); |
| |
| } |
| else if (STRNEQU(cmd_arg,"exit")) |
| { |
| Tcl_Obj* exit_flag; |
| |
| CHECK_REQUEST_REC(private,"::rivet::inspect") |
| /* thread exit status flag */ |
| exit_flag = Tcl_NewIntObj(private->thread_exit); |
| |
| Tcl_IncrRefCount(exit_flag); |
| Tcl_SetObjResult(interp,exit_flag); |
| Tcl_DecrRefCount(exit_flag); |
| } |
| else |
| { |
| Tcl_Obj* par_value = NULL; |
| |
| //CHECK_REQUEST_REC(private,"::rivet::inspect") |
| if (private == NULL) { |
| rsc = RIVET_SERVER_CONF(module_globals->server->module_config); |
| } else { |
| |
| if (private->r == NULL) { |
| rsc = private->running_conf; |
| } else { |
| rsc = Rivet_GetConf(private->r); |
| } |
| |
| } |
| |
| par_value = Rivet_ReadConfParameter(interp,rsc,par_name); |
| if (par_value == NULL) |
| { |
| Tcl_Obj* errorinfo = Tcl_NewStringObj("mod_rivet internal error invalid argument: ",-1); |
| |
| Tcl_IncrRefCount(errorinfo); |
| Tcl_AppendObjToObj(errorinfo,par_name); |
| Tcl_AppendObjToErrorInfo(interp,errorinfo); |
| Tcl_DecrRefCount(errorinfo); |
| status = TCL_ERROR; |
| } |
| else |
| { |
| Tcl_IncrRefCount(par_value); |
| Tcl_SetObjResult(interp,par_value); |
| Tcl_DecrRefCount(par_value); |
| } |
| |
| } |
| |
| Tcl_DecrRefCount(par_name); |
| } |
| else |
| { |
| Tcl_WrongNumArgs( interp, 1, objv, "?server | dir | user? ?parameter name?" ); |
| status = TCL_ERROR; |
| } |
| return status; |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_LogError -- |
| * |
| * Log an error from Rivet |
| * |
| * log_error priority message |
| * |
| * priority can be one of "emerg", "alert", "crit", "err", |
| * "warning", "notice", "info", "debug" |
| * |
| * Results: |
| * A message is logged to the Apache error log. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_LogErrorCmd ) |
| { |
| char *message = NULL; |
| |
| server_rec *serverRec; |
| |
| int loglevelindex; |
| int apLogLevel = 0; |
| |
| static CONST84 char *logLevel[] = { |
| "emerg", |
| "alert", |
| "crit", |
| "err", |
| "warning", |
| "notice", |
| "info", |
| "debug", |
| NULL |
| }; |
| |
| enum loglevel { |
| EMERG, |
| ALERT, |
| CRIT, |
| ERR, |
| WARNING, |
| NOTICE, |
| INFO, |
| DEBUG |
| }; |
| |
| rivet_thread_private* private; |
| |
| if( objc != 3 ) { |
| Tcl_WrongNumArgs( interp, 1, objv, "loglevel message" ); |
| return TCL_ERROR; |
| } |
| |
| THREAD_PRIVATE_DATA(private) |
| message = Tcl_GetString (objv[2]); |
| if (Tcl_GetIndexFromObj(interp, objv[1], logLevel, |
| "emerg|alert|crit|err|warning|notice|info|debug", |
| 0, &loglevelindex) == TCL_ERROR) { |
| return TCL_ERROR; |
| } |
| |
| switch ((enum loglevel)loglevelindex) |
| { |
| case EMERG: |
| apLogLevel = APLOG_EMERG; |
| break; |
| |
| case ALERT: |
| apLogLevel = APLOG_ALERT; |
| break; |
| |
| case CRIT: |
| apLogLevel = APLOG_CRIT; |
| break; |
| |
| case ERR: |
| apLogLevel = APLOG_ERR; |
| break; |
| |
| case WARNING: |
| apLogLevel = APLOG_WARNING; |
| break; |
| |
| case NOTICE: |
| apLogLevel = APLOG_NOTICE; |
| break; |
| |
| case INFO: |
| apLogLevel = APLOG_INFO; |
| break; |
| |
| case DEBUG: |
| apLogLevel = APLOG_DEBUG; |
| break; |
| } |
| |
| /* if we are serving a page, we know our server, |
| * else send null for server |
| */ |
| serverRec = ((private == NULL) || (private->r == NULL)) ? module_globals->server : private->r->server; |
| |
| ap_log_error (APLOG_MARK, apLogLevel, 0, serverRec, "%s", message); |
| return TCL_OK; |
| } |
| |
| #undef TESTPANIC |
| #ifdef TESTPANIC |
| /* |
| *---------------------------------------------------------------------- |
| * |
| * TestpanicCmd -- |
| * |
| * Calls the panic routine. |
| * |
| * Results: |
| * Always returns TCL_OK. |
| * |
| * Side effects: |
| * May exit application. |
| * |
| *---------------------------------------------------------------------- |
| */ |
| |
| static int |
| TestpanicCmd(dummy, interp, argc, argv) |
| ClientData dummy; /* Not used. */ |
| Tcl_Interp *interp; /* Current interpreter. */ |
| int argc; /* Number of arguments. */ |
| CONST char **argv; /* Argument strings. */ |
| { |
| CONST char *argString; |
| |
| /* |
| * Put the arguments into a var args structure |
| * Append all of the arguments together separated by spaces |
| */ |
| |
| argString = Tcl_Merge(argc-1, argv+1); |
| panic("%s",argString); |
| ckfree((char *)argString); |
| |
| return TCL_OK; |
| } |
| #endif /* TESTPANIC */ |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_UrlScript -- |
| * |
| * Builds the full URL referenced script composed by before_script, |
| * url referenced script and after_script. This command should not |
| * be called by ordinary application development |
| * |
| * Results: |
| * Standard Tcl result. |
| * |
| * Side Effects: |
| * returns a Tcl script. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_UrlScript ) |
| { |
| rivet_thread_private* private; |
| char* cache_key; |
| rivet_thread_interp* rivet_interp; |
| Tcl_HashEntry* entry = NULL; |
| Tcl_Obj* script = NULL; |
| int result; |
| unsigned int user_conf; |
| time_t ctime; |
| time_t mtime; |
| |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::url_script") |
| |
| user_conf = IS_USER_CONF(private->running_conf); |
| |
| rivet_interp = RIVET_PEEK_INTERP(private,private->running_conf); |
| ctime = private->r->finfo.ctime; |
| mtime = private->r->finfo.mtime; |
| cache_key = RivetCache_MakeKey(private->pool,private->r->filename,ctime,mtime,user_conf,1); |
| |
| entry = RivetCache_EntryLookup (rivet_interp,cache_key); |
| if (entry == NULL) |
| { |
| Tcl_Interp* interp; |
| |
| interp = rivet_interp->interp; |
| |
| script = Tcl_NewObj(); |
| Tcl_IncrRefCount(script); |
| /* |
| * We check whether we are dealing with a pure Tcl script or a Rivet template. |
| * Actually this check is done only if we are processing a toplevel file, every nested |
| * file (files included through the 'parse' command) is treated as a template. |
| */ |
| |
| if (Rivet_CheckType(private->r) == RIVET_TEMPLATE) |
| { |
| result = Rivet_GetRivetFile(private->r->filename,script,interp); |
| |
| } else { |
| |
| /* It's a plain Tcl file */ |
| result = Rivet_GetTclFile(private->r->filename, script, interp); |
| |
| } |
| |
| if (result == TCL_OK) |
| { |
| /* let's check the cache for free entries */ |
| |
| if (rivet_interp->cache_free > 0) |
| { |
| int isNew; |
| Tcl_HashEntry* entry; |
| |
| entry = RivetCache_CreateEntry (rivet_interp,cache_key,&isNew); |
| |
| /* Sanity check: we are here for this reason */ |
| |
| ap_assert(isNew == 1); |
| |
| /* we proceed storing the script in the cache */ |
| |
| RivetCache_StoreScript(rivet_interp,entry,script); |
| } |
| else if ((rivet_interp->flags & RIVET_CACHE_FULL) == 0) |
| { |
| rivet_interp->flags |= RIVET_CACHE_FULL; |
| ap_log_error (APLOG_MARK, APLOG_NOTICE, APR_EGENERAL,private->r->server,"%s %s (%s),", |
| "Rivet cache full when serving ", |
| private->r->filename, |
| private->r->server->server_hostname); |
| } |
| } |
| Tcl_SetObjResult(rivet_interp->interp, script); |
| Tcl_DecrRefCount(script); |
| |
| } else { |
| Tcl_SetObjResult(rivet_interp->interp,RivetCache_FetchScript(entry)); |
| } |
| |
| return TCL_OK; |
| } |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * Rivet_GetThreadId -- |
| * |
| * With a threaded bridge (worker and lazy) command [pid] on Unix systems return |
| * the same process id when called from all threads running within that process. |
| * For any debugging reason this command returns a unique thread identification |
| * that can, for instance, be matched with the thread id (tid) information |
| * in the error log file. |
| * |
| * Arguments: |
| * |
| * Output format: -hex (default) | -decimal |
| * |
| * Results: |
| * |
| * Tcl object with a string representation of the thread id |
| * |
| * Side effects: |
| * |
| * None |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| #define SMALL_BUFFER_SIZE 32 |
| |
| TCL_CMD_HEADER( Rivet_GetThreadId ) |
| { |
| char buff[SMALL_BUFFER_SIZE]; |
| apr_os_thread_t threadid; |
| char* format_hex = "0x%8.8lx"; |
| char* format_dec = "%ld"; |
| char* output_format = format_hex; |
| int wrong_args = false; |
| //rivet_thread_interp* interp_obj; |
| |
| // interp_obj = RIVET_PEEK_INTERP(private,private->running_conf); |
| if (objc == 2) |
| { |
| Tcl_Obj* argobj = objv[1]; |
| char* cmd_arg; |
| |
| Tcl_IncrRefCount(argobj); |
| cmd_arg = Tcl_GetStringFromObj(argobj,NULL); |
| if (STRNEQU(cmd_arg,"-hex")) |
| { |
| output_format = format_hex; |
| } |
| else if (STRNEQU(cmd_arg,"-decimal")) |
| { |
| output_format = format_dec; |
| } |
| else |
| { |
| wrong_args = true; |
| } |
| Tcl_DecrRefCount(argobj); |
| |
| if (wrong_args) |
| { |
| Tcl_AddObjErrorInfo(interp,"Wrong argument: it must be -decimal | -hex", -1); |
| return TCL_ERROR; |
| } |
| } |
| else if (objc > 2) |
| { |
| Tcl_WrongNumArgs(interp,1,objv,"-decimal | -hex" ); |
| return TCL_ERROR; |
| } |
| |
| /* Let's get the thread id and return it in the requested format */ |
| |
| threadid = apr_os_thread_current(); |
| snprintf(buff,SMALL_BUFFER_SIZE,output_format,threadid); |
| |
| Tcl_SetObjResult(interp,Tcl_NewStringObj(buff,strlen(buff))); |
| return TCL_OK; |
| } |
| |
| #ifdef RIVET_DEBUG_BUILD |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_CacheContent -- |
| * |
| * Dumping in a list the cache content. For debugging purposes. |
| * This command will be placed within conditional compilation and |
| * documented within the 'Rivet Internals' section of the manual |
| * |
| * Results: |
| * |
| * a Tcl list of the keys in the interpreter cache |
| * |
| * Side Effects: |
| * |
| * none |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| TCL_CMD_HEADER( Rivet_CacheContent ) |
| { |
| Tcl_Obj* entry_list; |
| rivet_thread_private* private; |
| rivet_thread_interp* rivet_interp; |
| int ep; |
| THREAD_PRIVATE_DATA(private) |
| CHECK_REQUEST_REC(private,"::rivet::cache_content") |
| |
| rivet_interp = RIVET_PEEK_INTERP(private,private->running_conf); |
| interp = rivet_interp->interp; |
| |
| entry_list = Tcl_NewObj(); |
| Tcl_IncrRefCount(entry_list); |
| |
| ep = rivet_interp->cache_size - 1; |
| |
| while ((ep >= 0) && (rivet_interp->objCacheList[ep])) |
| { |
| int tcl_status; |
| |
| tcl_status = Tcl_ListObjAppendElement(interp,entry_list,Tcl_NewStringObj(rivet_interp->objCacheList[ep],-1)); |
| |
| if (tcl_status != TCL_OK) { |
| return tcl_status; |
| } |
| |
| ep--; |
| } |
| Tcl_SetObjResult(interp,entry_list); |
| Tcl_DecrRefCount(entry_list); |
| return TCL_OK; |
| } |
| |
| #endif /* RIVET_DEBUG_BUILD */ |
| |
| /* |
| *----------------------------------------------------------------------------- |
| * |
| * Rivet_InitCore -- |
| * |
| * Creates the core rivet commands. |
| * |
| * Results: |
| * A standard Tcl result. |
| * |
| * Side Effects: |
| * Creates new commands. |
| * |
| *----------------------------------------------------------------------------- |
| */ |
| |
| DLLEXPORT int |
| Rivet_InitCore(Tcl_Interp *interp,rivet_thread_private* private) |
| { |
| rivet_server_conf* server_conf; |
| |
| RIVET_OBJ_CMD ("makeurl",Rivet_MakeURL,private); |
| RIVET_OBJ_CMD ("headers",Rivet_Headers,private); |
| RIVET_OBJ_CMD ("load_env",Rivet_LoadEnv,private); |
| RIVET_OBJ_CMD ("load_headers",Rivet_LoadHeaders,private); |
| RIVET_OBJ_CMD ("var",Rivet_Var,private); |
| RIVET_OBJ_CMD ("abort_page",Rivet_AbortPageCmd,private); |
| RIVET_OBJ_CMD ("abort_code", Rivet_AbortCodeCmd,private); |
| RIVET_OBJ_CMD ("virtual_filename",Rivet_VirtualFilenameCmd,private); |
| RIVET_OBJ_CMD ("apache_table",Rivet_ApacheTable,private); |
| RIVET_OBJ_CMD ("var_qs",Rivet_Var,private); |
| RIVET_OBJ_CMD ("var_post",Rivet_Var,private); |
| RIVET_OBJ_CMD ("raw_post",Rivet_RawPost,private); |
| RIVET_OBJ_CMD ("upload",Rivet_Upload,private); |
| RIVET_OBJ_CMD ("include",Rivet_Include,private); |
| RIVET_OBJ_CMD ("parse",Rivet_Parse,private); |
| RIVET_OBJ_CMD ("no_body",Rivet_NoBody,private); |
| RIVET_OBJ_CMD ("env",Rivet_EnvCmd,private); |
| RIVET_OBJ_CMD ("apache_log_error",Rivet_LogErrorCmd,private); |
| RIVET_OBJ_CMD ("inspect",Rivet_InspectCmd,private); |
| RIVET_OBJ_CMD ("exit",Rivet_ExitCmd,private); |
| RIVET_OBJ_CMD ("url_script",Rivet_UrlScript,private); |
| RIVET_OBJ_CMD ("thread_id",Rivet_GetThreadId,private); |
| |
| #ifdef RIVET_DEBUG_BUILD |
| /* code compiled conditionally for debugging */ |
| RIVET_OBJ_CMD ("cache_content",Rivet_CacheContent,private); |
| #endif |
| #ifdef TESTPANIC |
| RIVET_OBJ_CMD ("testpanic",TestpanicCmd,private); |
| #endif |
| |
| /* |
| * we don't need to check the virtual host server conf |
| * stored in 'private' in order to determine if we are |
| * exporting the command set, as this flag is meaningful |
| * at the global level |
| */ |
| server_conf = RIVET_SERVER_CONF(module_globals->server->module_config); |
| |
| if (server_conf->export_rivet_ns) |
| { |
| rivet_interp_globals *globals = NULL; |
| Tcl_Namespace *rivet_ns; |
| |
| globals = Tcl_GetAssocData(interp, "rivet", NULL); |
| rivet_ns = globals->rivet_ns; |
| |
| RIVET_EXPORT_CMD(interp,rivet_ns,"makeurl"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"headers"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"load_env"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"load_headers"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"var"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"abort_page"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"abort_code"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"virtual_filename"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"apache_table"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"var_qs"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"var_post"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"raw_post"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"upload"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"include"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"parse"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"no_body"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"env"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"apache_log_error"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"inspect"); |
| RIVET_EXPORT_CMD(interp,rivet_ns,"thread_id"); |
| // ::rivet::exit is not exported |
| } |
| |
| return TCL_OK; |
| } |