Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -773,11 +773,25 @@ ** case, invoke this routine with useAttach as 1. */ void db_open_config(int useAttach){ char *zDbName; const char *zHome; - if( g.configOpen ) return; + if( g.configOpen ){ + if( useAttach==g.useAttach ) return; + if( g.useAttach ){ + db_detach("configdb"); + g.useAttach = 0; + }else if( g.dbConfig ){ + sqlite3_close(g.dbConfig); + g.dbConfig = 0; + g.zConfigDbType = 0; + }else if( g.db ){ + sqlite3_close(g.db); + g.db = 0; + g.zMainDbType = 0; + } + } #if defined(_WIN32) zHome = fossil_getenv("LOCALAPPDATA"); if( zHome==0 ){ zHome = fossil_getenv("APPDATA"); if( zHome==0 ){ Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -198,10 +198,11 @@ char zCsrfToken[12]; /* Value of the anti-CSRF token */ int okCsrf; /* Anti-CSRF token is present and valid */ int parseCnt[10]; /* Counts of artifacts parsed */ FILE *fDebug; /* Write debug information here, if the file exists */ + int fNoThHook; /* Disable all TH1 command/webpage hooks */ int thTrace; /* True to enable TH1 debugging output */ Blob thLog; /* Text of the TH1 debugging output */ int isHome; /* True if rendering the "home" page */ @@ -513,10 +514,11 @@ g.fSystemTrace = find_option("systemtrace", 0, 0)!=0; g.fSshTrace = find_option("sshtrace", 0, 0)!=0; if( g.fSqlTrace ) g.fSqlStats = 1; g.fSqlPrint = find_option("sqlprint", 0, 0)!=0; g.fHttpTrace = find_option("httptrace", 0, 0)!=0; + g.fNoThHook = find_option("no-th-hook", 0, 0)!=0; g.zLogin = find_option("user", "U", 1); g.zSSLIdentity = find_option("ssl-identity", 0, 1); if( find_option("utc",0,0) ) g.fTimeFormat = 1; if( find_option("localtime",0,0) ) g.fTimeFormat = 2; if( zChdir && chdir(zChdir) ){ @@ -536,13 +538,26 @@ } zCmdName = g.argv[1]; } rc = name_search(zCmdName, aCommand, count(aCommand), &idx); if( rc==1 ){ - fossil_fatal("%s: unknown command: %s\n" - "%s: use \"help\" for more information\n", - g.argv[0], zCmdName, g.argv[0]); + if( !g.isHTTP && !g.fNoThHook ){ + rc = Th_CommandHook(zCmdName, 0); + }else{ + rc = TH_OK; + } + if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ + if( rc==TH_OK || rc==TH_RETURN ){ + fossil_fatal("%s: unknown command: %s\n" + "%s: use \"help\" for more information\n", + g.argv[0], zCmdName, g.argv[0]); + } + if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ + Th_CommandNotify(zCmdName, 0); + } + } + fossil_exit(0); }else if( rc==2 ){ int i, n; Blob couldbe; blob_zero(&couldbe); n = strlen(zCmdName); @@ -556,11 +571,36 @@ "%s: use \"help\" for more information\n", g.argv[0], zCmdName, g.argv[0], blob_str(&couldbe), g.argv[0]); fossil_exit(1); } atexit( fossil_atexit ); - aCommand[idx].xFunc(); + /* + ** The TH1 return codes from the hook will be handled as follows: + ** + ** TH_OK: The xFunc() and the TH1 notification will both be executed. + ** + ** TH_ERROR: The xFunc() and the TH1 notification will both be skipped. + ** + ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped. + ** + ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be + ** skipped. + ** + ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be + ** executed. + */ + if( !g.isHTTP && !g.fNoThHook ){ + rc = Th_CommandHook(aCommand[idx].zName, aCommand[idx].cmdFlags); + }else{ + rc = TH_OK; + } + if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ + if( rc==TH_OK || rc==TH_RETURN ){ aCommand[idx].xFunc(); } + if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ + Th_CommandNotify(aCommand[idx].zName, aCommand[idx].cmdFlags); + } + } fossil_exit(0); /*NOT_REACHED*/ return 0; } @@ -1508,11 +1548,37 @@ @ <h1>Server Configuration Error</h1> @ <p>The database schema on the server is out-of-date. Please ask @ the administrator to run <b>fossil rebuild</b>.</p> } }else{ - aWebpage[idx].xFunc(); + /* + ** The TH1 return codes from the hook will be handled as follows: + ** + ** TH_OK: The xFunc() and the TH1 notification will both be executed. + ** + ** TH_ERROR: The xFunc() and the TH1 notification will both be skipped. + ** + ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped. + ** + ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be + ** skipped. + ** + ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be + ** executed. + */ + int rc; + if( !g.isHTTP && !g.fNoThHook ){ + rc = Th_WebpageHook(aWebpage[idx].zName, aWebpage[idx].cmdFlags); + }else{ + rc = TH_OK; + } + if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ + if( rc==TH_OK || rc==TH_RETURN ){ aWebpage[idx].xFunc(); } + if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ + Th_WebpageNotify(aWebpage[idx].zName, aWebpage[idx].cmdFlags); + } + } } /* Return the result. */ cgi_reply(); Index: src/th_main.c ================================================================== --- src/th_main.c +++ src/th_main.c @@ -20,10 +20,19 @@ */ #include "config.h" #include "th_main.h" #include "sqlite3.h" +/* +** These are the "well-known" TH1 error messages that occur when no hook is +** registered to be called prior to executing a command or processing a web +** page, respectively. If one of these errors is seen, it will not be sent +** or displayed to the remote user or local interactive user, respectively. +*/ +#define NO_COMMAND_HOOK_ERROR "no such command: command_hook" +#define NO_WEBPAGE_HOOK_ERROR "no such command: webpage_hook" + /* ** Global variable counting the number of outstanding calls to malloc() ** made by the th1 implementation. This is used to catch memory leaks ** in the interpreter. Obviously, it also means th1 is not threadsafe. */ @@ -856,10 +865,136 @@ if( z[0]!='>' ) return 0; i += 2; } return i; } + +/* +** This function is called by Fossil just prior to dispatching a command. +** Returning a value other than TH_OK from this function (i.e. via an +** evaluated script raising an error or calling [break]/[continue]) will +** cause the actual command execution to be skipped. +*/ +int Th_CommandHook( + const char *zName, + char cmdFlags +){ + int rc = TH_OK; + Th_FossilInit(1, 1); + Th_Store("cmd_name", zName); + Th_StoreInt("cmd_flags", cmdFlags); + rc = Th_Eval(g.interp, 0, "command_hook", -1); + if( rc==TH_ERROR ){ + int nResult = 0; + char *zResult = (char*)Th_GetResult(g.interp, &nResult); + /* + ** Make sure that the TH1 script error was not caused by a "missing" + ** command hook handler as that is not actually an error condition. + */ + if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){ + sendError(zResult, nResult, 0); + } + } + /* + ** If the script returned TH_ERROR (e.g. the "command_hook" TH1 command does + ** not exist because commands are not being hooked), return TH_OK because we + ** do not want to skip executing essential commands unless the called command + ** (i.e. "command_hook") explicitly forbids this by successfully returning + ** TH_BREAK or TH_CONTINUE. + */ + if( g.thTrace ){ + Th_Trace("[command_hook {%h}] => %h<br />\n", zName, + Th_ReturnCodeName(rc)); + } + return (rc != TH_ERROR) ? rc : TH_OK; +} + +/* +** This function is called by Fossil just after dispatching a command. +** Returning a value other than TH_OK from this function (i.e. via an +** evaluated script raising an error or calling [break]/[continue]) may +** cause an error message to be displayed to the local interactive user. +** Currently, TH1 error messages generated by this function are ignored. +*/ +int Th_CommandNotify( + const char *zName, + char cmdFlags +){ + int rc; + Th_FossilInit(1, 1); + Th_Store("cmd_name", zName); + Th_StoreInt("cmd_flags", cmdFlags); + rc = Th_Eval(g.interp, 0, "command_notify", -1); + if( g.thTrace ){ + Th_Trace("[command_notify {%h}] => %h<br />\n", zName, + Th_ReturnCodeName(rc)); + } + return rc; +} + +/* +** This function is called by Fossil just prior to processing a web page. +** Returning a value other than TH_OK from this function (i.e. via an +** evaluated script raising an error or calling [break]/[continue]) will +** cause the actual web page processing to be skipped. +*/ +int Th_WebpageHook( + const char *zName, + char cmdFlags +){ + int rc = TH_OK; + Th_FossilInit(1, 1); + Th_Store("web_name", zName); + Th_StoreInt("web_flags", cmdFlags); + rc = Th_Eval(g.interp, 0, "webpage_hook", -1); + if( rc==TH_ERROR ){ + int nResult = 0; + char *zResult = (char*)Th_GetResult(g.interp, &nResult); + /* + ** Make sure that the TH1 script error was not caused by a "missing" + ** webpage hook handler as that is not actually an error condition. + */ + if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){ + sendError(zResult, nResult, 1); + } + } + /* + ** If the script returned TH_ERROR (e.g. the "webpage_hook" TH1 command does + ** not exist because commands are not being hooked), return TH_OK because we + ** do not want to skip processing essential web pages unless the called + ** command (i.e. "webpage_hook") explicitly forbids this by successfully + ** returning TH_BREAK or TH_CONTINUE. + */ + if( g.thTrace ){ + Th_Trace("[webpage_hook {%h}] => %h<br />\n", zName, + Th_ReturnCodeName(rc)); + } + return (rc != TH_ERROR) ? rc : TH_OK; +} + +/* +** This function is called by Fossil just after processing a web page. +** Returning a value other than TH_OK from this function (i.e. via an +** evaluated script raising an error or calling [break]/[continue]) may +** cause an error message to be displayed to the remote user. +** Currently, TH1 error messages generated by this function are ignored. +*/ +int Th_WebpageNotify( + const char *zName, + char cmdFlags +){ + int rc; + Th_FossilInit(1, 1); + Th_Store("web_name", zName); + Th_StoreInt("web_flags", cmdFlags); + rc = Th_Eval(g.interp, 0, "webpage_notify", -1); + if( g.thTrace ){ + Th_Trace("[webpage_notify {%h}] => %h<br />\n", zName, + Th_ReturnCodeName(rc)); + } + return rc; +} /* ** The z[] input contains text mixed with TH1 scripts. ** The TH1 scripts are contained within <th1>...</th1>. ** TH1 variables are $aaa or $<aaa>. The first form of @@ -932,5 +1067,34 @@ db_open_config(0); /* Needed for global "tcl" setting. */ blob_zero(&in); blob_read_from_file(&in, g.argv[2]); Th_Render(blob_str(&in)); } + +/* +** COMMAND: test-th-hook +*/ +void test_th_hook(void){ + int rc = TH_OK; + int nResult = 0; + char *zResult; + if( g.argc<5 ){ + usage("TYPE NAME FLAGS"); + } + if( fossil_stricmp(g.argv[2], "cmdhook")==0 ){ + rc = Th_CommandHook(g.argv[3], (char)atoi(g.argv[4])); + }else if( fossil_stricmp(g.argv[2], "cmdnotify")==0 ){ + rc = Th_CommandNotify(g.argv[3], (char)atoi(g.argv[4])); + }else if( fossil_stricmp(g.argv[2], "webhook")==0 ){ + rc = Th_WebpageHook(g.argv[3], (char)atoi(g.argv[4])); + }else if( fossil_stricmp(g.argv[2], "webnotify")==0 ){ + rc = Th_WebpageNotify(g.argv[3], (char)atoi(g.argv[4])); + }else{ + fossil_fatal("Unknown TH1 hook %s\n", g.argv[2]); + } + zResult = (char*)Th_GetResult(g.interp, &nResult); + sendText("RESULT (", -1, 0); + sendText(Th_ReturnCodeName(rc), -1, 0); + sendText("): ", -1, 0); + sendText(zResult, nResult, 0); + sendText("\n", -1, 0); +}